jsi 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -0
  3. data/LICENSE.md +1 -1
  4. data/README.md +11 -6
  5. data/jsi.gemspec +30 -0
  6. data/lib/jsi/base/node.rb +183 -0
  7. data/lib/jsi/base.rb +135 -161
  8. data/lib/jsi/jsi_coder.rb +3 -3
  9. data/lib/jsi/metaschema.rb +0 -1
  10. data/lib/jsi/metaschema_node/bootstrap_schema.rb +9 -8
  11. data/lib/jsi/metaschema_node.rb +48 -51
  12. data/lib/jsi/ptr.rb +28 -17
  13. data/lib/jsi/schema/application/child_application/contains.rb +11 -2
  14. data/lib/jsi/schema/application/child_application/items.rb +3 -3
  15. data/lib/jsi/schema/application/child_application/properties.rb +3 -3
  16. data/lib/jsi/schema/application/child_application.rb +1 -3
  17. data/lib/jsi/schema/application/inplace_application/dependencies.rb +1 -1
  18. data/lib/jsi/schema/application/inplace_application/ifthenelse.rb +3 -3
  19. data/lib/jsi/schema/application/inplace_application/ref.rb +1 -1
  20. data/lib/jsi/schema/application/inplace_application/someof.rb +26 -11
  21. data/lib/jsi/schema/application/inplace_application.rb +1 -6
  22. data/lib/jsi/schema/ref.rb +3 -2
  23. data/lib/jsi/schema/schema_ancestor_node.rb +11 -17
  24. data/lib/jsi/schema/validation/array.rb +3 -3
  25. data/lib/jsi/schema/validation/const.rb +1 -1
  26. data/lib/jsi/schema/validation/contains.rb +1 -1
  27. data/lib/jsi/schema/validation/dependencies.rb +1 -1
  28. data/lib/jsi/schema/validation/draft04/minmax.rb +6 -6
  29. data/lib/jsi/schema/validation/enum.rb +1 -1
  30. data/lib/jsi/schema/validation/ifthenelse.rb +5 -5
  31. data/lib/jsi/schema/validation/items.rb +4 -4
  32. data/lib/jsi/schema/validation/not.rb +1 -1
  33. data/lib/jsi/schema/validation/numeric.rb +5 -5
  34. data/lib/jsi/schema/validation/object.rb +2 -2
  35. data/lib/jsi/schema/validation/pattern.rb +1 -1
  36. data/lib/jsi/schema/validation/properties.rb +3 -3
  37. data/lib/jsi/schema/validation/property_names.rb +1 -1
  38. data/lib/jsi/schema/validation/ref.rb +1 -1
  39. data/lib/jsi/schema/validation/required.rb +1 -1
  40. data/lib/jsi/schema/validation/someof.rb +3 -3
  41. data/lib/jsi/schema/validation/string.rb +2 -2
  42. data/lib/jsi/schema/validation/type.rb +1 -1
  43. data/lib/jsi/schema/validation.rb +1 -1
  44. data/lib/jsi/schema.rb +91 -85
  45. data/lib/jsi/schema_classes.rb +70 -45
  46. data/lib/jsi/schema_registry.rb +15 -5
  47. data/lib/jsi/schema_set.rb +42 -2
  48. data/lib/jsi/simple_wrap.rb +23 -4
  49. data/lib/jsi/util/{attr_struct.rb → private/attr_struct.rb} +40 -19
  50. data/lib/jsi/util/private.rb +204 -0
  51. data/lib/jsi/{typelike_modules.rb → util/typelike.rb} +56 -82
  52. data/lib/jsi/util.rb +68 -148
  53. data/lib/jsi/version.rb +1 -1
  54. data/lib/jsi.rb +1 -17
  55. data/lib/schemas/json-schema.org/draft-04/schema.rb +3 -1
  56. data/lib/schemas/json-schema.org/draft-06/schema.rb +3 -1
  57. data/lib/schemas/json-schema.org/draft-07/schema.rb +3 -1
  58. metadata +11 -9
  59. data/lib/jsi/pathed_node.rb +0 -116
@@ -25,27 +25,31 @@ module JSI
25
25
 
26
26
  # @param jsi_document the document containing the metaschema
