jsi 0.8.1 → 0.9.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 (146) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +3 -2
  3. data/CHANGELOG.md +9 -0
  4. data/LICENSE.md +2 -3
  5. data/README.md +68 -31
  6. data/docs/Glossary.md +313 -0
  7. data/jsi.gemspec +1 -0
  8. data/lib/jsi/base/mutability.rb +4 -0
  9. data/lib/jsi/base/node.rb +63 -24
  10. data/lib/jsi/base.rb +556 -173
  11. data/lib/jsi/metaschema_node/bootstrap_schema.rb +106 -56
  12. data/lib/jsi/metaschema_node.rb +227 -160
  13. data/lib/jsi/ptr.rb +32 -15
  14. data/lib/jsi/ref.rb +197 -0
  15. data/lib/jsi/registry.rb +311 -0
  16. data/lib/jsi/schema/cxt/child_application.rb +35 -0
  17. data/lib/jsi/schema/cxt/inplace_application.rb +37 -0
  18. data/lib/jsi/schema/cxt.rb +80 -0
  19. data/lib/jsi/schema/dialect.rb +137 -0
  20. data/lib/jsi/schema/draft04.rb +113 -5
  21. data/lib/jsi/schema/draft06.rb +123 -5
  22. data/lib/jsi/schema/draft07.rb +157 -5
  23. data/lib/jsi/schema/draft202012.rb +303 -0
  24. data/lib/jsi/schema/dynamic_anchor_map.rb +63 -0
  25. data/lib/jsi/schema/element.rb +69 -0
  26. data/lib/jsi/schema/elements/anchor.rb +13 -0
  27. data/lib/jsi/schema/elements/array_validation.rb +82 -0
  28. data/lib/jsi/schema/elements/comment.rb +10 -0
  29. data/lib/jsi/schema/{validation → elements}/const.rb +11 -7
  30. data/lib/jsi/schema/elements/contains.rb +59 -0
  31. data/lib/jsi/schema/elements/contains_minmax.rb +91 -0
  32. data/lib/jsi/schema/elements/content_encoding.rb +10 -0
  33. data/lib/jsi/schema/elements/content_media_type.rb +10 -0
  34. data/lib/jsi/schema/elements/content_schema.rb +16 -0
  35. data/lib/jsi/schema/elements/default.rb +11 -0
  36. data/lib/jsi/schema/elements/definitions.rb +19 -0
  37. data/lib/jsi/schema/elements/dependencies.rb +99 -0
  38. data/lib/jsi/schema/elements/dependent_required.rb +49 -0
  39. data/lib/jsi/schema/elements/dependent_schemas.rb +69 -0
  40. data/lib/jsi/schema/elements/dynamic_ref.rb +69 -0
  41. data/lib/jsi/schema/elements/enum.rb +26 -0
  42. data/lib/jsi/schema/elements/examples.rb +10 -0
  43. data/lib/jsi/schema/elements/format.rb +10 -0
  44. data/lib/jsi/schema/elements/id.rb +30 -0
  45. data/lib/jsi/schema/elements/if_then_else.rb +82 -0
  46. data/lib/jsi/schema/elements/info_bool.rb +10 -0
  47. data/lib/jsi/schema/elements/info_string.rb +10 -0
  48. data/lib/jsi/schema/elements/items.rb +93 -0
  49. data/lib/jsi/schema/elements/items_prefixed.rb +96 -0
  50. data/lib/jsi/schema/elements/not.rb +31 -0
  51. data/lib/jsi/schema/elements/numeric.rb +137 -0
  52. data/lib/jsi/schema/elements/numeric_draft04.rb +77 -0
  53. data/lib/jsi/schema/elements/object_validation.rb +55 -0
  54. data/lib/jsi/schema/elements/pattern.rb +35 -0
  55. data/lib/jsi/schema/elements/properties.rb +145 -0
  56. data/lib/jsi/schema/elements/property_names.rb +48 -0
  57. data/lib/jsi/schema/elements/ref.rb +62 -0
  58. data/lib/jsi/schema/elements/required.rb +34 -0
  59. data/lib/jsi/schema/elements/self.rb +24 -0
  60. data/lib/jsi/schema/elements/some_of.rb +180 -0
  61. data/lib/jsi/schema/elements/string_validation.rb +57 -0
  62. data/lib/jsi/schema/elements/type.rb +43 -0
  63. data/lib/jsi/schema/elements/unevaluated_items.rb +54 -0
  64. data/lib/jsi/schema/elements/unevaluated_properties.rb +54 -0
  65. data/lib/jsi/schema/elements/xschema.rb +10 -0
  66. data/lib/jsi/schema/elements/xvocabulary.rb +10 -0
  67. data/lib/jsi/schema/elements.rb +101 -0
  68. data/lib/jsi/schema/issue.rb +3 -4
  69. data/lib/jsi/schema/schema_ancestor_node.rb +103 -50
  70. data/lib/jsi/schema/vocabulary.rb +36 -0
  71. data/lib/jsi/schema.rb +520 -338
  72. data/lib/jsi/schema_classes.rb +168 -124
  73. data/lib/jsi/schema_set.rb +67 -126
  74. data/lib/jsi/set.rb +23 -0
  75. data/lib/jsi/simple_wrap.rb +13 -16
  76. data/lib/jsi/struct.rb +57 -0
  77. data/lib/jsi/uri.rb +40 -0
  78. data/lib/jsi/util/private/memo_map.rb +9 -13
  79. data/lib/jsi/util/private.rb +57 -12
  80. data/lib/jsi/util/typelike.rb +19 -64
  81. data/lib/jsi/util.rb +52 -34
  82. data/lib/jsi/validation/error.rb +41 -2
  83. data/lib/jsi/validation/result.rb +118 -71
  84. data/lib/jsi/validation.rb +1 -6
  85. data/lib/jsi/version.rb +1 -1
  86. data/lib/jsi.rb +158 -41
  87. data/lib/schemas/json-schema.org/draft/2020-12/schema.rb +67 -0
  88. data/lib/schemas/json-schema.org/draft-04/schema.rb +63 -106
  89. data/lib/schemas/json-schema.org/draft-06/schema.rb +56 -105
  90. data/lib/schemas/json-schema.org/draft-07/schema.rb +67 -124
  91. data/readme.rb +3 -3
  92. data/{resources}/schemas/2020-12_strict.json +19 -0
  93. data/{resources}/schemas/json-schema.org/draft/2020-12/meta/applicator.json +48 -0
  94. data/{resources}/schemas/json-schema.org/draft/2020-12/meta/content.json +17 -0
  95. data/{resources}/schemas/json-schema.org/draft/2020-12/meta/core.json +51 -0
  96. data/{resources}/schemas/json-schema.org/draft/2020-12/meta/format-annotation.json +14 -0
  97. data/{resources}/schemas/json-schema.org/draft/2020-12/meta/format-assertion.json +14 -0
  98. data/{resources}/schemas/json-schema.org/draft/2020-12/meta/meta-data.json +37 -0
  99. data/{resources}/schemas/json-schema.org/draft/2020-12/meta/unevaluated.json +15 -0
  100. data/{resources}/schemas/json-schema.org/draft/2020-12/meta/validation.json +98 -0
  101. data/{resources}/schemas/json-schema.org/draft/2020-12/schema.json +58 -0
  102. metadata +70 -48
  103. data/lib/jsi/schema/application/child_application/contains.rb +0 -25
  104. data/lib/jsi/schema/application/child_application/draft04.rb +0 -21
  105. data/lib/jsi/schema/application/child_application/draft06.rb +0 -28
  106. data/lib/jsi/schema/application/child_application/draft07.rb +0 -28
  107. data/lib/jsi/schema/application/child_application/items.rb +0 -18
  108. data/lib/jsi/schema/application/child_application/properties.rb +0 -25
  109. data/lib/jsi/schema/application/child_application.rb +0 -13
  110. data/lib/jsi/schema/application/draft04.rb +0 -8
  111. data/lib/jsi/schema/application/draft06.rb +0 -8
  112. data/lib/jsi/schema/application/draft07.rb +0 -8
  113. data/lib/jsi/schema/application/inplace_application/dependencies.rb +0 -28
  114. data/lib/jsi/schema/application/inplace_application/draft04.rb +0 -25
  115. data/lib/jsi/schema/application/inplace_application/draft06.rb +0 -26
  116. data/lib/jsi/schema/application/inplace_application/draft07.rb +0 -32
  117. data/lib/jsi/schema/application/inplace_application/ifthenelse.rb +0 -20
  118. data/lib/jsi/schema/application/inplace_application/ref.rb +0 -18
  119. data/lib/jsi/schema/application/inplace_application/someof.rb +0 -44
  120. data/lib/jsi/schema/application/inplace_application.rb +0 -14
  121. data/lib/jsi/schema/application.rb +0 -12
  122. data/lib/jsi/schema/ref.rb +0 -186
  123. data/lib/jsi/schema/validation/array.rb +0 -69
  124. data/lib/jsi/schema/validation/contains.rb +0 -25
  125. data/lib/jsi/schema/validation/dependencies.rb +0 -49
  126. data/lib/jsi/schema/validation/draft04/minmax.rb +0 -93
  127. data/lib/jsi/schema/validation/draft04.rb +0 -110
  128. data/lib/jsi/schema/validation/draft06.rb +0 -120
  129. data/lib/jsi/schema/validation/draft07.rb +0 -157
  130. data/lib/jsi/schema/validation/enum.rb +0 -25
  131. data/lib/jsi/schema/validation/ifthenelse.rb +0 -46
  132. data/lib/jsi/schema/validation/items.rb +0 -54
  133. data/lib/jsi/schema/validation/not.rb +0 -20
  134. data/lib/jsi/schema/validation/numeric.rb +0 -121
  135. data/lib/jsi/schema/validation/object.rb +0 -45
  136. data/lib/jsi/schema/validation/pattern.rb +0 -34
  137. data/lib/jsi/schema/validation/properties.rb +0 -101
  138. data/lib/jsi/schema/validation/property_names.rb +0 -32
  139. data/lib/jsi/schema/validation/ref.rb +0 -40
  140. data/lib/jsi/schema/validation/required.rb +0 -27
  141. data/lib/jsi/schema/validation/someof.rb +0 -90
  142. data/lib/jsi/schema/validation/string.rb +0 -47
  143. data/lib/jsi/schema/validation/type.rb +0 -49
  144. data/lib/jsi/schema/validation.rb +0 -49
  145. data/lib/jsi/schema_registry.rb +0 -200
  146. data/lib/jsi/util/private/attr_struct.rb +0 -141
