jsi-dev 0.0.8 → 0.0.9

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 (148) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +3 -4
  3. data/CHANGELOG.md +19 -0
  4. data/LICENSE.md +2 -3
  5. data/README.md +87 -43
  6. data/docs/{glossary.md → Glossary.md} +84 -52
  7. data/jsi.gemspec +1 -1
  8. data/lib/jsi/base/mutability.rb +48 -0
  9. data/lib/jsi/base/node.rb +66 -52
  10. data/lib/jsi/base.rb +592 -176
  11. data/lib/jsi/jsi_coder.rb +4 -2
  12. data/lib/jsi/metaschema_node/bootstrap_schema.rb +118 -59
  13. data/lib/jsi/metaschema_node.rb +244 -154
  14. data/lib/jsi/ptr.rb +45 -17
  15. data/lib/jsi/ref.rb +197 -0
  16. data/lib/jsi/registry.rb +311 -0
  17. data/lib/jsi/schema/cxt/child_application.rb +35 -0
  18. data/lib/jsi/schema/cxt/inplace_application.rb +37 -0
  19. data/lib/jsi/schema/cxt.rb +80 -0
  20. data/lib/jsi/schema/dialect.rb +137 -0
  21. data/lib/jsi/schema/draft04.rb +113 -5
  22. data/lib/jsi/schema/draft06.rb +123 -5
  23. data/lib/jsi/schema/draft07.rb +157 -5
  24. data/lib/jsi/schema/draft202012.rb +303 -0
  25. data/lib/jsi/schema/dynamic_anchor_map.rb +63 -0
  26. data/lib/jsi/schema/element.rb +69 -0
  27. data/lib/jsi/schema/elements/anchor.rb +13 -0
  28. data/lib/jsi/schema/elements/array_validation.rb +82 -0
  29. data/lib/jsi/schema/elements/comment.rb +10 -0
  30. data/lib/jsi/schema/{validation → elements}/const.rb +11 -7
  31. data/lib/jsi/schema/elements/contains.rb +59 -0
  32. data/lib/jsi/schema/elements/contains_minmax.rb +91 -0
  33. data/lib/jsi/schema/elements/content_encoding.rb +10 -0
  34. data/lib/jsi/schema/elements/content_media_type.rb +10 -0
  35. data/lib/jsi/schema/elements/content_schema.rb +16 -0
  36. data/lib/jsi/schema/elements/default.rb +11 -0
  37. data/lib/jsi/schema/elements/definitions.rb +19 -0
  38. data/lib/jsi/schema/elements/dependencies.rb +99 -0
  39. data/lib/jsi/schema/elements/dependent_required.rb +49 -0
  40. data/lib/jsi/schema/elements/dependent_schemas.rb +69 -0
  41. data/lib/jsi/schema/elements/dynamic_ref.rb +69 -0
  42. data/lib/jsi/schema/elements/enum.rb +26 -0
  43. data/lib/jsi/schema/elements/examples.rb +10 -0
  44. data/lib/jsi/schema/elements/format.rb +10 -0
  45. data/lib/jsi/schema/elements/id.rb +30 -0
  46. data/lib/jsi/schema/elements/if_then_else.rb +82 -0
  47. data/lib/jsi/schema/elements/info_bool.rb +10 -0
  48. data/lib/jsi/schema/elements/info_string.rb +10 -0
  49. data/lib/jsi/schema/elements/items.rb +93 -0
  50. data/lib/jsi/schema/elements/items_prefixed.rb +96 -0
  51. data/lib/jsi/schema/elements/not.rb +31 -0
  52. data/lib/jsi/schema/elements/numeric.rb +137 -0
  53. data/lib/jsi/schema/elements/numeric_draft04.rb +77 -0
  54. data/lib/jsi/schema/elements/object_validation.rb +55 -0
  55. data/lib/jsi/schema/elements/pattern.rb +35 -0
  56. data/lib/jsi/schema/elements/properties.rb +145 -0
  57. data/lib/jsi/schema/elements/property_names.rb +48 -0
  58. data/lib/jsi/schema/elements/ref.rb +62 -0
  59. data/lib/jsi/schema/elements/required.rb +34 -0
  60. data/lib/jsi/schema/elements/self.rb +24 -0
  61. data/lib/jsi/schema/elements/some_of.rb +180 -0
  62. data/lib/jsi/schema/elements/string_validation.rb +57 -0
  63. data/lib/jsi/schema/elements/type.rb +43 -0
  64. data/lib/jsi/schema/elements/unevaluated_items.rb +54 -0
  65. data/lib/jsi/schema/elements/unevaluated_properties.rb +54 -0
  66. data/lib/jsi/schema/elements/xschema.rb +10 -0
  67. data/lib/jsi/schema/elements/xvocabulary.rb +10 -0
  68. data/lib/jsi/schema/elements.rb +101 -0
  69. data/lib/jsi/schema/issue.rb +3 -4
  70. data/lib/jsi/schema/schema_ancestor_node.rb +105 -52
  71. data/lib/jsi/schema/vocabulary.rb +36 -0
  72. data/lib/jsi/schema.rb +598 -383
  73. data/lib/jsi/schema_classes.rb +195 -141
  74. data/lib/jsi/schema_set.rb +85 -128
  75. data/lib/jsi/set.rb +23 -0
  76. data/lib/jsi/simple_wrap.rb +14 -17
  77. data/lib/jsi/struct.rb +57 -0
  78. data/lib/jsi/uri.rb +40 -0
  79. data/lib/jsi/util/private/memo_map.rb +9 -13
  80. data/lib/jsi/util/private.rb +59 -31
  81. data/lib/jsi/util/typelike.rb +19 -60
  82. data/lib/jsi/util.rb +53 -34
  83. data/lib/jsi/validation/error.rb +45 -2
  84. data/lib/jsi/validation/result.rb +121 -90
  85. data/lib/jsi/validation.rb +1 -6
  86. data/lib/jsi/version.rb +1 -1
  87. data/lib/jsi.rb +170 -36
  88. data/lib/schemas/json-schema.org/draft/2020-12/schema.rb +62 -0
  89. data/lib/schemas/json-schema.org/draft-04/schema.rb +60 -109
  90. data/lib/schemas/json-schema.org/draft-06/schema.rb +53 -108
  91. data/lib/schemas/json-schema.org/draft-07/schema.rb +63 -127
  92. data/readme.rb +4 -4
  93. data/{resources}/schemas/2020-12_strict.json +19 -0
  94. data/{resources}/schemas/json-schema.org/draft/2020-12/meta/applicator.json +48 -0
  95. data/{resources}/schemas/json-schema.org/draft/2020-12/meta/content.json +17 -0
  96. data/{resources}/schemas/json-schema.org/draft/2020-12/meta/core.json +51 -0
  97. data/{resources}/schemas/json-schema.org/draft/2020-12/meta/format-annotation.json +14 -0
  98. data/{resources}/schemas/json-schema.org/draft/2020-12/meta/format-assertion.json +14 -0
  99. data/{resources}/schemas/json-schema.org/draft/2020-12/meta/meta-data.json +37 -0
  100. data/{resources}/schemas/json-schema.org/draft/2020-12/meta/unevaluated.json +15 -0
  101. data/{resources}/schemas/json-schema.org/draft/2020-12/meta/validation.json +98 -0
  102. data/{resources}/schemas/json-schema.org/draft/2020-12/schema.json +58 -0
  103. metadata +73 -52
  104. data/lib/jsi/metaschema.rb +0 -6
  105. data/lib/jsi/schema/application/child_application/contains.rb +0 -25
  106. data/lib/jsi/schema/application/child_application/draft04.rb +0 -21
  107. data/lib/jsi/schema/application/child_application/draft06.rb +0 -28
  108. data/lib/jsi/schema/application/child_application/draft07.rb +0 -28
  109. data/lib/jsi/schema/application/child_application/items.rb +0 -18
  110. data/lib/jsi/schema/application/child_application/properties.rb +0 -25
  111. data/lib/jsi/schema/application/child_application.rb +0 -13
  112. data/lib/jsi/schema/application/draft04.rb +0 -8
  113. data/lib/jsi/schema/application/draft06.rb +0 -8
  114. data/lib/jsi/schema/application/draft07.rb +0 -8
  115. data/lib/jsi/schema/application/inplace_application/dependencies.rb +0 -28
  116. data/lib/jsi/schema/application/inplace_application/draft04.rb +0 -25
  117. data/lib/jsi/schema/application/inplace_application/draft06.rb +0 -26
  118. data/lib/jsi/schema/application/inplace_application/draft07.rb +0 -32
  119. data/lib/jsi/schema/application/inplace_application/ifthenelse.rb +0 -20
  120. data/lib/jsi/schema/application/inplace_application/ref.rb +0 -18
  121. data/lib/jsi/schema/application/inplace_application/someof.rb +0 -44
  122. data/lib/jsi/schema/application/inplace_application.rb +0 -14
  123. data/lib/jsi/schema/application.rb +0 -12
  124. data/lib/jsi/schema/ref.rb +0 -183
  125. data/lib/jsi/schema/validation/array.rb +0 -69
  126. data/lib/jsi/schema/validation/contains.rb +0 -25
  127. data/lib/jsi/schema/validation/dependencies.rb +0 -49
  128. data/lib/jsi/schema/validation/draft04/minmax.rb +0 -91
  129. data/lib/jsi/schema/validation/draft04.rb +0 -110
  130. data/lib/jsi/schema/validation/draft06.rb +0 -120
  131. data/lib/jsi/schema/validation/draft07.rb +0 -157
  132. data/lib/jsi/schema/validation/enum.rb +0 -25
  133. data/lib/jsi/schema/validation/ifthenelse.rb +0 -46
  134. data/lib/jsi/schema/validation/items.rb +0 -54
  135. data/lib/jsi/schema/validation/not.rb +0 -20
  136. data/lib/jsi/schema/validation/numeric.rb +0 -121
  137. data/lib/jsi/schema/validation/object.rb +0 -45
  138. data/lib/jsi/schema/validation/pattern.rb +0 -34
  139. data/lib/jsi/schema/validation/properties.rb +0 -101
  140. data/lib/jsi/schema/validation/property_names.rb +0 -32
  141. data/lib/jsi/schema/validation/ref.rb +0 -40
  142. data/lib/jsi/schema/validation/required.rb +0 -27
  143. data/lib/jsi/schema/validation/someof.rb +0 -90
  144. data/lib/jsi/schema/validation/string.rb +0 -47
  145. data/lib/jsi/schema/validation/type.rb +0 -49
  146. data/lib/jsi/schema/validation.rb +0 -49
  147. data/lib/jsi/schema_registry.rb +0 -190
  148. data/lib/jsi/util/private/attr_struct.rb +0 -130