27
27
  # @param jsi_ptr [JSI::Ptr] ptr to this MetaschemaNode in jsi_document
28
- # @param metaschema_instance_modules [Enumerable<Module>] modules which implement the functionality of the
29
- # schema, to be applied to every schema which is an instance of the metaschema. this must include
30
- # JSI::Schema directly or indirectly. these are the {Schema#jsi_schema_instance_modules} of the
31
- # metaschema.
28
+ # @param schema_implementation_modules [Enumerable<Module>] modules which implement the functionality
29
+ # of the schema. these are included on the {Schema#jsi_schema_module} of the metaschema.
30
+ # they extend any schema described by the metaschema, including those in the document containing
31
+ # the metaschema, and the metaschema itself.
32
+ # see {Schema#describes_schema!} param `schema_implementation_modules`.
32
33
  # @param metaschema_root_ptr [JSI::Ptr] ptr to the root of the metaschema in the jsi_document
33
34
  # @param root_schema_ptr [JSI::Ptr] ptr to the schema describing the root of the jsi_document
34
35
  def initialize(
35
36
  jsi_document,
36
37
  jsi_ptr: Ptr[],
37
- metaschema_instance_modules: ,
38
+ schema_implementation_modules: ,
38
39
  metaschema_root_ptr: Ptr[],
39
40
  root_schema_ptr: Ptr[],
40
- jsi_schema_base_uri: nil
41
+ jsi_schema_base_uri: nil,
42
+ jsi_root_node: nil
41
43
  )
42
44
  jsi_initialize_memos
43
45
 
44
46
  self.jsi_document = jsi_document
45
47
  self.jsi_ptr = jsi_ptr
46
- @metaschema_instance_modules = Util.ensure_module_set(metaschema_instance_modules)
48
+ @schema_implementation_modules = Util.ensure_module_set(schema_implementation_modules)
47
49
  @metaschema_root_ptr = metaschema_root_ptr
48
50
  @root_schema_ptr = root_schema_ptr
51
+ raise(Bug, 'jsi_root_node') if jsi_ptr.root? ^ !jsi_root_node
52
+ @jsi_root_node = jsi_ptr.root? ? self : jsi_root_node
49
53
 
50
54
  if jsi_ptr.root? && jsi_schema_base_uri
51
55
  raise(NotImplementedError, "unsupported jsi_schema_base_uri on metaschema document root")
@@ -55,57 +59,58 @@ module JSI
55
59
  jsi_node_content = self.jsi_node_content
56
60
 
57
61
  if jsi_node_content.respond_to?(:to_hash)
58
- extend PathedHashNode
62
+ extend HashNode
59
63
  end
60
64
  if jsi_node_content.respond_to?(:to_ary)
61
- extend PathedArrayNode
65
+ extend ArrayNode
62
66
  end
63
67
 
64
68
  instance_for_schemas = jsi_document
65
- bootstrap_schema_class = JSI::SchemaClasses.bootstrap_schema_class(metaschema_instance_modules)
69
+ bootstrap_schema_class = JSI::SchemaClasses.bootstrap_schema_class(schema_implementation_modules)
66
70
  root_bootstrap_schema = bootstrap_schema_class.new(
67
71
  jsi_document,
68
72
  jsi_ptr: root_schema_ptr,
69
73
  jsi_schema_base_uri: nil, # supplying jsi_schema_base_uri on root bootstrap schema is not supported
70
74
  )
71
75
  our_bootstrap_schemas = jsi_ptr.tokens.inject(SchemaSet[root_bootstrap_schema]) do |bootstrap_schemas, tok|
72
- child_instance_for_schemas = instance_for_schemas[tok]
73
- bootstrap_schemas_for_instance = SchemaSet.build do |schemas|
74
- bootstrap_schemas.each do |bootstrap_schema|
75
- bootstrap_schema.each_child_applicator_schema(tok, instance_for_schemas) do |child_app_schema|
76
- child_app_schema.each_inplace_applicator_schema(child_instance_for_schemas) do |child_inpl_app_schema|
77
- schemas << child_inpl_app_schema
78
- end
79
- end
80
- end
81
- end
82
- instance_for_schemas = child_instance_for_schemas
83
- bootstrap_schemas_for_instance
76
+ child_indicated_schemas = bootstrap_schemas.child_applicator_schemas(tok, instance_for_schemas)
77
+ child_schemas = child_indicated_schemas.inplace_applicator_schemas(instance_for_schemas[tok])
78
+ instance_for_schemas = instance_for_schemas[tok]
79
+ child_schemas
84
80
  end