@@ -1,19 +1,93 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JSI
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)
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
4
+ # class SchemaModule < class SchemaModule::Connection < class Module
5
+ # this unusual configuration of a class (SchemaModule) subclassing a class inside its own
6
+ # namespace (SchemaModule::Connection) follows reconfiguration changing Connection from a regular
7
+ # class to subclass Module, so that a JSI that isn't a Schema can have a named module, and schemas
8
+ # within that JSI's document have a useful name_from_ancestor when inspecting their instances.
9
+ begin # shenanigans to get classes configured while not confusing yard
10
+ SchemaModule = Class.new(Class.new(Module))
11
+ SchemaModule.const_set(:Connection, SchemaModule.superclass)
12
+ end
16
13
 
14
+ # A Module associated with a JSI Schema (its {Schema#jsi_schema_module #jsi_schema_module}).
15
+ #
16
+ # This module may be opened by the application to define methods for instances described by its schema.
17
+ #
18
+ # The schema module can also be used in some of the same ways as its schema:
19
+ # JSI instances of the schema can be instantiated using {#new_jsi}, or instances
20
+ # can be validated with {#instance_valid?} or {#instance_validate}.
21
+ # Often the schema module is the more convenient object to work with with than the JSI Schema.
22
+ #
23
+ # Naming the schema module (assigning it to a constant) can be useful in a few ways.
24
+ #
25
+ # - When inspected, instances of a schema with a named schema module will show that name.
26
+ # - Naming the module allows it to be opened with Ruby's `module` syntax. Any schema module
27
+ # can be opened with [Module#module_exec](https://ruby-doc.org/core/Module.html#method-i-module_exec)
28
+ # (or from the Schema with {Schema#jsi_schema_module_exec jsi_schema_module_exec})
29
+ # but the `module` syntax can be more convenient, especially for assigning or accessing constants.
30
+ #
31
+ # The schema module makes it straightforward to access the schema modules of the schema's subschemas.
32
+ # It defines readers for schema properties (keywords) on its singleton (that is,
33
+ # called on the module itself, not on instances of it) to access these.
34
+ # The {SchemaModule::Connection#[] #[]} method can also be used.
35
+ #
36
+ # For example, given a schema with an `items` subschema, then `schema.items.jsi_schema_module`
37
+ # and `schema.jsi_schema_module.items` both refer to the same module.
38
+ # Subscripting with {SchemaModule::Connection#[] #[]} can refer to subschemas on properties
39
+ # that can have any name, e.g. `schema.properties['foo'].jsi_schema_module` is the same as
40
+ # `schema.jsi_schema_module.properties['foo']`.
41
+ #
42
+ # Schema module property readers and `#[]` can also take a block, which is passed to `module_exec`.
43
+ #
44
+ # Putting the above together, here is example usage with the schema module of the Contact
45
+ # schema used in the README:
46
+ #
47
+ # ```ruby
48
+ # Contact = JSI.new_schema_module({
49
+ # "$schema" => "http://json-schema.org/draft-07/schema",
50
+ # "type" => "object",
51
+ # "properties" => {
52
+ # "name" => {"type" => "string"},
53
+ # "phone" => {
54
+ # "type" => "array",
55
+ # "items" => {
56
+ # "type" => "object",
57
+ # "properties" => {
58
+ # "location" => {"type" => "string"},
59
+ # "number" => {"type" => "string"}
60
+ # }
61
+ # }
62
+ # }
63
+ # }
64
+ # })
65
+ #
66
+ # module Contact
67
+ # # name a subschema's schema module
68
+ # PhoneNumber = properties['phone'].items
69
+ #
70
+ # # open a subschema's schema module to define methods
71
+ # properties['phone'] do
72
+ # def numbers
73
+ # map(&:number)
74
+ # end
75
+ # end
76
+ # end
77
+ #
78
+ # bill = Contact.new_jsi({"name" => "bill", "phone" => [{"location" => "home", "number" => "555"}]})
79
+ # #> #{<JSI (Contact)>
80
+ # #> "name" => "bill",
81
+ # #> "phone" => #[<JSI (Contact.properties["phone"])>
82
+ # #> #{<JSI (Contact::PhoneNumber)> "location" => "home", "number" => "555"}
83
+ # #> ],
84
+ # #> "nickname" => "big b"
85
+ # #> }
86
+ # ```
87
+ #
88
+ # Note that when `bill` is inspected, schema module names `Contact`, `Contact.properties["phone"]`,
89
+ # and `Contact::PhoneNumber` are informatively shown on respective instances.
90
+ class SchemaModule < SchemaModule::Connection
17
91
  # The schema for which this is the JSI Schema Module
