jsi 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|