jsi 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +6 -1
  3. data/CHANGELOG.md +33 -0
  4. data/LICENSE.md +1 -1
  5. data/README.md +29 -23
  6. data/jsi.gemspec +29 -0
  7. data/lib/jsi/base/mutability.rb +44 -0
  8. data/lib/jsi/base/node.rb +348 -0
  9. data/lib/jsi/base.rb +497 -339
  10. data/lib/jsi/jsi_coder.rb +19 -17
  11. data/lib/jsi/metaschema_node/bootstrap_schema.rb +61 -26
  12. data/lib/jsi/metaschema_node.rb +161 -133
  13. data/lib/jsi/ptr.rb +80 -47
  14. data/lib/jsi/schema/application/child_application/contains.rb +11 -2
  15. data/lib/jsi/schema/application/child_application/draft04.rb +0 -1
  16. data/lib/jsi/schema/application/child_application/draft06.rb +0 -1
  17. data/lib/jsi/schema/application/child_application/draft07.rb +0 -1
  18. data/lib/jsi/schema/application/child_application/items.rb +3 -3
  19. data/lib/jsi/schema/application/child_application/properties.rb +3 -3
  20. data/lib/jsi/schema/application/child_application.rb +0 -27
  21. data/lib/jsi/schema/application/inplace_application/dependencies.rb +1 -1
  22. data/lib/jsi/schema/application/inplace_application/draft04.rb +0 -1
  23. data/lib/jsi/schema/application/inplace_application/draft06.rb +0 -1
  24. data/lib/jsi/schema/application/inplace_application/draft07.rb +0 -1
  25. data/lib/jsi/schema/application/inplace_application/ifthenelse.rb +3 -3
  26. data/lib/jsi/schema/application/inplace_application/ref.rb +2 -2
  27. data/lib/jsi/schema/application/inplace_application/someof.rb +26 -11
  28. data/lib/jsi/schema/application/inplace_application.rb +0 -32
  29. data/lib/jsi/schema/draft04.rb +0 -1
  30. data/lib/jsi/schema/draft06.rb +0 -1
  31. data/lib/jsi/schema/draft07.rb +0 -1
  32. data/lib/jsi/schema/ref.rb +46 -19
  33. data/lib/jsi/schema/schema_ancestor_node.rb +69 -66
  34. data/lib/jsi/schema/validation/array.rb +3 -3
  35. data/lib/jsi/schema/validation/const.rb +1 -1
  36. data/lib/jsi/schema/validation/contains.rb +2 -2
  37. data/lib/jsi/schema/validation/dependencies.rb +1 -1
  38. data/lib/jsi/schema/validation/draft04/minmax.rb +8 -6
  39. data/lib/jsi/schema/validation/draft04.rb +0 -2
  40. data/lib/jsi/schema/validation/draft06.rb +0 -2
  41. data/lib/jsi/schema/validation/draft07.rb +0 -2
  42. data/lib/jsi/schema/validation/enum.rb +1 -1
  43. data/lib/jsi/schema/validation/ifthenelse.rb +5 -5
  44. data/lib/jsi/schema/validation/items.rb +7 -7
  45. data/lib/jsi/schema/validation/not.rb +1 -1
  46. data/lib/jsi/schema/validation/numeric.rb +5 -5
  47. data/lib/jsi/schema/validation/object.rb +2 -2
  48. data/lib/jsi/schema/validation/pattern.rb +2 -2
  49. data/lib/jsi/schema/validation/properties.rb +7 -7
  50. data/lib/jsi/schema/validation/property_names.rb +1 -1
  51. data/lib/jsi/schema/validation/ref.rb +2 -2
  52. data/lib/jsi/schema/validation/required.rb +1 -1
  53. data/lib/jsi/schema/validation/someof.rb +3 -3
  54. data/lib/jsi/schema/validation/string.rb +2 -2
  55. data/lib/jsi/schema/validation/type.rb +1 -1
  56. data/lib/jsi/schema/validation.rb +1 -3
  57. data/lib/jsi/schema.rb +443 -226
  58. data/lib/jsi/schema_classes.rb +241 -147
  59. data/lib/jsi/schema_registry.rb +78 -19
  60. data/lib/jsi/schema_set.rb +114 -28
  61. data/lib/jsi/simple_wrap.rb +18 -4
  62. data/lib/jsi/util/private/attr_struct.rb +141 -0
  63. data/lib/jsi/util/private/memo_map.rb +75 -0
  64. data/lib/jsi/util/private.rb +185 -0
  65. data/lib/jsi/{typelike_modules.rb → util/typelike.rb} +79 -105
  66. data/lib/jsi/util.rb +157 -153
  67. data/lib/jsi/validation/error.rb +4 -0
  68. data/lib/jsi/validation/result.rb +18 -32
  69. data/lib/jsi/version.rb +1 -1
  70. data/lib/jsi.rb +65 -39
  71. data/lib/schemas/json-schema.org/draft-04/schema.rb +160 -3
  72. data/lib/schemas/json-schema.org/draft-06/schema.rb +162 -3
  73. data/lib/schemas/json-schema.org/draft-07/schema.rb +189 -3
  74. metadata +27 -11
  75. data/lib/jsi/metaschema.rb +0 -7
  76. data/lib/jsi/pathed_node.rb +0 -116
  77. data/lib/jsi/schema/validation/core.rb +0 -39
  78. data/lib/jsi/util/attr_struct.rb +0 -106