18
92
  # @return [Base + Schema]
19
93
  def schema
@@ -28,14 +102,15 @@ module JSI
28
102
 
29
103
  # @return [String]
30
104
  def inspect
105
+ dam_s = " #{schema.jsi_schema_dynamic_anchor_map.anchor_schemas_identifier}" if !schema.jsi_schema_dynamic_anchor_map.empty?
31
106
  if name_from_ancestor
32
- if schema.schema_absolute_uri
33
- -"#{name_from_ancestor} <#{schema.schema_absolute_uri}> (JSI Schema Module)"
107
+ if schema.jsi_resource_uri
108
+ -"#{name_from_ancestor} <#{schema.jsi_resource_uri}>#{dam_s} (JSI Schema Module)"
34
109
  else
35
- -"#{name_from_ancestor} (JSI Schema Module)"
110
+ -"#{name_from_ancestor}#{dam_s} (JSI Schema Module)"
36
111
  end
37
112
  else
38
- -"(JSI Schema Module: #{schema.schema_uri || schema.jsi_ptr.uri})"
113
+ -"(JSI Schema Module: #{schema.schema_uri || schema.jsi_ptr.uri}#{dam_s})"
39
114
  end
40
115
  end
41
116
 
@@ -45,10 +120,10 @@ module JSI
45
120
 