85
81
 
86
82
  our_bootstrap_schemas.each do |bootstrap_schema|
87
83
  if bootstrap_schema.jsi_ptr == metaschema_root_ptr
88
- metaschema_instance_modules.each do |metaschema_instance_module|
89
- extend metaschema_instance_module
84
+ # this is described by the metaschema, i.e. this is a schema
85
+ schema_implementation_modules.each do |schema_implementation_module|
86
+ extend schema_implementation_module
90
87
  end
91
88
  end
92
89
  if bootstrap_schema.jsi_ptr == jsi_ptr
90
+ # this is the metaschema (it is described by itself)
93
91
  extend Metaschema
94
- self.jsi_schema_instance_modules = metaschema_instance_modules
95
92
  end
96
93
  end
97
94
 
98
95
  @jsi_schemas = SchemaSet.new(our_bootstrap_schemas) do |bootstrap_schema|
99
96
  if bootstrap_schema.jsi_ptr == jsi_ptr
100
97
  self
98
+ elsif bootstrap_schema.jsi_ptr.root?
99
+ @jsi_root_node
101
100
  else
102
101
  new_node(
103
102
  jsi_ptr: bootstrap_schema.jsi_ptr,
104
103
  jsi_schema_base_uri: bootstrap_schema.jsi_schema_base_uri,
104
+ jsi_root_node: @jsi_root_node,
105
105
  )
106
106
  end
107
107
  end
108
108
 
109
+ # note: jsi_schemas must already be set for jsi_schema_module to be used/extended
110
+ if is_a?(Metaschema)
111
+ describes_schema!(schema_implementation_modules)
112
+ end
113
+
109
114
  @jsi_schemas.each do |schema|
110
115
  extend schema.jsi_schema_module
111
116
  end
@@ -114,19 +119,19 @@ module JSI
114
119
  begin # draft 4 boolean schema workaround
115
120
  # in draft 4, boolean schemas are not described in the root, but on anyOf schemas on
116
121
  # properties/additionalProperties and properties/additionalItems.
117
- # since these describe schemas, their jsi_schema_instance_modules are the metaschema_instance_modules.
122
+ # these still describe schemas, despite not being described by the metaschema.
118
123
  addtlPropsanyOf = metaschema_root_ptr["properties"]["additionalProperties"]["anyOf"]
119
124
  addtlItemsanyOf = metaschema_root_ptr["properties"]["additionalItems"]["anyOf"]
120
125
 
121
126
  if !jsi_ptr.root? && [addtlPropsanyOf, addtlItemsanyOf].include?(jsi_ptr.parent)
122
- self.jsi_schema_instance_modules = metaschema_instance_modules
127
+ describes_schema!(schema_implementation_modules)
123
128
  end
124
129
  end
125
130
  end
126
131
 
127
132
  # Set of modules to apply to schemas which are instances of (described by) the metaschema
128
133
  # @return [Set<Module>]
129
- attr_reader :metaschema_instance_modules
134
+ attr_reader :schema_implementation_modules
130
135
 
131
136
  # ptr to the root of the metaschema in the jsi_document
132
137
  # @return [JSI::Ptr]
@@ -140,25 +145,6 @@ module JSI
140
145
  # @return [JSI::SchemaSet]
141
146
  attr_reader :jsi_schemas
142
147
 
143
- # document root MetaschemaNode
144
- # @return [MetaschemaNode]
145
- def jsi_root_node
146
- if jsi_ptr.root?
147
- self
148
- else
149
- new_node(
150
- jsi_ptr: Ptr[],
151
- jsi_schema_base_uri: nil,
152
- )
153
- end
154
- end
155
-
156
- # parent MetaschemaNode
157
- # @return [MetaschemaNode]
158
- def jsi_parent_node
159
- jsi_ptr.parent.evaluate(jsi_root_node)
160
- end
161
-
162
148
  # subscripts to return a child value identified by the given token.
