jsi 0.6.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.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +6 -1
  3. data/CHANGELOG.md +33 -0
  4. data/LICENSE.md +1 -1
  5. data/README.md +29 -23
  6. data/jsi.gemspec +29 -0
  7. data/lib/jsi/base/mutability.rb +44 -0
  8. data/lib/jsi/base/node.rb +348 -0
  9. data/lib/jsi/base.rb +497 -339
  10. data/lib/jsi/jsi_coder.rb +19 -17
  11. data/lib/jsi/metaschema_node/bootstrap_schema.rb +61 -26
  12. data/lib/jsi/metaschema_node.rb +161 -133
  13. data/lib/jsi/ptr.rb +80 -47
  14. data/lib/jsi/schema/application/child_application/contains.rb +11 -2
  15. data/lib/jsi/schema/application/child_application/draft04.rb +0 -1
  16. data/lib/jsi/schema/application/child_application/draft06.rb +0 -1
  17. data/lib/jsi/schema/application/child_application/draft07.rb +0 -1
  18. data/lib/jsi/schema/application/child_application/items.rb +3 -3
  19. data/lib/jsi/schema/application/child_application/properties.rb +3 -3
  20. data/lib/jsi/schema/application/child_application.rb +0 -27
  21. data/lib/jsi/schema/application/inplace_application/dependencies.rb +1 -1
  22. data/lib/jsi/schema/application/inplace_application/draft04.rb +0 -1
  23. data/lib/jsi/schema/application/inplace_application/draft06.rb +0 -1
  24. data/lib/jsi/schema/application/inplace_application/draft07.rb +0 -1
  25. data/lib/jsi/schema/application/inplace_application/ifthenelse.rb +3 -3
  26. data/lib/jsi/schema/application/inplace_application/ref.rb +2 -2
  27. data/lib/jsi/schema/application/inplace_application/someof.rb +26 -11
  28. data/lib/jsi/schema/application/inplace_application.rb +0 -32
  29. data/lib/jsi/schema/draft04.rb +0 -1
  30. data/lib/jsi/schema/draft06.rb +0 -1
  31. data/lib/jsi/schema/draft07.rb +0 -1
  32. data/lib/jsi/schema/ref.rb +46 -19
  33. data/lib/jsi/schema/schema_ancestor_node.rb +69 -66
  34. data/lib/jsi/schema/validation/array.rb +3 -3
  35. data/lib/jsi/schema/validation/const.rb +1 -1
  36. data/lib/jsi/schema/validation/contains.rb +2 -2
  37. data/lib/jsi/schema/validation/dependencies.rb +1 -1
  38. data/lib/jsi/schema/validation/draft04/minmax.rb +8 -6
  39. data/lib/jsi/schema/validation/draft04.rb +0 -2
  40. data/lib/jsi/schema/validation/draft06.rb +0 -2
  41. data/lib/jsi/schema/validation/draft07.rb +0 -2
  42. data/lib/jsi/schema/validation/enum.rb +1 -1
  43. data/lib/jsi/schema/validation/ifthenelse.rb +5 -5
  44. data/lib/jsi/schema/validation/items.rb +7 -7
  45. data/lib/jsi/schema/validation/not.rb +1 -1
  46. data/lib/jsi/schema/validation/numeric.rb +5 -5
  47. data/lib/jsi/schema/validation/object.rb +2 -2
  48. data/lib/jsi/schema/validation/pattern.rb +2 -2
  49. data/lib/jsi/schema/validation/properties.rb +7 -7
  50. data/lib/jsi/schema/validation/property_names.rb +1 -1
  51. data/lib/jsi/schema/validation/ref.rb +2 -2
  52. data/lib/jsi/schema/validation/required.rb +1 -1
  53. data/lib/jsi/schema/validation/someof.rb +3 -3
  54. data/lib/jsi/schema/validation/string.rb +2 -2
  55. data/lib/jsi/schema/validation/type.rb +1 -1
  56. data/lib/jsi/schema/validation.rb +1 -3
  57. data/lib/jsi/schema.rb +443 -226
  58. data/lib/jsi/schema_classes.rb +241 -147
  59. data/lib/jsi/schema_registry.rb +78 -19
  60. data/lib/jsi/schema_set.rb +114 -28
  61. data/lib/jsi/simple_wrap.rb +18 -4
  62. data/lib/jsi/util/private/attr_struct.rb +141 -0
  63. data/lib/jsi/util/private/memo_map.rb +75 -0
  64. data/lib/jsi/util/private.rb +185 -0
  65. data/lib/jsi/{typelike_modules.rb → util/typelike.rb} +79 -105
  66. data/lib/jsi/util.rb +157 -153
  67. data/lib/jsi/validation/error.rb +4 -0
  68. data/lib/jsi/validation/result.rb +18 -32
  69. data/lib/jsi/version.rb +1 -1
  70. data/lib/jsi.rb +65 -39
  71. data/lib/schemas/json-schema.org/draft-04/schema.rb +160 -3
  72. data/lib/schemas/json-schema.org/draft-06/schema.rb +162 -3
  73. data/lib/schemas/json-schema.org/draft-07/schema.rb +189 -3
  74. metadata +27 -11
  75. data/lib/jsi/metaschema.rb +0 -7
  76. data/lib/jsi/pathed_node.rb +0 -116
  77. data/lib/jsi/schema/validation/core.rb +0 -39
  78. data/lib/jsi/util/attr_struct.rb +0 -106
