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
@@ -4,31 +4,17 @@ module JSI
4
4
  # a Set of JSI Schemas. always frozen.
5
5
  #
6
6
  # any schema instance is described by a set of schemas.
7
- class SchemaSet < ::Set
7
+ class SchemaSet < Set
8
+ COMPARE_BY_IDENTITY_DEFINED = method_defined?(:compare_by_identity)
9
+ private_constant(:COMPARE_BY_IDENTITY_DEFINED)
10
+
8
11
  class << self
9
- # builds a SchemaSet from a mutable Set which is added to by the given block
12
+ # Builds a SchemaSet, yielding a yielder to be called with each schema of the SchemaSet.
10
13
  #
11
- # @yield [Set] a Set to which the block may add schemas
14
+ # @yield [Enumerator::Yielder]
12
15
  # @return [SchemaSet]
13
- def build
14
- mutable_set = Set.new
15
- yield mutable_set
16
- new(mutable_set)
17
- end
18
-
19
- # ensures the given param becomes a SchemaSet. returns the param if it is already SchemaSet, otherwise
20
- # initializes a SchemaSet from it.
21
- #
22
- # @param schemas [SchemaSet, Enumerable] the object to ensure becomes a SchemaSet
23
- # @return [SchemaSet] the given SchemaSet, or a SchemaSet initialized from the given Enumerable
24
- # @raise [ArgumentError] when the schemas param is not an Enumerable
25
- # @raise [Schema::NotASchemaError] when the schemas param contains objects which are not Schemas
26
- def ensure_schema_set(schemas)
27
- if schemas.is_a?(SchemaSet)
28
- schemas
29
- else
30
- new(schemas)
31
- end
16
+ def build(&block)
17
+ new(Enumerator.new(&block))
32
18
  end
33
19
  end
34
20
 
@@ -38,7 +24,7 @@ module JSI
38
24
  # if no block is given, the enum must contain only Schemas.
39
25
  #
40
26
  # @param enum [#each] the schemas to be included in the SchemaSet, or items to be passed to the block
41
- # @yieldparam yields each element of enum for preprocessing into a Schema
27
+ # @yieldparam yields each element of `enum` for preprocessing into a Schema
42
28
  # @yieldreturn [JSI::Schema]
43
29
  # @raise [JSI::Schema::NotASchemaError]
44
30
  def initialize(enum, &block)
@@ -55,7 +41,20 @@ module JSI
55
41
  raise(ArgumentError, "#{SchemaSet} initialized with non-Enumerable: #{enum.pretty_inspect.chomp}")
56
42
  end
57
43
 
58
- super
44
+ super(&nil) # note super() does implicitly pass block without &nil
45
+ if COMPARE_BY_IDENTITY_DEFINED
46
+ compare_by_identity
47
+ else
48
+ # TODO rm when Set#compare_by_identity is universally available.
49
+ # note does not work on JRuby, but JRuby has Set#compare_by_identity.
50
+ @hash.compare_by_identity
51
+ end
52
+
53
+ if block
54
+ enum.each_entry { |o| add(block[o]) }
55
+ else
56
+ merge(enum)
57
+ end
59
58
 
60
59
  not_schemas = reject { |s| s.is_a?(Schema) }
61
60
  if !not_schemas.empty?
@@ -69,122 +68,87 @@ module JSI
69
68
  end
70
69
 
71
70
  # Instantiates a new JSI whose content comes from the given `instance` param.
72
- # This SchemaSet indicates the schemas of the JSI - its schemas are inplace
71
+ #
72
+ # The schemas of the JSI (its {Base#jsi_schemas}) are in-place
73
73
  # applicators of this set's schemas which apply to the given instance.
74
+ # The JSI's {Base#jsi_indicated_schemas} set is this set.
75
+ #
76
+ # The resulting JSI is an instance of a number of modules:
77
+ #
78
+ # - The {SchemaModule JSI schema module} of each applicator schema.
79
+ # - {Base::HashNode}, {Base::ArrayNode}, or {Base::StringNode} if the instance is
80
+ # a hash/object, array, or string.
81
+ # - A module defining readers for properties described by applicator schemas.
82
+ # If the instance is mutable, writers as well.
74
83
  #
75
84
  # @param instance [Object] the instance to be represented as a JSI