163
149
  #
164
150
  # @param token (see JSI::Base#[])
@@ -177,7 +163,7 @@ module JSI
177
163
 
178
164
  begin
179
165
  if token_in_range
180
- value_node = jsi_subinstance_memos[token]
166
+ value_node = jsi_subinstance_memos[token: token]
181
167
 
182
168
  jsi_subinstance_as_jsi(value, value_node.jsi_schemas, as_jsi) do
183
169
  value_node
@@ -194,7 +180,15 @@ module JSI
194
180
  # in a (nondestructively) modified copy of this.
195
181
  # @return [MetaschemaNode] modified copy of self
196
182
  def jsi_modified_copy(&block)
197
- MetaschemaNode.new(jsi_ptr.modified_document_copy(jsi_document, &block), **our_initialize_params)
183
+ if jsi_ptr.root?
184
+ modified_document = jsi_ptr.modified_document_copy(jsi_document, &block)
185
+ MetaschemaNode.new(modified_document, **our_initialize_params)
186
+ else
187
+ modified_jsi_root_node = jsi_root_node.jsi_modified_copy do |root|
188
+ jsi_ptr.modified_document_copy(root, &block)
189
+ end
190
+ modified_jsi_root_node.jsi_descendent_node(jsi_ptr)
191
+ end
198
192
  end
199
193
 
200
194
  # @private
@@ -219,25 +213,28 @@ module JSI
219
213
 
220
214
  private
221
215
 
216
+ # note: does not include jsi_root_node
222
217
  def our_initialize_params
223
218
  {
224
219
  jsi_ptr: jsi_ptr,
225
- metaschema_instance_modules: metaschema_instance_modules,
220
+ schema_implementation_modules: schema_implementation_modules,
226
221
  metaschema_root_ptr: metaschema_root_ptr,
227
222
  root_schema_ptr: root_schema_ptr,
228
223
  jsi_schema_base_uri: jsi_schema_base_uri,
229
224
  }
230
225
  end
231
226
 
227
+ # note: not for root node
232
228
  def new_node(params)
233
229
  MetaschemaNode.new(jsi_document, **our_initialize_params.merge(params))
234
230
  end
235
231
 
236
232
  def jsi_subinstance_memos
237
- jsi_memomap(:subinstance) do |token|
233
+ jsi_memomap(:subinstance) do |token: |
238
234
  new_node(
239
235
  jsi_ptr: jsi_ptr[token],
240
236
  jsi_schema_base_uri: jsi_resource_ancestor_uri,
237
+ jsi_root_node: jsi_root_node,
241
238
  )
242
239
  end
243
240
  end
data/lib/jsi/ptr.rb CHANGED
@@ -31,7 +31,7 @@ module JSI
31
31
  #
32
32
  # JSI::Ptr[]
33
33
  #
34
- # instantes a root pointer.
34
+ # instantiates a root pointer.
35
35
  #
36
36
  # JSI::Ptr['a', 'b']
37
37
  # JSI::Ptr['a']['b']
@@ -99,9 +99,6 @@ module JSI
99
99
 
100
100
  attr_reader :tokens
101
101
 
102
- # @private @deprecated
103
- alias_method :reference_tokens, :tokens
104
-
105
102
  # takes a root json document and evaluates this pointer through the document, returning the value
106
103
  # pointed to by this pointer.
107
104
  #
@@ -109,9 +106,9 @@ module JSI
109
106
  # @param a arguments are passed to each invocation of `#[]`
110
107
  # @return [Object] the content of the document pointed to by this pointer
111
108
  # @raise [JSI::Ptr::ResolutionError] the document does not contain the path this pointer references
112
- def evaluate(document, *a)
109
+ def evaluate(document, *a, **kw)
113
110
  res = tokens.inject(document) do |value, token|
114
- _, child = node_subscript_token_child(value, token, *a)
111
+ _, child = node_subscript_token_child(value, token, *a, **kw)
115
112
  child
116
113
  end
117
114
  res
@@ -151,28 +148,30 @@ module JSI
151
148
  def parent
152
149
  if root?