46
121
  # invokes {JSI::Schema#new_jsi} on this module's schema, passing the given parameters.
47
122
  #
48
- # @param (see JSI::Schema#new_jsi)
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.
123
+ # @return [Base] a JSI whose content comes from the given instance and whose schemas are
124
+ # in-place applicators of this module's schema.
51
125
  def new_jsi(instance, **kw)
126
+ raise(BlockGivenError) if block_given?
52
127
  schema.new_jsi(instance, **kw)
53
128
  end
54
129
 
@@ -66,35 +141,44 @@ module JSI
66
141
  def instance_valid?(instance)
67
142
  schema.instance_valid?(instance)
68
143
  end
144
+
145
+ # See {Schema#describes_schema!}
146
+ def describes_schema!(dialect = nil)
147
+ schema.describes_schema!(dialect)
148
+ end
149
+
150
+ # @private pending stronger stability of dynamic scope
151
+ # See {Schema#with_dynamic_scope_from}
152
+ def with_dynamic_scope_from(node)
153
+ schema.with_dynamic_scope_from(node).jsi_schema_module
154
+ end
69
155
  end
70
156
 
71
157
  # A module to extend the {SchemaModule} of a schema which describes other schemas (a {Schema::MetaSchema})
72
158
  module SchemaModule::MetaSchemaModule