76
- # @param uri [#to_str, Addressable::URI] The retrieval URI of the instance.
85
+ # @param base_uri [#to_str, URI, nil]
86
+ # The base URI of the instance document. An absolute URI.
87
+ #
88
+ # It is rare that this needs to be specified. It is useful when the instance contains schemas,
89
+ # and schemas in the document use relative URIs for `$id` or `$ref` without an absolute id
90
+ # in an ancestor schema - those URIs will be resolved relative to `base_uri`.
77
91
  #
78
- # It is rare that this needs to be specified, and only useful for instances which contain schemas.
79
- # See {Schema::DescribesSchema#new_schema}'s `uri` param documentation.
92
+ # See also {Base::Conf conf} {Base::Conf#root_uri `root_uri`}. `base_uri` is not used to identify
93
+ # any resource, only to resolve relative URIs. `root_uri` does identify the root resource.
80
94
  # @param register [Boolean] Whether schema resources in the instantiated JSI will be registered
81
- # in the schema registry indicated by param `schema_registry`.
95
+ # in the {Base::Conf configured} {Base::Conf#registry `registry`}.
82
96
  # This is only useful when the JSI is a schema or contains schemas.
83
- # The JSI's root will be registered with the `uri` param, if specified, whether or not the
84
- # root is a schema.
85
- # @param schema_registry [SchemaRegistry, nil] The registry to use for references to other schemas and,
86
- # depending on `register` and `uri` params, to register this JSI and/or any contained schemas with
87
- # declared URIs.
88
97
  # @param stringify_symbol_keys [Boolean] Whether the instance content will have any Symbol keys of Hashes
89
98
  # replaced with Strings (recursively through the document).
90
99
  # Replacement is done on a copy; the given instance is not modified.
91
- # @return [JSI::Base subclass] a JSI whose content comes from the given instance and whose schemas are
92
- # inplace applicators of the schemas in this set.
100
+ # @param mutable [Boolean] Whether the instantiated JSI will be mutable.
101
+ # The instance content will be transformed with the {Base::Conf configured}
102
+ # {Base::Conf#to_immutable `to_immutable`} if the JSI will be immutable.
103
+ # @param conf_kw Additional keyword params are passed to initialize a {Base::Conf}, the JSI's {Base#jsi_conf}.
104
+ # @return [Base] a JSI whose content comes from the given instance and whose schemas are
105
+ # in-place applicators of the schemas in this set.
93
106
  def new_jsi(instance,
94
- uri: nil,
107
+ base_uri: nil,
95
108
  register: false,
96
- schema_registry: JSI.schema_registry,
97
- stringify_symbol_keys: false
109
+ stringify_symbol_keys: false,
110
+ mutable: false,
111
+ **conf_kw
98
112
  )
99
- if stringify_symbol_keys
100
- instance = Util.deep_stringify_symbol_keys(instance)
101
- end
113
+ raise(BlockGivenError) if block_given?
114
+
115
+ conf = Base::Conf.new(**conf_kw)
102
116
 
103
- applied_schemas = inplace_applicator_schemas(instance)
117
+ instance = Util.deep_stringify_symbol_keys(instance) if stringify_symbol_keys
104
118
 
105
- if uri
106
- unless uri.respond_to?(:to_str)
107
- raise(TypeError, "uri must be string or Addressable::URI; got: #{uri.inspect}")
108
- end
109
- uri = Util.uri(uri)
110
- unless uri.absolute? && !uri.fragment
111
- raise(ArgumentError, "uri must be an absolute URI with no fragment; got: #{uri.inspect}")
112
- end
119
+ instance = conf.to_immutable.call(instance) if !mutable && conf.to_immutable
120
+
121
+ applied_schemas = SchemaSet.build do |y|
122
+ c = y.method(:yield) # TODO drop c, just pass y, when all supported Enumerator::Yielder.method_defined?(:to_proc)
123
+ each { |is| is.each_inplace_applicator_schema(instance, &c) }
113
124
  end
114
125
 
126
+ base_uri = Util.uri(base_uri, nnil: false, yabs: true) || conf.root_uri
127
+
115
128
  jsi_class = JSI::SchemaClasses.class_for_schemas(applied_schemas,
116
129
  includes: SchemaClasses.includes_for(instance),
130
+ mutable: mutable,
117
131
  )