153
150
  raise(Ptr::Error, "cannot access parent of root pointer: #{pretty_inspect.chomp}")
154
- else
155
- Ptr.new(tokens[0...-1])
156
151
  end
152
+ Ptr.new(tokens[0...-1])
157
153
  end
158
154
 
159
155
  # 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.
156
+ # of `other_ptr`, a descendent pointer. `contains?` is inclusive; a pointer does contain itself.
161
157
  # @return [Boolean]
162
158
  def contains?(other_ptr)
163
- self.tokens == other_ptr.tokens[0...self.tokens.size]
159
+ tokens == other_ptr.tokens[0...tokens.size]
164
160
  end
165
161
 
166
162
  # part of this pointer relative to the given ancestor_ptr
167
163
  # @return [JSI::Ptr]
168
164
  # @raise [JSI::Ptr::Error] if the given ancestor_ptr is not an ancestor of this pointer
169
- def ptr_relative_to(ancestor_ptr)
165
+ def relative_to(ancestor_ptr)
170
166
  unless ancestor_ptr.contains?(self)
171
167
  raise(Error, "ancestor_ptr #{ancestor_ptr.inspect} is not ancestor of #{inspect}")
172
168
  end
173
169
  Ptr.new(tokens[ancestor_ptr.tokens.size..-1])
174
170
  end
175
171
 
172
+ # @deprecated after v0.6
173
+ alias_method :ptr_relative_to, :relative_to
174
+
176
175
  # a pointer with the tokens of this one plus the given `ptr`'s.
177
176
  # @param ptr [JSI::Ptr, #to_ary]
178
177
  # @return [JSI::Ptr]
@@ -182,9 +181,9 @@ module JSI
182
181
  elsif ptr.respond_to?(:to_ary)
183
182
  ptr_tokens = ptr
184
183
  else
185
- raise(TypeError, "ptr must be a JSI::Ptr or Array of tokens; got: #{ptr.inspect}")
184
+ raise(TypeError, "ptr must be a #{Ptr} or Array of tokens; got: #{ptr.inspect}")
186
185
  end
187
- Ptr.new(self.tokens + ptr_tokens)
186
+ Ptr.new(tokens + ptr_tokens)
188
187
  end
189
188
 
190
189
  # a pointer consisting of the first `n` of our tokens
@@ -225,7 +224,7 @@ module JSI
225
224
  # or hash in the path above the node we point to. this node's content is modified by the
226
225
  # caller, and that is recursively merged up to the document root.
227
226
  if empty?
228
- Typelike.modified_copy(document, &block)
227
+ Util.modified_copy(document, &block)
229
228
  else
230
229
  car = tokens[0]
231
230
  cdr = Ptr.new(tokens[1..-1])
@@ -260,7 +259,7 @@ module JSI
260
259
 
261
260
  private
262
261
 
263
- def node_subscript_token_child(value, token, *a)
262
+ def node_subscript_token_child(value, token, *a, **kw)
264
263
  if value.respond_to?(:to_ary)
265
264
  if token.is_a?(String) && token =~ /\A\d|[1-9]\d+\z/
266
265
  token = token.to_i
@@ -276,13 +275,25 @@ module JSI
276
275
  raise(ResolutionError, "Invalid resolution: #{token.inspect} is not a valid index of #{value.inspect}")
277
276
  end
278
277
 
279
- child = (value.respond_to?(:[]) ? value : value.to_ary)[token, *a]
278
+ ary = (value.respond_to?(:[]) ? value : value.to_ary)
279
+ if kw.empty?
280
+ # TODO remove eventually (keyword argument compatibility)
281
+ child = ary[token, *a]
282
+ else
283
+ child = ary[token, *a, **kw]
284
+ end
280
285
  elsif value.respond_to?(:to_hash)
281
286
  unless (value.respond_to?(:key?) ? value : value.to_hash).key?(token)
282
287
  raise(ResolutionError, "Invalid resolution: #{token.inspect} is not a valid key of #{value.inspect}")
283
288
  end
284
289
 
285
- child = (value.respond_to?(:[]) ? value : value.to_hash)[token, *a]
290
+ hsh = (value.respond_to?(:[]) ? value : value.to_hash)
291
+ if kw.empty?
292
+ # TODO remove eventually (keyword argument compatibility)
293
+ child = hsh[token, *a]
294
+ else
295
+ child = hsh[token, *a, **kw]
296
+ end
286
297
  else