@@ -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,25 +102,28 @@ 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
 
42
- alias_method :to_s, :inspect
117
+ def to_s
118
+ inspect
119
+ end
43
120
 
44
- # invokes {JSI::Schema#new_jsi} on this module's schema, passing the given instance.
121
+ # invokes {JSI::Schema#new_jsi} on this module's schema, passing the given parameters.
45
122
  #
46
- # @param (see JSI::Schema#new_jsi)
47
- # @return [JSI::Base subclass] a JSI whose content comes from the given instance and whose schemas are
48
- # 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.
49
125
  def new_jsi(instance, **kw)
126
+ raise(BlockGivenError) if block_given?
50
127
  schema.new_jsi(instance, **kw)
51
128
  end
52
129
 
@@ -64,35 +141,47 @@ module JSI
64
141
  def instance_valid?(instance)
65
142
  schema.instance_valid?(instance)
66
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
67
155
  end
68
156
 
69
- # A module to extend the {SchemaModule} of a schema which describes other schemas (a {Schema::DescribesSchema})
70
- module SchemaModule::DescribesSchemaModule
157
+ # A module to extend the {SchemaModule} of a schema which describes other schemas (a {Schema::MetaSchema})
158
+ module SchemaModule::MetaSchemaModule
71
159
  # Instantiates the given schema content as a JSI Schema.
