jsi 0.6.0 → 0.7.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 (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