@@ -1,13 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JSI
4
- # JSI Schema Modules are extended with JSI::SchemaModule
5
- module SchemaModule
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
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,256 +29,339 @@ module JSI
18
29
  # @return [String]
19
30
  def inspect
20
31
  if name_from_ancestor
21
- "#{name_from_ancestor} (JSI Schema Module)"
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
- # invokes {JSI::Schema#new_jsi} on this module's schema, passing the given instance.
42
+ def to_s
43
+ inspect
44
+ end
45
+
46
+ # invokes {JSI::Schema#new_jsi} on this module's schema, passing the given parameters.
28
47
  #
29
48
  # @param (see JSI::Schema#new_jsi)
30
- # @return [JSI::Base] a JSI whose instance is the given instance
31
- def new_jsi(instance, **kw, &b)
32
- schema.new_jsi(instance, **kw, &b)
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.
51
+ def new_jsi(instance, **kw)
52
+ schema.new_jsi(instance, **kw)
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)
33
68
  end
34
69
  end
35
70
 
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.
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.
39
74
  #
40
- # see {JSI::Schema::DescribesSchema#new_schema}
75
+ # see {JSI::Schema::MetaSchema#new_schema}
41
76
  #
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)
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)
47
83
  end
48
84
 
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
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
88
+ end
89
+
90
+ # @return [Set<Module>]
91
+ def schema_implementation_modules
92
+ schema.schema_implementation_modules
57
93
  end
58
94
  end
59
95
 
60
96
  # this module is a namespace for building schema classes and schema modules.
97
+ # @api private
61
98
  module SchemaClasses
62
- extend Util::Memoize
63
-
64
99
  class << self
100
+ # @private
101
+ # @return [Set<Module>]
102
+ def includes_for(instance)
103
+ includes = Set[]
104
+ includes << Base::ArrayNode if instance.respond_to?(:to_ary)
105
+ includes << Base::HashNode if instance.respond_to?(:to_hash)
106
+ includes << Base::StringNode if instance.respond_to?(:to_str)
107
+ includes.freeze
108
+ end
109
+
65
110
  # a JSI Schema Class which represents the given schemas.
66
111
  # an instance of the class is a JSON Schema instance described by all of the given schemas.
67
- # @private
112
+ # @api private
68
113
  # @param schemas [Enumerable<JSI::Schema>] schemas which the class will represent
114
+ # @param includes [Enumerable<Module>] modules which will be included on the class
69
115
  # @return [Class subclassing JSI::Base]
70
- def class_for_schemas(schemas)
71
- schemas = SchemaSet.ensure_schema_set(schemas)
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
72
123
 
