jsi 0.4.0 → 0.6.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.
- checksums.yaml +4 -4
- data/.yardopts +1 -1
- data/CHANGELOG.md +15 -0
- data/README.md +105 -38
- data/lib/jsi/base.rb +349 -155
- data/lib/jsi/jsi_coder.rb +5 -4
- data/lib/jsi/metaschema_node/bootstrap_schema.rb +100 -0
- data/lib/jsi/metaschema_node.rb +156 -129
- data/lib/jsi/pathed_node.rb +47 -49
- data/lib/jsi/ptr.rb +292 -0
- data/lib/jsi/schema/application/child_application/contains.rb +16 -0
- data/lib/jsi/schema/application/child_application/draft04.rb +22 -0
- data/lib/jsi/schema/application/child_application/draft06.rb +29 -0
- data/lib/jsi/schema/application/child_application/draft07.rb +29 -0
- data/lib/jsi/schema/application/child_application/items.rb +18 -0
- data/lib/jsi/schema/application/child_application/properties.rb +25 -0
- data/lib/jsi/schema/application/child_application.rb +40 -0
- data/lib/jsi/schema/application/draft04.rb +8 -0
- data/lib/jsi/schema/application/draft06.rb +8 -0
- data/lib/jsi/schema/application/draft07.rb +8 -0
- data/lib/jsi/schema/application/inplace_application/dependencies.rb +28 -0
- data/lib/jsi/schema/application/inplace_application/draft04.rb +26 -0
- data/lib/jsi/schema/application/inplace_application/draft06.rb +27 -0
- data/lib/jsi/schema/application/inplace_application/draft07.rb +33 -0
- data/lib/jsi/schema/application/inplace_application/ifthenelse.rb +20 -0
- data/lib/jsi/schema/application/inplace_application/ref.rb +18 -0
- data/lib/jsi/schema/application/inplace_application/someof.rb +29 -0
- data/lib/jsi/schema/application/inplace_application.rb +46 -0
- data/lib/jsi/schema/application.rb +12 -0
- data/lib/jsi/schema/draft04.rb +14 -0
- data/lib/jsi/schema/draft06.rb +14 -0
- data/lib/jsi/schema/draft07.rb +14 -0
- data/lib/jsi/schema/issue.rb +36 -0
- data/lib/jsi/schema/ref.rb +159 -0
- data/lib/jsi/schema/schema_ancestor_node.rb +119 -0
- data/lib/jsi/schema/validation/array.rb +69 -0
- data/lib/jsi/schema/validation/const.rb +20 -0
- data/lib/jsi/schema/validation/contains.rb +25 -0
- data/lib/jsi/schema/validation/core.rb +39 -0
- data/lib/jsi/schema/validation/dependencies.rb +49 -0
- data/lib/jsi/schema/validation/draft04/minmax.rb +91 -0
- data/lib/jsi/schema/validation/draft04.rb +112 -0
- data/lib/jsi/schema/validation/draft06.rb +122 -0
- data/lib/jsi/schema/validation/draft07.rb +159 -0
- data/lib/jsi/schema/validation/enum.rb +25 -0
- data/lib/jsi/schema/validation/ifthenelse.rb +46 -0
- data/lib/jsi/schema/validation/items.rb +54 -0
- data/lib/jsi/schema/validation/not.rb +20 -0
- data/lib/jsi/schema/validation/numeric.rb +121 -0
- data/lib/jsi/schema/validation/object.rb +45 -0
- data/lib/jsi/schema/validation/pattern.rb +34 -0
- data/lib/jsi/schema/validation/properties.rb +101 -0
- data/lib/jsi/schema/validation/property_names.rb +32 -0
- data/lib/jsi/schema/validation/ref.rb +40 -0
- data/lib/jsi/schema/validation/required.rb +27 -0
- data/lib/jsi/schema/validation/someof.rb +90 -0
- data/lib/jsi/schema/validation/string.rb +47 -0
- data/lib/jsi/schema/validation/type.rb +49 -0
- data/lib/jsi/schema/validation.rb +51 -0
- data/lib/jsi/schema.rb +486 -133
- data/lib/jsi/schema_classes.rb +157 -42
- data/lib/jsi/schema_registry.rb +141 -0
- data/lib/jsi/schema_set.rb +141 -0
- data/lib/jsi/simple_wrap.rb +2 -2
- data/lib/jsi/typelike_modules.rb +52 -37
- data/lib/jsi/util/attr_struct.rb +106 -0
- data/lib/jsi/util.rb +141 -25
- data/lib/jsi/validation/error.rb +34 -0
- data/lib/jsi/validation/result.rb +210 -0
- data/lib/jsi/validation.rb +15 -0
- data/lib/jsi/version.rb +3 -1
- data/lib/jsi.rb +55 -9
- data/lib/schemas/json-schema.org/draft-04/schema.rb +8 -3
- data/lib/schemas/json-schema.org/draft-06/schema.rb +8 -3
- data/lib/schemas/json-schema.org/draft-07/schema.rb +12 -0
- data/readme.rb +138 -0
- data/{resources}/schemas/json-schema.org/draft-04/schema.json +149 -0
- data/{resources}/schemas/json-schema.org/draft-06/schema.json +154 -0
- data/{resources}/schemas/json-schema.org/draft-07/schema.json +168 -0
- metadata +69 -118
- data/.simplecov +0 -3
- data/Rakefile.rb +0 -9
- data/jsi.gemspec +0 -28
- data/lib/jsi/base/to_rb.rb +0 -128
- data/lib/jsi/json/node.rb +0 -203
- data/lib/jsi/json/pointer.rb +0 -419
- data/lib/jsi/json-schema-fragments.rb +0 -61
- data/lib/jsi/json.rb +0 -10
- data/resources/icons/AGPL-3.0.png +0 -0
- data/test/base_array_test.rb +0 -323
- data/test/base_hash_test.rb +0 -337
- data/test/base_test.rb +0 -486
- data/test/jsi_coder_test.rb +0 -85
- data/test/jsi_json_arraynode_test.rb +0 -150
- data/test/jsi_json_hashnode_test.rb +0 -132
- data/test/jsi_json_node_test.rb +0 -257
- data/test/jsi_json_pointer_test.rb +0 -102
- data/test/jsi_test.rb +0 -11
- data/test/jsi_typelike_as_json_test.rb +0 -53
- data/test/metaschema_node_test.rb +0 -19
- data/test/schema_module_test.rb +0 -21
- data/test/schema_test.rb +0 -208
- data/test/spreedly_openapi_test.rb +0 -8
- data/test/test_helper.rb +0 -97
- data/test/util_test.rb +0 -62
data/lib/jsi/schema.rb
CHANGED
|
@@ -6,6 +6,18 @@ module JSI
|
|
|
6
6
|
# the content of an instance which is a JSI::Schema (referred to in this context as schema_content) is
|
|
7
7
|
# expected to be a Hash (JSON object) or a Boolean.
|
|
8
8
|
module Schema
|
|
9
|
+
autoload :Application, 'jsi/schema/application'
|
|
10
|
+
autoload :Validation, 'jsi/schema/validation'
|
|
11
|
+
autoload :Issue, 'jsi/schema/issue'
|
|
12
|
+
|
|
13
|
+
autoload :SchemaAncestorNode, 'jsi/schema/schema_ancestor_node'
|
|
14
|
+
|
|
15
|
+
autoload :Ref, 'jsi/schema/ref'
|
|
16
|
+
|
|
17
|
+
autoload :Draft04, 'jsi/schema/draft04'
|
|
18
|
+
autoload :Draft06, 'jsi/schema/draft06'
|
|
19
|
+
autoload :Draft07, 'jsi/schema/draft07'
|
|
20
|
+
|
|
9
21
|
class Error < StandardError
|
|
10
22
|
end
|
|
11
23
|
|
|
@@ -13,6 +25,109 @@ module JSI
|
|
|
13
25
|
class NotASchemaError < Error
|
|
14
26
|
end
|
|
15
27
|
|
|
28
|
+
# an exception raised when we are unable to resolve a schema reference
|
|
29
|
+
class ReferenceError < StandardError
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# extends any schema which uses the keyword '$id' to identify its canonical URI
|
|
33
|
+
module BigMoneyId
|
|
34
|
+
# the contents of a $id keyword whose value is a string, or nil
|
|
35
|
+
# @return [#to_str, nil]
|
|
36
|
+
def id
|
|
37
|
+
if schema_content.respond_to?(:to_hash) && schema_content['$id'].respond_to?(:to_str)
|
|
38
|
+
schema_content['$id']
|
|
39
|
+
else
|
|
40
|
+
nil
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# extends any schema which uses the keyword 'id' to identify its canonical URI
|
|
46
|
+
module OldId
|
|
47
|
+
# the contents of an `id` keyword whose value is a string, or nil
|
|
48
|
+
# @return [#to_str, nil]
|
|
49
|
+
def id
|
|
50
|
+
if schema_content.respond_to?(:to_hash) && schema_content['id'].respond_to?(:to_str)
|
|
51
|
+
schema_content['id']
|
|
52
|
+
else
|
|
53
|
+
nil
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# extends any schema which defines an anchor as a URI fragment in the schema id
|
|
59
|
+
module IdWithAnchor
|
|
60
|
+
# a URI for the schema's id, unless the id defines an anchor in its
|
|
61
|
+
# fragment. nil if the schema defines no id.
|
|
62
|
+
# @return [Addressable::URI, nil]
|
|
63
|
+
def id_without_fragment
|
|
64
|
+
if id
|
|
65
|
+
id_uri = Addressable::URI.parse(id)
|
|
66
|
+
if id_uri.merge(fragment: nil).empty?
|
|
67
|
+
# fragment-only id is just an anchor
|
|
68
|
+
# e.g. #foo
|
|
69
|
+
nil
|
|
70
|
+
elsif id_uri.fragment == nil
|
|
71
|
+
# no fragment
|
|
72
|
+
# e.g. http://localhost:1234/bar
|
|
73
|
+
id_uri
|
|
74
|
+
elsif id_uri.fragment == ''
|
|
75
|
+
# empty fragment
|
|
76
|
+
# e.g. http://json-schema.org/draft-07/schema#
|
|
77
|
+
id_uri.merge(fragment: nil)
|
|
78
|
+
elsif jsi_schema_base_uri && jsi_schema_base_uri.join(id_uri).merge(fragment: nil) == jsi_schema_base_uri
|
|
79
|
+
# the id, resolved against the base uri, consists of the base uri plus an anchor fragment.
|
|
80
|
+
# so there's no non-fragment id.
|
|
81
|
+
# e.g. base uri is http://localhost:1234/bar
|
|
82
|
+
# and id is http://localhost:1234/bar#foo
|
|
83
|
+
nil
|
|
84
|
+
else
|
|
85
|
+
# e.g. http://localhost:1234/bar#foo
|
|
86
|
+
id_uri.merge(fragment: nil)
|
|
87
|
+
end
|
|
88
|
+
else
|
|
89
|
+
nil
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# an anchor defined by a non-empty fragment in the id uri
|
|
94
|
+
# @return [String]
|
|
95
|
+
def anchor
|
|
96
|
+
if id
|
|
97
|
+
id_uri = Addressable::URI.parse(id)
|
|
98
|
+
if id_uri.fragment == ''
|
|
99
|
+
nil
|
|
100
|
+
else
|
|
101
|
+
id_uri.fragment
|
|
102
|
+
end
|
|
103
|
+
else
|
|
104
|
+
nil
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# @private
|
|
110
|
+
module IntegerAllows0Fraction
|
|
111
|
+
# is `value` an integer?
|
|
112
|
+
# @private
|
|
113
|
+
# @param value
|
|
114
|
+
# @return [Boolean]
|
|
115
|
+
def internal_integer?(value)
|
|
116
|
+
value.is_a?(Integer) || (value.is_a?(Numeric) && value % 1.0 == 0.0)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# @private
|
|
121
|
+
module IntegerDisallows0Fraction
|
|
122
|
+
# is `value` an integer?
|
|
123
|
+
# @private
|
|
124
|
+
# @param value
|
|
125
|
+
# @return [Boolean]
|
|
126
|
+
def internal_integer?(value)
|
|
127
|
+
value.is_a?(Integer)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
16
131
|
# JSI::Schema::DescribesSchema: a schema which describes another schema. this module
|
|
17
132
|
# extends a JSI::Schema instance and indicates that JSIs which instantiate the schema
|
|
18
133
|
# are themselves also schemas.
|
|
@@ -20,233 +135,471 @@ module JSI
|
|
|
20
135
|
# examples of a schema which describes a schema include the draft JSON Schema metaschemas and
|
|
21
136
|
# the OpenAPI schema definition which describes "A deterministic version of a JSON Schema object."
|
|
22
137
|
module DescribesSchema
|
|
138
|
+
# instantiates the given schema content as a JSI Schema.
|
|
139
|
+
#
|
|
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.
|
|
143
|
+
#
|
|
144
|
+
# the schema will be registered with the `JSI.schema_registry`.
|
|
145
|
+
#
|
|
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.
|
|
152
|
+
def new_schema(schema_content,
|
|
153
|
+
uri: nil
|
|
154
|
+
)
|
|
155
|
+
schema_jsi = new_jsi(Util.deep_stringify_symbol_keys(schema_content),
|
|
156
|
+
uri: uri,
|
|
157
|
+
)
|
|
158
|
+
JSI.schema_registry.register(schema_jsi)
|
|
159
|
+
schema_jsi
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# instantiates a given schema object as a JSI Schema and returns its JSI Schema Module.
|
|
163
|
+
#
|
|
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
|
|
170
|
+
end
|
|
23
171
|
end
|
|
24
172
|
|
|
25
173
|
class << self
|
|
26
|
-
#
|
|
174
|
+
# an application-wide default metaschema set by {default_metaschema=}, used by {JSI.new_schema}
|
|
175
|
+
#
|
|
176
|
+
# @return [nil, #new_schema]
|
|
27
177
|
def default_metaschema
|
|
28
|
-
|
|
178
|
+
return @default_metaschema if instance_variable_defined?(:@default_metaschema)
|
|
179
|
+
return JSONSchemaOrgDraft07
|
|
29
180
|
end
|
|
30
181
|
|
|
31
|
-
#
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
182
|
+
# sets an application-wide default metaschema used by {JSI.new_schema}
|
|
183
|
+
#
|
|
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`).
|
|
186
|
+
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
|
|
37
191
|
end
|
|
38
192
|
|
|
39
|
-
# instantiates a given schema object as a JSI
|
|
193
|
+
# instantiates a given schema object as a JSI Schema.
|
|
40
194
|
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
195
|
+
# the metaschema to use to instantiate the schema must be indicated.
|
|
196
|
+
#
|
|
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
|
+
# - if no `$schema` property is present, the `default_metaschema` param is used, if the caller
|
|
200
|
+
# specifies it.
|
|
201
|
+
# - 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.
|
|
204
|
+
#
|
|
205
|
+
# an ArgumentError is raised if none of these indicate a metaschema to use.
|
|
206
|
+
#
|
|
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)`
|
|
43
210
|
#
|
|
44
211
|
# if the given schema_object is a JSI::Base but not already a JSI::Schema, an error
|
|
45
|
-
# will be raised.
|
|
46
|
-
#
|
|
212
|
+
# will be raised. schemas which describe schemas must have JSI::Schema in their
|
|
213
|
+
# Schema#jsi_schema_instance_modules.
|
|
47
214
|
#
|
|
48
215
|
# @param schema_object [#to_hash, Boolean, JSI::Schema] an object to be instantiated as a schema.
|
|
49
|
-
# if it's already a
|
|
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`).
|
|
50
221
|
# @return [JSI::Schema] a JSI::Schema representing the given schema_object
|
|
51
|
-
def
|
|
222
|
+
def new_schema(schema_object, default_metaschema: nil, **kw)
|
|
223
|
+
default_metaschema_new_schema = -> {
|
|
224
|
+
default_metaschema ||= JSI::Schema.default_metaschema
|
|
225
|
+
if default_metaschema.nil?
|
|
226
|
+
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}",
|
|
232
|
+
].join("\n"))
|
|
233
|
+
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)
|
|
238
|
+
}
|
|
52
239
|
if schema_object.is_a?(Schema)
|
|
53
240
|
schema_object
|
|
54
241
|
elsif schema_object.is_a?(JSI::Base)
|
|
55
242
|
raise(NotASchemaError, "the given schema_object is a JSI::Base, but is not a JSI::Schema: #{schema_object.pretty_inspect.chomp}")
|
|
56
243
|
elsif schema_object.respond_to?(:to_hash)
|
|
57
|
-
schema_object = JSI.deep_stringify_symbol_keys(schema_object)
|
|
58
244
|
if schema_object.key?('$schema') && schema_object['$schema'].respond_to?(:to_str)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
metaschema = supported_metaschemas.detect { |ms| schema_object['$schema'] == ms['$id'] || schema_object['$schema'] == ms['id'] }
|
|
63
|
-
unless metaschema
|
|
64
|
-
raise(NotImplementedError, "metaschema not supported: #{schema_object['$schema']}")
|
|
65
|
-
end
|
|
66
|
-
metaschema.new_jsi(schema_object)
|
|
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")
|
|
67
248
|
end
|
|
249
|
+
metaschema.new_schema(schema_object, **kw)
|
|
68
250
|
else
|
|
69
|
-
|
|
251
|
+
default_metaschema_new_schema.call
|
|
70
252
|
end
|
|
71
253
|
elsif [true, false].include?(schema_object)
|
|
72
|
-
|
|
254
|
+
default_metaschema_new_schema.call
|
|
73
255
|
else
|
|
74
256
|
raise(TypeError, "cannot instantiate Schema from: #{schema_object.pretty_inspect.chomp}")
|
|
75
257
|
end
|
|
76
258
|
end
|
|
77
259
|
|
|
78
|
-
|
|
79
|
-
|
|
260
|
+
# @deprecated
|
|
261
|
+
alias_method :new, :new_schema
|
|
80
262
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
def schema_id
|
|
84
|
-
return @schema_id if instance_variable_defined?(:@schema_id)
|
|
85
|
-
@schema_id = begin
|
|
86
|
-
# start from self and ascend parents looking for an 'id' property.
|
|
87
|
-
# append a fragment to that id (appending to an existing fragment if there
|
|
88
|
-
# is one) consisting of the path from that parent to our schema_node.
|
|
89
|
-
node_for_id = self
|
|
90
|
-
path_from_id_node = []
|
|
91
|
-
done = false
|
|
263
|
+
# @deprecated
|
|
264
|
+
alias_method :from_object, :new_schema
|
|
92
265
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
266
|
+
# ensure the given object is a JSI Schema
|
|
267
|
+
#
|
|
268
|
+
# @param schema [Object] the thing the caller wishes to ensure is a Schema
|
|
269
|
+
# @param msg [#to_s, #to_ary] lines of the error message preceding the pretty-printed schema param
|
|
270
|
+
# if the schema param is not a schema
|
|
271
|
+
# @raise [NotASchemaError] if the schema param is not a schema
|
|
272
|
+
# @return [Schema] the given schema
|
|
273
|
+
def ensure_schema(schema, msg: "indicated object is not a schema:", reinstantiate_as: nil)
|
|
274
|
+
if schema.is_a?(Schema)
|
|
275
|
+
schema
|
|
276
|
+
else
|
|
277
|
+
if reinstantiate_as
|
|
278
|
+
# TODO warn; behavior is undefined and I hate this implementation
|
|
279
|
+
|
|
280
|
+
result_schema_schemas = schema.jsi_schemas + reinstantiate_as
|
|
281
|
+
|
|
282
|
+
result_schema_class = JSI::SchemaClasses.class_for_schemas(result_schema_schemas)
|
|
99
283
|
|
|
100
|
-
|
|
101
|
-
|
|
284
|
+
result_schema_class.new(Base::NOINSTANCE,
|
|
285
|
+
jsi_document: schema.jsi_document,
|
|
286
|
+
jsi_ptr: schema.jsi_ptr,
|
|
287
|
+
jsi_root_node: schema.jsi_root_node,
|
|
288
|
+
jsi_schema_base_uri: schema.jsi_schema_base_uri,
|
|
289
|
+
jsi_schema_resource_ancestors: schema.jsi_schema_resource_ancestors,
|
|
290
|
+
)
|
|
102
291
|
else
|
|
103
|
-
|
|
104
|
-
|
|
292
|
+
raise(NotASchemaError, [
|
|
293
|
+
*msg,
|
|
294
|
+
schema.pretty_inspect.chomp,
|
|
295
|
+
].join("\n"))
|
|
105
296
|
end
|
|
106
297
|
end
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if parent_auri.fragment
|
|
110
|
-
# add onto the fragment
|
|
111
|
-
parent_id_path = JSI::JSON::Pointer.from_fragment(parent_auri.fragment).reference_tokens
|
|
112
|
-
path_from_id_node = parent_id_path + path_from_id_node
|
|
113
|
-
parent_auri.fragment = nil
|
|
114
|
-
#else: no fragment so parent_id good as is
|
|
115
|
-
end
|
|
298
|
+
end
|
|
299
|
+
end
|
|
116
300
|
|
|
117
|
-
|
|
301
|
+
# 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
|
|
303
|
+
# a schema.
|
|
304
|
+
def schema_content
|
|
305
|
+
jsi_node_content
|
|
306
|
+
end
|
|
118
307
|
|
|
119
|
-
|
|
308
|
+
# the URI of this schema, calculated from our `#id`, resolved against our `#jsi_schema_base_uri`
|
|
309
|
+
# @return [Addressable::URI, nil]
|
|
310
|
+
def schema_absolute_uri
|
|
311
|
+
if respond_to?(:id_without_fragment) && id_without_fragment
|
|
312
|
+
if jsi_schema_base_uri
|
|
313
|
+
Addressable::URI.parse(jsi_schema_base_uri).join(id_without_fragment)
|
|
314
|
+
elsif id_without_fragment.absolute?
|
|
315
|
+
id_without_fragment
|
|
120
316
|
else
|
|
317
|
+
# TODO warn / schema_error
|
|
121
318
|
nil
|
|
122
319
|
end
|
|
123
320
|
end
|
|
124
321
|
end
|
|
125
322
|
|
|
126
|
-
#
|
|
323
|
+
# a nonrelative URI which refers to this schema.
|
|
324
|
+
# nil if no parent of this schema defines an id.
|
|
325
|
+
# see {#schema_uris} for all URIs known to refer to this schema.
|
|
326
|
+
# @return [Addressable::URI, nil]
|
|
327
|
+
def schema_uri
|
|
328
|
+
schema_uris.first
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
# nonrelative URIs (that is, absolute, but possibly with a fragment) which refer to this schema
|
|
332
|
+
# @return [Array<Addressable::URI>]
|
|
333
|
+
def schema_uris
|
|
334
|
+
jsi_memoize(:schema_uris) do
|
|
335
|
+
each_schema_uri.to_a
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
# see {#schema_uris}
|
|
340
|
+
# @yield [Addressable::URI]
|
|
341
|
+
# @return [Enumerator, nil]
|
|
342
|
+
def each_schema_uri
|
|
343
|
+
return to_enum(__method__) unless block_given?
|
|
344
|
+
|
|
345
|
+
yield schema_absolute_uri if schema_absolute_uri
|
|
346
|
+
|
|
347
|
+
parent_schemas = jsi_subschema_resource_ancestors.reverse_each.select do |resource|
|
|
348
|
+
resource.is_a?(Schema) && resource.schema_absolute_uri
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
anchored = self.anchor
|
|
352
|
+
parent_schemas.each do |parent_schema|
|
|
353
|
+
if anchored
|
|
354
|
+
if parent_schema.jsi_anchor_subschema(anchor) == self
|
|
355
|
+
yield parent_schema.schema_absolute_uri.merge(fragment: anchor)
|
|
356
|
+
else
|
|
357
|
+
anchored = false
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
|
|
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)
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
nil
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
# a module which extends all instances of this schema. this may be opened by the application to add
|
|
369
|
+
# methods to schema instances.
|
|
370
|
+
#
|
|
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
|
+
# some functionality is also defined on the module itself (its singleton class, not for its instances):
|
|
375
|
+
#
|
|
376
|
+
# - the module is extended with {JSI::SchemaModule}, which defines .new_jsi to instantiate instances
|
|
377
|
+
# of this schema (see {#new_jsi}).
|
|
378
|
+
# - properties described by this schema's metaschema are defined as methods to get subschemas' schema
|
|
379
|
+
# modules, so for example `schema.jsi_schema_module.items` returns the same module
|
|
380
|
+
# as `schema.items.jsi_schema_module`.
|
|
381
|
+
# - method .schema which returns this schema.
|
|
382
|
+
#
|
|
383
|
+
# @return [Module]
|
|
127
384
|
def jsi_schema_module
|
|
128
385
|
JSI::SchemaClasses.module_for_schema(self)
|
|
129
386
|
end
|
|
130
387
|
|
|
131
|
-
#
|
|
388
|
+
# Evaluates the given block in the context of this schema's JSI schema module.
|
|
389
|
+
# Any arguments passed to this method will be passed to the block.
|
|
390
|
+
# shortcut to invoke [Module#module_exec](https://ruby-doc.org/core/Module.html#method-i-module_exec)
|
|
391
|
+
# on our {#jsi_schema_module}.
|
|
392
|
+
#
|
|
393
|
+
# @return the result of evaluating the block
|
|
394
|
+
def jsi_schema_module_exec(*a, **kw, &block)
|
|
395
|
+
jsi_schema_module.module_exec(*a, **kw, &block)
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
# @private @deprecated
|
|
132
399
|
def jsi_schema_class
|
|
133
|
-
JSI.class_for_schemas(
|
|
400
|
+
JSI::SchemaClasses.class_for_schemas(SchemaSet[self])
|
|
134
401
|
end
|
|
135
402
|
|
|
136
|
-
# instantiates the given
|
|
137
|
-
#
|
|
138
|
-
#
|
|
139
|
-
# any parameters are passed to JSI::Base#initialize, but none are normally used.
|
|
403
|
+
# instantiates the given instance as a JSI::Base class for schemas matched from this schema to the
|
|
404
|
+
# instance.
|
|
140
405
|
#
|
|
141
|
-
# @
|
|
142
|
-
#
|
|
143
|
-
|
|
144
|
-
|
|
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
|
+
)
|
|
413
|
+
SchemaSet[self].new_jsi(instance, **kw)
|
|
145
414
|
end
|
|
146
415
|
|
|
147
|
-
#
|
|
416
|
+
# does this schema itself describe a schema?
|
|
417
|
+
# @return [Boolean]
|
|
148
418
|
def describes_schema?
|
|
149
|
-
|
|
419
|
+
jsi_schema_instance_modules.any? { |m| m <= JSI::Schema }
|
|
420
|
+
end
|
|
421
|
+
|
|
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
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
# see {#jsi_schema_instance_modules}
|
|
431
|
+
#
|
|
432
|
+
# @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)
|
|
150
435
|
end
|
|
151
436
|
|
|
152
|
-
#
|
|
153
|
-
# returns these as a Set of {JSI::Schema}s.
|
|
437
|
+
# a resource containing this schema.
|
|
154
438
|
#
|
|
155
|
-
#
|
|
439
|
+
# if any parent, or this schema itself, is a schema with an absolute uri (see #schema_absolute_uri),
|
|
440
|
+
# the resource root is the closest schema with an absolute uri.
|
|
156
441
|
#
|
|
157
|
-
#
|
|
158
|
-
#
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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.
|
|
444
|
+
#
|
|
445
|
+
# @return [JSI::Base] resource containing this schema
|
|
446
|
+
def schema_resource_root
|
|
447
|
+
jsi_subschema_resource_ancestors.reverse_each.detect(&:schema_resource_root?) || jsi_root_node
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
# is this schema the root of a schema resource?
|
|
451
|
+
# @return [Boolean]
|
|
452
|
+
def schema_resource_root?
|
|
453
|
+
jsi_ptr.root? || !!schema_absolute_uri
|
|
163
454
|
end
|
|
164
455
|
|
|
165
|
-
#
|
|
166
|
-
# `properties`, `patternProperties`, and `additionalProperties`.
|
|
456
|
+
# a subschema of this Schema
|
|
167
457
|
#
|
|
168
|
-
# @param
|
|
169
|
-
# @return [
|
|
170
|
-
def
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
458
|
+
# @param subptr [JSI::Ptr, #to_ary] a relative pointer, or array of tokens, pointing to the subschema
|
|
459
|
+
# @return [JSI::Schema] the subschema at the location indicated by subptr. self if subptr is empty.
|
|
460
|
+
def subschema(subptr)
|
|
461
|
+
subschema_map[Ptr.ary_ptr(subptr)]
|
|
462
|
+
end
|
|
463
|
+
|
|
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: [
|
|
476
|
+
"subschema is not a schema at pointer: #{subptr.pointer}"
|
|
477
|
+
])
|
|
478
|
+
end
|
|
175
479
|
end
|
|
176
480
|
end
|
|
177
481
|
|
|
178
|
-
|
|
179
|
-
|
|
482
|
+
public
|
|
483
|
+
|
|
484
|
+
# a schema in the same schema resource as this one (see #schema_resource_root) at the given
|
|
485
|
+
# pointer relative to the root of the schema resource.
|
|
180
486
|
#
|
|
181
|
-
# @param
|
|
182
|
-
# @return [
|
|
183
|
-
def
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
487
|
+
# @param ptr [JSI::Ptr, #to_ary] a pointer to a schema from our schema resource root
|
|
488
|
+
# @return [JSI::Schema] the schema pointed to by ptr
|
|
489
|
+
def resource_root_subschema(ptr)
|
|
490
|
+
resource_root_subschema_map[Ptr.ary_ptr(ptr)]
|
|
491
|
+
end
|
|
492
|
+
|
|
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?)
|
|
515
|
+
)
|
|
516
|
+
end
|
|
188
517
|
end
|
|
189
518
|
end
|
|
190
519
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
#
|
|
520
|
+
public
|
|
521
|
+
|
|
522
|
+
# any object property names this schema indicates may be present on its instances.
|
|
523
|
+
# this includes any keys of this schema's "properties" object and any entries of this schema's
|
|
524
|
+
# array of "required" property keys.
|
|
525
|
+
# @return [Set]
|
|
194
526
|
def described_object_property_names
|
|
195
527
|
jsi_memoize(:described_object_property_names) do
|
|
196
528
|
Set.new.tap do |property_names|
|
|
197
|
-
if
|
|
198
|
-
property_names.merge(
|
|
529
|
+
if schema_content.respond_to?(:to_hash) && schema_content['properties'].respond_to?(:to_hash)
|
|
530
|
+
property_names.merge(schema_content['properties'].keys)
|
|
199
531
|
end
|
|
200
|
-
if
|
|
201
|
-
property_names.merge(
|
|
532
|
+
if schema_content.respond_to?(:to_hash) && schema_content['required'].respond_to?(:to_ary)
|
|
533
|
+
property_names.merge(schema_content['required'].to_ary)
|
|
202
534
|
end
|
|
203
|
-
end
|
|
535
|
+
end.freeze
|
|
204
536
|
end
|
|
205
537
|
end
|
|
206
538
|
|
|
207
|
-
#
|
|
208
|
-
#
|
|
539
|
+
# validates the given instance against this schema
|
|
540
|
+
#
|
|
541
|
+
# @param instance [Object] the instance to validate against this schema
|
|
542
|
+
# @return [JSI::Validation::Result]
|
|
543
|
+
def instance_validate(instance)
|
|
544
|
+
if instance.is_a?(JSI::PathedNode)
|
|
545
|
+
instance_ptr = instance.jsi_ptr
|
|
546
|
+
instance_document = instance.jsi_document
|
|
547
|
+
else
|
|
548
|
+
instance_ptr = Ptr[]
|
|
549
|
+
instance_document = instance
|
|
550
|
+
end
|
|
551
|
+
internal_validate_instance(instance_ptr, instance_document)
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
# whether the given instance is valid against this schema
|
|
555
|
+
# @param instance [Object] the instance to validate against this schema
|
|
556
|
+
# @return [Boolean]
|
|
557
|
+
def instance_valid?(instance)
|
|
558
|
+
if instance.is_a?(JSI::PathedNode)
|
|
559
|
+
instance = instance.jsi_node_content
|
|
560
|
+
end
|
|
561
|
+
internal_validate_instance(Ptr[], instance, validate_only: true).valid?
|
|
562
|
+
end
|
|
563
|
+
|
|
564
|
+
# @private
|
|
209
565
|
def fully_validate_instance(other_instance, errors_as_objects: false)
|
|
210
|
-
|
|
566
|
+
raise(NotImplementedError, "Schema#fully_validate_instance removed: see new validation interface Schema#instance_validate")
|
|
211
567
|
end
|
|
212
568
|
|
|
213
|
-
# @
|
|
569
|
+
# @private
|
|
214
570
|
def validate_instance(other_instance)
|
|
215
|
-
|
|
571
|
+
raise(NotImplementedError, "Schema#validate_instance renamed: see Schema#instance_valid?")
|
|
216
572
|
end
|
|
217
573
|
|
|
218
|
-
# @
|
|
219
|
-
# indicate the instance is valid against this schema
|
|
220
|
-
# @raise [::JSON::Schema::ValidationError] raises if the instance has
|
|
221
|
-
# validation errors against this schema
|
|
574
|
+
# @private
|
|
222
575
|
def validate_instance!(other_instance)
|
|
223
|
-
|
|
576
|
+
raise(NotImplementedError, "Schema#validate_instance! removed")
|
|
224
577
|
end
|
|
225
578
|
|
|
226
|
-
# @
|
|
227
|
-
# this schema, validated against its metaschema. a default metaschema
|
|
228
|
-
# is assumed if the schema does not specify a $schema.
|
|
579
|
+
# @private
|
|
229
580
|
def fully_validate_schema(errors_as_objects: false)
|
|
230
|
-
|
|
581
|
+
raise(NotImplementedError, "Schema#fully_validate_schema removed: use validation interface Base#jsi_validate on the schema")
|
|
231
582
|
end
|
|
232
583
|
|
|
233
|
-
# @
|
|
584
|
+
# @private
|
|
234
585
|
def validate_schema
|
|
235
|
-
|
|
586
|
+
raise(NotImplementedError, "Schema#validate_schema removed: use validation interface Base#jsi_valid? on the schema")
|
|
236
587
|
end
|
|
237
588
|
|
|
238
|
-
# @
|
|
239
|
-
# indicate this schema is valid against its metaschema
|
|
240
|
-
# @raise [::JSON::Schema::ValidationError] raises if this schema has
|
|
241
|
-
# validation errors against its metaschema
|
|
589
|
+
# @private
|
|
242
590
|
def validate_schema!
|
|
243
|
-
|
|
591
|
+
raise(NotImplementedError, "Schema#validate_schema! removed")
|
|
244
592
|
end
|
|
245
593
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
594
|
+
# schema resources which are ancestors of any subschemas below this schema.
|
|
595
|
+
# this may include this schema if this is a schema resource root.
|
|
596
|
+
# @private
|
|
597
|
+
# @return [Array<JSI::Schema>]
|
|
598
|
+
def jsi_subschema_resource_ancestors
|
|
599
|
+
if schema_resource_root?
|
|
600
|
+
jsi_schema_resource_ancestors + [self]
|
|
601
|
+
else
|
|
602
|
+
jsi_schema_resource_ancestors
|
|
250
603
|
end
|
|
251
604
|
end
|
|
252
605
|
end
|