jsi 0.6.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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