118
- jsi = jsi_class.new(instance,
132
+ jsi = jsi_class.new(
133
+ jsi_document: instance,
119
134
  jsi_indicated_schemas: self,
120
- jsi_schema_base_uri: uri,
121
- jsi_schema_registry: schema_registry,
122
- )
135
+ jsi_base_uri: base_uri,
136
+ jsi_conf: conf,
137
+ ).send(:jsi_initialized)
123
138
 
124
- if register && schema_registry
125
- schema_registry.register(jsi)
126
- end
139
+ conf.registry.register(jsi) if register && conf.registry
127
140
 
128
141
  jsi
129
142
  end
130
143
 
131
- # a set of inplace applicator schemas of each schema in this set which apply to the given instance.
132
- # (see {Schema#inplace_applicator_schemas})
133
- #
134
- # @param instance (see Schema#inplace_applicator_schemas)
135
- # @return [JSI::SchemaSet]
136
- def inplace_applicator_schemas(instance)
137
- SchemaSet.new(each_inplace_applicator_schema(instance))
138
- end
139
-
140
- # yields each inplace applicator schema which applies to the given instance.
141
- #
142
- # @param instance (see Schema#inplace_applicator_schemas)
143
- # @yield [JSI::Schema]
144
- # @return [nil, Enumerator] an Enumerator if invoked without a block; otherwise nil
145
- def each_inplace_applicator_schema(instance, &block)
146
- return to_enum(__method__, instance) unless block
147
-
148
- each do |schema|
149
- schema.each_inplace_applicator_schema(instance, &block)
150
- end
151
-
152
- nil
153
- end
154
-
155
- # a set of child applicator subschemas of each schema in this set which apply to the child
156
- # of the given instance on the given token.
157
- # (see {Schema#child_applicator_schemas})
158
- #
159
- # @param instance (see Schema#child_applicator_schemas)
160
- # @return [JSI::SchemaSet]
161
- def child_applicator_schemas(token, instance)
162
- SchemaSet.new(each_child_applicator_schema(token, instance))
163
- end
164
-
165
- # yields each child applicator schema which applies to the child of
166
- # the given instance on the given token.
167
- #
168
- # @param (see Schema#child_applicator_schemas)
169
- # @yield [JSI::Schema]
170
- # @return [nil, Enumerator] an Enumerator if invoked without a block; otherwise nil
171
- def each_child_applicator_schema(token, instance, &block)
172
- return to_enum(__method__, token, instance) unless block
173
-
174
- each do |schema|
175
- schema.each_child_applicator_schema(token, instance, &block)
176
- end
177
-
178
- nil
179
- end
180
-
181
144
  # validates the given instance against our schemas
182
145
  #
183
146
  # @param instance [Object] the instance to validate against our schemas
184
147
  # @return [JSI::Validation::Result]
185
148
  def instance_validate(instance)
186
- results = map { |schema| schema.instance_validate(instance) }
187
- results.inject(Validation::FullResult.new, &:merge).freeze
149
+ inject(Validation::Result::Full.new) do |result, schema|
150
+ result.merge(schema.instance_validate(instance))
151
+ end.freeze
188
152
  end
189
153
 
190
154
  # whether the given instance is valid against our schemas
@@ -194,26 +158,19 @@ module JSI
194
158
  all? { |schema| schema.instance_valid?(instance) }
195
159
  end
196
160
 
197
- # @return [String]
198
- def inspect
199
- -"#{self.class}[#{map(&:inspect).join(", ")}]"
161
+ # @return [Set<SchemaModule>]
162
+ def jsi_schema_modules
163
+ Set.new(self, &:jsi_schema_module).freeze
200
164
  end
201
165
 
202
- alias_method :to_s, :inspect
203
-
204
- def pretty_print(q)
205
- q.text self.class.to_s
206
- q.text '['
207
- q.group_sub {
208
- q.nest(2) {
209
- q.breakable('')
210
- q.seplist(self, nil, :each) { |e|
211
- q.pp e
212
- }
213
- }
214
- }
215
- q.breakable ''
216
- q.text ']'
166
+ # Builds a SchemaSet, yielding each schema and a callable to be called with each schema of the resulting SchemaSet.
167
+ # @yield [Schema, #to_proc]
168
+ # @return [SchemaSet]
169
+ def each_yield_set(&block)
170
+ self.class.new(Enumerator.new do |y|
171
+ c = y.method(:yield) # TODO drop c, just pass y, when all supported Enumerator::Yielder.method_defined?(:to_proc)
172
+ each { |schema| yield(schema, c) }
173
+ end)
217
174
  end