72
160
  #
73
- # see {JSI::Schema::DescribesSchema#new_schema}
161
+ # See {JSI::Schema::MetaSchema#new_schema}.
74
162
  #
75
- # @param (see JSI::Schema::DescribesSchema#new_schema)
76
- # @yield (see JSI::Schema::DescribesSchema#new_schema)
77
- # @return [JSI::Base subclass + JSI::Schema] a JSI which is a {JSI::Schema} whose content comes from
78
- # 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.
79
165
  def new_schema(schema_content, **kw, &block)
80
166
  schema.new_schema(schema_content, **kw, &block)
81
167
  end
82
168
 
83
- # (see Schema::DescribesSchema#new_schema_module)
169
+ # (see Schema::MetaSchema#new_schema_module)
84
170
  def new_schema_module(schema_content, **kw, &block)
85
- schema.new_schema(schema_content, **kw, &block).jsi_schema_module
171
+ schema.new_schema_module(schema_content, **kw, &block)
86
172
  end
87
173
 
88
- # @return [Set<Module>]
89
- attr_reader :schema_implementation_modules
174
+ # @return [Schema::Dialect]
175
+ def described_dialect
176
+ schema.described_dialect
177
+ end
90
178
  end
91
179
 
92
180
  # this module is a namespace for building schema classes and schema modules.