data/lib/jsi/ptr.rb CHANGED
@@ -16,12 +16,17 @@ module JSI
16
16
  class ResolutionError < Error
17
17
  end
18
18
 
19
+ POS_INT_RE = /\A[1-9]\d*\z/
20
+ private_constant :POS_INT_RE
21
+
19
22
  # instantiates a pointer or returns the given pointer
20
23
  # @param ary_ptr [#to_ary, JSI::Ptr] an array of tokens, or a pointer
21
24
  # @return [JSI::Ptr]
22
25
  def self.ary_ptr(ary_ptr)
23
26
  if ary_ptr.is_a?(Ptr)
24
27
  ary_ptr
28
+ elsif ary_ptr == Util::EMPTY_ARY
29
+ EMPTY
25
30
  else
26
31
  new(ary_ptr)
27
32
  end
@@ -31,7 +36,7 @@ module JSI
31
36
  #
32
37
  # JSI::Ptr[]
33
38
  #
34
- # instantes a root pointer.
39
+ # instantiates a root pointer.
35
40
  #
36
41
  # JSI::Ptr['a', 'b']
37
42
  # JSI::Ptr['a']['b']
@@ -42,7 +47,7 @@ module JSI
42
47
  # @param tokens any number of tokens
43
48
  # @return [JSI::Ptr]
44
49
  def self.[](*tokens)
45
- new(tokens)
50
+ tokens.empty? ? EMPTY : new(tokens.freeze)
46
51
  end
47
52
 
48
53
  # parse a URI-escaped fragment and instantiate as a JSI::Ptr
@@ -55,6 +60,12 @@ module JSI
55
60
  # JSI::Ptr.from_fragment('/foo%20bar')
56
61
  # => JSI::Ptr["foo bar"]
57
62
  #
63
+ # Note: A fragment does not include a leading '#'. The string "#/foo" is a URI containing the
64
+ # fragment "/foo", which should be parsed by `Addressable::URI` before passing to this method, e.g.:
65
+ #
66
+ # JSI::Ptr.from_fragment(Addressable::URI.parse("#/foo").fragment)
67
+ # => JSI::Ptr["foo"]
68
+ #
58
69
  # @param fragment [String] a fragment containing a pointer
59
70
  # @return [JSI::Ptr]
60
71
  # @raise [JSI::Ptr::PointerSyntaxError] when the fragment does not contain a pointer with
@@ -75,13 +86,17 @@ module JSI
75
86
  # @return [JSI::Ptr]
76
87
  # @raise [JSI::Ptr::PointerSyntaxError] when the pointer_string does not have valid pointer syntax
77
88
  def self.from_pointer(pointer_string)
78
- tokens = pointer_string.split('/', -1).map! do |piece|
79
- piece.gsub('~1', '/').gsub('~0', '~')
80
- end
81
- if tokens[0] == ''
82
- new(tokens[1..-1])
83
- elsif tokens.empty?
84
- new(tokens)
89
+ pointer_string = pointer_string.to_str
90
+ if pointer_string[0] == ?/
91
+ tokens = pointer_string.split('/', -1).map! do |piece|
92
+ piece.gsub!('~1', '/')
93
+ piece.gsub!('~0', '~')
94
+ piece.freeze
95
+ end
96
+ tokens.shift
97
+ new(tokens.freeze)
98
+ elsif pointer_string.empty?
99
+ EMPTY
85
100
  else