218
175
  end
219
176
  end
data/lib/jsi/set.rb ADDED
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSI
4
+ # @private
5
+ class Set < ::Set
6
+ include(Util::Pretty)
7
+
8
+ def pretty_print(q)
9
+ q.text(self.class.to_s)
10
+ q.text('[')
11
+ q.group do
12
+ q.nest(2) do
13
+ q.breakable('')
14
+ q.seplist(self) do |e|
15
+ q.pp(e)
16
+ end
17
+ end
18
+ q.breakable('')
19
+ end
20
+ q.text(']')
21
+ end
22
+ end
23
+ end
@@ -1,26 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JSI
4
- simple_wrap_implementation = Module.new do
5
- def internal_child_applicate_keywords(token, instance)
6
- yield self
7
- end
4
+ dialect = Schema::Dialect.new(
5
+ vocabularies: [
6
+ Schema::Vocabulary.new(elements: [
7
+ Schema::Element.new do |element|
8
+ element.add_action(:inplace_applicate) { inplace_schema_applicate(schema) }
9
+ element.add_action(:child_applicate) { child_schema_applicate(schema) }
10
+ end,
11
+ ]),
12
+ ],
13
+ )
8
14
 
9
- def internal_inplace_applicate_keywords(instance, visited_refs)
10
- yield self
11
- end
12
-
13
- def internal_validate_keywords(result_builder)
14
- end
15
- end
16
-
17
- simple_wrap_metaschema = JSI.new_metaschema(nil, schema_implementation_modules: [simple_wrap_implementation])
18
- SimpleWrap = simple_wrap_metaschema.new_schema_module({})
15
+ simple_wrap_metaschema = JSI.new_metaschema_node(nil, dialect: dialect)
16
+ SimpleWrap = simple_wrap_metaschema.new_schema_module(Util::EMPTY_HASH)
19
17
 
20
18
  # SimpleWrap is a JSI schema module which recursively wraps nested structures
21
- module SimpleWrap
22
- end
19
+ module SimpleWrap end
23
20
 
24
- SimpleWrap::Implementation = simple_wrap_implementation
21
+ SimpleWrap::DIALECT = dialect
25
22
  SimpleWrap::METASCHEMA = simple_wrap_metaschema
26
23
  end
data/lib/jsi/struct.rb ADDED
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSI
4
+ # JSI::Struct adds to Struct:
5
+ #
6
+ # - always initialized by keywords
7
+ # - .subclass enables hierarchical class inheritance with added members
8
+ # - better pretty/inspect
9
+ # @private
10
+ class Struct < ::Struct
11
+ include(Util::Pretty)
12
+
13
+ STRUCT_NEW = Struct.singleton_class.instance_method(:new)
14
+ private_constant(:STRUCT_NEW)
15
+
16
+ HAS_KEYWORD_INIT = Struct.new(:_, keyword_init: true) && true rescue false
17
+ private_constant(:HAS_KEYWORD_INIT)
18
+
19
+ class << self
20
+ # @return [Class]
21
+ def subclass(*members)
22
+ #chkbug fail if !members.all? { |m| m.is_a?(Symbol) }
23
+ self_members = self.members rescue [] # NoMethodError on mri, NameError on truffle
24
+ # Struct does not enable adding members to subclasses of its generated classes,
25
+ # but that is still possible by binding Struct.new to the class and calling
26
+ # that with both existing and new members.
27
+ if HAS_KEYWORD_INIT
28
+ STRUCT_NEW.bind(self).call(*self_members, *members, keyword_init: true)
29
+ else
30
+ STRUCT_NEW.bind(self).call(*self_members, *members)
31
+ end
32
+ end
33
+ end
34
+
35
+ if !HAS_KEYWORD_INIT
36
+ def initialize(h = {})
37
+ super(*members.map { |m| h.key?(m) ? h.delete(m) : nil })
38
+ raise(ArgumentError, "#{self.class} given non-members: #{h}") if !h.empty?
39
+ end
40
+ end
41
+
42
+ # @return [self.class]
43
+ def merge(**h)
44
+ self.class.new(**to_h, **h)
45
+ end
46
+
47
+ def pretty_print(q)
48
+ jsi_pp_object_group(q) do
49
+ q.seplist(each_pair) do |k, v|
50
+ q.text(k.to_s)
51
+ q.text(': ')
52
+ q.pp(v)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
data/lib/jsi/uri.rb ADDED
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSI
4
+ # JSI::URI adds to Addressable::URI:
5
+ #
6
+ # - `JSI::URI["http://x"]` parses, and JSI::URI#inspect shows this form, copy/pastable
7
+ # - Immutable when instantiated with `.[]`, `.parse`, or modified-copy instance methods join, merge, or normalize.
8
+ # However `.new` and `#dup` do not freeze for compatibility with some libraries (Faraday) that dup and mutate URIs.
9
+ # @private
10
+ class URI < Addressable::URI
11
+ class << self
12
+ # @param uri [#to_str]
13
+ # @return [URI]
14
+ def [](uri)
15
+ parse(uri)
16
+ end
17
+
18
+ def parse(uri)
19
+ super.freeze
20
+ end
21
+ end
22
+
23
+ def join(uri)
24
+ super.freeze
25
+ end
26
+
27
+ def merge(hash)
28
+ super.freeze
29
+ end
30
+
31
+ def normalize
32
+ super.freeze
33
+ end
34
+
35
+ # @return [String]
36
+ def inspect
37
+ -"#{self.class}[#{to_s.inspect}]"
38
+ end
39
+ end
40
+ end
@@ -25,15 +25,6 @@ module JSI
25
25
  end