73
159
  # Instantiates the given schema content as a JSI Schema.
74
160
  #
75
- # see {JSI::Schema::MetaSchema#new_schema}
161
+ # See {JSI::Schema::MetaSchema#new_schema}.
76
162
  #
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
163
+ # @return [Base + Schema] A JSI which is a {Schema} whose content comes from
164
+ # the given `schema_content` and whose schemas are in-place applicators of this module's schema.
81
165
  def new_schema(schema_content, **kw, &block)
82
166
  schema.new_schema(schema_content, **kw, &block)
83
167
  end
84
168
 
85
169
  # (see Schema::MetaSchema#new_schema_module)
86
170
  def new_schema_module(schema_content, **kw, &block)
87
- schema.new_schema(schema_content, **kw, &block).jsi_schema_module
171
+ schema.new_schema_module(schema_content, **kw, &block)
88
172
  end
89
173
 
90
- # @return [Set<Module>]
91
- def schema_implementation_modules
92
- schema.schema_implementation_modules
174
+ # @return [Schema::Dialect]
175
+ def described_dialect
176
+ schema.described_dialect
93
177
  end
94
178
  end
95
179
 
96
180
  # this module is a namespace for building schema classes and schema modules.
97
- # @api private
181
+ # @private
98
182
  module SchemaClasses
99
183
  class << self
100
184
  # @private
@@ -112,24 +196,26 @@ module JSI
112
196
  # @api private
113
197
  # @param schemas [Enumerable<JSI::Schema>] schemas which the class will represent
114
198
  # @param includes [Enumerable<Module>] modules which will be included on the class
115
- # @return [Class subclassing JSI::Base]
199
+ # @return [Class subclass of JSI::Base]
116
200
  def class_for_schemas(schemas, includes: , mutable: )
117
201
  @class_for_schemas_map[
118
- schemas: SchemaSet.ensure_schema_set(schemas),
119
- includes: Util.ensure_module_set(includes),
202
+ schema_modules: schemas.map(&:jsi_schema_module).to_set.freeze,
203
+ includes: includes.to_set.freeze,
120
204
  mutable: mutable,
121
205
  ]
122
206
  end
123
207
 
124
- private def class_for_schemas_compute(schemas: , includes: , mutable: )
208
+ private def class_for_schemas_compute(schema_modules: , includes: , mutable: )
125
209
  Class.new(Base) do
210
+ schemas = SchemaSet.new(schema_modules.map(&:schema))
211
+
126
212
  define_singleton_method(:jsi_class_schemas) { schemas }
127
213
  define_method(:jsi_schemas) { schemas }
128
214
 
129
215
  define_singleton_method(:jsi_class_includes) { includes }
130
216
 
131
217
  mutability_module = mutable ? Base::Mutable : Base::Immutable