86
101
  raise(PointerSyntaxError, "Invalid pointer syntax in #{pointer_string.inspect}: pointer must begin with /")
87
102
  end
@@ -94,14 +109,11 @@ module JSI
94
109
  unless tokens.respond_to?(:to_ary)
95
110
  raise(TypeError, "tokens must be an array. got: #{tokens.inspect}")
96
111
  end
97
- @tokens = tokens.to_ary.map(&:freeze).freeze
112
+ @tokens = Util.deep_to_frozen(tokens.to_ary, not_implemented: proc { |o| o })
98
113
  end
99
114
 
100
115
  attr_reader :tokens
101
116
 
102
- # @private @deprecated
103
- alias_method :reference_tokens, :tokens
104
-
105
117
  # takes a root json document and evaluates this pointer through the document, returning the value
106
118
  # pointed to by this pointer.
107
119
  #
@@ -109,9 +121,9 @@ module JSI
109
121
  # @param a arguments are passed to each invocation of `#[]`
110
122
  # @return [Object] the content of the document pointed to by this pointer
111
123
  # @raise [JSI::Ptr::ResolutionError] the document does not contain the path this pointer references
112
- def evaluate(document, *a)
124
+ def evaluate(document, *a, **kw)
113
125
  res = tokens.inject(document) do |value, token|
114
- _, child = node_subscript_token_child(value, token, *a)
126
+ _, child = node_subscript_token_child(value, token, *a, **kw)
115
127
  child
116
128
  end
117
129
  res
@@ -120,19 +132,19 @@ module JSI
120
132
  # the pointer string representation of this pointer
121
133
  # @return [String]
122
134
  def pointer
123
- tokens.map { |t| '/' + t.to_s.gsub('~', '~0').gsub('/', '~1') }.join('')
135
+ tokens.map { |t| '/' + t.to_s.gsub('~', '~0').gsub('/', '~1') }.join('').freeze
124
136
  end
125
137
 
126
138
  # the fragment string representation of this pointer
127
139
  # @return [String]
128
140
  def fragment
129
- Addressable::URI.escape(pointer)
141
+ Addressable::URI.escape(pointer).freeze
130
142
  end
131
143
 
132
144
  # a URI consisting of a fragment containing this pointer's fragment string representation
133
145
  # @return [Addressable::URI]
134
146
  def uri
135
- Addressable::URI.new(fragment: fragment)
147
+ Addressable::URI.new(fragment: fragment).freeze
136
148
  end
137
149
 
138
150
  # whether this pointer is empty, i.e. it has no tokens
@@ -151,26 +163,32 @@ module JSI
151
163
  def parent
152
164
  if root?
153
165
  raise(Ptr::Error, "cannot access parent of root pointer: #{pretty_inspect.chomp}")
154
- else
155
- Ptr.new(tokens[0...-1])
156
166
  end
167
+ tokens.size == 1 ? EMPTY : Ptr.new(tokens[0...-1].freeze)
157
168
  end
158
169
 
159
- # whether this pointer contains the other_ptr - that is, whether this pointer is an ancestor
160
- # of `other_ptr`, a child pointer. `contains?` is inclusive; a pointer does contain itself.
170
+ # whether this pointer is an ancestor of `other_ptr`, a descendent pointer.
171
+ # `ancestor_of?` is inclusive; a pointer is an ancestor of itself.
172
+ #
161
173
  # @return [Boolean]
174
+ def ancestor_of?(other_ptr)
175
+ tokens == other_ptr.tokens[0...tokens.size]
176
+ end
177
+
178
+ # @deprecated
162
179
  def contains?(other_ptr)
163
- self.tokens == other_ptr.tokens[0...self.tokens.size]
180
+ ancestor_of?(other_ptr)
164
181
  end
165
182
 
166
183
  # part of this pointer relative to the given ancestor_ptr
167
184
  # @return [JSI::Ptr]
168
185
  # @raise [JSI::Ptr::Error] if the given ancestor_ptr is not an ancestor of this pointer
