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.
- checksums.yaml +4 -4
- data/.yardopts +3 -4
- data/CHANGELOG.md +19 -0
- data/LICENSE.md +2 -3
- data/README.md +87 -43
- data/docs/{glossary.md → Glossary.md} +84 -52
- data/jsi.gemspec +1 -1
- data/lib/jsi/base/mutability.rb +48 -0
- data/lib/jsi/base/node.rb +66 -52
- data/lib/jsi/base.rb +592 -176
- data/lib/jsi/jsi_coder.rb +4 -2
- data/lib/jsi/metaschema_node/bootstrap_schema.rb +118 -59
- data/lib/jsi/metaschema_node.rb +244 -154
- data/lib/jsi/ptr.rb +45 -17
- data/lib/jsi/ref.rb +197 -0
- data/lib/jsi/registry.rb +311 -0
- data/lib/jsi/schema/cxt/child_application.rb +35 -0
- data/lib/jsi/schema/cxt/inplace_application.rb +37 -0
- data/lib/jsi/schema/cxt.rb +80 -0
- data/lib/jsi/schema/dialect.rb +137 -0
- data/lib/jsi/schema/draft04.rb +113 -5
- data/lib/jsi/schema/draft06.rb +123 -5
- data/lib/jsi/schema/draft07.rb +157 -5
- data/lib/jsi/schema/draft202012.rb +303 -0
- data/lib/jsi/schema/dynamic_anchor_map.rb +63 -0
- data/lib/jsi/schema/element.rb +69 -0
- data/lib/jsi/schema/elements/anchor.rb +13 -0
- data/lib/jsi/schema/elements/array_validation.rb +82 -0
- data/lib/jsi/schema/elements/comment.rb +10 -0
- data/lib/jsi/schema/{validation → elements}/const.rb +11 -7
- data/lib/jsi/schema/elements/contains.rb +59 -0
- data/lib/jsi/schema/elements/contains_minmax.rb +91 -0
- data/lib/jsi/schema/elements/content_encoding.rb +10 -0
- data/lib/jsi/schema/elements/content_media_type.rb +10 -0
- data/lib/jsi/schema/elements/content_schema.rb +16 -0
- data/lib/jsi/schema/elements/default.rb +11 -0
- data/lib/jsi/schema/elements/definitions.rb +19 -0
- data/lib/jsi/schema/elements/dependencies.rb +99 -0
- data/lib/jsi/schema/elements/dependent_required.rb +49 -0
- data/lib/jsi/schema/elements/dependent_schemas.rb +69 -0
- data/lib/jsi/schema/elements/dynamic_ref.rb +69 -0
- data/lib/jsi/schema/elements/enum.rb +26 -0
- data/lib/jsi/schema/elements/examples.rb +10 -0
- data/lib/jsi/schema/elements/format.rb +10 -0
- data/lib/jsi/schema/elements/id.rb +30 -0
- data/lib/jsi/schema/elements/if_then_else.rb +82 -0
- data/lib/jsi/schema/elements/info_bool.rb +10 -0
- data/lib/jsi/schema/elements/info_string.rb +10 -0
- data/lib/jsi/schema/elements/items.rb +93 -0
- data/lib/jsi/schema/elements/items_prefixed.rb +96 -0
- data/lib/jsi/schema/elements/not.rb +31 -0
- data/lib/jsi/schema/elements/numeric.rb +137 -0
- data/lib/jsi/schema/elements/numeric_draft04.rb +77 -0
- data/lib/jsi/schema/elements/object_validation.rb +55 -0
- data/lib/jsi/schema/elements/pattern.rb +35 -0
- data/lib/jsi/schema/elements/properties.rb +145 -0
- data/lib/jsi/schema/elements/property_names.rb +48 -0
- data/lib/jsi/schema/elements/ref.rb +62 -0
- data/lib/jsi/schema/elements/required.rb +34 -0
- data/lib/jsi/schema/elements/self.rb +24 -0
- data/lib/jsi/schema/elements/some_of.rb +180 -0
- data/lib/jsi/schema/elements/string_validation.rb +57 -0
- data/lib/jsi/schema/elements/type.rb +43 -0
- data/lib/jsi/schema/elements/unevaluated_items.rb +54 -0
- data/lib/jsi/schema/elements/unevaluated_properties.rb +54 -0
- data/lib/jsi/schema/elements/xschema.rb +10 -0
- data/lib/jsi/schema/elements/xvocabulary.rb +10 -0
- data/lib/jsi/schema/elements.rb +101 -0
- data/lib/jsi/schema/issue.rb +3 -4
- data/lib/jsi/schema/schema_ancestor_node.rb +105 -52
- data/lib/jsi/schema/vocabulary.rb +36 -0
- data/lib/jsi/schema.rb +598 -383
- data/lib/jsi/schema_classes.rb +195 -141
- data/lib/jsi/schema_set.rb +85 -128
- data/lib/jsi/set.rb +23 -0
- data/lib/jsi/simple_wrap.rb +14 -17
- data/lib/jsi/struct.rb +57 -0
- data/lib/jsi/uri.rb +40 -0
- data/lib/jsi/util/private/memo_map.rb +9 -13
- data/lib/jsi/util/private.rb +59 -31
- data/lib/jsi/util/typelike.rb +19 -60
- data/lib/jsi/util.rb +53 -34
- data/lib/jsi/validation/error.rb +45 -2
- data/lib/jsi/validation/result.rb +121 -90
- data/lib/jsi/validation.rb +1 -6
- data/lib/jsi/version.rb +1 -1
- data/lib/jsi.rb +170 -36
- data/lib/schemas/json-schema.org/draft/2020-12/schema.rb +62 -0
- data/lib/schemas/json-schema.org/draft-04/schema.rb +60 -109
- data/lib/schemas/json-schema.org/draft-06/schema.rb +53 -108
- data/lib/schemas/json-schema.org/draft-07/schema.rb +63 -127
- data/readme.rb +4 -4
- data/{resources}/schemas/2020-12_strict.json +19 -0
- data/{resources}/schemas/json-schema.org/draft/2020-12/meta/applicator.json +48 -0
- data/{resources}/schemas/json-schema.org/draft/2020-12/meta/content.json +17 -0
- data/{resources}/schemas/json-schema.org/draft/2020-12/meta/core.json +51 -0
- data/{resources}/schemas/json-schema.org/draft/2020-12/meta/format-annotation.json +14 -0
- data/{resources}/schemas/json-schema.org/draft/2020-12/meta/format-assertion.json +14 -0
- data/{resources}/schemas/json-schema.org/draft/2020-12/meta/meta-data.json +37 -0
- data/{resources}/schemas/json-schema.org/draft/2020-12/meta/unevaluated.json +15 -0
- data/{resources}/schemas/json-schema.org/draft/2020-12/meta/validation.json +98 -0
- data/{resources}/schemas/json-schema.org/draft/2020-12/schema.json +58 -0
- metadata +73 -52
- data/lib/jsi/metaschema.rb +0 -6
- data/lib/jsi/schema/application/child_application/contains.rb +0 -25
- data/lib/jsi/schema/application/child_application/draft04.rb +0 -21
- data/lib/jsi/schema/application/child_application/draft06.rb +0 -28
- data/lib/jsi/schema/application/child_application/draft07.rb +0 -28
- data/lib/jsi/schema/application/child_application/items.rb +0 -18
- data/lib/jsi/schema/application/child_application/properties.rb +0 -25
- data/lib/jsi/schema/application/child_application.rb +0 -13
- data/lib/jsi/schema/application/draft04.rb +0 -8
- data/lib/jsi/schema/application/draft06.rb +0 -8
- data/lib/jsi/schema/application/draft07.rb +0 -8
- data/lib/jsi/schema/application/inplace_application/dependencies.rb +0 -28
- data/lib/jsi/schema/application/inplace_application/draft04.rb +0 -25
- data/lib/jsi/schema/application/inplace_application/draft06.rb +0 -26
- data/lib/jsi/schema/application/inplace_application/draft07.rb +0 -32
- data/lib/jsi/schema/application/inplace_application/ifthenelse.rb +0 -20
- data/lib/jsi/schema/application/inplace_application/ref.rb +0 -18
- data/lib/jsi/schema/application/inplace_application/someof.rb +0 -44
- data/lib/jsi/schema/application/inplace_application.rb +0 -14
- data/lib/jsi/schema/application.rb +0 -12
- data/lib/jsi/schema/ref.rb +0 -183
- data/lib/jsi/schema/validation/array.rb +0 -69
- data/lib/jsi/schema/validation/contains.rb +0 -25
- data/lib/jsi/schema/validation/dependencies.rb +0 -49
- data/lib/jsi/schema/validation/draft04/minmax.rb +0 -91
- data/lib/jsi/schema/validation/draft04.rb +0 -110
- data/lib/jsi/schema/validation/draft06.rb +0 -120
- data/lib/jsi/schema/validation/draft07.rb +0 -157
- data/lib/jsi/schema/validation/enum.rb +0 -25
- data/lib/jsi/schema/validation/ifthenelse.rb +0 -46
- data/lib/jsi/schema/validation/items.rb +0 -54
- data/lib/jsi/schema/validation/not.rb +0 -20
- data/lib/jsi/schema/validation/numeric.rb +0 -121
- data/lib/jsi/schema/validation/object.rb +0 -45
- data/lib/jsi/schema/validation/pattern.rb +0 -34
- data/lib/jsi/schema/validation/properties.rb +0 -101
- data/lib/jsi/schema/validation/property_names.rb +0 -32
- data/lib/jsi/schema/validation/ref.rb +0 -40
- data/lib/jsi/schema/validation/required.rb +0 -27
- data/lib/jsi/schema/validation/someof.rb +0 -90
- data/lib/jsi/schema/validation/string.rb +0 -47
- data/lib/jsi/schema/validation/type.rb +0 -49
- data/lib/jsi/schema/validation.rb +0 -49
- data/lib/jsi/schema_registry.rb +0 -190
- 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
|
data/lib/jsi/registry.rb
ADDED
|
@@ -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
|