181
+ # @private
93
182
  module SchemaClasses
94
183
  class << self
95
- # @api private
184
+ # @private
96
185
  # @return [Set<Module>]
97
186
  def includes_for(instance)
98
187
  includes = Set[]
@@ -107,22 +196,28 @@ module JSI
107
196
  # @api private
108
197
  # @param schemas [Enumerable<JSI::Schema>] schemas which the class will represent
109
198
  # @param includes [Enumerable<Module>] modules which will be included on the class
110
- # @return [Class subclassing JSI::Base]
111
- def class_for_schemas(schemas, includes: )
199
+ # @return [Class subclass of JSI::Base]
200
+ def class_for_schemas(schemas, includes: , mutable: )
112
201
  @class_for_schemas_map[
113
- schemas: SchemaSet.ensure_schema_set(schemas),
114
- includes: Util.ensure_module_set(includes),
202
+ schema_modules: schemas.map(&:jsi_schema_module).to_set.freeze,
203
+ includes: includes.to_set.freeze,
204
+ mutable: mutable,
115
205
  ]
116
206
  end
117
207
 
118
- private def class_for_schemas_compute(schemas: , includes: )
208
+ private def class_for_schemas_compute(schema_modules: , includes: , mutable: )
119
209
  Class.new(Base) do
210
+ schemas = SchemaSet.new(schema_modules.map(&:schema))
211
+
120
212
  define_singleton_method(:jsi_class_schemas) { schemas }
121
213
  define_method(:jsi_schemas) { schemas }
122
214
 
123
215
  define_singleton_method(:jsi_class_includes) { includes }
124
216
 
125
- conflicting_modules = Set[JSI::Base] + includes + schemas.map(&:jsi_schema_module)
217
+ mutability_module = mutable ? Base::Mutable : Base::Immutable
218
+ conflicting_modules = Set[JSI::Base, mutability_module] + includes + schema_modules
219
+
220
+ include(mutability_module)
126
221
 
127
222
  reader_modules = schemas.map do |schema|
128
223
  JSI::SchemaClasses.schema_property_reader_module(schema, conflicting_modules: conflicting_modules)
@@ -132,82 +227,38 @@ module JSI
132
227
  define_method(:jsi_property_readers) { readers }
133
228
  define_singleton_method(:jsi_property_readers) { readers }
134
229
 
135
- writer_modules = schemas.map do |schema|
136
- JSI::SchemaClasses.schema_property_writer_module(schema, conflicting_modules: conflicting_modules)
230
+ if mutable
231
+ writer_modules = schemas.map do |schema|
232
+ JSI::SchemaClasses.schema_property_writer_module(schema, conflicting_modules: conflicting_modules)
233
+ end
234
+ writer_modules.each { |m| include(m) }
137
235
  end
138
- writer_modules.each { |m| include m }
139
236
 
140
237
  includes.each { |m| include(m) }
141
- schemas.each { |schema| include(schema.jsi_schema_module) }
142
- jsi_class = self
143
- define_method(:jsi_class) { jsi_class }
144
-
145
- self
146
- end
147
- end
148
-
149
- # a subclass of MetaschemaNode::BootstrapSchema with the given modules included
150
- # @api private
151
- # @param modules [Set<Module>] schema implementation modules
152
- # @return [Class]
153
- def bootstrap_schema_class(modules)
154
- @bootstrap_schema_class_map[
155
- modules: Util.ensure_module_set(modules),
156
- ]
157
- end
158
-
159
- private def bootstrap_schema_class_compute(modules: )
160
- Class.new(MetaschemaNode::BootstrapSchema) do
161
- define_singleton_method(:schema_implementation_modules) { modules }
162
- define_method(:schema_implementation_modules) { modules }
163
- modules.each { |mod| include(mod) }
164
-
165
- self
238
+ schema_modules.to_a.reverse_each { |m| include(m) }
166
239
  end
167
240
  end
168
241
 