169
- def ptr_relative_to(ancestor_ptr)
170
- unless ancestor_ptr.contains?(self)
186
+ def relative_to(ancestor_ptr)
187
+ return self if ancestor_ptr.empty?
188
+ unless ancestor_ptr.ancestor_of?(self)
171
189
  raise(Error, "ancestor_ptr #{ancestor_ptr.inspect} is not ancestor of #{inspect}")
172
190
  end
173
- Ptr.new(tokens[ancestor_ptr.tokens.size..-1])
191
+ ancestor_ptr.tokens.size == tokens.size ? EMPTY : Ptr.new(tokens[ancestor_ptr.tokens.size..-1].freeze)
174
192
  end
175
193
 
176
194
  # a pointer with the tokens of this one plus the given `ptr`'s.
@@ -182,9 +200,9 @@ module JSI
182
200
  elsif ptr.respond_to?(:to_ary)
183
201
  ptr_tokens = ptr
184
202
  else
185
- raise(TypeError, "ptr must be a JSI::Ptr or Array of tokens; got: #{ptr.inspect}")
203
+ raise(TypeError, "ptr must be a #{Ptr} or Array of tokens; got: #{ptr.inspect}")
186
204
  end
187
- Ptr.new(self.tokens + ptr_tokens)
205
+ ptr_tokens.empty? ? self : Ptr.new((tokens + ptr_tokens).freeze)
188
206
  end
189
207
 
190
208
  # a pointer consisting of the first `n` of our tokens
@@ -192,10 +210,10 @@ module JSI
192
210
  # @return [JSI::Ptr]
193
211
  # @raise [ArgumentError] if n is not between 0 and the size of our tokens
194
212
  def take(n)
195
- unless (0..tokens.size).include?(n)
213
+ unless n.is_a?(Integer) && n >= 0 && n <= tokens.size
196
214
  raise(ArgumentError, "n not in range (0..#{tokens.size}): #{n.inspect}")
197
215
  end
198
- Ptr.new(tokens.take(n))
216
+ n == tokens.size ? self : Ptr.new(tokens.take(n).freeze)
199
217
  end
200
218
 
201
219
  # appends the given token to this pointer's tokens and returns the result
@@ -203,7 +221,7 @@ module JSI
203
221
  # @param token [Object]
204
222
  # @return [JSI::Ptr] pointer to a child node of this pointer with the given token
205
223
  def [](token)
206
- Ptr.new(tokens + [token])
224
+ Ptr.new(tokens.dup.push(token).freeze)
207
225
  end
208
226
 
209
227
  # takes a document and a block. the block is yielded the content of the given document at this
@@ -225,10 +243,10 @@ module JSI
225
243
  # or hash in the path above the node we point to. this node's content is modified by the
226
244
  # caller, and that is recursively merged up to the document root.
227
245
  if empty?
228
- Typelike.modified_copy(document, &block)
246
+ Util.modified_copy(document, &block)
229
247
  else
230
248
  car = tokens[0]
231
- cdr = Ptr.new(tokens[1..-1])
249
+ cdr = tokens.size == 1 ? EMPTY : Ptr.new(tokens[1..-1].freeze)
232
250
  token, document_child = node_subscript_token_child(document, car)
233
251
  modified_document_child = cdr.modified_document_copy(document_child, &block)
234
252
  if modified_document_child.object_id == document_child.object_id
@@ -247,42 +265,57 @@ module JSI
247
265
  # a string representation of this pointer
248
266
  # @return [String]
249
267
  def inspect
250
- "#{self.class.name}[#{tokens.map(&:inspect).join(", ")}]"
268
+ -"#{self.class.name}[#{tokens.map(&:inspect).join(", ")}]"
251
269
  end
252
270
 
253
- alias_method :to_s, :inspect
271
+ def to_s
272
+ inspect
273
+ end
254
274
 
255
- # pointers are equal if the tokens are equal
275
+ # see {Util::Private::FingerprintHash}
276
+ # @api private
256
277
  def jsi_fingerprint
257
- {class: Ptr, tokens: tokens}
278
+ {class: Ptr, tokens: tokens}.freeze
258
279
  end
