jsi 0.7.0 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +6 -1
- data/CHANGELOG.md +15 -0
- data/README.md +19 -18
- data/jsi.gemspec +2 -3
- data/lib/jsi/base/mutability.rb +44 -0
- data/lib/jsi/base/node.rb +199 -34
- data/lib/jsi/base.rb +412 -228
- data/lib/jsi/jsi_coder.rb +18 -16
- data/lib/jsi/metaschema_node/bootstrap_schema.rb +57 -23
- data/lib/jsi/metaschema_node.rb +138 -107
- data/lib/jsi/ptr.rb +59 -37
- data/lib/jsi/schema/application/child_application/draft04.rb +0 -1
- data/lib/jsi/schema/application/child_application/draft06.rb +0 -1
- data/lib/jsi/schema/application/child_application/draft07.rb +0 -1
- data/lib/jsi/schema/application/child_application.rb +0 -25
- data/lib/jsi/schema/application/inplace_application/draft04.rb +0 -1
- data/lib/jsi/schema/application/inplace_application/draft06.rb +0 -1
- data/lib/jsi/schema/application/inplace_application/draft07.rb +0 -1
- data/lib/jsi/schema/application/inplace_application/ref.rb +1 -1
- data/lib/jsi/schema/application/inplace_application/someof.rb +1 -1
- data/lib/jsi/schema/application/inplace_application.rb +0 -27
- data/lib/jsi/schema/draft04.rb +0 -1
- data/lib/jsi/schema/draft06.rb +0 -1
- data/lib/jsi/schema/draft07.rb +0 -1
- data/lib/jsi/schema/ref.rb +44 -18
- data/lib/jsi/schema/schema_ancestor_node.rb +65 -56
- data/lib/jsi/schema/validation/contains.rb +1 -1
- data/lib/jsi/schema/validation/draft04/minmax.rb +2 -0
- data/lib/jsi/schema/validation/draft04.rb +0 -2
- data/lib/jsi/schema/validation/draft06.rb +0 -2
- data/lib/jsi/schema/validation/draft07.rb +0 -2
- data/lib/jsi/schema/validation/items.rb +3 -3
- data/lib/jsi/schema/validation/pattern.rb +1 -1
- data/lib/jsi/schema/validation/properties.rb +4 -4
- data/lib/jsi/schema/validation/ref.rb +1 -1
- data/lib/jsi/schema/validation.rb +0 -2
- data/lib/jsi/schema.rb +405 -194
- data/lib/jsi/schema_classes.rb +196 -127
- data/lib/jsi/schema_registry.rb +66 -17
- data/lib/jsi/schema_set.rb +76 -30
- data/lib/jsi/simple_wrap.rb +2 -7
- data/lib/jsi/util/private/attr_struct.rb +28 -14
- data/lib/jsi/util/private/memo_map.rb +75 -0
- data/lib/jsi/util/private.rb +73 -92
- data/lib/jsi/util/typelike.rb +28 -28
- data/lib/jsi/util.rb +120 -36
- data/lib/jsi/validation/error.rb +4 -0
- data/lib/jsi/validation/result.rb +18 -32
- data/lib/jsi/version.rb +1 -1
- data/lib/jsi.rb +67 -25
- data/lib/schemas/json-schema.org/draft-04/schema.rb +159 -4
- data/lib/schemas/json-schema.org/draft-06/schema.rb +161 -4
- data/lib/schemas/json-schema.org/draft-07/schema.rb +188 -4
- metadata +19 -5
- data/lib/jsi/metaschema.rb +0 -6
- data/lib/jsi/schema/validation/core.rb +0 -39
data/lib/jsi/schema_classes.rb
CHANGED
@@ -1,13 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module JSI
|
4
|
-
#
|
5
|
-
|
6
|
-
#
|
7
|
-
|
8
|
-
|
9
|
-
# note: defined on JSI Schema Module by JSI::SchemaClasses.module_for_schema
|
4
|
+
# A Module associated with a JSI Schema. See {Schema#jsi_schema_module}.
|
5
|
+
class SchemaModule < Module
|
6
|
+
# @private
|
7
|
+
def initialize(schema, &block)
|
8
|
+
super(&block)
|
10
9
|
|
10
|
+
@jsi_node = schema
|
11
|
+
|
12
|
+
schema.jsi_schemas.each do |schema_schema|
|
13
|
+
extend SchemaClasses.schema_property_reader_module(schema_schema, conflicting_modules: Set[SchemaModule])
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# The schema for which this is the JSI Schema Module
|
18
|
+
# @return [Base + Schema]
|
19
|
+
def schema
|
20
|
+
@jsi_node
|
21
|
+
end
|
11
22
|
|
12
23
|
# a URI which refers to the schema. see {Schema#schema_uri}.
|
13
24
|
# @return (see Schema#schema_uri)
|
@@ -18,61 +29,81 @@ module JSI
|
|
18
29
|
# @return [String]
|
19
30
|
def inspect
|
20
31
|
if name_from_ancestor
|
21
|
-
|
32
|
+
if schema.schema_absolute_uri
|
33
|
+
-"#{name_from_ancestor} <#{schema.schema_absolute_uri}> (JSI Schema Module)"
|
34
|
+
else
|
35
|
+
-"#{name_from_ancestor} (JSI Schema Module)"
|
36
|
+
end
|
22
37
|
else
|
23
|
-
"(JSI Schema Module: #{schema.schema_uri || schema.jsi_ptr.uri})"
|
38
|
+
-"(JSI Schema Module: #{schema.schema_uri || schema.jsi_ptr.uri})"
|
24
39
|
end
|
25
40
|
end
|
26
41
|
|
27
|
-
|
42
|
+
def to_s
|
43
|
+
inspect
|
44
|
+
end
|
28
45
|
|
29
|
-
# invokes {JSI::Schema#new_jsi} on this module's schema, passing the given
|
46
|
+
# invokes {JSI::Schema#new_jsi} on this module's schema, passing the given parameters.
|
30
47
|
#
|
31
48
|
# @param (see JSI::Schema#new_jsi)
|
32
|
-
# @return [JSI::Base] a JSI whose
|
49
|
+
# @return [JSI::Base subclass] a JSI whose content comes from the given instance and whose schemas are
|
50
|
+
# inplace applicators of this module's schema.
|
33
51
|
def new_jsi(instance, **kw)
|
34
52
|
schema.new_jsi(instance, **kw)
|
35
53
|
end
|
54
|
+
|
55
|
+
# See {Schema#schema_content}
|
56
|
+
def schema_content
|
57
|
+
schema.jsi_node_content
|
58
|
+
end
|
59
|
+
|
60
|
+
# See {Schema#instance_validate}
|
61
|
+
def instance_validate(instance)
|
62
|
+
schema.instance_validate(instance)
|
63
|
+
end
|
64
|
+
|
65
|
+
# See {Schema#instance_valid?}
|
66
|
+
def instance_valid?(instance)
|
67
|
+
schema.instance_valid?(instance)
|
68
|
+
end
|
36
69
|
end
|
37
70
|
|
38
|
-
#
|
39
|
-
module
|
40
|
-
#
|
71
|
+
# A module to extend the {SchemaModule} of a schema which describes other schemas (a {Schema::MetaSchema})
|
72
|
+
module SchemaModule::MetaSchemaModule
|
73
|
+
# Instantiates the given schema content as a JSI Schema.
|
41
74
|
#
|
42
|
-
# see {JSI::Schema::
|
75
|
+
# see {JSI::Schema::MetaSchema#new_schema}
|
43
76
|
#
|
44
|
-
# @param (see
|
45
|
-
# @
|
46
|
-
#
|
47
|
-
|
48
|
-
|
77
|
+
# @param (see Schema::MetaSchema#new_schema)
|
78
|
+
# @yield (see Schema::MetaSchema#new_schema)
|
79
|
+
# @return [JSI::Base subclass + JSI::Schema] a JSI which is a {JSI::Schema} whose content comes from
|
80
|
+
# the given `schema_content` and whose schemas are inplace applicators of this module's schema
|
81
|
+
def new_schema(schema_content, **kw, &block)
|
82
|
+
schema.new_schema(schema_content, **kw, &block)
|
49
83
|
end
|
50
84
|
|
51
|
-
#
|
52
|
-
|
53
|
-
|
54
|
-
#
|
55
|
-
# @param (see JSI::Schema::DescribesSchema#new_schema)
|
56
|
-
# @return [Module, JSI::SchemaModule] the JSI Schema Module of the schema
|
57
|
-
def new_schema_module(schema_content, **kw)
|
58
|
-
schema.new_schema(schema_content, **kw).jsi_schema_module
|
85
|
+
# (see Schema::MetaSchema#new_schema_module)
|
86
|
+
def new_schema_module(schema_content, **kw, &block)
|
87
|
+
schema.new_schema(schema_content, **kw, &block).jsi_schema_module
|
59
88
|
end
|
60
89
|
|
61
90
|
# @return [Set<Module>]
|
62
|
-
|
91
|
+
def schema_implementation_modules
|
92
|
+
schema.schema_implementation_modules
|
93
|
+
end
|
63
94
|
end
|
64
95
|
|
65
96
|
# this module is a namespace for building schema classes and schema modules.
|
97
|
+
# @api private
|
66
98
|
module SchemaClasses
|
67
|
-
extend Util::Memoize
|
68
|
-
|
69
99
|
class << self
|
70
|
-
# @
|
100
|
+
# @private
|
71
101
|
# @return [Set<Module>]
|
72
102
|
def includes_for(instance)
|
73
103
|
includes = Set[]
|
74
104
|
includes << Base::ArrayNode if instance.respond_to?(:to_ary)
|
75
105
|
includes << Base::HashNode if instance.respond_to?(:to_hash)
|
106
|
+
includes << Base::StringNode if instance.respond_to?(:to_str)
|
76
107
|
includes.freeze
|
77
108
|
end
|
78
109
|
|
@@ -82,14 +113,41 @@ module JSI
|
|
82
113
|
# @param schemas [Enumerable<JSI::Schema>] schemas which the class will represent
|
83
114
|
# @param includes [Enumerable<Module>] modules which will be included on the class
|
84
115
|
# @return [Class subclassing JSI::Base]
|
85
|
-
def class_for_schemas(schemas, includes: )
|
86
|
-
|
87
|
-
|
116
|
+
def class_for_schemas(schemas, includes: , mutable: )
|
117
|
+
@class_for_schemas_map[
|
118
|
+
schemas: SchemaSet.ensure_schema_set(schemas),
|
119
|
+
includes: Util.ensure_module_set(includes),
|
120
|
+
mutable: mutable,
|
121
|
+
]
|
122
|
+
end
|
88
123
|
|
89
|
-
|
90
|
-
Class.new(Base)
|
124
|
+
private def class_for_schemas_compute(schemas: , includes: , mutable: )
|
125
|
+
Class.new(Base) do
|
91
126
|
define_singleton_method(:jsi_class_schemas) { schemas }
|
92
127
|
define_method(:jsi_schemas) { schemas }
|
128
|
+
|
129
|
+
define_singleton_method(:jsi_class_includes) { includes }
|
130
|
+
|
131
|
+
mutability_module = mutable ? Base::Mutable : Base::Immutable
|
132
|
+
conflicting_modules = Set[JSI::Base, mutability_module] + includes + schemas.map(&:jsi_schema_module)
|
133
|
+
|
134
|
+
include(mutability_module)
|
135
|
+
|
136
|
+
reader_modules = schemas.map do |schema|
|
137
|
+
JSI::SchemaClasses.schema_property_reader_module(schema, conflicting_modules: conflicting_modules)
|
138
|
+
end
|
139
|
+
reader_modules.each { |m| include m }
|
140
|
+
readers = reader_modules.map(&:jsi_property_readers).inject(Set[], &:merge).freeze
|
141
|
+
define_method(:jsi_property_readers) { readers }
|
142
|
+
define_singleton_method(:jsi_property_readers) { readers }
|
143
|
+
|
144
|
+
if mutable
|
145
|
+
writer_modules = schemas.map do |schema|
|
146
|
+
JSI::SchemaClasses.schema_property_writer_module(schema, conflicting_modules: conflicting_modules)
|
147
|
+
end
|
148
|
+
writer_modules.each { |m| include(m) }
|
149
|
+
end
|
150
|
+
|
93
151
|
includes.each { |m| include(m) }
|
94
152
|
schemas.each { |schema| include(schema.jsi_schema_module) }
|
95
153
|
jsi_class = self
|
@@ -97,115 +155,119 @@ module JSI
|
|
97
155
|
|
98
156
|
self
|
99
157
|
end
|
100
|
-
end
|
101
158
|
end
|
102
159
|
|
103
|
-
# a subclass of
|
160
|
+
# a subclass of MetaSchemaNode::BootstrapSchema with the given modules included
|
104
161
|
# @api private
|
105
162
|
# @param modules [Set<Module>] schema implementation modules
|
106
163
|
# @return [Class]
|
107
164
|
def bootstrap_schema_class(modules)
|
108
|
-
|
109
|
-
|
110
|
-
|
165
|
+
@bootstrap_schema_class_map[
|
166
|
+
modules: Util.ensure_module_set(modules),
|
167
|
+
]
|
168
|
+
end
|
169
|
+
|
170
|
+
private def bootstrap_schema_class_compute(modules: )
|
171
|
+
Class.new(MetaSchemaNode::BootstrapSchema) do
|
111
172
|
define_singleton_method(:schema_implementation_modules) { modules }
|
112
173
|
define_method(:schema_implementation_modules) { modules }
|
113
174
|
modules.each { |mod| include(mod) }
|
114
175
|
|
115
176
|
self
|
116
177
|
end
|
117
|
-
end
|
118
178
|
end
|
119
179
|
|
120
180
|
# see {Schema#jsi_schema_module}
|
121
181
|
# @api private
|
122
|
-
# @return [
|
182
|
+
# @return [SchemaModule]
|
123
183
|
def module_for_schema(schema)
|
124
184
|
Schema.ensure_schema(schema)
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
define_singleton_method(:schema) { schema }
|
129
|
-
|
130
|
-
extend SchemaModule
|
131
|
-
|
132
|
-
schema.jsi_schema_instance_modules.each do |mod|
|
133
|
-
include(mod)
|
134
|
-
end
|
185
|
+
raise(Bug, "non-Base schema cannot have schema module: #{schema}") unless schema.is_a?(Base)
|
186
|
+
@schema_module_map[schema]
|
187
|
+
end
|
135
188
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
define_singleton_method(:jsi_property_accessors) { accessor_module.jsi_property_accessors }
|
143
|
-
|
144
|
-
@possibly_schema_node = schema
|
145
|
-
extend(SchemaModulePossibly)
|
146
|
-
schema.jsi_schemas.each do |schema_schema|
|
147
|
-
extend JSI::SchemaClasses.accessor_module_for_schema(schema_schema,
|
148
|
-
conflicting_modules: Set[Module, SchemaModule, SchemaModulePossibly],
|
149
|
-
setters: false,
|
150
|
-
)
|
151
|
-
end
|
152
|
-
end
|
153
|
-
end
|
189
|
+
# @private
|
190
|
+
# @deprecated after v0.7
|
191
|
+
def accessor_module_for_schema(schema, conflicting_modules: , setters: true)
|
192
|
+
Module.new do
|
193
|
+
include SchemaClasses.schema_property_reader_module(schema, conflicting_modules: conflicting_modules)
|
194
|
+
include SchemaClasses.schema_property_writer_module(schema, conflicting_modules: conflicting_modules) if setters
|
154
195
|
end
|
155
196
|
end
|
156
197
|
|
157
|
-
# a module of
|
158
|
-
# getters are always defined. setters are defined by default.
|
198
|
+
# a module of readers for described property names of the given schema.
|
159
199
|
#
|
160
|
-
# @
|
161
|
-
# @param schema [JSI::Schema] a schema for which to define
|
200
|
+
# @private
|
201
|
+
# @param schema [JSI::Schema] a schema for which to define readers for any described property names
|
162
202
|
# @param conflicting_modules [Enumerable<Module>] an array of modules (or classes) which
|
163
203
|
# may be used alongside the accessor module. methods defined by any conflicting_module
|
164
204
|
# will not be defined as accessors.
|
165
|
-
# @param setters [Boolean] whether to define setter methods
|
166
205
|
# @return [Module]
|
167
|
-
def
|
206
|
+
def schema_property_reader_module(schema, conflicting_modules: )
|
168
207
|
Schema.ensure_schema(schema)
|
169
|
-
|
208
|
+
@schema_property_reader_module_map[schema: schema, conflicting_modules: conflicting_modules]
|
209
|
+
end
|
210
|
+
|
211
|
+
private def schema_property_reader_module_compute(schema: , conflicting_modules: )
|
170
212
|
Module.new do
|
171
|
-
|
172
|
-
|
213
|
+
readers = schema.described_object_property_names.select do |name|
|
214
|
+
Util.ok_ruby_method_name?(name) &&
|
215
|
+
!conflicting_modules.any? { |m| m.method_defined?(name) || m.private_method_defined?(name) }
|
216
|
+
end.to_set.freeze
|
173
217
|
|
174
|
-
|
175
|
-
mod.instance_methods + mod.private_instance_methods
|
176
|
-
end.inject(Set.new, &:|)
|
218
|
+
define_singleton_method(:inspect) { "(JSI Schema Property Reader Module: #{readers.join(', ')})" }
|
177
219
|
|
178
|
-
|
179
|
-
# must not conflict with any method on a conflicting module
|
180
|
-
Util.ok_ruby_method_name?(name) && !conflicting_instance_methods.any? { |mn| mn.to_s == name }
|
181
|
-
end.to_set.freeze
|
220
|
+
define_singleton_method(:jsi_property_readers) { readers }
|
182
221
|
|
183
|
-
|
222
|
+
readers.each do |property_name|
|
223
|
+
define_method(property_name) do |**kw, &block|
|
224
|
+
self[property_name, **kw, &block]
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
184
229
|
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
230
|
+
# a module of writers for described property names of the given schema.
|
231
|
+
# @private
|
232
|
+
def schema_property_writer_module(schema, conflicting_modules: )
|
233
|
+
Schema.ensure_schema(schema)
|
234
|
+
@schema_property_writer_module_map[schema: schema, conflicting_modules: conflicting_modules]
|
235
|
+
end
|
236
|
+
|
237
|
+
private def schema_property_writer_module_compute(schema: , conflicting_modules: )
|
238
|
+
Module.new do
|
239
|
+
define_singleton_method(:inspect) { '(JSI Schema Property Writer Module)' }
|
240
|
+
|
241
|
+
writers = schema.described_object_property_names.select do |name|
|
242
|
+
writer = "#{name}="
|
243
|
+
Util.ok_ruby_method_name?(name) &&
|
244
|
+
!conflicting_modules.any? { |m| m.method_defined?(writer) || m.private_method_defined?(writer) }
|
245
|
+
end.to_set.freeze
|
246
|
+
|
247
|
+
define_singleton_method(:jsi_property_writers) { writers }
|
248
|
+
|
249
|
+
writers.each do |property_name|
|
190
250
|
define_method("#{property_name}=") do |value|
|
191
251
|
self[property_name] = value
|
192
252
|
end
|
193
|
-
end
|
194
|
-
end
|
195
253
|
end
|
196
254
|
end
|
197
|
-
end
|
198
255
|
end
|
199
256
|
end
|
257
|
+
|
258
|
+
@class_for_schemas_map = Hash.new { |h, k| h[k] = class_for_schemas_compute(**k) }
|
259
|
+
@bootstrap_schema_class_map = Hash.new { |h, k| h[k] = bootstrap_schema_class_compute(**k) }
|
260
|
+
@schema_module_map = Hash.new { |h, schema| h[schema] = SchemaModule.new(schema) }
|
261
|
+
@schema_property_reader_module_map = Hash.new { |h, k| h[k] = schema_property_reader_module_compute(**k) }
|
262
|
+
@schema_property_writer_module_map = Hash.new { |h, k| h[k] = schema_property_writer_module_compute(**k) }
|
200
263
|
end
|
201
264
|
|
202
|
-
#
|
203
|
-
|
204
|
-
|
205
|
-
attr_reader :possibly_schema_node
|
265
|
+
# connecting {SchemaModule}s via {SchemaModule::Connection}s
|
266
|
+
module SchemaModule::Connects
|
267
|
+
attr_reader :jsi_node
|
206
268
|
|
207
269
|
# a name relative to a named schema module of an ancestor schema.
|
208
|
-
# for example, if `Foos = JSI::
|
270
|
+
# for example, if `Foos = JSI::JSONSchemaDraft07.new_schema_module({'items' => {}})`
|
209
271
|
# then the module `Foos.items` will have a name_from_ancestor of `"Foos.items"`
|
210
272
|
# @api private
|
211
273
|
# @return [String, nil]
|
@@ -216,7 +278,7 @@ module JSI
|
|
216
278
|
name = named_ancestor_schema.jsi_schema_module.name
|
217
279
|
ancestor = named_ancestor_schema
|
218
280
|
tokens.each do |token|
|
219
|
-
if ancestor.
|
281
|
+
if ancestor.jsi_property_readers.include?(token)
|
220
282
|
name += ".#{token}"
|
221
283
|
elsif [String, Numeric, TrueClass, FalseClass, NilClass].any? { |m| token.is_a?(m) }
|
222
284
|
name += "[#{token.inspect}]"
|
@@ -225,22 +287,28 @@ module JSI
|
|
225
287
|
end
|
226
288
|
ancestor = ancestor[token]
|
227
289
|
end
|
228
|
-
name
|
290
|
+
name.freeze
|
229
291
|
end
|
230
292
|
|
231
|
-
#
|
293
|
+
# Subscripting a JSI schema module or a {SchemaModule::Connection} will subscript its node, and
|
232
294
|
# if the result is a JSI::Schema, return the JSI Schema module of that schema; if it is a JSI::Base,
|
233
|
-
# return a
|
295
|
+
# return a SchemaModule::Connection; or if it is another value (a basic type), return that value.
|
234
296
|
#
|
235
297
|
# @param token [Object]
|
236
|
-
# @
|
237
|
-
|
298
|
+
# @yield If the token identifies a schema and a block is given,
|
299
|
+
# it is evaluated in the context of the schema's JSI schema module
|
300
|
+
# using [Module#module_exec](https://ruby-doc.org/core/Module.html#method-i-module_exec).
|
301
|
+
# @return [SchemaModule, SchemaModule::Connection, Object]
|
302
|
+
def [](token, **kw, &block)
|
238
303
|
raise(ArgumentError) unless kw.empty? # TODO remove eventually (keyword argument compatibility)
|
239
|
-
sub = @
|
304
|
+
sub = @jsi_node[token]
|
240
305
|
if sub.is_a?(JSI::Schema)
|
306
|
+
sub.jsi_schema_module_exec(&block) if block
|
241
307
|
sub.jsi_schema_module
|
308
|
+
elsif block
|
309
|
+
raise(ArgumentError, "block given but token #{token.inspect} does not identify a schema")
|
242
310
|
elsif sub.is_a?(JSI::Base)
|
243
|
-
|
311
|
+
SchemaModule::Connection.new(sub)
|
244
312
|
else
|
245
313
|
sub
|
246
314
|
end
|
@@ -250,49 +318,50 @@ module JSI
|
|
250
318
|
|
251
319
|
# @return [Array<JSI::Schema, Array>, nil]
|
252
320
|
def named_ancestor_schema_tokens
|
253
|
-
schema_ancestors =
|
321
|
+
schema_ancestors = @jsi_node.jsi_ancestor_nodes
|
254
322
|
named_ancestor_schema = schema_ancestors.detect { |jsi| jsi.is_a?(JSI::Schema) && jsi.jsi_schema_module.name }
|
255
323
|
return nil unless named_ancestor_schema
|
256
|
-
tokens =
|
324
|
+
tokens = @jsi_node.jsi_ptr.relative_to(named_ancestor_schema.jsi_ptr).tokens
|
257
325
|
[named_ancestor_schema, tokens]
|
258
326
|
end
|
259
327
|
end
|
260
328
|
|
261
|
-
|
329
|
+
class SchemaModule
|
330
|
+
include Connects
|
331
|
+
end
|
332
|
+
|
333
|
+
# A JSI Schema Module is a module which represents a schema. A SchemaModule::Connection represents
|
262
334
|
# a node in a schema's document which is not a schema, such as the 'properties'
|
263
335
|
# object (which contains schemas but is not a schema).
|
264
336
|
#
|
265
337
|
# instances of this class act as a stand-in to allow users to subscript or call property accessors on
|
266
338
|
# schema modules to refer to their subschemas' schema modules.
|
267
339
|
#
|
268
|
-
#
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
# is another node, a NotASchemaModule for that node is returned. otherwise - when the
|
273
|
-
# value is a basic type - that value itself is returned.
|
274
|
-
class NotASchemaModule
|
340
|
+
# A SchemaModule::Connection has readers for property names described by the node's schemas.
|
341
|
+
class SchemaModule::Connection
|
342
|
+
include SchemaModule::Connects
|
343
|
+
|
275
344
|
# @param node [JSI::Base]
|
276
345
|
def initialize(node)
|
277
346
|
raise(Bug, "node must be JSI::Base: #{node.pretty_inspect.chomp}") unless node.is_a?(JSI::Base)
|
278
347
|
raise(Bug, "node must not be JSI::Schema: #{node.pretty_inspect.chomp}") if node.is_a?(JSI::Schema)
|
279
|
-
@
|
348
|
+
@jsi_node = node
|
280
349
|
node.jsi_schemas.each do |schema|
|
281
|
-
extend(JSI::SchemaClasses.
|
350
|
+
extend(JSI::SchemaClasses.schema_property_reader_module(schema, conflicting_modules: [SchemaModule::Connection]))
|
282
351
|
end
|
283
352
|
end
|
284
353
|
|
285
|
-
include SchemaModulePossibly
|
286
|
-
|
287
354
|
# @return [String]
|
288
355
|
def inspect
|
289
356
|
if name_from_ancestor
|
290
|
-
"#{name_from_ancestor} (
|
357
|
+
-"#{name_from_ancestor} (#{self.class})"
|
291
358
|
else
|
292
|
-
"(
|
359
|
+
-"(#{self.class}: #{@jsi_node.jsi_ptr.uri})"
|
293
360
|
end
|
294
361
|
end
|
295
362
|
|
296
|
-
|
363
|
+
def to_s
|
364
|
+
inspect
|
365
|
+
end
|
297
366
|
end
|
298
367
|
end
|
data/lib/jsi/schema_registry.rb
CHANGED
@@ -14,6 +14,7 @@ module JSI
|
|
14
14
|
|
15
15
|
# an exception raised when a URI we are looking for has not been registered
|
16
16
|
class ResourceNotFound < StandardError
|
17
|
+
attr_accessor :uri
|
17
18
|
end
|
18
19
|
|
19
20
|
def initialize
|
@@ -45,12 +46,12 @@ module JSI
|
|
45
46
|
# allow for registration of resources at the root of a document whether or not they are schemas.
|
46
47
|
# jsi_schema_base_uri at the root comes from the `uri` parameter to new_jsi / new_schema.
|
47
48
|
if resource.jsi_schema_base_uri && resource.jsi_ptr.root?
|
48
|
-
|
49
|
+
internal_store(resource.jsi_schema_base_uri, resource)
|
49
50
|
end
|
50
51
|
|
51
52
|
resource.jsi_each_descendent_node do |node|
|
52
53
|
if node.is_a?(JSI::Schema) && node.schema_absolute_uri
|
53
|
-
|
54
|
+
internal_store(node.schema_absolute_uri, node)
|
54
55
|
end
|
55
56
|
end
|
56
57
|
|
@@ -72,12 +73,18 @@ module JSI
|
|
72
73
|
#
|
73
74
|
# the block would normally load JSON from the filesystem or similar.
|
74
75
|
#
|
75
|
-
# @param uri [
|
76
|
+
# @param uri [#to_str]
|
76
77
|
# @yieldreturn [JSI::Base] a JSI instance containing the resource identified by the given uri
|
77
78
|
# @return [void]
|
78
79
|
def autoload_uri(uri, &block)
|
79
|
-
uri =
|
80
|
-
|
80
|
+
uri = registration_uri(uri)
|
81
|
+
mutating
|
82
|
+
unless block
|
83
|
+
raise(ArgumentError, ["#{SchemaRegistry}#autoload_uri must be invoked with a block", "URI: #{uri}"].join("\n"))
|
84
|
+
end
|
85
|
+
if @autoload_uris.key?(uri)
|
86
|
+
raise(Collision, ["already registered URI for autoload", "URI: #{uri}", "loader: #{@autoload_uris[uri]}"].join("\n"))
|
87
|
+
end
|
81
88
|
@autoload_uris[uri] = block
|
82
89
|
nil
|
83
90
|
end
|
@@ -86,32 +93,50 @@ module JSI
|
|
86
93
|
# @return [JSI::Base]
|
87
94
|
# @raise [JSI::SchemaRegistry::ResourceNotFound]
|
88
95
|
def find(uri)
|
89
|
-
uri =
|
90
|
-
|
91
|
-
if @autoload_uris.key?(uri) && !@resources.key?(uri)
|
96
|
+
uri = registration_uri(uri)
|
97
|
+
if @autoload_uris.key?(uri)
|
92
98
|
autoloaded = @autoload_uris[uri].call
|
93
99
|
register(autoloaded)
|
100
|
+
@autoload_uris.delete(uri)
|
94
101
|
end
|
95
|
-
|
96
|
-
|
97
|
-
if @autoload_uris.key?(uri)
|
102
|
+
if !@resources.key?(uri)
|
103
|
+
if autoloaded
|
98
104
|
msg = [
|
99
105
|
"URI #{uri} was registered with autoload_uri but the result did not contain a resource with that URI.",
|
100
106
|
"the resource resulting from autoload_uri was:",
|
101
107
|
autoloaded.pretty_inspect.chomp,
|
102
108
|
]
|
103
109
|
else
|
104
|
-
msg = ["URI #{uri} is not registered. registered URIs:", *
|
110
|
+
msg = ["URI #{uri} is not registered. registered URIs:", *(@resources.keys | @autoload_uris.keys)]
|
105
111
|
end
|
106
|
-
raise(ResourceNotFound
|
112
|
+
raise(ResourceNotFound.new(msg.join("\n")).tap { |e| e.uri = uri })
|
107
113
|
end
|
108
114
|
@resources[uri]
|
109
115
|
end
|
110
116
|
|
117
|
+
def inspect
|
118
|
+
[
|
119
|
+
"#<#{self.class}",
|
120
|
+
*[['resources', @resources.keys], ['autoload', @autoload_uris.keys]].map do |label, uris|
|
121
|
+
[
|
122
|
+
" #{label} (#{uris.size})#{uris.empty? ? "" : ":"}",
|
123
|
+
*uris.map do |uri|
|
124
|
+
" #{uri}"
|
125
|
+
end,
|
126
|
+
]
|
127
|
+
end.inject([], &:+),
|
128
|
+
'>',
|
129
|
+
].join("\n").freeze
|
130
|
+
end
|
131
|
+
|
132
|
+
def to_s
|
133
|
+
inspect
|
134
|
+
end
|
135
|
+
|
111
136
|
def dup
|
112
137
|
self.class.new.tap do |reg|
|
113
138
|
@resources.each do |uri, resource|
|
114
|
-
reg.
|
139
|
+
reg.internal_store(uri, resource)
|
115
140
|
end
|
116
141
|
@autoload_uris.each do |uri, autoload|
|
117
142
|
reg.autoload_uri(uri, &autoload)
|
@@ -119,13 +144,21 @@ module JSI
|
|
119
144
|
end
|
120
145
|
end
|
121
146
|
|
147
|
+
def freeze
|
148
|
+
@resources.freeze
|
149
|
+
@autoload_uris.freeze
|
150
|
+
@resources_mutex = nil
|
151
|
+
super
|
152
|
+
end
|
153
|
+
|
122
154
|
protected
|
123
155
|
# @param uri [Addressable::URI]
|
124
156
|
# @param resource [JSI::Base]
|
125
157
|
# @return [void]
|
126
|
-
def
|
158
|
+
def internal_store(uri, resource)
|
159
|
+
mutating
|
127
160
|
@resources_mutex.synchronize do
|
128
|
-
|
161
|
+
uri = registration_uri(uri)
|
129
162
|
if @resources.key?(uri)
|
130
163
|
if @resources[uri] != resource
|
131
164
|
raise(Collision, "URI collision on #{uri}.\nexisting:\n#{@resources[uri].pretty_inspect.chomp}\nnew:\n#{resource.pretty_inspect.chomp}")
|
@@ -139,13 +172,29 @@ module JSI
|
|
139
172
|
|
140
173
|
private
|
141
174
|
|
142
|
-
|
175
|
+
# registration URIs are
|
176
|
+
# - absolute
|
177
|
+
# - without fragment
|
178
|
+
# - not relative
|
179
|
+
# - normalized
|
180
|
+
# - frozen
|
181
|
+
# @param uri [#to_str]
|
182
|
+
# @return [Addressable::URI]
|
183
|
+
def registration_uri(uri)
|
184
|
+
uri = Util.uri(uri)
|
143
185
|
if uri.fragment
|
144
186
|
raise(NonAbsoluteURI, "#{self.class} only registers absolute URIs. cannot access URI with fragment: #{uri}")
|
145
187
|
end
|
146
188
|
if uri.relative?
|
147
189
|
raise(NonAbsoluteURI, "#{self.class} only registers absolute URIs. cannot access relative URI: #{uri}")
|
148
190
|
end
|
191
|
+
uri.normalize.freeze
|
192
|
+
end
|
193
|
+
|
194
|
+
def mutating
|
195
|
+
if frozen?
|
196
|
+
raise(FrozenError, "cannot modify frozen #{self.class}")
|
197
|
+
end
|
149
198
|
end
|
150
199
|
end
|
151
200
|
end
|