73
- jsi_memoize(:class_for_schemas, schemas) do |schemas|
74
- Class.new(Base).instance_exec(schemas) do |schemas|
124
+ private def class_for_schemas_compute(schemas: , includes: , mutable: )
125
+ Class.new(Base) do
75
126
  define_singleton_method(:jsi_class_schemas) { schemas }
76
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
+
151
+ includes.each { |m| include(m) }
77
152
  schemas.each { |schema| include(schema.jsi_schema_module) }
78
153
  jsi_class = self
79
154
  define_method(:jsi_class) { jsi_class }
80
155
 
81
156
  self
82
157
  end
83
- end
84
158
  end
85
159
 
86
- # @private
87
- # a subclass of MetaschemaNode::BootstrapSchema with the given modules included
88
- # @param modules [Set<Module>] metaschema instance modules
160
+ # a subclass of MetaSchemaNode::BootstrapSchema with the given modules included
161
+ # @api private
162
+ # @param modules [Set<Module>] schema implementation modules
89
163
  # @return [Class]
90
164
  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 }
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
172
+ define_singleton_method(:schema_implementation_modules) { modules }
173
+ define_method(:schema_implementation_modules) { modules }
96
174
  modules.each { |mod| include(mod) }
97
175
 
98
176
  self
99
177
  end
100
- end
101
178
  end
102
179
 
103
180
  # see {Schema#jsi_schema_module}
104
181
  # @api private
105
- # @return [Module]
182
+ # @return [SchemaModule]
106
183
  def module_for_schema(schema)
107
184
  Schema.ensure_schema(schema)
108
- jsi_memoize(:module_for_schema, schema) do |schema|
109
- Module.new.tap do |m|
110
- m.module_eval do
111
- define_singleton_method(:schema) { schema }
185
+ raise(Bug, "non-Base schema cannot have schema module: #{schema}") unless schema.is_a?(Base)
186
+ @schema_module_map[schema]
187
+ end
112
188
 
113
- extend SchemaModule
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
195
+ end
196
+ end
114
197
 
115
- schema.jsi_schema_instance_modules.each do |mod|
116
- include(mod)
117
- end
198
+ # a module of readers for described property names of the given schema.
199
+ #
200
+ # @private
201
+ # @param schema [JSI::Schema] a schema for which to define readers for any described property names
202
+ # @param conflicting_modules [Enumerable<Module>] an array of modules (or classes) which
203
+ # may be used alongside the accessor module. methods defined by any conflicting_module
204
+ # will not be defined as accessors.
205
+ # @return [Module]
206
+ def schema_property_reader_module(schema, conflicting_modules: )
207
+ Schema.ensure_schema(schema)
208
+ @schema_property_reader_module_map[schema: schema, conflicting_modules: conflicting_modules]
209
+ end
118
210
 
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
211
+ private def schema_property_reader_module_compute(schema: , conflicting_modules: )
212
+ Module.new do
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
124
217
 
125
- define_singleton_method(:jsi_property_accessors) { accessor_module.jsi_property_accessors }
218
+ define_singleton_method(:inspect) { "(JSI Schema Property Reader Module: #{readers.join(', ')})" }
126
219
 
127
- if schema.describes_schema?
128
- extend DescribesSchemaModule
129
- end
220
+ define_singleton_method(:jsi_property_readers) { readers }
130
221
 
131
- @possibly_schema_node = schema
132
- extend(SchemaModulePossibly)
133
- schema.jsi_schemas.each do |schema_schema|
134
- extend JSI::SchemaClasses.accessor_module_for_schema(schema_schema,
135
- conflicting_modules: Set[Module, SchemaModule, SchemaModulePossibly],
136
- setters: false,
137
- )
222
+ readers.each do |property_name|
223
+ define_method(property_name) do |**kw, &block|
224
+ self[property_name, **kw, &block]
138
225
  end
139
226
  end
140
227
  end
141
- end
142
228
  end
143
229
 
