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/schema.rb CHANGED
@@ -1,19 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JSI
4
- # JSI::Schema is a module which extends instances which represent JSON schemas.
4
+ # JSI::Schema is a module which extends {JSI::Base} instances which represent JSON schemas.
5
5
  #
6
- # the content of an instance which is a JSI::Schema (referred to in this context as schema_content) is
7
- # expected to be a Hash (JSON object) or a Boolean.
6
+ # This module is included on the {Schema#jsi_schema_module JSI Schema module} of any schema
7
+ # that describes other schemas, i.e. is a meta-schema (a {Schema::MetaSchema}).
8
+ # Therefore, any JSI instance described by a schema which is a {Schema::MetaSchema} is
9
+ # a schema and is extended by this module.
10
+ #
11
+ # The content of an instance which is a JSI::Schema (referred to in this context as schema_content) is
12
+ # typically a Hash (JSON object) or a boolean.
8
13
  module Schema
9
14
  autoload :Application, 'jsi/schema/application'
10
15
  autoload :Validation, 'jsi/schema/validation'
16
+
11
17
  autoload :Issue, 'jsi/schema/issue'
18
+ autoload :Ref, 'jsi/schema/ref'
12
19
 
13
20
  autoload :SchemaAncestorNode, 'jsi/schema/schema_ancestor_node'
14
21
 
15
- autoload :Ref, 'jsi/schema/ref'
16
-
17
22
  autoload :Draft04, 'jsi/schema/draft04'
18
23
  autoload :Draft06, 'jsi/schema/draft06'
19
24
  autoload :Draft07, 'jsi/schema/draft07'
@@ -34,7 +39,7 @@ module JSI
34
39
  # the contents of a $id keyword whose value is a string, or nil
35
40
  # @return [#to_str, nil]
36
41
  def id
37
- if schema_content.respond_to?(:to_hash) && schema_content['$id'].respond_to?(:to_str)
42
+ if keyword?('$id') && schema_content['$id'].respond_to?(:to_str)
38
43
  schema_content['$id']
39
44
  else
40
45
  nil
@@ -47,7 +52,7 @@ module JSI
47
52
  # the contents of an `id` keyword whose value is a string, or nil
48
53
  # @return [#to_str, nil]
49
54
  def id
50
- if schema_content.respond_to?(:to_hash) && schema_content['id'].respond_to?(:to_str)
55
+ if keyword?('id') && schema_content['id'].respond_to?(:to_str)
51
56
  schema_content['id']
52
57
  else
53
58
  nil
@@ -62,7 +67,7 @@ module JSI
62
67
  # @return [Addressable::URI, nil]
63
68
  def id_without_fragment
64
69
  if id
65
- id_uri = Addressable::URI.parse(id)
70
+ id_uri = Util.uri(id)
66
71
  if id_uri.merge(fragment: nil).empty?
67
72
  # fragment-only id is just an anchor
68
73
  # e.g. #foo
@@ -74,7 +79,7 @@ module JSI
74
79
  elsif id_uri.fragment == ''
75
80
  # empty fragment
76
81
  # e.g. http://json-schema.org/draft-07/schema#
77
- id_uri.merge(fragment: nil)
82
+ id_uri.merge(fragment: nil).freeze
78
83
  elsif jsi_schema_base_uri && jsi_schema_base_uri.join(id_uri).merge(fragment: nil) == jsi_schema_base_uri
79
84
  # the id, resolved against the base uri, consists of the base uri plus an anchor fragment.
80
85
  # so there's no non-fragment id.
@@ -83,7 +88,7 @@ module JSI
83
88
  nil
84
89
  else
85
90
  # e.g. http://localhost:1234/bar#foo
86
- id_uri.merge(fragment: nil)
91
+ id_uri.merge(fragment: nil).freeze
87
92
  end
88
93
  else
89
94
  nil
@@ -94,7 +99,7 @@ module JSI
94
99
  # @return [String]
95
100
  def anchor
96
101
  if id
97
- id_uri = Addressable::URI.parse(id)
102
+ id_uri = Util.uri(id)
98
103
  if id_uri.fragment == ''
99
104
  nil
100
105
  else
@@ -128,141 +133,235 @@ module JSI
128
133
  end
129
134
  end
130
135
 
131
- # JSI::Schema::DescribesSchema: a schema which describes another schema. this module
132
- # extends a JSI::Schema instance and indicates that JSIs which instantiate the schema
133
- # are themselves also schemas.
136
+ # This module extends any JSI Schema that is a meta-schema, i.e. it describes schemas.
134
137
  #
135
- # examples of a schema which describes a schema include the draft JSON Schema metaschemas and
138
+ # Examples of a meta-schema include the JSON Schema meta-schemas and
136
139
  # the OpenAPI schema definition which describes "A deterministic version of a JSON Schema object."
137
- module DescribesSchema
138
- # instantiates the given schema content as a JSI Schema.
140
+ #
141
+ # Meta-schemas include {JSI::Schema} in their
142
+ # {Schema#jsi_schema_module JSI Schema module}, so for a schema which is an instance of
143
+ # JSI::Schema::MetaSchema, instances of that schema are instances of {JSI::Schema} and are schemas.
144
+ #
145
+ # A schema is indicated as describing other schemas using the {Schema#describes_schema!} method.
146
+ module MetaSchema
147
+ # @return [Set<Module>]
148
+ attr_reader(:schema_implementation_modules)
149
+
150
+ # Instantiates the given schema content as a JSI Schema.
151
+ #
152
+ # By default, the schema will be registered with the {JSI.schema_registry}.
153
+ # This can be controlled by params `register` and `schema_registry`.
139
154
  #
140
- # the schema is instantiated after recursively converting any symbol hash keys in the structure
141
- # to strings. note that this is in contrast to {JSI::Schema#new_jsi}, which does not alter its
142
- # given instance.
155
+ # By default, the `schema_content` will have any Symbol keys of Hashes replaced with Strings
156
+ # (recursively through the document). This is controlled by the param `stringify_symbol_keys`.
143
157
  #