259
- include Util::FingerprintHash
280
+ include Util::FingerprintHash::Immutable
281
+
282
+ EMPTY = new(Util::EMPTY_ARY)
260
283
 
261
284
  private
262
285
 
263
- def node_subscript_token_child(value, token, *a)
286
+ def node_subscript_token_child(value, token, *a, **kw)
264
287
  if value.respond_to?(:to_ary)
265
- if token.is_a?(String) && token =~ /\A\d|[1-9]\d+\z/
288
+ if token.is_a?(String) && (token == '0' || token =~ POS_INT_RE)
266
289
  token = token.to_i
267
290
  elsif token == '-'
268
291
  # per rfc6901, - refers "to the (nonexistent) member after the last array element" and is
269
292
  # expected to raise an error condition.
270
293
  raise(ResolutionError, "Invalid resolution: #{token.inspect} refers to a nonexistent element in array #{value.inspect}")
271
294
  end
272
- unless token.is_a?(Integer)
273
- raise(ResolutionError, "Invalid resolution: #{token.inspect} is not an integer and cannot be resolved in array #{value.inspect}")
274
- end
275
- unless (0...(value.respond_to?(:size) ? value : value.to_ary).size).include?(token)
276
- raise(ResolutionError, "Invalid resolution: #{token.inspect} is not a valid index of #{value.inspect}")
295
+ size = (value.respond_to?(:size) ? value : value.to_ary).size
296
+ unless token.is_a?(Integer) && token >= 0 && token < size
297
+ raise(ResolutionError, "Invalid resolution: #{token.inspect} is not a valid array index of #{value.inspect}")
277
298
  end
278
299
 
279
- child = (value.respond_to?(:[]) ? value : value.to_ary)[token, *a]
300
+ ary = (value.respond_to?(:[]) ? value : value.to_ary)
301
+ if kw.empty?
302
+ # TODO remove eventually (keyword argument compatibility)
303
+ child = ary[token, *a]
304
+ else
305
+ child = ary[token, *a, **kw]
306
+ end
280
307
  elsif value.respond_to?(:to_hash)
281
308
  unless (value.respond_to?(:key?) ? value : value.to_hash).key?(token)
282
309
  raise(ResolutionError, "Invalid resolution: #{token.inspect} is not a valid key of #{value.inspect}")
283
310
  end
284
311
 
285
- child = (value.respond_to?(:[]) ? value : value.to_hash)[token, *a]
312
+ hsh = (value.respond_to?(:[]) ? value : value.to_hash)
313
+ if kw.empty?
314
+ # TODO remove eventually (keyword argument compatibility)
315
+ child = hsh[token, *a]
316
+ else
317
+ child = hsh[token, *a, **kw]
318
+ end
286
319
  else
287
320
  raise(ResolutionError, "Invalid resolution: #{token.inspect} cannot be resolved in #{value.inspect}")
288
321
  end
@@ -4,11 +4,20 @@ module JSI
4
4
  module Schema::Application::ChildApplication::Contains
5
5
  # @private
6
6
  def internal_applicate_contains(idx, instance, &block)
7
- if schema_content.key?('contains')
7
+ if keyword?('contains')
8
8
  contains_schema = subschema(['contains'])
9
9
 
10
- if contains_schema.instance_valid?(instance[idx])
10
+ child_idx_valid = Hash.new { |h, i| h[i] = contains_schema.instance_valid?(instance[i]) }
11
+
12
+ if child_idx_valid[idx]
11
13
  yield contains_schema
14
+ else
15
+ instance_valid = instance.each_index.any? { |i| child_idx_valid[i] }
16
+
17
+ unless instance_valid
18
+ # invalid application: if contains_schema does not validate against any child, it applies to every child
19
+ yield contains_schema
20
+ end
12
21
  end
13
22
  end
14
23
  end
@@ -2,7 +2,6 @@
2
2
 
3
3
  module JSI
4
4
  module Schema::Application::ChildApplication::Draft04
5
- include Schema::Application::ChildApplication
6
5
  include Schema::Application::ChildApplication::Items
7
6
  include Schema::Application::ChildApplication::Properties
8
7
 
@@ -2,7 +2,6 @@
2
2
 
3
3
  module JSI
4
4
  module Schema::Application::ChildApplication::Draft06
5
- include Schema::Application::ChildApplication
6
5
  include Schema::Application::ChildApplication::Items