169
- # see {Schema#jsi_schema_module}
170
- # @api private
171
- # @return [Module + SchemaModule]
172
- def module_for_schema(schema)
173
- Schema.ensure_schema(schema)
174
- raise(Bug, "non-Base schema cannot have schema module: #{schema}") unless schema.is_a?(Base)
175
- @schema_module_map[schema: schema]
176
- end
177
-
178
- private def schema_module_compute(schema: )
179
- SchemaModule.new(schema)
180
- end
181
-
182
- # @deprecated after v0.7
183
- def accessor_module_for_schema(schema, conflicting_modules: , setters: true)
184
- Module.new do
185
- include SchemaClasses.schema_property_reader_module(schema, conflicting_modules: conflicting_modules)
186
- include SchemaClasses.schema_property_writer_module(schema, conflicting_modules: conflicting_modules) if setters
187
- end
188
- end
189
-
190
242
  # a module of readers for described property names of the given schema.
191
243
  #
192
- # @api private
244
+ # @private
193
245
  # @param schema [JSI::Schema] a schema for which to define readers for any described property names
194
246
  # @param conflicting_modules [Enumerable<Module>] an array of modules (or classes) which
195
247
  # may be used alongside the accessor module. methods defined by any conflicting_module
196
248
  # will not be defined as accessors.
197
249
  # @return [Module]
198
250
  def schema_property_reader_module(schema, conflicting_modules: )
199
- Schema.ensure_schema(schema)
200
- @schema_property_reader_module_map[schema: schema, conflicting_modules: conflicting_modules]
201
- end
202
-
203
- private def schema_property_reader_module_compute(schema: , conflicting_modules: )
204
- Module.new do
205
- define_singleton_method(:inspect) { '(JSI Schema Property Reader Module)' }
206
-
207
- readers = schema.described_object_property_names.select do |name|
251
+ @schema_property_reader_module_map[
252
+ schema.described_object_property_names.select do |name|
208
253
  Util.ok_ruby_method_name?(name) &&
209
254
  !conflicting_modules.any? { |m| m.method_defined?(name) || m.private_method_defined?(name) }
210
255
  end.to_set.freeze
256
+ ]
257
+ end
258
+
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(', ')})" }
211
262
 
212
263
  define_singleton_method(:jsi_property_readers) { readers }
213
264
 
@@ -220,21 +271,20 @@ module JSI
220
271
  end
221
272
 
222
273
  # a module of writers for described property names of the given schema.
223
- # @api private
274
+ # @private
224
275
  def schema_property_writer_module(schema, conflicting_modules: )
225
- Schema.ensure_schema(schema)
226
- @schema_property_writer_module_map[schema: schema, conflicting_modules: conflicting_modules]
227
- end
228
-
229
- private def schema_property_writer_module_compute(schema: , conflicting_modules: )
230
- Module.new do
231
- define_singleton_method(:inspect) { '(JSI Schema Property Writer Module)' }
232
-
233
- writers = schema.described_object_property_names.select do |name|
276
+ @schema_property_writer_module_map[
277
+ schema.described_object_property_names.select do |name|
234
278
  writer = "#{name}="
235
279
  Util.ok_ruby_method_name?(name) &&
236
280
  !conflicting_modules.any? { |m| m.method_defined?(writer) || m.private_method_defined?(writer) }
237
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(', ')})" }
238
288
 
239
289
  define_singleton_method(:jsi_property_writers) { writers }
240
290
 
@@ -248,14 +298,11 @@ module JSI
248
298
  end
249
299
 
250
300
  @class_for_schemas_map = Hash.new { |h, k| h[k] = class_for_schemas_compute(**k) }
251
- @bootstrap_schema_class_map = Hash.new { |h, k| h[k] = bootstrap_schema_class_compute(**k) }
252
- @schema_module_map = Hash.new { |h, k| h[k] = schema_module_compute(**k) }
253
- @schema_property_reader_module_map = Hash.new { |h, k| h[k] = schema_property_reader_module_compute(**k) }
254
- @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) }
255
303
  end
256
304
 
257
- # connecting {SchemaModule}s via {SchemaModule::Connection}s
258
- module SchemaModule::Connects
305
+ class SchemaModule::Connection
259
306
  attr_reader :jsi_node
260
307
 
261
308
  # a name relative to a named schema module of an ancestor schema.
@@ -264,11 +311,11 @@ module JSI
264
311
  # @api private
