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_classes.rb
CHANGED
|
@@ -3,37 +3,73 @@
|
|
|
3
3
|
module JSI
|
|
4
4
|
# JSI Schema Modules are extended with JSI::SchemaModule
|
|
5
5
|
module SchemaModule
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
# @!method schema
|
|
7
|
+
# the schema of which this is the JSI Schema Module
|
|
8
|
+
# @return [Schema]
|
|
9
|
+
# note: defined on JSI Schema Module by JSI::SchemaClasses.module_for_schema
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# a URI which refers to the schema. see {Schema#schema_uri}.
|
|
13
|
+
# @return (see Schema#schema_uri)
|
|
14
|
+
def schema_uri
|
|
15
|
+
schema.schema_uri
|
|
10
16
|
end
|
|
11
17
|
|
|
12
18
|
# @return [String]
|
|
13
19
|
def inspect
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"#{name} (#{uri})"
|
|
20
|
+
if name_from_ancestor
|
|
21
|
+
"#{name_from_ancestor} (JSI Schema Module)"
|
|
17
22
|
else
|
|
18
|
-
"(JSI Schema Module: #{uri})"
|
|
23
|
+
"(JSI Schema Module: #{schema.schema_uri || schema.jsi_ptr.uri})"
|
|
19
24
|
end
|
|
20
25
|
end
|
|
21
26
|
|
|
22
27
|
# invokes {JSI::Schema#new_jsi} on this module's schema, passing the given instance.
|
|
28
|
+
#
|
|
29
|
+
# @param (see JSI::Schema#new_jsi)
|
|
23
30
|
# @return [JSI::Base] a JSI whose instance is the given instance
|
|
24
|
-
def new_jsi(instance,
|
|
25
|
-
schema.new_jsi(instance,
|
|
31
|
+
def new_jsi(instance, **kw, &b)
|
|
32
|
+
schema.new_jsi(instance, **kw, &b)
|
|
26
33
|
end
|
|
27
34
|
end
|
|
28
35
|
|
|
29
|
-
#
|
|
36
|
+
# a module to extend the JSI Schema Module of a schema which describes other schemas
|
|
37
|
+
module DescribesSchemaModule
|
|
38
|
+
# instantiates the given schema content as a JSI Schema.
|
|
39
|
+
#
|
|
40
|
+
# see {JSI::Schema::DescribesSchema#new_schema}
|
|
41
|
+
#
|
|
42
|
+
# @param (see JSI::Schema::DescribesSchema#new_schema)
|
|
43
|
+
# @return [JSI::Base, JSI::Schema] a JSI whose instance is the given schema_content and whose schemas
|
|
44
|
+
# consist of this module's schema.
|
|
45
|
+
def new_schema(schema_content, **kw)
|
|
46
|
+
schema.new_schema(schema_content, **kw)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# instantiates a given schema object as a JSI Schema and returns its JSI Schema Module.
|
|
50
|
+
#
|
|
51
|
+
# shortcut to chain {JSI::Schema::DescribesSchema#new_schema} + {Schema#jsi_schema_module}.
|
|
52
|
+
#
|
|
53
|
+
# @param (see JSI::Schema::DescribesSchema#new_schema)
|
|
54
|
+
# @return [Module, JSI::SchemaModule] the JSI Schema Module of the schema
|
|
55
|
+
def new_schema_module(schema_content, **kw)
|
|
56
|
+
schema.new_schema(schema_content, **kw).jsi_schema_module
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# this module is a namespace for building schema classes and schema modules.
|
|
30
61
|
module SchemaClasses
|
|
62
|
+
extend Util::Memoize
|
|
63
|
+
|
|
31
64
|
class << self
|
|
32
|
-
|
|
65
|
+
# a JSI Schema Class which represents the given schemas.
|
|
66
|
+
# an instance of the class is a JSON Schema instance described by all of the given schemas.
|
|
67
|
+
# @private
|
|
68
|
+
# @param schemas [Enumerable<JSI::Schema>] schemas which the class will represent
|
|
69
|
+
# @return [Class subclassing JSI::Base]
|
|
70
|
+
def class_for_schemas(schemas)
|
|
71
|
+
schemas = SchemaSet.ensure_schema_set(schemas)
|
|
33
72
|
|
|
34
|
-
# see {JSI.class_for_schemas}
|
|
35
|
-
def class_for_schemas(schema_objects)
|
|
36
|
-
schemas = schema_objects.map { |schema_object| JSI::Schema.from_object(schema_object) }.to_set
|
|
37
73
|
jsi_memoize(:class_for_schemas, schemas) do |schemas|
|
|
38
74
|
Class.new(Base).instance_exec(schemas) do |schemas|
|
|
39
75
|
define_singleton_method(:jsi_class_schemas) { schemas }
|
|
@@ -47,13 +83,28 @@ module JSI
|
|
|
47
83
|
end
|
|
48
84
|
end
|
|
49
85
|
|
|
50
|
-
#
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
86
|
+
# @private
|
|
87
|
+
# a subclass of MetaschemaNode::BootstrapSchema with the given modules included
|
|
88
|
+
# @param modules [Set<Module>] metaschema instance modules
|
|
89
|
+
# @return [Class]
|
|
90
|
+
def bootstrap_schema_class(modules)
|
|
91
|
+
modules = Util.ensure_module_set(modules)
|
|
92
|
+
jsi_memoize(__method__, modules) do |modules|
|
|
93
|
+
Class.new(MetaschemaNode::BootstrapSchema).instance_exec(modules) do |modules|
|
|
94
|
+
define_singleton_method(:metaschema_instance_modules) { modules }
|
|
95
|
+
define_method(:metaschema_instance_modules) { modules }
|
|
96
|
+
modules.each { |mod| include(mod) }
|
|
97
|
+
|
|
98
|
+
self
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# see {Schema#jsi_schema_module}
|
|
104
|
+
# @api private
|
|
105
|
+
# @return [Module]
|
|
106
|
+
def module_for_schema(schema)
|
|
107
|
+
Schema.ensure_schema(schema)
|
|
57
108
|
jsi_memoize(:module_for_schema, schema) do |schema|
|
|
58
109
|
Module.new.tap do |m|
|
|
59
110
|
m.module_eval do
|
|
@@ -61,41 +112,67 @@ module JSI
|
|
|
61
112
|
|
|
62
113
|
extend SchemaModule
|
|
63
114
|
|
|
64
|
-
|
|
115
|
+
schema.jsi_schema_instance_modules.each do |mod|
|
|
116
|
+
include(mod)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
accessor_module = JSI::SchemaClasses.accessor_module_for_schema(schema,
|
|
120
|
+
conflicting_modules: Set[JSI::Base, JSI::PathedArrayNode, JSI::PathedHashNode] +
|
|
121
|
+
schema.jsi_schema_instance_modules,
|
|
122
|
+
)
|
|
123
|
+
include accessor_module
|
|
124
|
+
|
|
125
|
+
define_singleton_method(:jsi_property_accessors) { accessor_module.jsi_property_accessors }
|
|
126
|
+
|
|
127
|
+
if schema.describes_schema?
|
|
128
|
+
extend DescribesSchemaModule
|
|
129
|
+
end
|
|
65
130
|
|
|
66
131
|
@possibly_schema_node = schema
|
|
67
132
|
extend(SchemaModulePossibly)
|
|
68
133
|
schema.jsi_schemas.each do |schema_schema|
|
|
69
|
-
extend
|
|
134
|
+
extend JSI::SchemaClasses.accessor_module_for_schema(schema_schema,
|
|
135
|
+
conflicting_modules: Set[Module, SchemaModule, SchemaModulePossibly],
|
|
136
|
+
setters: false,
|
|
137
|
+
)
|
|
70
138
|
end
|
|
71
139
|
end
|
|
72
140
|
end
|
|
73
141
|
end
|
|
74
142
|
end
|
|
75
143
|
|
|
144
|
+
# a module of accessors for described property names of the given schema.
|
|
145
|
+
# getters are always defined. setters are defined by default.
|
|
76
146
|
# @param schema [JSI::Schema] a schema for which to define accessors for any described property names
|
|
77
147
|
# @param conflicting_modules [Enumerable<Module>] an array of modules (or classes) which
|
|
78
148
|
# may be used alongside the accessor module. methods defined by any conflicting_module
|
|
79
149
|
# will not be defined as accessors.
|
|
80
|
-
# @
|
|
81
|
-
#
|
|
82
|
-
def accessor_module_for_schema(schema, conflicting_modules: )
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
end
|
|
86
|
-
jsi_memoize(:accessor_module_for_schema, schema, conflicting_modules) do |schema, conflicting_modules|
|
|
150
|
+
# @param setters [Boolean] whether to define setter methods
|
|
151
|
+
# @return [Module]
|
|
152
|
+
def accessor_module_for_schema(schema, conflicting_modules: , setters: true)
|
|
153
|
+
Schema.ensure_schema(schema)
|
|
154
|
+
jsi_memoize(:accessor_module_for_schema, schema, conflicting_modules, setters) do |schema, conflicting_modules, setters|
|
|
87
155
|
Module.new.tap do |m|
|
|
88
156
|
m.module_eval do
|
|
89
157
|
conflicting_instance_methods = (conflicting_modules + [m]).map do |mod|
|
|
90
158
|
mod.instance_methods + mod.private_instance_methods
|
|
91
159
|
end.inject(Set.new, &:|)
|
|
92
|
-
|
|
160
|
+
|
|
161
|
+
accessors_to_define = schema.described_object_property_names.select do |name|
|
|
162
|
+
# must not conflict with any method on a conflicting module
|
|
163
|
+
Util.ok_ruby_method_name?(name) && !conflicting_instance_methods.any? { |mn| mn.to_s == name }
|
|
164
|
+
end.to_set.freeze
|
|
165
|
+
|
|
166
|
+
define_singleton_method(:jsi_property_accessors) { accessors_to_define }
|
|
167
|
+
|
|
93
168
|
accessors_to_define.each do |property_name|
|
|
94
|
-
define_method(property_name) do
|
|
95
|
-
self[property_name]
|
|
169
|
+
define_method(property_name) do |*a|
|
|
170
|
+
self[property_name, *a]
|
|
96
171
|
end
|
|
97
|
-
|
|
98
|
-
|
|
172
|
+
if setters
|
|
173
|
+
define_method("#{property_name}=") do |value|
|
|
174
|
+
self[property_name] = value
|
|
175
|
+
end
|
|
99
176
|
end
|
|
100
177
|
end
|
|
101
178
|
end
|
|
@@ -105,17 +182,43 @@ module JSI
|
|
|
105
182
|
end
|
|
106
183
|
end
|
|
107
184
|
|
|
108
|
-
# a JSI
|
|
185
|
+
# a JSI Schema module and a JSI::NotASchemaModule are both a SchemaModulePossibly.
|
|
109
186
|
# this module provides a #[] method.
|
|
110
187
|
module SchemaModulePossibly
|
|
111
188
|
attr_reader :possibly_schema_node
|
|
112
189
|
|
|
190
|
+
# a name relative to a named schema module of an ancestor schema.
|
|
191
|
+
# for example, if `Foos = JSI::JSONSchemaOrgDraft07.new_schema_module({'items' => {}})`
|
|
192
|
+
# then the module `Foos.items` will have a name_from_ancestor of `"Foos.items"`
|
|
193
|
+
# @return [String, nil]
|
|
194
|
+
def name_from_ancestor
|
|
195
|
+
schema_ancestors = [possibly_schema_node] + possibly_schema_node.jsi_parent_nodes
|
|
196
|
+
named_parent_schema = schema_ancestors.detect { |jsi| jsi.is_a?(JSI::Schema) && jsi.jsi_schema_module.name }
|
|
197
|
+
|
|
198
|
+
return nil unless named_parent_schema
|
|
199
|
+
|
|
200
|
+
tokens = possibly_schema_node.jsi_ptr.ptr_relative_to(named_parent_schema.jsi_ptr).tokens
|
|
201
|
+
name = named_parent_schema.jsi_schema_module.name
|
|
202
|
+
parent = named_parent_schema
|
|
203
|
+
tokens.each do |token|
|
|
204
|
+
if parent.jsi_schemas.any? { |s| s.jsi_schema_module.jsi_property_accessors.include?(token) }
|
|
205
|
+
name += ".#{token}"
|
|
206
|
+
elsif [String, Numeric, TrueClass, FalseClass, NilClass].any? { |m| token.is_a?(m) }
|
|
207
|
+
name += "[#{token.inspect}]"
|
|
208
|
+
else
|
|
209
|
+
return nil
|
|
210
|
+
end
|
|
211
|
+
parent = parent[token]
|
|
212
|
+
end
|
|
213
|
+
name
|
|
214
|
+
end
|
|
215
|
+
|
|
113
216
|
# subscripting a JSI schema module or a NotASchemaModule will subscript the node, and
|
|
114
|
-
# if the result is a JSI::Schema, return
|
|
217
|
+
# if the result is a JSI::Schema, return the JSI Schema module of that schema; if it is a PathedNode,
|
|
115
218
|
# return a NotASchemaModule; or if it is another value (a basic type), return that value.
|
|
116
219
|
#
|
|
117
220
|
# @param token [Object]
|
|
118
|
-
# @return [
|
|
221
|
+
# @return [Module, NotASchemaModule, Object]
|
|
119
222
|
def [](token)
|
|
120
223
|
sub = @possibly_schema_node[token]
|
|
121
224
|
if sub.is_a?(JSI::Schema)
|
|
@@ -128,9 +231,12 @@ module JSI
|
|
|
128
231
|
end
|
|
129
232
|
end
|
|
130
233
|
|
|
131
|
-
# a
|
|
234
|
+
# a JSI Schema Module is a module which represents a schema. a NotASchemaModule represents
|
|
132
235
|
# a node in a schema's document which is not a schema, such as the 'properties'
|
|
133
|
-
#
|
|
236
|
+
# object (which contains schemas but is not a schema).
|
|
237
|
+
#
|
|
238
|
+
# instances of this class act as a stand-in to allow users to subscript or call property accessors on
|
|
239
|
+
# schema modules to refer to their subschemas' schema modules.
|
|
134
240
|
#
|
|
135
241
|
# a NotASchemaModule is extended with the module_for_schema of the node's schema.
|
|
136
242
|
#
|
|
@@ -149,10 +255,19 @@ module JSI
|
|
|
149
255
|
end
|
|
150
256
|
@possibly_schema_node = node
|
|
151
257
|
node.jsi_schemas.each do |schema|
|
|
152
|
-
extend(JSI::SchemaClasses.accessor_module_for_schema(schema, conflicting_modules: [NotASchemaModule, SchemaModulePossibly]))
|
|
258
|
+
extend(JSI::SchemaClasses.accessor_module_for_schema(schema, conflicting_modules: [NotASchemaModule, SchemaModulePossibly], setters: false))
|
|
153
259
|
end
|
|
154
260
|
end
|
|
155
261
|
|
|
156
262
|
include SchemaModulePossibly
|
|
263
|
+
|
|
264
|
+
# @return [String]
|
|
265
|
+
def inspect
|
|
266
|
+
if name_from_ancestor
|
|
267
|
+
"#{name_from_ancestor} (JSI wrapper for Schema Module)"
|
|
268
|
+
else
|
|
269
|
+
"(JSI wrapper for Schema Module: #{@possibly_schema_node.jsi_ptr.uri})"
|
|
270
|
+
end
|
|
271
|
+
end
|
|
157
272
|
end
|
|
158
273
|
end
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
class SchemaRegistry
|
|
5
|
+
# an exception raised when an attempt is made to register a resource using a URI which is already
|
|
6
|
+
# registered with another resource
|
|
7
|
+
class Collision < StandardError
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# an exception raised when an attempt is made to access (register or find) a resource of the
|
|
11
|
+
# registry using a URI which is not absolute (it is a relative URI or it contains a fragment)
|
|
12
|
+
class NonAbsoluteURI < StandardError
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# an exception raised when a URI we are looking for has not been registered
|
|
16
|
+
class ResourceNotFound < StandardError
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def initialize
|
|
20
|
+
@resources = {}
|
|
21
|
+
@autoload_uris = {}
|
|
22
|
+
@resources_mutex = Mutex.new
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# registers the given resource and/or schema resources it contains in the registry.
|
|
26
|
+
#
|
|
27
|
+
# each child of the resource (including the resource itself) is registered if it is a schema that
|
|
28
|
+
# has an absolute URI (generally defined by the '$id' keyword).
|
|
29
|
+
#
|
|
30
|
+
# the given resource itself will be registered, whether or not it is a schema, if it is the root
|
|
31
|
+
# of its document and was instantiated with the option `uri` specified.
|
|
32
|
+
#
|
|
33
|
+
# @param resource [JSI::Base] a JSI containing resources to register
|
|
34
|
+
# @return [void]
|
|
35
|
+
def register(resource)
|
|
36
|
+
unless resource.is_a?(JSI::Base)
|
|
37
|
+
raise(ArgumentError, "resource must be a JSI::Base. got: #{resource.pretty_inspect.chomp}")
|
|
38
|
+
end
|
|
39
|
+
unless resource.is_a?(JSI::Schema) || resource.jsi_ptr.root?
|
|
40
|
+
# unsure, should this be allowed? the given JSI is not a "resource" as we define it, but
|
|
41
|
+
# if this check is removed it will just register any resources (schemas) below the given JSI.
|
|
42
|
+
raise(ArgumentError, "undefined behavior: registration of a JSI which is not a schema and is not at the root of a document")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# allow for registration of resources at the root of a document whether or not they are schemas.
|
|
46
|
+
# jsi_schema_base_uri at the root comes from the `uri` parameter to new_jsi / new_schema.
|
|
47
|
+
if resource.jsi_schema_base_uri && resource.jsi_ptr.root?
|
|
48
|
+
register_single(resource.jsi_schema_base_uri, resource)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
resource.jsi_each_child_node do |node|
|
|
52
|
+
if node.is_a?(JSI::Schema) && node.schema_absolute_uri
|
|
53
|
+
register_single(node.schema_absolute_uri, node)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
nil
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# takes a URI identifying a resource to be loaded by the given block
|
|
61
|
+
# when a reference to the URI is followed.
|
|
62
|
+
#
|
|
63
|
+
# for example:
|
|
64
|
+
#
|
|
65
|
+
# JSI.schema_registry.autoload_uri('http://example.com/schema.json') do
|
|
66
|
+
# JSI.new_schema({
|
|
67
|
+
# '$schema' => 'http://json-schema.org/draft-07/schema#',
|
|
68
|
+
# '$id' => 'http://example.com/schema.json',
|
|
69
|
+
# 'title' => 'my schema',
|
|
70
|
+
# })
|
|
71
|
+
# end
|
|
72
|
+
#
|
|
73
|
+
# the block would normally load JSON from the filesystem or similar.
|
|
74
|
+
#
|
|
75
|
+
# @param uri [Addressable::URI]
|
|
76
|
+
# @yieldreturn [JSI::Base] a JSI instance containing the resource identified by the given uri
|
|
77
|
+
# @return [void]
|
|
78
|
+
def autoload_uri(uri, &block)
|
|
79
|
+
uri = Addressable::URI.parse(uri)
|
|
80
|
+
ensure_uri_absolute(uri)
|
|
81
|
+
@autoload_uris[uri] = block
|
|
82
|
+
nil
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# @param uri [Addressable::URI, #to_str]
|
|
86
|
+
# @return [JSI::Base]
|
|
87
|
+
# @raise [JSI::SchemaRegistry::ResourceNotFound]
|
|
88
|
+
def find(uri)
|
|
89
|
+
uri = Addressable::URI.parse(uri)
|
|
90
|
+
ensure_uri_absolute(uri)
|
|
91
|
+
if @autoload_uris.key?(uri) && !@resources.key?(uri)
|
|
92
|
+
register(@autoload_uris[uri].call)
|
|
93
|
+
end
|
|
94
|
+
registered_uris = @resources.keys
|
|
95
|
+
if !registered_uris.include?(uri)
|
|
96
|
+
raise(ResourceNotFound, "URI #{uri} is not registered. registered URIs:\n#{registered_uris.join("\n")}")
|
|
97
|
+
end
|
|
98
|
+
@resources[uri]
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def dup
|
|
102
|
+
self.class.new.tap do |reg|
|
|
103
|
+
@resources.each do |uri, resource|
|
|
104
|
+
reg.register_single(uri, resource)
|
|
105
|
+
end
|
|
106
|
+
@autoload_uris.each do |uri, autoload|
|
|
107
|
+
reg.autoload_uri(uri, &autoload)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
protected
|
|
113
|
+
# @param uri [Addressable::URI]
|
|
114
|
+
# @param resource [JSI::Base]
|
|
115
|
+
# @return [void]
|
|
116
|
+
def register_single(uri, resource)
|
|
117
|
+
@resources_mutex.synchronize do
|
|
118
|
+
ensure_uri_absolute(uri)
|
|
119
|
+
if @resources.key?(uri)
|
|
120
|
+
if @resources[uri] != resource
|
|
121
|
+
raise(Collision, "URI collision on #{uri}.\nexisting:\n#{@resources[uri].pretty_inspect.chomp}\nnew:\n#{resource.pretty_inspect.chomp}")
|
|
122
|
+
end
|
|
123
|
+
else
|
|
124
|
+
@resources[uri] = resource
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
nil
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
private
|
|
131
|
+
|
|
132
|
+
def ensure_uri_absolute(uri)
|
|
133
|
+
if uri.fragment
|
|
134
|
+
raise(NonAbsoluteURI, "#{self.class} only registers absolute URIs. cannot access URI with fragment: #{uri}")
|
|
135
|
+
end
|
|
136
|
+
if uri.relative?
|
|
137
|
+
raise(NonAbsoluteURI, "#{self.class} only registers absolute URIs. cannot access relative URI: #{uri}")
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
# a Set of JSI Schemas. always frozen.
|
|
5
|
+
#
|
|
6
|
+
# any schema instance is described by a set of schemas.
|
|
7
|
+
class SchemaSet < ::Set
|
|
8
|
+
class << self
|
|
9
|
+
# builds a SchemaSet from a mutable Set which is added to by the given block
|
|
10
|
+
#
|
|
11
|
+
# @yield [Set] a Set to which the block may add schemas
|
|
12
|
+
# @return [SchemaSet]
|
|
13
|
+
def build
|
|
14
|
+
mutable_set = Set.new
|
|
15
|
+
yield mutable_set
|
|
16
|
+
new(mutable_set)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# ensures the given param becomes a SchemaSet. returns the param if it is already SchemaSet, otherwise
|
|
20
|
+
# initializes a SchemaSet from it.
|
|
21
|
+
#
|
|
22
|
+
# @param schemas [SchemaSet, Enumerable] the object to ensure becomes a SchemaSet
|
|
23
|
+
# @return [SchemaSet] the given SchemaSet, or a SchemaSet initialized from the given Enumerable
|
|
24
|
+
# @raise [ArgumentError] when the schemas param is not an Enumerable
|
|
25
|
+
# @raise [Schema::NotASchemaError] when the schemas param contains objects which are not Schemas
|
|
26
|
+
def ensure_schema_set(schemas)
|
|
27
|
+
if schemas.is_a?(SchemaSet)
|
|
28
|
+
schemas
|
|
29
|
+
else
|
|
30
|
+
new(schemas)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# initializes a SchemaSet from the given enum and freezes it.
|
|
36
|
+
#
|
|
37
|
+
# if a block is given, each element of the enum is passed to it, and the result must be a Schema.
|
|
38
|
+
# if no block is given, the enum must contain only Schemas.
|
|
39
|
+
#
|
|
40
|
+
# @param enum [#each] the schemas to be included in the SchemaSet, or items to be passed to the block
|
|
41
|
+
# @yieldparam yields each element of enum for preprocessing into a Schema
|
|
42
|
+
# @yieldreturn [JSI::Schema]
|
|
43
|
+
# @raise [JSI::Schema::NotASchemaError]
|
|
44
|
+
def initialize(enum, &block)
|
|
45
|
+
super
|
|
46
|
+
|
|
47
|
+
not_schemas = reject { |s| s.is_a?(Schema) }
|
|
48
|
+
if !not_schemas.empty?
|
|
49
|
+
raise(Schema::NotASchemaError, [
|
|
50
|
+
"JSI::SchemaSet initialized with non-schema objects:",
|
|
51
|
+
*not_schemas.map { |ns| ns.pretty_inspect.chomp },
|
|
52
|
+
].join("\n"))
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
freeze
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# instantiates the given instance as a JSI. its schemas are inplace applicators matched from the schemas
|
|
59
|
+
# in this SchemaSet which apply to the given instance.
|
|
60
|
+
#
|
|
61
|
+
# @param instance [Object] the JSON Schema instance to be represented as a JSI
|
|
62
|
+
# @param uri [nil, #to_str, Addressable::URI] for an instance document containing schemas, this is
|
|
63
|
+
# the URI of the document, whether or not the document is itself a schema.
|
|
64
|
+
# in the normal case where the document does not contain any schemas, `uri` has no effect.
|
|
65
|
+
# schemas within the document use this uri as the base URI to resolve relative URIs.
|
|
66
|
+
# the resulting JSI may be registered with a {SchemaRegistry} (see {JSI.schema_registry}).
|
|
67
|
+
# @return [JSI::Base subclass] a JSI whose instance is the given instance and whose schemas are inplace
|
|
68
|
+
# applicators matched to the instance from the schemas in this set.
|
|
69
|
+
def new_jsi(instance,
|
|
70
|
+
uri: nil
|
|
71
|
+
)
|
|
72
|
+
applied_schemas = inplace_applicator_schemas(instance)
|
|
73
|
+
|
|
74
|
+
jsi = JSI::SchemaClasses.class_for_schemas(applied_schemas).new(instance,
|
|
75
|
+
jsi_schema_base_uri: uri,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
jsi
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# a set of inplace applicator schemas of each schema in this set which apply to the given instance.
|
|
82
|
+
# (see {Schema::Application::InplaceApplication#inplace_applicator_schemas})
|
|
83
|
+
#
|
|
84
|
+
# @param instance (see Schema::Application::InplaceApplication#inplace_applicator_schemas)
|
|
85
|
+
# @return [JSI::SchemaSet]
|
|
86
|
+
def inplace_applicator_schemas(instance)
|
|
87
|
+
SchemaSet.new(each_inplace_applicator_schema(instance))
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# yields each inplace applicator schema which applies to the given instance.
|
|
91
|
+
#
|
|
92
|
+
# @param instance (see Schema::Application::InplaceApplication#inplace_applicator_schemas)
|
|
93
|
+
# @yield [JSI::Schema]
|
|
94
|
+
# @return [nil, Enumerator] an Enumerator if invoked without a block; otherwise nil
|
|
95
|
+
def each_inplace_applicator_schema(instance, &block)
|
|
96
|
+
return to_enum(__method__, instance) unless block
|
|
97
|
+
|
|
98
|
+
each do |schema|
|
|
99
|
+
schema.each_inplace_applicator_schema(instance, &block)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
nil
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# validates the given instance against our schemas
|
|
106
|
+
#
|
|
107
|
+
# @param instance [Object] the instance to validate against our schemas
|
|
108
|
+
# @return [JSI::Validation::Result]
|
|
109
|
+
def instance_validate(instance)
|
|
110
|
+
results = map { |schema| schema.instance_validate(instance) }
|
|
111
|
+
results.inject(Validation::FullResult.new, &:merge).freeze
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# whether the given instance is valid against our schemas
|
|
115
|
+
# @param instance [Object] the instance to validate against our schemas
|
|
116
|
+
# @return [Boolean]
|
|
117
|
+
def instance_valid?(instance)
|
|
118
|
+
all? { |schema| schema.instance_valid?(instance) }
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# @return [String]
|
|
122
|
+
def inspect
|
|
123
|
+
"#{self.class}[#{map(&:inspect).join(", ")}]"
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def pretty_print(q)
|
|
127
|
+
q.text self.class.to_s
|
|
128
|
+
q.text '['
|
|
129
|
+
q.group_sub {
|
|
130
|
+
q.nest(2) {
|
|
131
|
+
q.breakable('')
|
|
132
|
+
q.seplist(self, nil, :each) { |e|
|
|
133
|
+
q.pp e
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
q.breakable ''
|
|
138
|
+
q.text ']'
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
data/lib/jsi/simple_wrap.rb
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module JSI
|
|
4
|
-
SimpleWrap = JSI::
|
|
4
|
+
SimpleWrap = JSI::JSONSchemaOrgDraft06.new_schema_module({
|
|
5
5
|
"additionalProperties": {"$ref": "#"},
|
|
6
6
|
"items": {"$ref": "#"}
|
|
7
|
-
})
|
|
7
|
+
})
|
|
8
8
|
|
|
9
9
|
# SimpleWrap is a JSI schema module which recursively wraps nested structures
|
|
10
10
|
module SimpleWrap
|