7
6
  include Schema::Application::ChildApplication::Contains
8
7
  include Schema::Application::ChildApplication::Properties
@@ -2,7 +2,6 @@
2
2
 
3
3
  module JSI
4
4
  module Schema::Application::ChildApplication::Draft07
5
- include Schema::Application::ChildApplication
6
5
  include Schema::Application::ChildApplication::Items
7
6
  include Schema::Application::ChildApplication::Contains
8
7
  include Schema::Application::ChildApplication::Properties
@@ -4,13 +4,13 @@ module JSI
4
4
  module Schema::Application::ChildApplication::Items
5
5
  # @private
6
6
  def internal_applicate_items(idx, &block)
7
- if schema_content['items'].respond_to?(:to_ary)
7
+ if keyword?('items') && schema_content['items'].respond_to?(:to_ary)
8
8
  if schema_content['items'].each_index.to_a.include?(idx)
9
9
  yield subschema(['items', idx])
10
- elsif schema_content.key?('additionalItems')
10
+ elsif keyword?('additionalItems')
11
11
  yield subschema(['additionalItems'])
12
12
  end
13
- elsif schema_content.key?('items')
13
+ elsif keyword?('items')
14
14
  yield subschema(['items'])
15
15
  end
16
16
  end
@@ -5,11 +5,11 @@ module JSI
5
5
  # @private
6
6
  def internal_applicate_properties(property_name, &block)
7
7
  apply_additional = true
8
- if schema_content.key?('properties') && schema_content['properties'].respond_to?(:to_hash) && schema_content['properties'].key?(property_name)
8
+ if keyword?('properties') && schema_content['properties'].respond_to?(:to_hash) && schema_content['properties'].key?(property_name)
9
9
  apply_additional = false
10
10
  yield subschema(['properties', property_name])
11
11
  end
12
- if schema_content['patternProperties'].respond_to?(:to_hash)
12
+ if keyword?('patternProperties') && schema_content['patternProperties'].respond_to?(:to_hash)
13
13
  schema_content['patternProperties'].each_key do |pattern|
14
14
  if pattern.respond_to?(:to_str) && property_name.to_s =~ Regexp.new(pattern) # TODO map pattern to ruby syntax
15
15
  apply_additional = false
@@ -17,7 +17,7 @@ module JSI
17
17
  end
18
18
  end
19
19
  end
20
- if apply_additional && schema_content.key?('additionalProperties')
20
+ if apply_additional && keyword?('additionalProperties')
21
21
  yield subschema(['additionalProperties'])
22
22
  end
23
23
  end
@@ -9,32 +9,5 @@ module JSI
9
9
  autoload :Items, 'jsi/schema/application/child_application/items'
10
10
  autoload :Contains, 'jsi/schema/application/child_application/contains'
11
11
  autoload :Properties, 'jsi/schema/application/child_application/properties'
12
-
13
- # a set of child applicator subschemas of this schema which apply to the child of the given instance
14
- # on the given token.
15
- #
16
- # @param token [Object] the array index or object property name for the child instance
17
- # @param instance [Object] the instance to check any child applicators against
18
- # @return [JSI::SchemaSet] child applicator subschemas of this schema for the given token
19
- # of the instance
20
- def child_applicator_schemas(token, instance)
21
- SchemaSet.new(each_child_applicator_schema(token, instance))
22
- end
23
-
24
- # yields each child applicator subschema (from properties, items, etc.) which applies to the child of
25
- # the given instance on the given token.
26
- #
27
- # @param (see #child_applicator_schemas)
28
- # @yield [JSI::Schema]
29
- # @return [nil, Enumerator] an Enumerator if invoked without a block; otherwise nil
30
- def each_child_applicator_schema(token, instance, &block)
31
- return to_enum(__method__, token, instance) unless block
32
-
33
- if schema_content.respond_to?(:to_hash)
34
- internal_child_applicate_keywords(token, instance, &block)
35
- end
36
-
37
- nil
38
- end
39
12
  end
40
13
  end
@@ -4,7 +4,7 @@ module JSI
4
4
  module Schema::Application::InplaceApplication::Dependencies
5
5
  # @private
6
6
  def internal_applicate_dependencies(instance, visited_refs, &block)