132
- conflicting_modules = Set[JSI::Base, mutability_module] + includes + schemas.map(&:jsi_schema_module)
218
+ conflicting_modules = Set[JSI::Base, mutability_module] + includes + schema_modules
133
219
 
134
220
  include(mutability_module)
135
221
 
@@ -149,52 +235,10 @@ module JSI
149
235
  end
150
236
 
151
237
  includes.each { |m| include(m) }
152
- schemas.each { |schema| include(schema.jsi_schema_module) }
153
- jsi_class = self
154
- define_method(:jsi_class) { jsi_class }
155
-
156
- self
157
- end
158
- end
159
-
160
- # a subclass of MetaSchemaNode::BootstrapSchema with the given modules included
161
- # @api private
162
- # @param modules [Set<Module>] schema implementation modules
163
- # @return [Class]
164
- def bootstrap_schema_class(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 }
174
- modules.each { |mod| include(mod) }
175
-
176
- self
238
+ schema_modules.to_a.reverse_each { |m| include(m) }
177
239
  end
178
240
  end
179
241
 
180
- # see {Schema#jsi_schema_module}
181
- # @api private
182
- # @return [SchemaModule]
183
- def module_for_schema(schema)
184
- Schema.ensure_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
188
-
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
197
-
198
242
  # a module of readers for described property names of the given schema.
199
243
  #
200
244
  # @private
@@ -204,18 +248,17 @@ module JSI
204
248
  # will not be defined as accessors.
205
249
  # @return [Module]
206
250
  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
210
-
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|
251
+ @schema_property_reader_module_map[
252
+ schema.described_object_property_names.select do |name|
214
253
  Util.ok_ruby_method_name?(name) &&
215
254
  !conflicting_modules.any? { |m| m.method_defined?(name) || m.private_method_defined?(name) }
216
255
  end.to_set.freeze
256
+ ]
257
+ end
217
258
 
218
- define_singleton_method(:inspect) { "(JSI Schema Property Reader Module: #{readers.join(', ')})" }
259
+ private def schema_property_reader_module_compute(readers)
260
+ Module.new do
261
+ define_singleton_method(:inspect) { -"(JSI Schema Property Reader Module: #{readers.to_a.join(', ')})" }
219
262
 
220
263
  define_singleton_method(:jsi_property_readers) { readers }
221
264
 
@@ -230,19 +273,18 @@ module JSI
230
273
  # a module of writers for described property names of the given schema.
231
274
  # @private
232
275
  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|
276
+ @schema_property_writer_module_map[
277
+ schema.described_object_property_names.select do |name|
242
278
  writer = "#{name}="
243
279
  Util.ok_ruby_method_name?(name) &&
244
280
  !conflicting_modules.any? { |m| m.method_defined?(writer) || m.private_method_defined?(writer) }
245
281
  end.to_set.freeze
282
+ ]
283
+ end
284
+
285
+ private def schema_property_writer_module_compute(writers)
286
+ Module.new do
287
+ define_singleton_method(:inspect) { -"(JSI Schema Property Writer Module: #{writers.to_a.join(', ')})" }
246
288
 
247
289
  define_singleton_method(:jsi_property_writers) { writers }
248
290
 
@@ -256,14 +298,11 @@ module JSI
256
298
  end
257
299
 
258
300
  @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) }
301
+ @schema_property_reader_module_map = Hash.new { |h, k| h[k] = schema_property_reader_module_compute(k) }
302
+ @schema_property_writer_module_map = Hash.new { |h, k| h[k] = schema_property_writer_module_compute(k) }
263
303
  end
264
304
 
265
- # connecting {SchemaModule}s via {SchemaModule::Connection}s
266
- module SchemaModule::Connects
305
+ class SchemaModule::Connection
267
306
  attr_reader :jsi_node
268
307
 
269
308
  # a name relative to a named schema module of an ancestor schema.
@@ -272,11 +311,11 @@ module JSI
272
311
  # @api private
273
312
  # @return [String, nil]
274
313
  def name_from_ancestor
275
- named_ancestor_schema, tokens = named_ancestor_schema_tokens
276
- return nil unless named_ancestor_schema
314
+ named_ancestor, tokens = named_ancestor_tokens
315
+ return nil unless named_ancestor
277
316
 