26
26
 
27
27
  class MemoMap::Mutable < MemoMap
28
- Result = AttrStruct[*%w(
29
- value
30
- inputs
31
- inputs_hash
32
- )]
33
-
34
- class Result
35
- end
36
-
37
28
  def [](**inputs)
38
29
  key = key_for(inputs)
39
30
 
@@ -43,11 +34,12 @@ module JSI
43
34
 
44
35
  result_mutex.synchronize do
45
36
  inputs_hash = inputs.hash
46
- if @results.key?(key) && inputs_hash == @results[key].inputs_hash && inputs == @results[key].inputs
47
- @results[key].value
37
+ result_value, result_inputs, result_inputs_hash = @results[key]
38
+ if inputs_hash == result_inputs_hash && inputs == result_inputs
39
+ result_value
48
40
  else
49
41
  value = @block.call(**inputs)
50
- @results[key] = Result.new(value: value, inputs: inputs, inputs_hash: inputs_hash)
42
+ @results[key] = [value, inputs, inputs_hash]
51
43
  value
52
44
  end
53
45
  end
@@ -58,6 +50,8 @@ module JSI
58
50
  def [](**inputs)
59
51
  key = key_for(inputs)
60
52
 
53
+ return @results[key] if @results.key?(key)
54
+
61
55
  result_mutex = @result_mutexes_mutex.synchronize do
62
56
  @result_mutexes[key] ||= Mutex.new
63
57
  end
@@ -66,7 +60,9 @@ module JSI
66
60
  if @results.key?(key)
67
61
  @results[key]
68
62
  else
69
- @results[key] = @block.call(**inputs)
63
+ result = @results[key] = @block.call(**inputs)
64
+ @result_mutexes.delete(key)
65
+ result
70
66
  end
71
67
  end
72
68
  end
@@ -5,13 +5,14 @@ module JSI
5
5
  #
6
6
  # @api private
7
7
  module Util::Private
8
- autoload :AttrStruct, 'jsi/util/private/attr_struct'
9
8
  autoload :MemoMap, 'jsi/util/private/memo_map'
10
9
 
11
10
  extend self
12
11
 
13
12
  EMPTY_ARY = [].freeze
14
13
 
14
+ EMPTY_HASH = {}.freeze
15
+
15
16
  EMPTY_SET = Set[].freeze
16
17
 
17
18
  CLASSES_ALWAYS_FROZEN = Set[TrueClass, FalseClass, NilClass, Integer, Float, BigDecimal, Rational, Symbol].freeze
@@ -67,10 +68,16 @@ module JSI
67
68
  end
68
69
 
69
70
  def const_name_from_parts(parts, join: '')
71
+ initAZ = false
70
72
  parts = parts.map do |part|