7
- if schema_content.key?('dependencies')
7
+ if keyword?('dependencies')
8
8
  value = schema_content['dependencies']
9
9
  # This keyword's value MUST be an object. Each property specifies a dependency. Each dependency
10
10
  # value MUST be an array or a valid JSON Schema.
@@ -2,7 +2,6 @@
2
2
 
3
3
  module JSI
4
4
  module Schema::Application::InplaceApplication::Draft04
5
- include Schema::Application::InplaceApplication
6
5
  include Schema::Application::InplaceApplication::Ref
7
6
  include Schema::Application::InplaceApplication::Dependencies
8
7
  include Schema::Application::InplaceApplication::SomeOf
@@ -2,7 +2,6 @@
2
2
 
3
3
  module JSI
4
4
  module Schema::Application::InplaceApplication::Draft06
5
- include Schema::Application::InplaceApplication
6
5
  include Schema::Application::InplaceApplication::Ref
7
6
  include Schema::Application::InplaceApplication::Dependencies
8
7
  include Schema::Application::InplaceApplication::SomeOf
@@ -2,7 +2,6 @@
2
2
 
3
3
  module JSI
4
4
  module Schema::Application::InplaceApplication::Draft07
5
- include Schema::Application::InplaceApplication
6
5
  include Schema::Application::InplaceApplication::Ref
7
6
  include Schema::Application::InplaceApplication::Dependencies
8
7
  include Schema::Application::InplaceApplication::IfThenElse
@@ -4,13 +4,13 @@ module JSI
4
4
  module Schema::Application::InplaceApplication::IfThenElse
5
5
  # @private
6
6
  def internal_applicate_ifthenelse(instance, visited_refs, &block)
7
- if schema_content.key?('if')
7
+ if keyword?('if')
8
8
  if subschema(['if']).instance_valid?(instance)
9
- if schema_content.key?('then')
9
+ if keyword?('then')
10
10
  subschema(['then']).each_inplace_applicator_schema(instance, visited_refs: visited_refs, &block)
11
11
  end
12
12
  else
13
- if schema_content.key?('else')
13
+ if keyword?('else')
14
14
  subschema(['else']).each_inplace_applicator_schema(instance, visited_refs: visited_refs, &block)
15
15
  end
16
16
  end
@@ -4,8 +4,8 @@ module JSI
4
4
  module Schema::Application::InplaceApplication::Ref
5
5
  # @private
6
6
  def internal_applicate_ref(instance, visited_refs, throw_done: false, &block)
7
- if schema_content['$ref'].respond_to?(:to_str)
8
- ref = jsi_memoize(:ref) { Schema::Ref.new(schema_content['$ref'], self) }
7
+ if keyword?('$ref') && schema_content['$ref'].respond_to?(:to_str)
8
+ ref = schema_ref('$ref')
9
9
  unless visited_refs.include?(ref)
10
10
  ref.deref_schema.each_inplace_applicator_schema(instance, visited_refs: visited_refs + [ref], &block)
11
11
  if throw_done
@@ -4,24 +4,39 @@ module JSI
4
4
  module Schema::Application::InplaceApplication::SomeOf
5
5
  # @private
6
6
  def internal_applicate_someOf(instance, visited_refs, &block)
7
- if schema_content['allOf'].respond_to?(:to_ary)
7
+ if keyword?('allOf') && schema_content['allOf'].respond_to?(:to_ary)
8
8
  schema_content['allOf'].each_index do |i|
9
9
  subschema(['allOf', i]).each_inplace_applicator_schema(instance, visited_refs: visited_refs, &block)
10
10
  end
11
11
  end
12
- if schema_content['anyOf'].respond_to?(:to_ary)
13
- schema_content['anyOf'].each_index do |i|
14
- if subschema(['anyOf', i]).instance_valid?(instance)
15
- subschema(['anyOf', i]).each_inplace_applicator_schema(instance, visited_refs: visited_refs, &block)
16
- end
12
+ if keyword?('anyOf') && schema_content['anyOf'].respond_to?(:to_ary)
13
+ anyOf = schema_content['anyOf'].each_index.map { |i| subschema(['anyOf', i]) }
14
+ validOf = anyOf.select { |schema| schema.instance_valid?(instance) }
15
+ if !validOf.empty?
16
+ applicators = validOf
17
+ else
18
+ # invalid application: if none of the anyOf were valid, we apply them all
19
+ applicators = anyOf
20
+ end
21
+
22
+ applicators.each do |applicator|
23
+ applicator.each_inplace_applicator_schema(instance, visited_refs: visited_refs, &block)
17
24
  end