144
- # the schema will be registered with the `JSI.schema_registry`.
158
+ # @param schema_content an object to be instantiated as a JSI Schema - typically a Hash
159
+ # @param uri [#to_str, Addressable::URI] The retrieval URI of the schema document.
160
+ # If specified, the root schema will be identified by this URI, in addition
161
+ # to any absolute URI declared with an id keyword, for resolution in the `schema_registry`.
145
162
  #
146
- # @param schema_content [#to_hash, Boolean] an object to be instantiated as a schema
147
- # @param uri [nil, #to_str, Addressable::URI] the URI of the schema document.
148
- # relative URIs within the document are resolved using this uri as their base.
149
- # the result schema will be registered with this URI in the {JSI.schema_registry}.
150
- # @return [JSI::Base, JSI::Schema] a JSI whose instance is the given schema_content and whose schemas
151
- # are inplace applicators matched from self to the schema being instantiated.
163
+ # It is rare that this needs to be specified. Most schemas, if they use absolute URIs, will
164
+ # use the `$id` keyword (`id` in draft 4) to specify this. A different retrieval URI is useful
165
+ # in unusual cases:
166
+ #
167
+ # - A schema in the document uses relative URIs for `$id` or `$ref` without an absolute id in an
168
+ # ancestor schema - these will be resolved relative to this URI
169
+ # - Another schema refers with `$ref` to the schema being instantiated by this retrieval URI,
170
+ # rather than an id declared in the schema - the schema is resolvable by this URI in the
171
+ # `schema_registry`.
172
+ # @param register [Boolean] Whether the instantiated schema and any subschemas with absolute URIs
173
+ # will be registered in the schema registry indicated by param `schema_registry`.
174
+ # @param schema_registry [SchemaRegistry, nil] The registry this schema will use.
175
+ #
176
+ # - The schema and subschemas will be registered here with any declared URI,
177
+ # unless the `register` param is false.
178
+ # - References from within the schema (typically from `$ref` keywords) are resolved using this registry.
179
+ # @param stringify_symbol_keys [Boolean] Whether the schema content will have any Symbol keys of Hashes
180
+ # replaced with Strings (recursively through the document).
181
+ # Replacement is done on a copy; the given schema content is not modified.
182
+ # @param to_immutable (see SchemaSet#new_jsi)
183
+ # @yield If a block is given, it is evaluated in the context of the schema's JSI schema module
184
+ # using [Module#module_exec](https://ruby-doc.org/core/Module.html#method-i-module_exec).
185
+ # @return [JSI::Base subclass + JSI::Schema] a JSI which is a {JSI::Schema} whose content comes from
186
+ # the given `schema_content` and whose schemas are this schema's inplace applicators.
152
187
  def new_schema(schema_content,
153
- uri: nil
188
+ uri: nil,
189
+ register: true,
190
+ schema_registry: JSI.schema_registry,
191
+ stringify_symbol_keys: true,
192
+ to_immutable: DEFAULT_CONTENT_TO_IMMUTABLE,
193
+ &block
154
194
  )