71
- part = part.dup
72
- part[/\A[^a-zA-Z]*/] = ''
73
- part[0] = part[0].upcase if part[0]
73
+ part = part.to_str.dup
74
+ if !initAZ
75
+ part[/\A[^a-zA-Z]*/] = ''
76
+ end
77
+ if part[0]
78
+ part[0] = part[0].upcase
79
+ initAZ = true
80
+ end
74
81
  part.gsub!(RUBY_REJECT_NAME_RE, '_')
75
82
  part
76
83
  end
@@ -81,18 +88,46 @@ module JSI
81
88
  end
82
89
  end
83
90
 
91
+ if JSON.parse('[]', freeze: true).frozen?
92
+ def json_parse_freeze(json)
93
+ JSON.parse(json, freeze: true)
94
+ end
95
+ else
96
+ def json_parse_freeze(json)
97
+ Util.deep_to_frozen(JSON.parse(json))
98
+ end
99
+ end
100
+
84
101
  # string or URI → frozen URI
85
- # @return [Addressable::URI]
86
- def uri(uri)
87
- if uri.is_a?(Addressable::URI)
88
- if uri.frozen?
89
- uri
102
+ # @param nnil must not be nil
103
+ # @param yabs must be absolute
104
+ # @param ynorm must be normalized
105
+ # @param tonorm normalize returned URI
106
+ # @return [URI, nil]
107
+ def uri(uri, nnil: false, yabs: false, ynorm: false, tonorm: false)
108
+ return nil if !nnil && uri.nil?
109
+ if uri.is_a?(URI)
110
+ auri = uri
111
+ elsif uri.is_a?(String) || uri.respond_to?(:to_str)
112
+ auri = URI.parse(uri)
113
+ else
114
+ raise(URIError, "URI is not a string: #{uri.inspect}")
115
+ end
116
+ if yabs && !auri.scheme
117
+ raise(URIError, "URI must be an absolute URI. got: #{uri.inspect}")
118
+ end
119
+ if yabs && auri.fragment
120
+ if auri.fragment.empty?
121
+ auri = auri.merge(fragment: nil)
90
122
  else
91
- uri.dup.freeze
123
+ raise(URIError, "URI must have no fragment. got: #{uri.inspect}")
92
124
  end
93
- else
94
- Addressable::URI.parse(uri).freeze
95
125
  end
126
+ if ynorm && uri.to_str != auri.normalize.to_s
127
+ raise(URIError, "URI must be in normalized form. got: #{uri.inspect}; normalized: #{auri.normalize.to_s.inspect}")
128
+ end
129
+ auri = auri.normalize if tonorm
130
+ auri
96
131
  end
97
132
 
98
133
  # this is the Y-combinator, which allows anonymous recursive functions. for a simple example,
@@ -139,6 +174,18 @@ module JSI
139
174
  nil
140
175
  end
141
176
 
177
+ # @param visited_refs [Array<Schema::Ref>]
178
+ # @param ref [Schema::Ref, nil]
179
+ # @return [Array<Schema::Ref>]
180
+ def add_visited_ref(visited_refs, ref)
181
+ return(visited_refs) if ref.nil?
182
+ #chkbug fail unless ref.is_a?(Schema::Ref) && visited_refs.is_a?(Array) && visited_refs.frozen?
183
+ if visited_refs.include?(ref)
184
+ raise(ResolutionError, "cyclical ref application with refs: #{visited_refs}")
185
+ end
186
+ visited_refs.dup.push(ref).freeze
187
+ end
188
+
142
189
  # Defines equality methods and #hash (for Hash / Set), based on a method #jsi_fingerprint
143
190
  # implemented by the includer. #jsi_fingerprint is to include the class and any properties
144
191
  # of the instance which constitute its identity.
@@ -179,24 +226,5 @@ module JSI
179
226
  super
180
227
  end
181
228
  end
182
-
183
- module Virtual
184
- class InstantiationError < StandardError
185
- end
186
-
187
- # this virtual class is not intended to be instantiated except by its subclasses, which override #initialize
188
- def initialize
189
- # :nocov:
190
- raise(InstantiationError, "cannot instantiate virtual class #{self.class}")
191
- # :nocov:
192
- end
193
-
194
- # virtual_method is used to indicate that the method calling it must be implemented on the (non-virtual) subclass
195
- def virtual_method
196
- # :nocov:
197
- raise(Bug, "class #{self.class} must implement #{caller_locations.first.label}")
198
- # :nocov:
199
- end
200
- end
201
229
  end
202
230
  end