18
25
  end
19
- if schema_content['oneOf'].respond_to?(:to_ary)
20
- one_i = schema_content['oneOf'].each_index.detect do |i|
21
- subschema(['oneOf', i]).instance_valid?(instance)
26
+ if keyword?('oneOf') && schema_content['oneOf'].respond_to?(:to_ary)
27
+ oneOf_idxs = schema_content['oneOf'].each_index
28
+ subschema_idx_valid = Hash.new { |h, i| h[i] = subschema(['oneOf', i]).instance_valid?(instance) }
29
+ # count up to 2 `oneOf` subschemas which `instance` validates against
30
+ nvalid = oneOf_idxs.inject(0) { |n, i| n <= 1 && subschema_idx_valid[i] ? n + 1 : n }
31
+ if nvalid == 1
32
+ applicator_idxs = oneOf_idxs.select { |i| subschema_idx_valid[i] }
33
+ else
34
+ # invalid application: if none or multiple of the oneOf were valid, we apply them all
35
+ applicator_idxs = oneOf_idxs
22
36
  end
23
- if one_i
24
- subschema(['oneOf', one_i]).each_inplace_applicator_schema(instance, visited_refs: visited_refs, &block)
37
+
38
+ applicator_idxs.each do |i|
39
+ subschema(['oneOf', i]).each_inplace_applicator_schema(instance, visited_refs: visited_refs, &block)
25
40
  end
26
41
  end
27
42
  end
@@ -10,37 +10,5 @@ module JSI
10
10
  autoload :SomeOf, 'jsi/schema/application/inplace_application/someof'
11
11
  autoload :IfThenElse, 'jsi/schema/application/inplace_application/ifthenelse'
12
12
  autoload :Dependencies, 'jsi/schema/application/inplace_application/dependencies'
13
-
14
- # a set of inplace applicator schemas of this schema (from $ref, allOf, etc.) which apply to the
15
- # given instance.
16
- #
17
- # the returned set will contain this schema itself, unless this schema contains a $ref keyword.
18
- #
19
- # @param instance [Object] the instance to check any applicators against
20
- # @return [JSI::SchemaSet] matched applicator schemas
21
- def inplace_applicator_schemas(instance)
22
- SchemaSet.new(each_inplace_applicator_schema(instance))
23
- end
24
-
25
- # yields each inplace applicator schema which applies to the given instance.
26
- #
27
- # @param instance (see #inplace_applicator_schemas)
28
- # @param visited_refs [Enumerable<JSI::Schema::Ref>]
29
- # @yield [JSI::Schema]
30
- # @return [nil, Enumerator] an Enumerator if invoked without a block; otherwise nil
31
- def each_inplace_applicator_schema(instance, visited_refs: [], &block)
32
- return to_enum(__method__, instance, visited_refs: visited_refs) unless block
33
-
34
- catch(:jsi_application_done) do
35
- if schema_content.respond_to?(:to_hash)
36
- internal_inplace_applicate_keywords(instance, visited_refs, &block)
37
- else
38
- # self is the only applicator schema if there are no keywords
39
- yield self
40
- end
41
- end
42
-
43
- nil
44
- end
45
13
  end
46
14
  end
@@ -3,7 +3,6 @@
3
3
  module JSI
4
4
  module Schema
5
5
  module Draft04
6
- include Schema
7
6
  include OldId
8
7
  include IdWithAnchor
9
8
  include IntegerDisallows0Fraction
@@ -3,7 +3,6 @@
3
3
  module JSI
4
4
  module Schema
5
5
  module Draft06
6
- include Schema
7
6
  include BigMoneyId
8
7
  include IdWithAnchor
9
8
  include IntegerAllows0Fraction
@@ -3,7 +3,6 @@
3
3
  module JSI
4
4
  module Schema
5
5
  module Draft07
6
- include Schema
7
6
  include BigMoneyId
8
7
  include IdWithAnchor
9
8
  include IntegerAllows0Fraction