287
298
  raise(ResolutionError, "Invalid resolution: #{token.inspect} cannot be resolved in #{value.inspect}")
288
299
  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
@@ -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
@@ -30,9 +30,7 @@ module JSI
30
30
  def each_child_applicator_schema(token, instance, &block)
31
31
  return to_enum(__method__, token, instance) unless block
32
32
 
33
- if schema_content.respond_to?(:to_hash)
34
- internal_child_applicate_keywords(token, instance, &block)
35
- end
33
+ internal_child_applicate_keywords(token, instance, &block)
36
34
 
37
35
  nil
38
36
  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.
@@ -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,7 +4,7 @@ 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)
7
+ if keyword?('$ref') && schema_content['$ref'].respond_to?(:to_str)
8
8
  ref = jsi_memoize(:ref) { Schema::Ref.new(schema_content['$ref'], self) }
9
9
  unless visited_refs.include?(ref)
10
10
  ref.deref_schema.each_inplace_applicator_schema(instance, visited_refs: visited_refs + [ref], &block)
@@ -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 ? n : 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
@@ -32,12 +32,7 @@ module JSI
32
32
  return to_enum(__method__, instance, visited_refs: visited_refs) unless block
33
33
 
34
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
35
+ internal_inplace_applicate_keywords(instance, visited_refs, &block)
41
36
  end
42
37
 
43
38
  nil
@@ -53,7 +53,6 @@ module JSI
53
53
  resolve_fragment_ptr = ref_schema.method(:resource_root_subschema)
54
54
  else
55
55
  # find the schema_resource_root from the non-fragment URI. we will resolve any fragment, either pointer or anchor, from there.
56
- schema_resource_root = nil
57
56
 
58
57
  if ref_uri_nofrag.absolute?
59
58
  ref_abs_uri = ref_uri_nofrag
@@ -84,7 +83,7 @@ module JSI
84
83
  else
85
84
  # Note: Schema#resource_root_subschema will reinstantiate nonschemas as schemas.
86
85
  # not implemented for remote refs when the schema_resource_root is not a schema.
87
- resolve_fragment_ptr = -> (ptr) { ptr.evaluate(schema_resource_root) }
86
+ resolve_fragment_ptr = -> (ptr) { schema_resource_root.jsi_descendent_node(ptr) }
88
87
  end
89
88
  end
90
89
 