144
- # a module of accessors for described property names of the given schema.
145
- # getters are always defined. setters are defined by default.
146
- # @param schema [JSI::Schema] a schema for which to define accessors for any described property names
147
- # @param conflicting_modules [Enumerable<Module>] an array of modules (or classes) which
148
- # may be used alongside the accessor module. methods defined by any conflicting_module
149
- # will not be defined as accessors.
150
- # @param setters [Boolean] whether to define setter methods
151
- # @return [Module]
152
- def accessor_module_for_schema(schema, conflicting_modules: , setters: true)
230
+ # a module of writers for described property names of the given schema.
231
+ # @private
232
+ def schema_property_writer_module(schema, conflicting_modules: )
153
233
  Schema.ensure_schema(schema)
154
- jsi_memoize(:accessor_module_for_schema, schema, conflicting_modules, setters) do |schema, conflicting_modules, setters|
155
- Module.new.tap do |m|
156
- m.module_eval do
157
- conflicting_instance_methods = (conflicting_modules + [m]).map do |mod|
158
- mod.instance_methods + mod.private_instance_methods
159
- end.inject(Set.new, &:|)
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
-
168
- accessors_to_define.each do |property_name|
169
- define_method(property_name) do |*a|
170
- self[property_name, *a]
171
- end
172
- if setters
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|
173
250
  define_method("#{property_name}=") do |value|
174
251
  self[property_name] = value
175
252
  end
176
- end
177
- end
178
253
  end
179
254
  end
180
- end
181
255
  end
182
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) }
183
263
  end
184
264
 
185
- # a JSI Schema module and a JSI::NotASchemaModule are both a SchemaModulePossibly.
186
- # this module provides a #[] method.
187
- module SchemaModulePossibly
188
- attr_reader :possibly_schema_node
265
+ # connecting {SchemaModule}s via {SchemaModule::Connection}s
266
+ module SchemaModule::Connects
267
+ attr_reader :jsi_node
189
268
 
190
269
  # a name relative to a named schema module of an ancestor schema.
191
- # for example, if `Foos = JSI::JSONSchemaOrgDraft07.new_schema_module({'items' => {}})`
270
+ # for example, if `Foos = JSI::JSONSchemaDraft07.new_schema_module({'items' => {}})`
192
271
  # then the module `Foos.items` will have a name_from_ancestor of `"Foos.items"`
272
+ # @api private
193
273
  # @return [String, nil]
194
274
  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
275
+ named_ancestor_schema, tokens = named_ancestor_schema_tokens
276
+ return nil unless named_ancestor_schema
199
277
 
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
278
+ name = named_ancestor_schema.jsi_schema_module.name
279
+ ancestor = named_ancestor_schema
203
280
  tokens.each do |token|
204
- if parent.jsi_schemas.any? { |s| s.jsi_schema_module.jsi_property_accessors.include?(token) }
281
+ if ancestor.jsi_property_readers.include?(token)
205
282
  name += ".#{token}"
206
283
  elsif [String, Numeric, TrueClass, FalseClass, NilClass].any? { |m| token.is_a?(m) }
207
284
  name += "[#{token.inspect}]"
208
285
  else
209
286
  return nil
210
287
  end
211
- parent = parent[token]
288
+ ancestor = ancestor[token]
212
289
  end
213
- name
290
+ name.freeze
214
291
  end
215
292
 
216
- # subscripting a JSI schema module or a NotASchemaModule will subscript the node, and
217
- # if the result is a JSI::Schema, return the JSI Schema module of that schema; if it is a PathedNode,
218
- # return a NotASchemaModule; or if it is another value (a basic type), return that value.
293
+ # Subscripting a JSI schema module or a {SchemaModule::Connection} will subscript its node, and
294
+ # if the result is a JSI::Schema, return the JSI Schema module of that schema; if it is a JSI::Base,
295
+ # return a SchemaModule::Connection; or if it is another value (a basic type), return that value.
219
296
  #
220
297
  # @param token [Object]
221
- # @return [Module, NotASchemaModule, Object]
222
- def [](token)
223
- sub = @possibly_schema_node[token]
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)
303
+ raise(ArgumentError) unless kw.empty? # TODO remove eventually (keyword argument compatibility)
304
+ sub = @jsi_node[token]
224
305
  if sub.is_a?(JSI::Schema)
306
+ sub.jsi_schema_module_exec(&block) if block
225
307
  sub.jsi_schema_module