278
- name = named_ancestor_schema.jsi_schema_module.name
279
- ancestor = named_ancestor_schema
317
+ name = named_ancestor.jsi_schema_module_connection.name
318
+ ancestor = named_ancestor
280
319
  tokens.each do |token|
281
320
  if ancestor.jsi_property_readers.include?(token)
282
321
  name += ".#{token}"
@@ -290,9 +329,16 @@ module JSI
290
329
  name.freeze
291
330
  end
292
331
 
332
+ # See {Base#/} - descendent's {Base#jsi_schema_module_connection}
333
+ # @param (see Base#/)
334
+ # @return [SchemaModule::Connection]
335
+ def /(ptr)
336
+ (jsi_node / ptr).jsi_schema_module_connection
337
+ end
338
+
293
339
  # Subscripting a JSI schema module or a {SchemaModule::Connection} will subscript its node, and
294
340
  # 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.
341
+ # return a SchemaModule::Connection; or if it is another value (a simple type), return that value.
296
342
  #
297
343
  # @param token [Object]
298
344
  # @yield If the token identifies a schema and a block is given,
@@ -301,35 +347,32 @@ module JSI
301
347
  # @return [SchemaModule, SchemaModule::Connection, Object]
302
348
  def [](token, **kw, &block)
303
349
  raise(ArgumentError) unless kw.empty? # TODO remove eventually (keyword argument compatibility)
350
+ @jsi_node.jsi_child_ensure_present(token)
304
351
  sub = @jsi_node[token]
305
352
  if sub.is_a?(JSI::Schema)
306
353
  sub.jsi_schema_module_exec(&block) if block
307
354
  sub.jsi_schema_module
308
355
  elsif block
309
- raise(ArgumentError, "block given but token #{token.inspect} does not identify a schema")
356
+ raise(BlockGivenError, "block given but token #{token.inspect} does not identify a schema")
310
357
  elsif sub.is_a?(JSI::Base)
311
- SchemaModule::Connection.new(sub)
358
+ sub.jsi_schema_module_connection
312
359
  else
313
360
  sub
314
361
  end
315
362
  end
316
363
 
317
- private
318
-
319
364
  # @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]
365
+ private def named_ancestor_tokens
366
+ ancestors = @jsi_node.jsi_ancestor_nodes
367
+ named_ancestor = ancestors.detect do |jsi|
368
+ jsi.jsi_schema_module_connection_defined? && jsi.jsi_schema_module_connection.name
369
+ end
370
+ return nil unless named_ancestor
371
+ tokens = @jsi_node.jsi_ptr.relative_to(named_ancestor.jsi_ptr).tokens
372
+ [named_ancestor, tokens]
326
373
  end
327
374
  end
328
375
 
329
- class SchemaModule
330
- include Connects
331
- end
332
-
333
376
  # A JSI Schema Module is a module which represents a schema. A SchemaModule::Connection represents
334
377
  # a node in a schema's document which is not a schema, such as the 'properties'
335
378
  # object (which contains schemas but is not a schema).
@@ -338,17 +381,18 @@ module JSI
338
381
  # schema modules to refer to their subschemas' schema modules.
339
382
  #
340
383
  # A SchemaModule::Connection has readers for property names described by the node's schemas.
341
- class SchemaModule::Connection
342
- include SchemaModule::Connects
343
-
384
+ #
385
+ # This class subclasses Module only so that it can be named, to identify schemas descendent of its node.
386
+ # No object is ever expected to be an instance of a SchemaModule::Connection module.
387
+ class SchemaModule::Connection < Module
344
388
  # @param node [JSI::Base]
345
389
  def initialize(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)
390
+ fail(Bug, "node must be JSI::Base: #{node.pretty_inspect.chomp}") unless node.is_a?(JSI::Base)
348
391
  @jsi_node = node
349
392
  node.jsi_schemas.each do |schema|
350
393
  extend(JSI::SchemaClasses.schema_property_reader_module(schema, conflicting_modules: [SchemaModule::Connection]))
351
394
  end
395
+ node.jsi_schema_module_connection_created(self)
352
396
  end
353
397
 
354
398
  # @return [String]