jsi 0.6.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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