@@ -140,6 +139,8 @@ module JSI
140
139
  %Q(\#<#{self.class.name} #{ref}>)
141
140
  end
142
141
 
142
+ alias_method :to_s, :inspect
143
+
143
144
  # pretty-prints a representation of self to the given printer
144
145
  # @return [void]
145
146
  def pretty_print(q)
@@ -7,16 +7,16 @@ module JSI
7
7
  # the base URI used to resolve the ids of schemas at or below this JSI.
8
8
  # this is always an absolute URI (with no fragment).
9
9
  # this may be the absolute schema URI of a parent schema or the URI from which the document was retrieved.
10
- # @private
10
+ # @api private
11
11
  # @return [Addressable::URI, nil]
12
12
  attr_reader :jsi_schema_base_uri
13
13
 
14
14
  # resources which are ancestors of this JSI in the document. this does not include self.
15
- # @private
15
+ # @api private
16
16
  # @return [Array<JSI::Schema>]
17
17
  def jsi_schema_resource_ancestors
18
18
  return @jsi_schema_resource_ancestors if instance_variable_defined?(:@jsi_schema_resource_ancestors)
19
- [].freeze
19
+ Util::EMPTY_ARY
20
20
  end
21
21
 
22
22
  # the URI of the resource containing this node.
@@ -25,18 +25,14 @@ module JSI
25
25
  # or nil if not contained by a resource with a URI.
26
26
  # @return [Addressable::URI, nil]
27
27
  def jsi_resource_ancestor_uri
28
- if is_a?(Schema) && schema_absolute_uri
29
- schema_absolute_uri
30
- else
31
- jsi_schema_base_uri
32
- end
28
+ (is_a?(Schema) && schema_absolute_uri) || jsi_schema_base_uri
33
29
  end
34
30
 
35
31
  # a schema at or below this node with the given anchor.
36
32
  #
37
33
  # @return [JSI::Schema, nil]
38
34
  def jsi_anchor_subschema(anchor)
39
- subschemas = jsi_anchor_subschemas_map[anchor]
35
+ subschemas = jsi_anchor_subschemas_map[anchor: anchor]
40
36
  if subschemas.size == 1
41
37
  subschemas.first
42
38
  else
@@ -48,7 +44,7 @@ module JSI
48
44
  #
49
45
  # @return [Array<JSI::Schema>]
50
46
  def jsi_anchor_subschemas(anchor)
51
- jsi_anchor_subschemas_map[anchor]
47
+ jsi_anchor_subschemas_map[anchor: anchor]
52
48
  end
53
49
 
54
50
  private
@@ -58,9 +54,7 @@ module JSI
58
54
  end
59
55
 
60
56
  def jsi_ptr=(jsi_ptr)
61
- unless jsi_ptr.is_a?(Ptr)
62
- raise(TypeError, "jsi_ptr must be a JSI::Ptr; got: #{jsi_ptr.inspect}")
63
- end
57
+ raise(Bug, "jsi_ptr not #{Ptr}: #{jsi_ptr.inspect}") unless jsi_ptr.is_a?(Ptr)
64
58
  @jsi_ptr = jsi_ptr
65
59
  end
66
60
 
@@ -83,7 +77,7 @@ module JSI
83
77
  unless jsi_schema_resource_ancestors.respond_to?(:to_ary)
84
78
  raise(TypeError, "jsi_schema_resource_ancestors must be an array; got: #{jsi_schema_resource_ancestors.inspect}")
85
79
  end
86
- jsi_schema_resource_ancestors.each { |a| Schema.ensure_schema(a) }
80
+ jsi_schema_resource_ancestors.each { |a| Schema.ensure_schema(a) }
87
81
  # sanity check the ancestors are in order
88
82
  last_anc_ptr = nil
89
83
  jsi_schema_resource_ancestors.each do |anc|
@@ -104,13 +98,13 @@ module JSI
104
98
 
105
99
  @jsi_schema_resource_ancestors = jsi_schema_resource_ancestors.to_ary.freeze
106
100
  else
107
- @jsi_schema_resource_ancestors = [].freeze
101
+ @jsi_schema_resource_ancestors = Util::EMPTY_ARY
108
102
  end
109
103
  end
110
104
 
111
105
  def jsi_anchor_subschemas_map
112
- jsi_memomap(__method__) do |anchor|
113
- jsi_each_child_node.select do |node|
106
+ jsi_memomap(__method__) do |anchor: |
107
+ jsi_each_descendent_node.select do |node|
114
108
  node.is_a?(Schema) && node.respond_to?(:anchor) && node.anchor == anchor
115
109
  end.freeze
116
110
  end
@@ -4,7 +4,7 @@ module JSI
4
4
  module Schema::Validation::ArrayLength
5
5
  # @private
6
6
  def internal_validate_maxItems(result_builder)
7
- if schema_content.key?('maxItems')
7
+ if keyword?('maxItems')
8
8
  value = schema_content['maxItems']
9
9
  # The value of this keyword MUST be a non-negative integer.
10
10
  if internal_integer?(value) && value >= 0
@@ -24,7 +24,7 @@ module JSI
24
24
 
25
25
  # @private
26
26
  def internal_validate_minItems(result_builder)
27
- if schema_content.key?('minItems')
27
+ if keyword?('minItems')
28
28
  value = schema_content['minItems']
29
29
  # The value of this keyword MUST be a non-negative integer.
30
30
  if internal_integer?(value) && value >= 0
@@ -45,7 +45,7 @@ module JSI
45
45
  module Schema::Validation::UniqueItems
46
46
  # @private
47
47
  def internal_validate_uniqueItems(result_builder)
48
- if schema_content.key?('uniqueItems')
48
+ if keyword?('uniqueItems')
49
49
  value = schema_content['uniqueItems']
50
50
  # The value of this keyword MUST be a boolean.
51
51
  if value == false