265
312
  # @return [String, nil]
266
313
  def name_from_ancestor
267
- named_ancestor_schema, tokens = named_ancestor_schema_tokens
268
- return nil unless named_ancestor_schema
314
+ named_ancestor, tokens = named_ancestor_tokens
315
+ return nil unless named_ancestor
269
316
 
270
- name = named_ancestor_schema.jsi_schema_module.name
271
- ancestor = named_ancestor_schema
317
+ name = named_ancestor.jsi_schema_module_connection.name
318
+ ancestor = named_ancestor
272
319
  tokens.each do |token|
273
320
  if ancestor.jsi_property_readers.include?(token)
274
321
  name += ".#{token}"
@@ -279,49 +326,53 @@ module JSI
279
326
  end
280
327
  ancestor = ancestor[token]
281
328
  end
282
- name
329
+ name.freeze
330
+ end
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
283
337
  end
284
338
 
285
339
  # Subscripting a JSI schema module or a {SchemaModule::Connection} will subscript its node, and
286
340
  # if the result is a JSI::Schema, return the JSI Schema module of that schema; if it is a JSI::Base,
287
- # 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.
288
342
  #
289
343
  # @param token [Object]
290
344
  # @yield If the token identifies a schema and a block is given,
291
345
  # it is evaluated in the context of the schema's JSI schema module
292
346
  # using [Module#module_exec](https://ruby-doc.org/core/Module.html#method-i-module_exec).
293
- # @return [Module, SchemaModule::Connection, Object]
347
+ # @return [SchemaModule, SchemaModule::Connection, Object]
294
348
  def [](token, **kw, &block)
295
349
  raise(ArgumentError) unless kw.empty? # TODO remove eventually (keyword argument compatibility)
350
+ @jsi_node.jsi_child_ensure_present(token)
296
351
  sub = @jsi_node[token]
297
352
  if sub.is_a?(JSI::Schema)
298
353
  sub.jsi_schema_module_exec(&block) if block
299
354
  sub.jsi_schema_module
300
355
  elsif block
301
- 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")
302
357
  elsif sub.is_a?(JSI::Base)
303
- SchemaModule::Connection.new(sub)
358
+ sub.jsi_schema_module_connection
304
359
  else
305
360
  sub
306
361
  end
307
362
  end
308
363
 
309
- private
310
-
311
364
  # @return [Array<JSI::Schema, Array>, nil]
312
- def named_ancestor_schema_tokens
313
- schema_ancestors = @jsi_node.jsi_ancestor_nodes
314
- named_ancestor_schema = schema_ancestors.detect { |jsi| jsi.is_a?(JSI::Schema) && jsi.jsi_schema_module.name }
315
- return nil unless named_ancestor_schema
316
- tokens = @jsi_node.jsi_ptr.relative_to(named_ancestor_schema.jsi_ptr).tokens
317
- [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]
318
373
  end
319
374
  end
320
375
 
321
- class SchemaModule
322
- include Connects
323
- end
324
-
325
376
  # A JSI Schema Module is a module which represents a schema. A SchemaModule::Connection represents
326
377
  # a node in a schema's document which is not a schema, such as the 'properties'
327
378
  # object (which contains schemas but is not a schema).
@@ -330,17 +381,18 @@ module JSI
330
381
  # schema modules to refer to their subschemas' schema modules.
331
382
  #
332
383
  # A SchemaModule::Connection has readers for property names described by the node's schemas.
333
- class SchemaModule::Connection
334
- include SchemaModule::Connects
335
-
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
336
388
  # @param node [JSI::Base]
337
389
  def initialize(node)
338
- raise(Bug, "node must be JSI::Base: #{node.pretty_inspect.chomp}") unless node.is_a?(JSI::Base)
339
- 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)
340
391
  @jsi_node = node
341
392
  node.jsi_schemas.each do |schema|
342
393
  extend(JSI::SchemaClasses.schema_property_reader_module(schema, conflicting_modules: [SchemaModule::Connection]))
343
394
  end
395
+ node.jsi_schema_module_connection_created(self)
344
396
  end
345
397
 
346
398
  # @return [String]
@@ -352,6 +404,8 @@ module JSI
352
404
  end
353
405
  end
354
406
 
355
- alias_method :to_s, :inspect
407
+ def to_s
408
+ inspect
409
+ end
356
410
  end
357
411
  end