226
- elsif sub.is_a?(JSI::PathedNode)
227
- NotASchemaModule.new(sub)
308
+ elsif block
309
+ raise(ArgumentError, "block given but token #{token.inspect} does not identify a schema")
310
+ elsif sub.is_a?(JSI::Base)
311
+ SchemaModule::Connection.new(sub)
228
312
  else
229
313
  sub
230
314
  end
231
315
  end
316
+
317
+ private
318
+
319
+ # @return [Array<JSI::Schema, Array>, nil]
320
+ def named_ancestor_schema_tokens
321
+ schema_ancestors = @jsi_node.jsi_ancestor_nodes
322
+ named_ancestor_schema = schema_ancestors.detect { |jsi| jsi.is_a?(JSI::Schema) && jsi.jsi_schema_module.name }
323
+ return nil unless named_ancestor_schema
324
+ tokens = @jsi_node.jsi_ptr.relative_to(named_ancestor_schema.jsi_ptr).tokens
325
+ [named_ancestor_schema, tokens]
326
+ end
327
+ end
328
+
329
+ class SchemaModule
330
+ include Connects
232
331
  end
233
332
 
234
- # a JSI Schema Module is a module which represents a schema. a NotASchemaModule represents
333
+ # A JSI Schema Module is a module which represents a schema. A SchemaModule::Connection represents
235
334
  # a node in a schema's document which is not a schema, such as the 'properties'
236
335
  # object (which contains schemas but is not a schema).
237
336
  #
238
337
  # instances of this class act as a stand-in to allow users to subscript or call property accessors on
239
338
  # schema modules to refer to their subschemas' schema modules.
240
339
  #
241
- # a NotASchemaModule is extended with the module_for_schema of the node's schema.
242
- #
243
- # NotASchemaModule holds a node which is not a schema. when subscripted, it subscripts
244
- # its node. if the value is a JSI::Schema, its schema module is returned. if the value
245
- # is another node, a NotASchemaModule for that node is returned. otherwise - when the
246
- # value is a basic type - that value itself is returned.
247
- class NotASchemaModule
248
- # @param node [JSI::PathedNode]
340
+ # A SchemaModule::Connection has readers for property names described by the node's schemas.
341
+ class SchemaModule::Connection
342
+ include SchemaModule::Connects
343
+
344
+ # @param node [JSI::Base]
249
345
  def initialize(node)
250
- unless node.is_a?(JSI::PathedNode)
251
- raise(TypeError, "not JSI::PathedNode: #{node.pretty_inspect.chomp}")
252
- end
253
- if node.is_a?(JSI::Schema)
254
- raise(TypeError, "cannot instantiate NotASchemaModule for a JSI::Schema node: #{node.pretty_inspect.chomp}")
255
- end
256
- @possibly_schema_node = node
346
+ raise(Bug, "node must be JSI::Base: #{node.pretty_inspect.chomp}") unless node.is_a?(JSI::Base)
347
+ raise(Bug, "node must not be JSI::Schema: #{node.pretty_inspect.chomp}") if node.is_a?(JSI::Schema)
348
+ @jsi_node = node
257
349
  node.jsi_schemas.each do |schema|
258
- extend(JSI::SchemaClasses.accessor_module_for_schema(schema, conflicting_modules: [NotASchemaModule, SchemaModulePossibly], setters: false))
350
+ extend(JSI::SchemaClasses.schema_property_reader_module(schema, conflicting_modules: [SchemaModule::Connection]))
259
351
  end
260
352
  end
261
353
 
262
- include SchemaModulePossibly
263
-
264
354
  # @return [String]
265
355
  def inspect
266
356
  if name_from_ancestor
267
- "#{name_from_ancestor} (JSI wrapper for Schema Module)"
357
+ -"#{name_from_ancestor} (#{self.class})"
268
358
  else
269
- "(JSI wrapper for Schema Module: #{@possibly_schema_node.jsi_ptr.uri})"
359
+ -"(#{self.class}: #{@jsi_node.jsi_ptr.uri})"
270
360
  end
271
361
  end
362
+
363
+ def to_s
364
+ inspect
365
+ end
272
366
  end
273
367
  end