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
data/lib/jsi/ref.rb ADDED
@@ -0,0 +1,197 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSI
4
+ # A reference to a JSI identified by a given URI.
5
+ class Ref
6
+ include(Util::Pretty)
7
+
8
+ # @param ref [#to_str] A reference URI, e.g. a `$ref` value of a referrer schema
9
+ # @param referrer [Base] A JSI from which the reference originated.
10
+ #
11
+ # If the ref URI consists of only a fragment, it is resolved from the `referrer`'s
12
+ # root (its {Schema::SchemaAncestorNode#jsi_resource_root} if resolving a {Schema::Ref}; its document root if not).
13
+ # Otherwise the resource is found in the `referrer`'s
14
+ # `#jsi_registry` (and any fragment is resolved from there).
15
+ # @param registry [Registry, nil] The registry in which the resource this ref refers to will be found.
16
+ # If `referrer` is specified and `registry` is not, defaults to its `#jsi_registry`.
17
+ # If neither is specified, {JSI.registry} is used.
18
+ def initialize(ref, referrer: nil, registry: (registry_undefined = true))
19
+ raise(ArgumentError, "ref is not a string") unless ref.respond_to?(:to_str)
20
+ @ref = ref
21
+ @ref_uri = Util.uri(ref, nnil: true)
22
+ @referrer = referrer && resolve_schema? ? Schema.ensure_schema(referrer) : referrer
23
+ @registry = !registry_undefined ? registry
24
+ : referrer ? referrer.jsi_registry
25
+ : JSI.registry
26
+ @resolved = nil
27
+ end
28
+
29
+ # @return [#to_str]
30
+ attr_reader :ref
31
+
32
+ # @return [URI]
33
+ attr_reader :ref_uri
34
+
35
+ # @return [Base, nil]
36
+ attr_reader(:referrer)
37
+
38
+ # @return [Registry, nil]
39
+ attr_reader(:registry)
40
+
41
+ # @return [Boolean]
42
+ def resolve_schema?
43
+ false
44
+ end
45
+
46
+ # Resolves the target of this reference.
47
+ # @return [JSI::Base]
48
+ # @raise [JSI::Schema::NotASchemaError] when the resolved target must be a Schema but is not
49
+ # @raise [ResolutionError] when this reference cannot be resolved
50
+ def resolve
51
+ return @resolved if @resolved
52
+
53
+ resource_root = nil
54
+ check_resource_root = proc {
55
+ unless resource_root
56
+ raise(ResolutionError.new([
57
+ "cannot resolve ref: #{ref}",
58
+ ("from: #{referrer.pretty_inspect.chomp}" if referrer),
59
+ ], uri: ref_uri))
60
+ end
61
+ }
62
+
63
+ ref_uri_nofrag = ref_uri.merge(fragment: nil)
64
+
65
+ if ref_uri_nofrag.empty?
66
+ unless referrer
67
+ raise(ResolutionError.new([
68
+ "cannot resolve ref: #{ref}",
69
+ "with no referrer",
70
+ ], uri: ref_uri))
71
+ end
72
+
73
+ # the URI only consists of a fragment (or is empty).
74
+ if resolve_schema?
75
+ # for a fragment pointer, resolve using Schema#resource_root_subschema on the referrer.
76
+ # for a fragment anchor, use the referrer's jsi_resource_root.
77
+ resource_root = referrer.jsi_resource_root # note: may be nil from bootstrap schema
78
+ resolve_fragment_ptr = referrer.method(:resource_root_subschema)
79
+ else
80
+ resource_root = referrer.jsi_root_node
81
+ resolve_fragment_ptr = resource_root.method(:jsi_descendent_node)
82
+ end
83
+ else
84
+ # find the resource_root from the non-fragment URI. we will resolve any fragment, either pointer or anchor, from there.
85
+
86
+ if ref_uri_nofrag.absolute?
87
+ ref_abs_uri = ref_uri_nofrag
88
+ elsif referrer && referrer.jsi_next_base_uri
89
+ ref_abs_uri = referrer.jsi_next_base_uri.join(ref_uri_nofrag)
90
+ else
91
+ ref_abs_uri = nil
92
+ end
93
+ if ref_abs_uri
94
+ unless registry
95
+ raise(ResolutionError.new([
96
+ "could not resolve remote ref with no registry specified",
97
+ "ref URI: #{ref_uri.to_s}",
98
+ ("from: #{referrer.pretty_inspect.chomp}" if referrer),
99
+ ], uri: ref_uri))
100
+ end
101
+ resource_root = registry.find(ref_abs_uri)
102
+ end
103
+
104
+ if !resource_root && resolve_schema?
105
+ # HAX for how google does refs and ids
106
+ if referrer && referrer.jsi_document.respond_to?(:to_hash) && referrer.jsi_document['schemas'].respond_to?(:to_hash)
107
+ referrer.jsi_document['schemas'].each do |k, v|
108
+ if URI[v['id']] == ref_uri_nofrag
109
+ resource_root = referrer.resource_root_subschema(['schemas', k])
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ check_resource_root.call
116
+
117
+ if resolve_schema? && resource_root.is_a?(Schema)
118
+ resolve_fragment_ptr = resource_root.method(:resource_root_subschema)
119
+ else
120
+ # Note: Schema#resource_root_subschema will reinstantiate nonschemas as schemas.
121
+ # not implemented for remote refs when the resource_root is not a schema.
122
+ resolve_fragment_ptr = proc { |ptr| resource_root.jsi_descendent_node(ptr) }
123
+ end
124
+ end
125
+
126
+ fragment = ref_uri.fragment
127
+
128
+ if fragment
129
+ begin
130
+ ptr_from_fragment = Ptr.from_fragment(fragment)
131
+ rescue Ptr::PointerSyntaxError
132
+ end
133
+ end
134
+
135
+ if ptr_from_fragment
136
+ begin
137
+ resolved = resolve_fragment_ptr.call(ptr_from_fragment)
138
+ rescue Ptr::ResolutionError
139
+ raise(ResolutionError.new([
140
+ "could not resolve pointer: #{ptr_from_fragment.pointer.inspect}",
141
+ ("from: #{referrer.pretty_inspect.chomp}" if referrer),
142
+ ("in resource root: #{resource_root.pretty_inspect.chomp}" if resource_root),
143
+ ], uri: ref_uri))
144
+ end
145
+ elsif fragment.nil?
146
+ check_resource_root.call
147
+ resolved = resource_root
148
+ elsif resolve_schema?
149
+ check_resource_root.call
150
+
151
+ # find an anchor that resembles the fragment
152
+ result_schemas = resource_root.jsi_anchor_subschemas(fragment)
153
+
154
+ if result_schemas.size == 1
155
+ resolved = result_schemas.first
156
+ elsif result_schemas.size == 0
157
+ raise(ResolutionError.new([
158
+ "could not resolve fragment: #{fragment.inspect}",
159
+ "in resource root: #{resource_root.pretty_inspect.chomp}",
160
+ ], uri: ref_uri))
161
+ else
162
+ raise(ResolutionError.new([
163
+ "found multiple schemas for plain name fragment #{fragment.inspect}:",
164
+ *result_schemas.map { |s| s.pretty_inspect.chomp },
165
+ ], uri: ref_uri))
166
+ end
167
+ else
168
+ raise(ResolutionError.new([
169
+ "could not resolve fragment #{fragment.inspect}. fragment must be a pointer.",
170
+ ("in resource root: #{resource_root.pretty_inspect.chomp}" if resource_root),
171
+ ], uri: ref_uri))
172
+ end
173
+
174
+ Schema.ensure_schema(resolved) { "object identified by uri #{ref} is not a schema:" } if resolve_schema?
175
+ return @resolved = resolved
176
+ end
177
+
178
+ # pretty-prints a representation of self to the given printer
179
+ # @return [void]
180
+ def pretty_print(q)
181
+ jsi_pp_object_group(q, [self.class.name, ref].freeze)
182
+ end
183
+
184
+ # see {Util::Private::FingerprintHash}
185
+ # @api private
186
+ def jsi_fingerprint
187
+ {
188
+ class: self.class,
189
+ ref: ref,
190
+ referrer: referrer,
191
+ registry: registry,
192
+ }.freeze
193
+ end
194
+
195
+ include(Util::FingerprintHash::Immutable)
196
+ end
197
+ end
@@ -0,0 +1,311 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSI
4
+ class Registry
5
+ # an exception raised when an attempt is made to register a resource using a URI which is already
6
+ # registered with another resource
7
+ class Collision < StandardError
8
+ end
9
+
10
+ # @deprecated alias after v0.8
11
+ # an exception raised when a URI we are looking for has not been registered
12
+ ResourceNotFound = ResolutionError
13
+
14
+ # @private
15
+ Autoloader = Struct.subclass(:block, :mutex)
16
+
17
+ include(Util::Pretty)
18
+
19
+ def initialize
20
+ @resources = {}
21
+ @resource_autoloaders = {}
22
+ @vocabularies = {}
23
+ @vocabulary_autoloaders = {}
24
+ @dialects = {}
25
+ @dialect_autoloaders = {}
26
+ @mutex = Mutex.new
27
+ end
28
+
29
+ # registers the given resource and/or schema resources it contains in the registry.
30
+ #
31
+ # each descendent node of the resource (including the resource itself) is registered if it is a schema
32
+ # that has an absolute URI (generally defined by the '$id' keyword).
33
+ #
34
+ # the given resource itself will be registered, whether or not it is a schema, if it is the root
35
+ # of its document and was instantiated with the option `uri` specified.
36
+ #
37
+ # @param resource [JSI::Base] a JSI containing resources to register
38
+ # @return [void]
39
+ def register(resource)
40
+ register_immediate(resource) if !resource.is_a?(Schema)
41
+
42
+ resource.jsi_each_descendent_schema do |node|
43
+ register_immediate(node)
44
+ end
45
+ end
46
+
47
+ # @param node [Base, Schema]
48
+ # @return [void]
49
+ def register_immediate(node)
50
+ unless node.is_a?(Base) || node.is_a?(Schema)
51
+ raise(ArgumentError, "resource must be a #{Base}. got: #{node.pretty_inspect.chomp}")
52
+ end
53
+
54
+ node.jsi_resource_uris.each do |uri|
55
+ internal_store(@resources, @resource_autoloaders, uri, node)
56
+ end
57
+ end
58
+
59
+ # takes a URI identifying a resource to be loaded by the given block
60
+ # when a reference to the URI is followed.
61
+ #
62
+ # for example:
63
+ #
64
+ # JSI.registry.autoload_uri('http://example.com/schema.json') do
65
+ # JSI.new_schema({
66
+ # '$schema' => 'http://json-schema.org/draft-07/schema#',
67
+ # '$id' => 'http://example.com/schema.json',
68
+ # 'title' => 'my schema',
69
+ # })
70
+ # end
71
+ #
72
+ # the block would normally load JSON from the filesystem or similar.
73
+ #
74
+ # @param uri [#to_str]
75
+ # @yieldparam registry [Registry] (keyword) this registry
76
+ # @yieldparam uri [URI] (keyword) the URI being autoloaded
77
+ # @yieldreturn [JSI::Base] a JSI instance containing the resource identified by the given uri
78
+ # @return [void]
79
+ def autoload_uri(uri, &block)
80
+ internal_autoload(@resource_autoloaders, @resources, uri, block)
81
+ end
82
+
83
+ private def internal_autoload(autoloaders, store, uri, block)
84
+ uri = registration_uri(uri)
85
+ mutating
86
+ unless block
87
+ raise(ArgumentError, ["#{Registry} autoload must be invoked with a block", "URI: #{uri}"].join("\n"))
88
+ end
89
+ if autoloaders.key?(uri)
90
+ raise(Collision, ["already registered URI for autoload", "URI: #{uri}", "loader: #{autoloaders[uri].block}"].join("\n"))
91
+ end
92
+ if store.key?(uri)
93
+ raise(Collision, ["already registered URI", "URI: #{uri}", "existing: #{store[uri].pretty_inspect.chomp}"].join("\n"))
94
+ end
95
+ autoloaders[uri] = Autoloader.new(block: block, mutex: nil)
96
+ nil
97
+ end
98
+
99
+ # @param uri [URI, #to_str]
100
+ # @return [JSI::Base]
101
+ # @raise [ResolutionError]
102
+ def find(uri)
103
+ internal_find(uri, @resources, @resource_autoloaders, proc { |r| register(r) }, 'resource')
104
+ end
105
+
106
+ private def internal_find(uri, store, autoloaders, registerer, typename)
107
+ uri = registration_uri(uri)
108
+ autoloaded = nil
109
+ autoloader = autoloaders[uri]
110
+ if autoloader
111
+ mutating
112
+ @mutex.synchronize { autoloader.mutex ||= Mutex.new }
113
+ autoloader.mutex.synchronize do
114
+ if autoloaders.key?(uri) # check against race
115
+ autoload_param = {
116
+ registry: self,
117
+ uri: uri,
118
+ }
119
+ # remove params the autoload proc does not accept
120
+ autoload_param.select! do |name, _|
121
+ autoloaders[uri].block.parameters.any? do |type, pname|
122
+ # dblsplat (**k) || required (k: ) || optional (k: nil)
123
+ type == :keyrest || ((type == :keyreq || type == :key) && pname == name)
124
+ end
125
+ end
126
+ autoloaded = autoloaders[uri].block.call(**autoload_param)
127
+ registerer[autoloaded]
128
+ end # if autoloaders.key?(uri)
129
+ end # autoloader.mutex.synchronize
130
+ end # if autoloader
131
+
132
+ if !store.key?(uri)
133
+ if autoloaded
134
+ msg = [
135
+ "#{typename} URI #{uri} was registered for autoload but the result did not contain an entity with that URI.",
136
+ "autoload result was:",
137
+ autoloaded.pretty_inspect.chomp,
138
+ ]
139
+ else
140
+ msg = ["#{typename} URI #{uri} is not registered. registered URIs:", *(store.keys | autoloaders.keys)]
141
+ end
142
+ raise(ResolutionError.new(msg, uri: uri))
143
+ end
144
+ store[uri]
145
+ end
146
+
147
+ # @param uri [#to_str]
148
+ # @return [Boolean]
149
+ def registered?(uri)
150
+ uri = registration_uri(uri)
151
+ @resources.key?(uri) || @resource_autoloaders.key?(uri)
152
+ end
153
+
154
+ # @param vocabulary [Schema::Vocabulary]
155
+ # @param uri [#to_str]
156
+ # @return [void]
157
+ def register_vocabulary(vocabulary, uri: vocabulary.id)
158
+ raise(ArgumentError, "not a #{Schema::Vocabulary}: #{vocabulary.inspect}") if !vocabulary.is_a?(Schema::Vocabulary)
159
+ internal_store(@vocabularies, @vocabulary_autoloaders, uri, vocabulary)
160
+ end
161
+
162
+ # @param uri [#to_str]
163
+ # @yieldparam registry [Registry] (keyword) this registry
164
+ # @yieldparam uri [URI] (keyword) the URI being autoloaded
165
+ # @yieldreturn [Schema::Vocabulary]
166
+ # @return [void]
167
+ def autoload_vocabulary_uri(uri, &block)
168
+ internal_autoload(@vocabulary_autoloaders, @vocabularies, uri, block)
169
+ end
170
+
171
+ # @param uri [#to_str]
172
+ # @return [Schema::Vocabulary]
173
+ # @raise [ResolutionError]
174
+ def find_vocabulary(uri)
175
+ internal_find(uri, @vocabularies, @vocabulary_autoloaders, proc { |v| register_vocabulary(v, uri: uri) }, 'vocabulary')
176
+ end
177
+
178
+ # @param uri [#to_str]
179
+ # @return [Boolean]
180
+ def vocabulary_registered?(uri)
181
+ uri = registration_uri(uri)
182
+ @vocabularies.key?(uri) || @vocabulary_autoloaders.key?(uri)
183
+ end
184
+
185
+ # @param dialect [Schema::Dialect]
186
+ # @param uri [#to_str]
187
+ # @return [void]
188
+ def register_dialect(dialect, uri: dialect.id)
189
+ raise(ArgumentError, "not a #{Schema::Dialect}: #{dialect.inspect}") if !dialect.is_a?(Schema::Dialect)
190
+ internal_store(@dialects, @dialect_autoloaders, uri, dialect)
191
+ end
192
+
193
+ # @param uri [#to_str]
194
+ # @yieldparam registry [Registry] (keyword) this registry
195
+ # @yieldparam uri [URI] (keyword) the URI being autoloaded
196
+ # @yieldreturn [Schema::Dialect]
197
+ # @return [void]
198
+ def autoload_dialect_uri(uri, &block)
199
+ internal_autoload(@dialect_autoloaders, @dialects, uri, block)
200
+ end
201
+
202
+ # @param uri [#to_str]
203
+ # @return [Schema::Dialect]
204
+ # @raise [ResolutionError]
205
+ def find_dialect(uri)
206
+ internal_find(uri, @dialects, @dialect_autoloaders, proc { |v| register_dialect(v, uri: uri) }, 'dialect')
207
+ end
208
+
209
+ # @param uri [#to_str]
210
+ # @return [Boolean]
211
+ def dialect_registered?(uri)
212
+ uri = registration_uri(uri)
213
+ @dialects.key?(uri) || @dialect_autoloaders.key?(uri)
214
+ end
215
+
216
+ def pretty_print(q)
217
+ jsi_pp_object_group(q) do
218
+ labels_uris = [
219
+ ['resources', @resources.keys],
220
+ ['resources autoload', @resource_autoloaders.keys],
221
+ ['vocabularies', @vocabularies.keys],
222
+ ['vocabularies autoload', @vocabulary_autoloaders.keys],
223
+ ['dialects', @dialects.keys],
224
+ ['dialects autoload', @dialect_autoloaders.keys],
225
+ ]
226
+ q.seplist(labels_uris, q.method(:breakable)) do |label, uris|
227
+ q.text("#{label} (#{uris.size})")
228
+ if !uris.empty?
229
+ q.text(": <")
230
+ q.group do
231
+ q.nest(2) do
232
+ q.breakable('')
233
+ q.seplist(uris) do |uri|
234
+ q.text(uri.to_s.inspect)
235
+ end
236
+ end
237
+ q.breakable('')
238
+ end
239
+ q.text '>'
240
+ end
241
+ end
242
+ end
243
+ end
244
+
245
+ def dup
246
+ self.class.new.tap do |reg|
247
+ reg.instance_variable_get(:@resources).update(@resources)
248
+ reg.instance_variable_get(:@resource_autoloaders).update(@resource_autoloaders)
249
+ reg.instance_variable_get(:@vocabularies).update(@vocabularies)
250
+ reg.instance_variable_get(:@vocabulary_autoloaders).update(@vocabulary_autoloaders)
251
+ reg.instance_variable_get(:@dialects).update(@dialects)
252
+ reg.instance_variable_get(:@dialect_autoloaders).update(@dialect_autoloaders)
253
+ end
254
+ end
255
+
256
+ def freeze
257
+ @resources.freeze
258
+ @resource_autoloaders.freeze
259
+ @vocabularies.freeze
260
+ @vocabulary_autoloaders.freeze
261
+ @dialects.freeze
262
+ @dialect_autoloaders.freeze
263
+ @mutex = nil
264
+ super
265
+ end
266
+
267
+ protected
268
+ # @param store [Hash]
269
+ # @param uri [URI]
270
+ # @param entity
271
+ # @return [void]
272
+ def internal_store(store, autoloaders, uri, entity)
273
+ mutating
274
+ @mutex.synchronize do
275
+ uri = registration_uri(uri)
276
+ if store.key?(uri)
277
+ if !store[uri].equal?(entity)
278
+ raise(Collision, "URI collision on #{uri}.\nexisting:\n#{store[uri].pretty_inspect.chomp}\nnew:\n#{entity.pretty_inspect.chomp}")
279
+ end
280
+ else
281
+ store[uri] = entity
282
+ end
283
+ autoloaders.delete(uri)
284
+ end
285
+ nil
286
+ end
287
+
288
+ private
289
+
290
+ # registration URIs are
291
+ # - absolute
292
+ # - without fragment
293
+ # - not relative
294
+ # - normalized
295
+ # - frozen
296
+ # @param uri [#to_str]
297
+ # @return [Addressable::URI]
298
+ def registration_uri(uri)
299
+ Util.uri(uri, nnil: true, yabs: true, tonorm: true)
300
+ end
301
+
302
+ def mutating
303
+ if frozen?
304
+ raise(FrozenError, "cannot modify frozen #{self.class}")
305
+ end
306
+ end
307
+ end
308
+
309
+ # @deprecated after v0.8
310
+ SchemaRegistry = Registry
311
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSI
4
+ class Schema::Cxt
5
+ ChildApplication = Block.subclass(*%i(
6
+ instance
7
+ token
8
+ collect_evaluated
9
+ collect_evaluated_validate
10
+ evaluated
11
+ ))
12
+
13
+ # @!attribute collect_evaluated
14
+ # Does application need to collect successful child evaluation?
15
+ # @return [Boolean]
16
+ # @!attribute evaluated
17
+ # Was the child successfully evaluated by a child applicator?
18
+ # @return [Boolean]
19
+ class ChildApplication < Block
20
+ # @param subschema_ptr [Ptr, #to_ary]
21
+ def child_subschema_applicate(subschema_ptr)
22
+ child_schema_applicate(schema.subschema(subschema_ptr))
23
+ end
24
+
25
+ # @param child_applicator_schema [Schema]
26
+ def child_schema_applicate(child_applicator_schema)
27
+ if collect_evaluated
28
+ self.evaluated ||= !collect_evaluated_validate || child_applicator_schema.instance_valid?(instance[token])
29
+ end
30
+
31
+ cxt_yield(child_applicator_schema)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSI
4
+ class Schema::Cxt
5
+ InplaceApplication = Block.subclass(*%i(
6
+ visited_refs
7
+ ))
8
+
9
+ # @!attribute collect_evaluated
10
+ # Does application need to collect successful child evaluation?
11
+ # @return [Boolean]
12
+ class InplaceApplication < Block
13
+ # @param subschema_ptr [Ptr, #to_ary]
14
+ def inplace_subschema_applicate(subschema_ptr, **kw)
15
+ inplace_schema_applicate(schema.subschema(subschema_ptr), **kw)
16
+ end
17
+
18
+ # @param applicator_schema [Schema]
19
+ def inplace_schema_applicate(applicator_schema, **kw)
20
+ block.call(applicator_schema, **kw)
21
+ end
22
+
23
+ def instance
24
+ raise(Bug, "in-place application is being invoked without an instance; the current element needs action :inplace_application_requires_instance")
25
+ end
26
+
27
+ def collect_evaluated
28
+ false
29
+ end
30
+ end
31
+
32
+ InplaceApplication::WithInstance = InplaceApplication.subclass(*%i(
33
+ instance
34
+ collect_evaluated
35
+ ))
36
+ end
37
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSI
4
+ module Schema
5
+ Cxt = Struct.subclass(*%i(
6
+ schema
7
+ abort
8
+ ))
9
+
10
+ # A Schema::Cxt is the `self` of a {Schema::Element} action.
11
+ # @!attribute schema
12
+ # The schema invoking an action in this context
13
+ # @return [JSI::Schema]
14
+ class Cxt
15
+ def subschema(subptr)
16
+ schema.subschema(subptr)
17
+ end
18
+
19
+ def schema_content
20
+ schema.jsi_node_content
21
+ end
22
+
23
+ def keyword?(keyword)
24
+ schema.keyword?(keyword)
25
+ end
26
+
27
+ # @return [Boolean]
28
+ def keyword_value_hash?(keyword)
29
+ keyword?(keyword) && schema_content[keyword].respond_to?(:to_hash)
30
+ end
31
+
32
+ # @return [Boolean]
33
+ def keyword_value_ary?(keyword)
34
+ keyword?(keyword) && schema_content[keyword].respond_to?(:to_ary)
35
+ end
36
+
37
+ # @return [Boolean]
38
+ def keyword_value_str?(keyword)
39
+ keyword?(keyword) && schema_content[keyword].respond_to?(:to_str)
40
+ end
41
+
42
+ # @return [Boolean]
43
+ def keyword_value_bool?(keyword)
44
+ keyword?(keyword) && (schema_content[keyword] == true || schema_content[keyword] == false)
45
+ end
46
+
47
+ # @return [Boolean]
48
+ def keyword_value_numeric?(keyword)
49
+ keyword?(keyword) && schema_content[keyword].is_a?(Numeric)
50
+ end
51
+
52
+ # is `value` an integer?
53
+ # @return [Boolean]
54
+ def internal_integer?(value)
55
+ value.is_a?(Integer) || (!schema.dialect.conf[:integer_disallows_0_fraction] && value.is_a?(Numeric) && value % 1.0 == 0.0)
56
+ end
57
+ end
58
+
59
+ class Cxt
60
+ autoload(:InplaceApplication, 'jsi/schema/cxt/inplace_application')
61
+ autoload(:ChildApplication, 'jsi/schema/cxt/child_application')
62
+ end
63
+
64
+ Cxt::Block = Cxt.subclass(:block)
65
+
66
+ # @!attribute block
67
+ # @return [#call]
68
+ class Cxt::Block < Cxt
69
+ if Util::LAST_ARGUMENT_AS_KEYWORD_PARAMETERS
70
+ def cxt_yield(*a)
71
+ block.call(*a)
72
+ end
73
+ else
74
+ def cxt_yield(*a, **kw)
75
+ block.call(*a, **kw)
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end