155
- schema_jsi = new_jsi(Util.deep_stringify_symbol_keys(schema_content),
195
+ schema_jsi = new_jsi(schema_content,
156
196
  uri: uri,
197
+ register: register,
198
+ schema_registry: schema_registry,
199
+ stringify_symbol_keys: stringify_symbol_keys,
200
+ to_immutable: to_immutable,
157
201
  )
158
- JSI.schema_registry.register(schema_jsi)
202
+
203
+ schema_jsi.jsi_schema_module_exec(&block) if block
204
+
159
205
  schema_jsi
160
206
  end
161
207
 
162
- # instantiates a given schema object as a JSI Schema and returns its JSI Schema Module.
208
+ # Instantiates the given schema content as a JSI Schema, passing all params to
209
+ # {Schema::MetaSchema#new_schema}, and returns its {Schema#jsi_schema_module JSI Schema Module}.
163
210
  #
164
- # shortcut to chain {#new_schema} + {Schema#jsi_schema_module}.
165
- #
166
- # @param (see #new_schema)
167
- # @return [Module, JSI::SchemaModule] the JSI Schema Module of the schema
168
- def new_schema_module(schema_content, **kw)
169
- new_schema(schema_content, **kw).jsi_schema_module
211
+ # @return [JSI::SchemaModule] the JSI Schema Module of the instantiated schema
212
+ def new_schema_module(schema_content, **kw, &block)
213
+ new_schema(schema_content, **kw, &block).jsi_schema_module
170
214
  end
171
215
  end
172
216
 
173
217
  class << self
174
- # an application-wide default metaschema set by {default_metaschema=}, used by {JSI.new_schema}
218
+ def extended(o)
219
+ super
220
+ o.send(:jsi_schema_initialize)
221
+ end
222
+ end
223
+ end
224
+
225
+ class << self
226
+ # An application-wide default meta-schema set by {default_metaschema=}, used by {JSI.new_schema}
227
+ # to instantiate schemas that do not specify their meta-schema using a `$schema` property.
175
228
  #
176
- # @return [nil, #new_schema]
229
+ # @return [nil, Base + Schema + Schema::MetaSchema]
177
230
  def default_metaschema
178
- return @default_metaschema if instance_variable_defined?(:@default_metaschema)
179
- return JSONSchemaOrgDraft07
231
+ @default_metaschema
180
232
  end
181
233
 
182
- # sets an application-wide default metaschema used by {JSI.new_schema}
234
+ # Sets {default_metaschema} to a schema indicated by the given param.
235
+ #
236
+ # @param default_metaschema [Schema::MetaSchema, SchemaModule::MetaSchemaModule, #to_str, nil]
237
+ # Indicates the default meta-schema.
238
+ # This may be a meta-schema or a meta-schema's schema module (e.g. `JSI::JSONSchemaDraft07`),
239
+ # or a URI (as would be in a `$schema` keyword).
183
240
  #
184
- # @param default_metaschema [#new_schema] the default metaschema. this may be a metaschema or a
185
- # metaschema's schema module (e.g. `JSI::JSONSchemaOrgDraft07`).
241
+ # `nil` to unset.
186
242
  def default_metaschema=(default_metaschema)
187
- unless default_metaschema.respond_to?(:new_schema)
188
- raise(TypeError, "given default_metaschema does not respond to #new_schema")
189
- end
190
- @default_metaschema = default_metaschema
243
+ @default_metaschema = default_metaschema.nil? ? nil : ensure_metaschema(default_metaschema)
191
244
  end
192
245
 
193
- # instantiates a given schema object as a JSI Schema.
246
+ # Instantiates the given schema content as a JSI Schema.
247
+ #
248
+ # The meta-schema that describes the schema must be indicated:
194
249
  #
195
- # the metaschema to use to instantiate the schema must be indicated.
250
+ # - If the schema object has a `$schema` property, that URI is resolved using the `schema_registry`
251
+ # param (by default {JSI.schema_registry}), and that meta-schema is used. For example:
252
+ #
253
+ # ```ruby
254
+ # JSI.new_schema({
255
+ # "$schema" => "http://json-schema.org/draft-07/schema#",
256
+ # "properties" => ...,
257
+ # })
258
+ # ```
196
259
  #
197
- # - if the schema object has a `$schema` property, that URI is resolved using the {JSI.schema_registry},
198
- # and that metaschema is used.
199
260
  # - if no `$schema` property is present, the `default_metaschema` param is used, if the caller
200
- # specifies it.
261
+ # specifies it. For example:
262
+ #
263
+ # ```ruby
264
+ # JSI.new_schema({"properties" => ...}, default_metaschema: JSI::JSONSchemaDraft07)
265
+ # ```
266
+ #
201
267
  # - if no `default_metaschema` param is specified, the application-wide default
202
- # {JSI::Schema.default_metaschema JSI::Schema.default_metaschema} is used,
203
- # if the application has set it.
268
+ # {JSI.default_metaschema JSI.default_metaschema} is used,
269
+ # if the application has set it. For example:
204
270
  #
205
- # an ArgumentError is raised if none of these indicate a metaschema to use.
271
+ # ```ruby
272
+ # JSI.default_metaschema = JSI::JSONSchemaDraft07
273
+ # JSI.new_schema({"properties" => ...})
274
+ # ```
206
275
  #
207
- # note that if you are instantiating a schema known to have no `$schema` property, an alternative to
208
- # passing the `default_metaschema` param is to use `.new_schema` on the metaschema or its module, e.g.
209
- # `JSI::JSONSchemaOrgDraft07.new_schema(my_schema_object)`
276
+ # An ArgumentError is raised if none of these indicates a meta-schema to use.
210
277
  #
211
- # if the given schema_object is a JSI::Base but not already a JSI::Schema, an error
212
- # will be raised. schemas which describe schemas must have JSI::Schema in their
213
- # Schema#jsi_schema_instance_modules.
278
+ # Note that if you are instantiating a schema known to have no `$schema` property, an alternative to
279
+ # specifying a `default_metaschema` is to call `new_schema` on the
280
+ # {Schema::MetaSchema#new_schema meta-schema} or its
281
+ # {SchemaModule::MetaSchemaModule#new_schema schema module}, e.g.
282
+ # `JSI::JSONSchemaDraft07.new_schema(my_schema_content)`
214
283
  #
215
- # @param schema_object [#to_hash, Boolean, JSI::Schema] an object to be instantiated as a schema.
216
- # if it's already a JSI::Schema, it is returned as-is.
217
- # @param uri (see DescribesSchema#new_schema)
218
- # @param default_metaschema [#new_schema] the metaschema to use if the schema_object does not have
219
- # a '$schema' property. this may be a metaschema or a metaschema's schema module
220
- # (e.g. `JSI::JSONSchemaOrgDraft07`).
221
- # @return [JSI::Schema] a JSI::Schema representing the given schema_object
222
- def new_schema(schema_object, default_metaschema: nil, **kw)
284
+ # @param schema_content (see Schema::MetaSchema#new_schema)
285
+ # @param default_metaschema [Schema::MetaSchema, SchemaModule::MetaSchemaModule, #to_str]
286
+ # Indicates the meta-schema to use if the given `schema_content` does not have a `$schema` property.
287
+ # This may be a meta-schema or a meta-schema's schema module (e.g. `JSI::JSONSchemaDraft07`),
288
+ # or a URI (as would be in a `$schema` keyword).
289
+ # @param uri (see Schema::MetaSchema#new_schema)
290
+ # @param register (see Schema::MetaSchema#new_schema)
291
+ # @param schema_registry (see Schema::MetaSchema#new_schema)
292
+ # @param stringify_symbol_keys (see Schema::MetaSchema#new_schema)
293
+ # @param to_immutable (see Schema::DescribesSchema#new_schema)
294
+ # @yield (see Schema::MetaSchema#new_schema)
295
+ # @return [JSI::Base subclass + JSI::Schema] a JSI which is a {JSI::Schema} whose content comes from
296
+ # the given `schema_content` and whose schemas are inplace applicators of the indicated meta-schema
297
+ def new_schema(schema_content,
298
+ default_metaschema: nil,
299
+ # params of Schema::MetaSchema#new_schema have their default values repeated here. delegating in a splat
300
+ # would remove repetition, but yard doesn't display delegated defaults with its (see X) directive.
301
+ uri: nil,
302
+ register: true,
303
+ schema_registry: JSI.schema_registry,
304
+ stringify_symbol_keys: true,
305
+ to_immutable: DEFAULT_CONTENT_TO_IMMUTABLE,
306
+ &block
307
+ )
308
+ new_schema_params = {
309
+ uri: uri,
310
+ register: register,
311
+ schema_registry: schema_registry,
312
+ stringify_symbol_keys: stringify_symbol_keys,
313
+ to_immutable: to_immutable,
314
+ }
223
315
  default_metaschema_new_schema = -> {
224
- default_metaschema ||= JSI::Schema.default_metaschema
225
- if default_metaschema.nil?
316
+ default_metaschema = if default_metaschema
317
+ Schema.ensure_metaschema(default_metaschema, name: "default_metaschema")
318
+ elsif self.default_metaschema
319
+ self.default_metaschema
320
+ else
226
321
  raise(ArgumentError, [
227
- "when instantiating a schema with no `$schema` property, you must specify the metaschema.",
228
- "you may pass the `default_metaschema` param to this method.",
229
- "JSI::Schema.default_metaschema may be set to an application-wide default metaschema.",
230
- "you may alternatively use new_schema on the appropriate metaschema or its schema module.",
231
- "instantiating schema_object: #{schema_object.pretty_inspect.chomp}",
322
+ "When instantiating a schema with no `$schema` property, you must specify its meta-schema by one of these methods:",
323
+ "- pass the `default_metaschema` param to this method",
324
+ " e.g.: JSI.new_schema(..., default_metaschema: JSI::JSONSchemaDraft07)",
325
+ "- invoke `new_schema` on the appropriate meta-schema or its schema module",
326
+ " e.g.: JSI::JSONSchemaDraft07.new_schema(...)",
327
+ "- set JSI.default_metaschema to an application-wide default meta-schema initially",
328
+ " e.g.: JSI.default_metaschema = JSI::JSONSchemaDraft07",
329
+ "instantiating schema_content: #{schema_content.pretty_inspect.chomp}",
232
330
  ].join("\n"))
233
331
  end
234
- if !default_metaschema.respond_to?(:new_schema)
235
- raise(TypeError, "given default_metaschema does not respond to #new_schema: #{default_metaschema.pretty_inspect.chomp}")
236
- end
237
- default_metaschema.new_schema(schema_object, **kw)
332
+ default_metaschema.new_schema(schema_content, **new_schema_params, &block)
238
333
  }
239
- if schema_object.is_a?(Schema)
240
- schema_object
241
- elsif schema_object.is_a?(JSI::Base)
242
- raise(NotASchemaError, "the given schema_object is a JSI::Base, but is not a JSI::Schema: #{schema_object.pretty_inspect.chomp}")
243
- elsif schema_object.respond_to?(:to_hash)
244
- if schema_object.key?('$schema') && schema_object['$schema'].respond_to?(:to_str)
245
- metaschema = Schema::Ref.new(schema_object['$schema']).deref_schema
246
- unless metaschema.describes_schema?
247
- raise(Schema::ReferenceError, "given schema_object contains a $schema but the resource it identifies does not describe a schema")
334
+ if schema_content.is_a?(Schema)
335
+ raise(TypeError, [
336
+ "Given schema_content is already a JSI::Schema. It cannot be instantiated as the content of a schema.",
337
+ "given: #{schema_content.pretty_inspect.chomp}",
338
+ ].join("\n"))
339
+ elsif schema_content.is_a?(JSI::Base)
340
+ raise(TypeError, [
341
+ "Given schema_content is a JSI::Base. It cannot be instantiated as the content of a schema.",
342
+ "given: #{schema_content.pretty_inspect.chomp}",
343
+ ].join("\n"))
344
+ elsif schema_content.respond_to?(:to_hash)
345
+ id = schema_content['$schema'] || stringify_symbol_keys && schema_content[:'$schema']
346
+ if id
347
+ unless id.respond_to?(:to_str)
348
+ raise(ArgumentError, "given schema_content keyword `$schema` is not a string")
248
349
  end
249
- metaschema.new_schema(schema_object, **kw)
350
+ metaschema = Schema.ensure_metaschema(id, name: '$schema', schema_registry: schema_registry)
351
+ metaschema.new_schema(schema_content, **new_schema_params, &block)
250
352
  else
251
353
  default_metaschema_new_schema.call
252
354
  end
253
- elsif [true, false].include?(schema_object)
254
- default_metaschema_new_schema.call
255
355
  else
256
- raise(TypeError, "cannot instantiate Schema from: #{schema_object.pretty_inspect.chomp}")
356
+ default_metaschema_new_schema.call
257
357
  end
258
358
  end
359
+ end
259
360
 
260
- # @deprecated
261
- alias_method :new, :new_schema
262
-
263
- # @deprecated
264
- alias_method :from_object, :new_schema
361
+ self.default_metaschema = nil
265
362
 
363
+ module Schema
364
+ class << self
266
365
  # ensure the given object is a JSI Schema
267
366
  #
268
367
  # @param schema [Object] the thing the caller wishes to ensure is a Schema
@@ -274,19 +373,25 @@ module JSI
274
373
  if schema.is_a?(Schema)
275
374
  schema
276
375
  else
277
- if reinstantiate_as
376
+ if reinstantiate_as && schema.is_a?(JSI::Base)
278
377
  # TODO warn; behavior is undefined and I hate this implementation
279
378
 
280
- result_schema_schemas = schema.jsi_schemas + reinstantiate_as
379
+ result_schema_indicated_schemas = SchemaSet.new(schema.jsi_indicated_schemas + reinstantiate_as)
380
+ result_schema_applied_schemas = result_schema_indicated_schemas.inplace_applicator_schemas(schema.jsi_node_content)
281
381
 
282
- result_schema_class = JSI::SchemaClasses.class_for_schemas(result_schema_schemas)
382
+ result_schema_class = JSI::SchemaClasses.class_for_schemas(result_schema_applied_schemas,
383
+ includes: SchemaClasses.includes_for(schema.jsi_node_content),
384
+ mutable: schema.jsi_mutable?,
385
+ )
283
386
 
284
- result_schema_class.new(Base::NOINSTANCE,
285
- jsi_document: schema.jsi_document,
387
+ result_schema_class.new(schema.jsi_document,
286
388
  jsi_ptr: schema.jsi_ptr,
287
- jsi_root_node: schema.jsi_root_node,
389
+ jsi_indicated_schemas: result_schema_indicated_schemas,
288
390
  jsi_schema_base_uri: schema.jsi_schema_base_uri,
289
391
  jsi_schema_resource_ancestors: schema.jsi_schema_resource_ancestors,
392
+ jsi_schema_registry: schema.jsi_schema_registry,
393
+ jsi_content_to_immutable: schema.jsi_content_to_immutable,
394
+ jsi_root_node: schema.jsi_ptr.root? ? nil : schema.jsi_root_node, # bad
290
395
  )
291
396
  else
292
397
  raise(NotASchemaError, [
@@ -296,21 +401,62 @@ module JSI
296
401
  end
297
402
  end
298
403
  end
404
+
405
+ # Ensures the given param identifies a meta-schema and returns that meta-schema.
406
+ #
407
+ # @api private
408
+ # @param metaschema [Schema::MetaSchema, SchemaModule::MetaSchemaModule, #to_str]
409
+ # @raise [TypeError] if the param does not indicate a meta-schema
410
+ # @return [Base + Schema + Schema::MetaSchema]
411
+ def ensure_metaschema(metaschema, name: nil, schema_registry: JSI.schema_registry)
412
+ if metaschema.respond_to?(:to_str)
413
+ schema = Schema::Ref.new(metaschema, schema_registry: schema_registry).deref_schema
414
+ if !schema.describes_schema?
415
+ raise(TypeError, [name, "URI indicates a schema that is not a meta-schema: #{metaschema.pretty_inspect.chomp}"].compact.join(" "))
416
+ end
417
+ schema
418
+ elsif metaschema.is_a?(SchemaModule::MetaSchemaModule)
419
+ metaschema.schema
420
+ elsif metaschema.is_a?(Schema::MetaSchema)
421
+ metaschema
422
+ else
423
+ raise(TypeError, "#{name || "param"} does not indicate a meta-schema: #{metaschema.pretty_inspect.chomp}")
424
+ end
425
+ end
426
+ end
427
+
428
+ if Util::LAST_ARGUMENT_AS_KEYWORD_PARAMETERS
429
+ def initialize(*)
430
+ super
431
+ jsi_schema_initialize
432
+ end
433
+ else
434
+ def initialize(*, **)
435
+ super
436
+ jsi_schema_initialize
437
+ end
299
438
  end
300
439
 
301
440
  # the underlying JSON data used to instantiate this JSI::Schema.
302
- # this is an alias for PathedNode#jsi_node_content, named for clarity in the context of working with
441
+ # this is an alias for {Base#jsi_node_content}, named for clarity in the context of working with
303
442
  # a schema.
304
443
  def schema_content
305
444
  jsi_node_content
306
445
  end
307
446
 
447
+ # does this schema contain the given keyword?
448
+ # @return [Boolean]
449
+ def keyword?(keyword)
450
+ schema_content = jsi_node_content
451
+ schema_content.respond_to?(:to_hash) && schema_content.key?(keyword)
452
+ end
453
+
308
454
  # the URI of this schema, calculated from our `#id`, resolved against our `#jsi_schema_base_uri`
309
455
  # @return [Addressable::URI, nil]
310
456
  def schema_absolute_uri
311
457
  if respond_to?(:id_without_fragment) && id_without_fragment
312
458
  if jsi_schema_base_uri
313
- Addressable::URI.parse(jsi_schema_base_uri).join(id_without_fragment)
459
+ jsi_schema_base_uri.join(id_without_fragment).freeze
314
460
  elsif id_without_fragment.absolute?
315
461
  id_without_fragment
316
462
  else
@@ -321,7 +467,7 @@ module JSI
321
467
  end
322
468
 
323
469
  # a nonrelative URI which refers to this schema.
324
- # nil if no parent of this schema defines an id.
470
+ # `nil` if no ancestor of this schema defines an id.
325
471
  # see {#schema_uris} for all URIs known to refer to this schema.
326
472
  # @return [Addressable::URI, nil]
327
473
  def schema_uri
@@ -331,9 +477,11 @@ module JSI
331
477
  # nonrelative URIs (that is, absolute, but possibly with a fragment) which refer to this schema
332
478
  # @return [Array<Addressable::URI>]
333
479
  def schema_uris
334
- jsi_memoize(:schema_uris) do
480
+ @schema_uris_map[]
481
+ end
482
+
483
+ private def schema_uris_compute(**_) # TODO remove **_ eventually (keyword argument compatibility)
335
484
  each_schema_uri.to_a
336
- end
337
485
  end
338
486
 
339
487
  # see {#schema_uris}
@@ -344,22 +492,22 @@ module JSI
344
492
 
345
493
  yield schema_absolute_uri if schema_absolute_uri
346
494
 
347
- parent_schemas = jsi_subschema_resource_ancestors.reverse_each.select do |resource|
348
- resource.is_a?(Schema) && resource.schema_absolute_uri
495
+ ancestor_schemas = jsi_subschema_resource_ancestors.reverse_each.select do |resource|
496
+ resource.schema_absolute_uri
349
497
  end
350
498
 
351
- anchored = self.anchor
352
- parent_schemas.each do |parent_schema|
499
+ anchored = respond_to?(:anchor) ? anchor : nil
500
+ ancestor_schemas.each do |ancestor_schema|
353
501
  if anchored
354
- if parent_schema.jsi_anchor_subschema(anchor) == self
355
- yield parent_schema.schema_absolute_uri.merge(fragment: anchor)
502
+ if ancestor_schema.jsi_anchor_subschema(anchor) == self
503
+ yield(ancestor_schema.schema_absolute_uri.merge(fragment: anchor).freeze)
356
504
  else
357
505
  anchored = false
358
506
  end
359
507
  end
360
508
 
361
- relative_ptr = self.jsi_ptr.ptr_relative_to(parent_schema.jsi_ptr)
362
- yield parent_schema.schema_absolute_uri.merge(fragment: relative_ptr.fragment)
509
+ relative_ptr = jsi_ptr.relative_to(ancestor_schema.jsi_ptr)
510
+ yield(ancestor_schema.schema_absolute_uri.merge(fragment: relative_ptr.fragment).freeze)
363
511
  end
364
512
 
365
513
  nil
@@ -368,9 +516,6 @@ module JSI
368
516
  # a module which extends all instances of this schema. this may be opened by the application to add
369
517
  # methods to schema instances.
370
518
  #
371
- # this module includes accessor methods for object property names this schema
372
- # describes (see {#described_object_property_names}). these accessors wrap {Base#[]} and {Base#[]=}.
373
- #
374
519
  # some functionality is also defined on the module itself (its singleton class, not for its instances):
375
520
  #
376
521
  # - the module is extended with {JSI::SchemaModule}, which defines .new_jsi to instantiate instances
@@ -380,7 +525,7 @@ module JSI
380
525
  # as `schema.items.jsi_schema_module`.
381
526
  # - method .schema which returns this schema.
382
527
  #
383
- # @return [Module]
528
+ # @return [SchemaModule]
384
529
  def jsi_schema_module
385
530
  JSI::SchemaClasses.module_for_schema(self)
386
531
  end
@@ -395,56 +540,79 @@ module JSI
395
540
  jsi_schema_module.module_exec(*a, **kw, &block)
396
541
  end
397
542
 
398
- # @private @deprecated
399
- def jsi_schema_class
400
- JSI::SchemaClasses.class_for_schemas(SchemaSet[self])
401
- end
402
-
403
- # instantiates the given instance as a JSI::Base class for schemas matched from this schema to the
404
- # instance.
543
+ # Instantiates a new JSI whose content comes from the given `instance` param.
544
+ # This schema indicates the schemas of the JSI - its schemas are inplace
545
+ # applicators of this schema which apply to the given instance.
405
546
  #
406
- # @param instance [Object] the JSON Schema instance to be represented as a JSI
407
- # @param uri (see SchemaSet#new_jsi)
408
- # @return [JSI::Base subclass] a JSI whose instance is the given instance and whose schemas are matched
409
- # from this schema.
410
- def new_jsi(instance,
411
- **kw
412
- )
547
+ # @param (see SchemaSet#new_jsi)
548
+ # @return [JSI::Base subclass] a JSI whose content comes from the given instance and whose schemas are
549
+ # inplace applicators of this schema.
550
+ def new_jsi(instance, **kw)
413
551
  SchemaSet[self].new_jsi(instance, **kw)
414
552
  end
415
553
 
416
- # does this schema itself describe a schema?
554
+ # @param keyword schema keyword e.g. "$ref", "$schema"
555
+ # @return [Schema::Ref]
556
+ def schema_ref(keyword = "$ref")
557
+ raise(ArgumentError, "keyword not present: #{keyword}") unless keyword?(keyword)
558
+ @schema_ref_map[keyword: keyword, value: schema_content[keyword]]
559
+ end
560
+
561
+ # Does this schema itself describe a schema? I.e. is this schema a meta-schema?
417
562
  # @return [Boolean]
418
563
  def describes_schema?
419
- jsi_schema_instance_modules.any? { |m| m <= JSI::Schema }
564
+ jsi_schema_module <= JSI::Schema || false
420
565
  end
421
566
 
422
- # modules to apply to instances described by this schema. these modules are included
423
- # on this schema's {#jsi_schema_module}
424
- # @return [Set<Module>]
425
- def jsi_schema_instance_modules
426
- return @jsi_schema_instance_modules if instance_variable_defined?(:@jsi_schema_instance_modules)
427
- return Set[].freeze
567
+ # Is this a JSI Schema?
568
+ # @return [Boolean]
569
+ def jsi_is_schema?
570
+ true
428
571
  end
429
572
 
430
- # see {#jsi_schema_instance_modules}
573
+ # Indicates that this schema describes schemas, i.e. it is a meta-schema.
574
+ # this schema is extended with {Schema::MetaSchema} and its {#jsi_schema_module} is extended
575
+ # with {SchemaModule::MetaSchemaModule}, and the JSI Schema Module will include
576
+ # JSI::Schema and the given modules.
431
577
  #
578
+ # @param schema_implementation_modules [Enumerable<Module>] modules which implement the functionality of
579
+ # the schema to extend schemas described by this schema.
432
580
  # @return [void]
433
- def jsi_schema_instance_modules=(jsi_schema_instance_modules)
434
- @jsi_schema_instance_modules = Util.ensure_module_set(jsi_schema_instance_modules)
581
+ def describes_schema!(schema_implementation_modules)
582
+ schema_implementation_modules = Util.ensure_module_set(schema_implementation_modules)
583
+
584
+ if describes_schema?
585
+ # this schema, or one equal to it, has already had describes_schema! called on it.
586
+ # this is to be avoided, but is not particularly a problem.
587
+ # it is a bug if it was called different times with different schema_implementation_modules, though.
588
+ unless jsi_schema_module.schema_implementation_modules == schema_implementation_modules
589
+ raise(ArgumentError, "this schema already describes a schema with different schema_implementation_modules")
590
+ end
591
+ else
592
+ jsi_schema_module.include(Schema)
593
+ schema_implementation_modules.each do |mod|
594
+ jsi_schema_module.include(mod)
595
+ end
596
+ jsi_schema_module.extend(SchemaModule::MetaSchemaModule)
597
+ end
598
+
599
+ @schema_implementation_modules = schema_implementation_modules
600
+ extend(Schema::MetaSchema)
601
+
602
+ nil
435
603
  end
436
604
 
437
605
  # a resource containing this schema.
438
606
  #
439
- # if any parent, or this schema itself, is a schema with an absolute uri (see #schema_absolute_uri),
607
+ # If any ancestor, or this schema itself, is a schema with an absolute uri (see {#schema_absolute_uri}),
440
608
  # the resource root is the closest schema with an absolute uri.
441
609
  #
442
- # if no parent schema has an absolute uri, the schema_resource_root is the root of the document
443
- # (our #jsi_root_node). in this case, the resource root may or may not be a schema itself.
610
+ # If no ancestor schema has an absolute uri, the schema_resource_root is the {Base#jsi_root_node document's root node}.
611
+ # In this case, the resource root may or may not be a schema itself.
444
612
  #
445
613
  # @return [JSI::Base] resource containing this schema
446
614
  def schema_resource_root
447
- jsi_subschema_resource_ancestors.reverse_each.detect(&:schema_resource_root?) || jsi_root_node
615
+ jsi_subschema_resource_ancestors.last || jsi_root_node
448
616
  end
449
617
 
450
618
  # is this schema the root of a schema resource?
@@ -458,73 +626,95 @@ module JSI
458
626
  # @param subptr [JSI::Ptr, #to_ary] a relative pointer, or array of tokens, pointing to the subschema
459
627
  # @return [JSI::Schema] the subschema at the location indicated by subptr. self if subptr is empty.
460
628
  def subschema(subptr)
461
- subschema_map[Ptr.ary_ptr(subptr)]
629
+ @subschema_map[subptr: Ptr.ary_ptr(subptr)]
462
630
  end
463
631
 
464
- private
465
-
466
- def subschema_map
467
- jsi_memomap(:subschema) do |subptr|
468
- if is_a?(MetaschemaNode::BootstrapSchema)
469
- self.class.new(
470
- jsi_document,
471
- jsi_ptr: jsi_ptr + subptr,
472
- jsi_schema_base_uri: jsi_resource_ancestor_uri,
473
- )
474
- else
475
- Schema.ensure_schema(subptr.evaluate(self, as_jsi: true), msg: [
632
+ private def subschema_compute(subptr: )
633
+ Schema.ensure_schema(jsi_descendent_node(subptr), msg: [
476
634
  "subschema is not a schema at pointer: #{subptr.pointer}"
477
635
  ])
478
- end
479
- end
480
636
  end
481
637
 
482
- public
483
-
484
- # a schema in the same schema resource as this one (see #schema_resource_root) at the given
638
+ # a schema in the same schema resource as this one (see {#schema_resource_root}) at the given
485
639
  # pointer relative to the root of the schema resource.
486
640
  #
487
641
  # @param ptr [JSI::Ptr, #to_ary] a pointer to a schema from our schema resource root
488
642
  # @return [JSI::Schema] the schema pointed to by ptr
489
643
  def resource_root_subschema(ptr)
490
- resource_root_subschema_map[Ptr.ary_ptr(ptr)]
644
+ @resource_root_subschema_map[ptr: Ptr.ary_ptr(ptr)]
491
645
  end
492
646
 
493
- private
494
-
495
- def resource_root_subschema_map
496
- jsi_memomap(:resource_root_subschema_map) do |ptr|
497
- schema = self
498
- if schema.is_a?(MetaschemaNode::BootstrapSchema)
499
- # BootstrapSchema does not track jsi_schema_resource_ancestors used by #schema_resource_root;
500
- # resource_root_subschema is always relative to the document root.
501
- # BootstrapSchema also does not implement jsi_root_node or #[]. we instantiate the ptr directly
502
- # rather than as a subschema from the root.
503
- schema.class.new(
504
- schema.jsi_document,
505
- jsi_ptr: ptr,
506
- jsi_schema_base_uri: nil,
507
- )
508
- else
509
- resource_root = schema.schema_resource_root
510
- Schema.ensure_schema(ptr.evaluate(resource_root, as_jsi: true),
511
- msg: [
512
- "subschema is not a schema at pointer: #{ptr.pointer}"
513
- ],
514
- reinstantiate_as: schema.jsi_schemas.select(&:describes_schema?)
647
+ private def resource_root_subschema_compute(ptr: )
648
+ Schema.ensure_schema(schema_resource_root.jsi_descendent_node(ptr),
649
+ reinstantiate_as: jsi_schemas.select(&:describes_schema?)
515
650
  )
516
- end
651
+ end
652
+
653
+ # a set of inplace applicator schemas of this schema (from $ref, allOf, etc.) which apply to the
654
+ # given instance.
655
+ #
656
+ # the returned set will contain this schema itself, unless this schema contains a $ref keyword.
657
+ #
658
+ # @param instance [Object] the instance to check any applicators against
659
+ # @return [JSI::SchemaSet] matched applicator schemas
660
+ def inplace_applicator_schemas(instance)
661
+ SchemaSet.new(each_inplace_applicator_schema(instance))
662
+ end
663
+
664
+ # yields each inplace applicator schema which applies to the given instance.
665
+ #
666
+ # @param instance (see #inplace_applicator_schemas)
667
+ # @param visited_refs [Enumerable<JSI::Schema::Ref>]
668
+ # @yield [JSI::Schema]
669
+ # @return [nil, Enumerator] an Enumerator if invoked without a block; otherwise nil
670
+ def each_inplace_applicator_schema(
671
+ instance,
672
+ visited_refs: Util::EMPTY_ARY,
673
+ &block
674
+ )
675
+ return to_enum(__method__, instance, visited_refs: visited_refs) unless block
676
+
677
+ catch(:jsi_application_done) do
678
+ internal_inplace_applicate_keywords(instance, visited_refs, &block)
517
679
  end
680
+
681
+ nil
518
682
  end
519
683
 
520
- public
684
+ # a set of child applicator subschemas of this schema which apply to the child of the given instance
685
+ # on the given token.
686
+ #
687
+ # @param token [Object] the array index or object property name for the child instance
688
+ # @param instance [Object] the instance to check any child applicators against
689
+ # @return [JSI::SchemaSet] child applicator subschemas of this schema for the given token
690
+ # of the instance
691
+ def child_applicator_schemas(token, instance)
692
+ SchemaSet.new(each_child_applicator_schema(token, instance))
693
+ end
694
+
695
+ # yields each child applicator subschema (from properties, items, etc.) which applies to the child of
696
+ # the given instance on the given token.
697
+ #
698
+ # @param (see #child_applicator_schemas)
699
+ # @yield [JSI::Schema]
700
+ # @return [nil, Enumerator] an Enumerator if invoked without a block; otherwise nil
701
+ def each_child_applicator_schema(token, instance, &block)
702
+ return to_enum(__method__, token, instance) unless block
703
+
704
+ internal_child_applicate_keywords(token, instance, &block)
705
+
706
+ nil
707
+ end
521
708
 
522
709
  # any object property names this schema indicates may be present on its instances.
523
710
  # this includes any keys of this schema's "properties" object and any entries of this schema's
524
711
  # array of "required" property keys.
525
712
  # @return [Set]
526
713
  def described_object_property_names
527
- jsi_memoize(:described_object_property_names) do
714
+ @described_object_property_names_map[]
715
+ end
716
+
717
+ private def described_object_property_names_compute(**_) # TODO remove **_ eventually (keyword argument compatibility)
528
718
  Set.new.tap do |property_names|
529
719
  if schema_content.respond_to?(:to_hash) && schema_content['properties'].respond_to?(:to_hash)
530
720
  property_names.merge(schema_content['properties'].keys)
@@ -533,7 +723,6 @@ module JSI
533
723
  property_names.merge(schema_content['required'].to_ary)
534
724
  end
535
725
  end.freeze
536
- end
537
726
  end
538
727
 
539
728
  # validates the given instance against this schema
@@ -541,7 +730,7 @@ module JSI
541
730
  # @param instance [Object] the instance to validate against this schema
542
731
  # @return [JSI::Validation::Result]
543
732
  def instance_validate(instance)
544
- if instance.is_a?(JSI::PathedNode)
733
+ if instance.is_a?(SchemaAncestorNode)
545
734
  instance_ptr = instance.jsi_ptr
546
735
  instance_document = instance.jsi_document
547
736
  else
@@ -555,52 +744,80 @@ module JSI
555
744
  # @param instance [Object] the instance to validate against this schema
556
745
  # @return [Boolean]
557
746
  def instance_valid?(instance)
558
- if instance.is_a?(JSI::PathedNode)
747
+ if instance.is_a?(SchemaAncestorNode)
559
748
  instance = instance.jsi_node_content
560
749
  end
561
750
  internal_validate_instance(Ptr[], instance, validate_only: true).valid?
562
751
  end
563
752
 
753
+ # validates the given instance against this schema
754
+ #
564
755
  # @private
565
- def fully_validate_instance(other_instance, errors_as_objects: false)
566
- raise(NotImplementedError, "Schema#fully_validate_instance removed: see new validation interface Schema#instance_validate")
567
- end
568
-
569
- # @private
570
- def validate_instance(other_instance)
571
- raise(NotImplementedError, "Schema#validate_instance renamed: see Schema#instance_valid?")
572
- end
573
-
574
- # @private
575
- def validate_instance!(other_instance)
576
- raise(NotImplementedError, "Schema#validate_instance! removed")
577
- end
578
-
579
- # @private
580
- def fully_validate_schema(errors_as_objects: false)
581
- raise(NotImplementedError, "Schema#fully_validate_schema removed: use validation interface Base#jsi_validate on the schema")
582
- end
583
-
584
- # @private
585
- def validate_schema
586
- raise(NotImplementedError, "Schema#validate_schema removed: use validation interface Base#jsi_valid? on the schema")
587
- end
756
+ # @param instance_ptr [JSI::Ptr] a pointer to the instance to validate against the schema, in the instance_document
757
+ # @param instance_document [#to_hash, #to_ary, Object] document containing the instance instance_ptr pointer points to
758
+ # @param validate_only [Boolean] whether to return a full schema validation result or a simple, validation-only result
759
+ # @param visited_refs [Enumerable<JSI::Schema::Ref>]
760
+ # @return [JSI::Validation::Result]
761
+ def internal_validate_instance(
762
+ instance_ptr,
763
+ instance_document,
764
+ visited_refs: Util::EMPTY_ARY,
765
+ validate_only: false
766
+ )
767
+ if validate_only
768
+ result = JSI::Validation::VALID
769
+ else
770
+ result = JSI::Validation::FullResult.new
771
+ end
772
+ result_builder = result.class::Builder.new(
773
+ result: result,
774
+ schema: self,
775
+ instance_ptr: instance_ptr,
776
+ instance_document: instance_document,
777
+ validate_only: validate_only,
778
+ visited_refs: visited_refs,
779
+ )
588
780
 
589
- # @private
590
- def validate_schema!
591
- raise(NotImplementedError, "Schema#validate_schema! removed")
781
+ catch(:jsi_validation_result) do
782
+ # note: true/false are not valid as schemas in draft 4; they are only values of
783
+ # additionalProperties / additionalItems. since their behavior is undefined, though,
784
+ # it's fine for them to behave the same as boolean schemas in later drafts.
785
+ # I don't care about draft 4 to implement a different structuring for that.
786
+ if schema_content == true
787
+ # noop
788
+ elsif schema_content == false
789
+ result_builder.validate(false, 'instance is not valid against `false` schema')
790
+ elsif schema_content.respond_to?(:to_hash)
791
+ internal_validate_keywords(result_builder)
792
+ else
793
+ result_builder.schema_error('schema is not a boolean or a JSON object')
794
+ end
795
+ result
796
+ end.freeze
592
797
  end
593
798
 
594
799
  # schema resources which are ancestors of any subschemas below this schema.
595
800
  # this may include this schema if this is a schema resource root.
596
- # @private
801
+ # @api private
597
802
  # @return [Array<JSI::Schema>]
598
803
  def jsi_subschema_resource_ancestors
599
804
  if schema_resource_root?
600
- jsi_schema_resource_ancestors + [self]
805
+ jsi_schema_resource_ancestors.dup.push(self).freeze
601
806
  else
602
807
  jsi_schema_resource_ancestors
603
808
  end
604
809
  end
810
+
811
+ private
812
+
813
+ def jsi_schema_initialize
814
+ @schema_ref_map = jsi_memomap(key_by: proc { |i| i[:keyword] }) do |keyword: , value: |
815
+ Schema::Ref.new(value, ref_schema: self)
816
+ end
817
+ @schema_uris_map = jsi_memomap(&method(:schema_uris_compute))
818
+ @subschema_map = jsi_memomap(&method(:subschema_compute))
819
+ @resource_root_subschema_map = jsi_memomap(&method(:resource_root_subschema_compute))
820
+ @described_object_property_names_map = jsi_memomap(&method(:described_object_property_names_compute))
821
+ end
605
822
  end
606
823
  end