jsi 0.8.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.yardopts +3 -2
- data/CHANGELOG.md +9 -0
- data/LICENSE.md +2 -3
- data/README.md +68 -31
- data/docs/Glossary.md +313 -0
- data/jsi.gemspec +1 -0
- data/lib/jsi/base/mutability.rb +4 -0
- data/lib/jsi/base/node.rb +63 -24
- data/lib/jsi/base.rb +556 -173
- data/lib/jsi/metaschema_node/bootstrap_schema.rb +106 -56
- data/lib/jsi/metaschema_node.rb +227 -160
- data/lib/jsi/ptr.rb +32 -15
- 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 +103 -50
- data/lib/jsi/schema/vocabulary.rb +36 -0
- data/lib/jsi/schema.rb +520 -338
- data/lib/jsi/schema_classes.rb +168 -124
- data/lib/jsi/schema_set.rb +67 -126
- data/lib/jsi/set.rb +23 -0
- data/lib/jsi/simple_wrap.rb +13 -16
- 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 +57 -12
- data/lib/jsi/util/typelike.rb +19 -64
- data/lib/jsi/util.rb +52 -34
- data/lib/jsi/validation/error.rb +41 -2
- data/lib/jsi/validation/result.rb +118 -71
- data/lib/jsi/validation.rb +1 -6
- data/lib/jsi/version.rb +1 -1
- data/lib/jsi.rb +158 -41
- data/lib/schemas/json-schema.org/draft/2020-12/schema.rb +67 -0
- data/lib/schemas/json-schema.org/draft-04/schema.rb +63 -106
- data/lib/schemas/json-schema.org/draft-06/schema.rb +56 -105
- data/lib/schemas/json-schema.org/draft-07/schema.rb +67 -124
- data/readme.rb +3 -3
- 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 +70 -48
- 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 -186
- 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 -93
- 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 -200
- data/lib/jsi/util/private/attr_struct.rb +0 -141
data/lib/jsi/base.rb
CHANGED
|
@@ -10,9 +10,10 @@ module JSI
|
|
|
10
10
|
# These subclasses are generally intended to be ignored by applications using this library - the purpose
|
|
11
11
|
# they serve is to include modules relevant to the instance. The modules these classes include are:
|
|
12
12
|
#
|
|
13
|
-
# - the {Schema
|
|
13
|
+
# - the {SchemaModule JSI Schema Module} of each schema that describes the instance
|
|
14
14
|
# - {Base::HashNode}, {Base::ArrayNode}, or {Base::StringNode} if the instance is
|
|
15
15
|
# a hash/object, array, or string
|
|
16
|
+
# - {Base::Immutable} or {Base::Mutable}
|
|
16
17
|
# - Modules defining accessor methods for property names described by the schemas
|
|
17
18
|
class Base
|
|
18
19
|
autoload :ArrayNode, 'jsi/base/node'
|
|
@@ -22,39 +23,139 @@ module JSI
|
|
|
22
23
|
autoload(:Immutable, 'jsi/base/mutability')
|
|
23
24
|
|
|
24
25
|
include Schema::SchemaAncestorNode
|
|
26
|
+
include(Util::Pretty)
|
|
25
27
|
|
|
26
28
|
# An exception raised when attempting to access a child of a node which cannot have children.
|
|
27
29
|
# A complex node can have children, a simple node cannot.
|
|
28
30
|
class SimpleNodeChildError < StandardError
|
|
29
31
|
end
|
|
30
32
|
|
|
33
|
+
class ChildNotPresent < StandardError
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
conf_attrs = {
|
|
37
|
+
root_uri: {fingerprint: true },
|
|
38
|
+
registry: {fingerprint: true },
|
|
39
|
+
application_collect_evaluated_validate: {fingerprint: false},
|
|
40
|
+
reinstantiate_nonschemas: {fingerprint: false},
|
|
41
|
+
after_initialize: {fingerprint: false},
|
|
42
|
+
child_as_jsi: {fingerprint: false},
|
|
43
|
+
child_use_default: {fingerprint: false},
|
|
44
|
+
to_immutable: {fingerprint: false},
|
|
45
|
+
}.freeze
|
|
46
|
+
Conf = Struct.subclass(*conf_attrs.keys)
|
|
47
|
+
class Conf end
|
|
48
|
+
Conf::ATTRS = conf_attrs
|
|
49
|
+
|
|
50
|
+
# Configuration, shared across all nodes of a document. A JSI's {Base#jsi_conf}.
|
|
51
|
+
#
|
|
52
|
+
# Configuration parameters are set from `**conf_kw` params passed to {SchemaSet#new_jsi #new_jsi},
|
|
53
|
+
# {Schema::MetaSchema#new_schema #new_schema} and related methods.
|
|
54
|
+
#
|
|
55
|
+
# @!attribute root_uri
|
|
56
|
+
# A URI identifying the document root resource.
|
|
57
|
+
# References (e.g. a schema `$ref`) can resolve the resource with this URI.
|
|
58
|
+
#
|
|
59
|
+
# It is rare that this needs to be specified. Most resources that would be
|
|
60
|
+
# referenced are schemas that use the `$id` keyword to specify their URI.
|
|
61
|
+
# However, there are cases when a resource may be referenced using a retrieval URI
|
|
62
|
+
# that does not match the resource's `$id`, and `root_uri` enables resolution.
|
|
63
|
+
# @return [URI, nil]
|
|
64
|
+
# @!attribute registry
|
|
65
|
+
# The registry from which references are resolved.
|
|
66
|
+
# For schemas (or documents containing schemas), this is mainly used with `$ref` values.
|
|
67
|
+
# It is unused in instances that do not contain schemas.
|
|
68
|
+
#
|
|
69
|
+
# Default: {JSI.registry}
|
|
70
|
+
# @return [Registry, nil]
|
|
71
|
+
# @!attribute application_collect_evaluated_validate
|
|
72
|
+
# Shall schema application perform validation when collecting child evaluation
|
|
73
|
+
# (for `unevaluatedProperties`, `unevaluatedItems`)?
|
|
74
|
+
#
|
|
75
|
+
# A child should not be considered evaluated by a schema when it fails to validate[^1].
|
|
76
|
+
# This means that `unevaluatedItems` or `unevaluatedProperties` should
|
|
77
|
+
# only apply to a child if no other applicator schema validates the child.
|
|
78
|
+
# The computational cost of this validation is significant, however, and may be unacceptable for performance.
|
|
79
|
+
#
|
|
80
|
+
# Set to `true`, child evaluation will perform validation, and `unevaluated*` will applicate
|
|
81
|
+
# correctly, at some cost in CPU time.
|
|
82
|
+
#
|
|
83
|
+
# Set to `false`, a child will be considered evaluated when a child applicator schema applies to it,
|
|
84
|
+
# regardless of validity, which will result in an `unevaluated*` schema incorrectly failing to
|
|
85
|
+
# applicate when the child is not valid.
|
|
86
|
+
#
|
|
87
|
+
# The default is false. It is expected that application of `unevaluated*` schemas to such children
|
|
88
|
+
# is not typically relied on, so validation is not typically worth the cost of its computation.
|
|
89
|
+
#
|
|
90
|
+
# [^1]: (ref: the JSON Schema spec states, "Schema objects that produce a false assertion result MUST
|
|
91
|
+
# NOT produce any annotation results, whether from their own keywords or from keywords in subschemas.")
|
|
92
|
+
#
|
|
93
|
+
# Default: false
|
|
94
|
+
# @return [Boolean]
|
|
95
|
+
# @!attribute reinstantiate_nonschemas
|
|
96
|
+
# _private, not officially supported_. whether Schema#resource_root_subschema reinstantiates.
|
|
97
|
+
# @!attribute after_initialize
|
|
98
|
+
# _EXPERIMENTAL_ - a callback that is called with each JSI node in the document after the node is initialized.
|
|
99
|
+
# @return [#call, nil]
|
|
100
|
+
# @!attribute child_as_jsi
|
|
101
|
+
# Default value for {Base#jsi_as_child_default_as_jsi}.
|
|
102
|
+
# @return [Boolean]
|
|
103
|
+
# @!attribute child_use_default
|
|
104
|
+
# Default value for {Base#jsi_child_use_default_default}.
|
|
105
|
+
# @return [Boolean]
|
|
106
|
+
# @!attribute to_immutable
|
|
107
|
+
# A callable that transforms given instance content to an immutable (i.e. deeply frozen) object equal to it.
|
|
108
|
+
#
|
|
109
|
+
# Used when instantiating immutable JSIs and modified copies of them, so their content is immutable.
|
|
110
|
+
#
|
|
111
|
+
# If the instantiated JSI will be mutable, this is not used.
|
|
112
|
+
#
|
|
113
|
+
# Though not recommended, this may be nil with immutable JSIs if the instance content is otherwise
|
|
114
|
+
# guaranteed to be immutable, as well as any modified copies of the instance.
|
|
115
|
+
#
|
|
116
|
+
# Default: {DEFAULT_CONTENT_TO_IMMUTABLE}
|
|
117
|
+
# @return [#call, nil]
|
|
118
|
+
class Conf
|
|
119
|
+
def initialize(
|
|
120
|
+
root_uri: nil,
|
|
121
|
+
registry: JSI.registry,
|
|
122
|
+
application_collect_evaluated_validate: false,
|
|
123
|
+
child_as_jsi: false,
|
|
124
|
+
child_use_default: false,
|
|
125
|
+
to_immutable: DEFAULT_CONTENT_TO_IMMUTABLE,
|
|
126
|
+
**kw
|
|
127
|
+
)
|
|
128
|
+
super(
|
|
129
|
+
root_uri: Util.uri(root_uri, nnil: false, yabs: true),
|
|
130
|
+
registry: registry,
|
|
131
|
+
application_collect_evaluated_validate: application_collect_evaluated_validate,
|
|
132
|
+
child_as_jsi: child_as_jsi,
|
|
133
|
+
child_use_default: child_use_default,
|
|
134
|
+
to_immutable: to_immutable,
|
|
135
|
+
**kw,
|
|
136
|
+
)
|
|
137
|
+
freeze
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# @private
|
|
141
|
+
# @return [Hash]
|
|
142
|
+
def for_fingerprint
|
|
143
|
+
to_h.select { |k, _| self.class::ATTRS.fetch(k).fetch(:fingerprint) }.freeze
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
31
147
|
class << self
|
|
32
148
|
# A string indicating the schema module name
|
|
33
149
|
# and/or schema URI of each schema the class represents.
|
|
34
150
|
# @return [String]
|
|
35
151
|
def inspect
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if mod_name && schema.schema_absolute_uri
|
|
42
|
-
"#{mod_name} <#{schema.schema_absolute_uri}>"
|
|
43
|
-
elsif mod_name
|
|
44
|
-
mod_name
|
|
45
|
-
elsif schema.schema_uri
|
|
46
|
-
schema.schema_uri.to_s
|
|
47
|
-
else
|
|
48
|
-
schema.jsi_ptr.uri.to_s
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
if schema_names.empty?
|
|
53
|
-
"(JSI Schema Class for 0 schemas#{jsi_class_includes.map { |n| " + #{n}" }})"
|
|
54
|
-
else
|
|
55
|
-
-"(JSI Schema Class: #{(schema_names + jsi_class_includes.map(&:name)).join(' + ')})"
|
|
56
|
-
end
|
|
152
|
+
return super unless respond_to?(:jsi_class_schemas)
|
|
153
|
+
schema_names = jsi_class_schemas.map do |schema|
|
|
154
|
+
mod_name = schema.jsi_schema_module_name_from_ancestor
|
|
155
|
+
next "#{mod_name} <#{schema.jsi_resource_uri}>" if mod_name && schema.jsi_resource_uri
|
|
156
|
+
mod_name || "<#{schema.schema_uri || schema.jsi_ptr.uri}>"
|
|
57
157
|
end
|
|
158
|
+
"(#{[superclass, *schema_names, *jsi_class_includes].join(' + ')})"
|
|
58
159
|
end
|
|
59
160
|
|
|
60
161
|
def to_s
|
|
@@ -74,9 +175,9 @@ module JSI
|
|
|
74
175
|
return super unless respond_to?(:jsi_class_schemas)
|
|
75
176
|
alnum = proc { |id| (id % 36**4).to_s(36).rjust(4, '0').upcase }
|
|
76
177
|
schema_names = jsi_class_schemas.map do |schema|
|
|
77
|
-
|
|
78
|
-
if
|
|
79
|
-
[
|
|
178
|
+
named_ancestor, tokens = schema.jsi_schema_module.send(:named_ancestor_tokens)
|
|
179
|
+
if named_ancestor
|
|
180
|
+
[named_ancestor.jsi_schema_module_connection.name, *tokens].join('_')
|
|
80
181
|
elsif schema.schema_uri
|
|
81
182
|
schema.schema_uri.to_s
|
|
82
183
|
else
|
|
@@ -108,36 +209,43 @@ module JSI
|
|
|
108
209
|
# @api private
|
|
109
210
|
# @param jsi_document [Object] the document containing the instance
|
|
110
211
|
# @param jsi_ptr [JSI::Ptr] a pointer pointing to the JSI's instance in the document
|
|
111
|
-
# @param
|
|
212
|
+
# @param jsi_base_uri [URI, nil] see {SchemaSet#new_jsi} param `base_uri`
|
|
112
213
|
# @param jsi_schema_resource_ancestors [Array<JSI::Base + JSI::Schema>]
|
|
113
|
-
# @param
|
|
114
|
-
|
|
214
|
+
# @param jsi_schema_dynamic_anchor_map [Schema::DynamicAnchorMap]
|
|
215
|
+
# @param jsi_dynamic_root_map map of (ptr, dynamic_anchor_map) → (Base root node), shared across dynamic root nodes
|
|
216
|
+
# @param jsi_conf [Base::Conf]
|
|
217
|
+
# @param jsi_root_node [JSI::Base, nil]
|
|
218
|
+
def initialize(
|
|
219
|
+
jsi_document: ,
|
|
115
220
|
jsi_ptr: Ptr[],
|
|
116
221
|
jsi_indicated_schemas: ,
|
|
117
|
-
|
|
222
|
+
jsi_base_uri: nil,
|
|
118
223
|
jsi_schema_resource_ancestors: Util::EMPTY_ARY,
|
|
119
|
-
|
|
120
|
-
|
|
224
|
+
jsi_schema_dynamic_anchor_map: Schema::DynamicAnchorMap::EMPTY,
|
|
225
|
+
jsi_dynamic_root_map: nil,
|
|
226
|
+
jsi_conf: nil,
|
|
121
227
|
jsi_root_node: nil
|
|
122
228
|
)
|
|
123
|
-
#chkbug
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
229
|
+
#chkbug fail(Bug, "no #jsi_schemas") unless respond_to?(:jsi_schemas)
|
|
230
|
+
|
|
231
|
+
#chkbug fail(Bug) if !jsi_root_node ^ jsi_conf
|
|
232
|
+
@jsi_conf = jsi_conf = jsi_conf || jsi_root_node.jsi_conf
|
|
233
|
+
@jsi_document = jsi_document
|
|
234
|
+
#chkbug fail(Bug) unless jsi_ptr.is_a?(Ptr)
|
|
235
|
+
#chkbug fail(Bug) unless jsi_ptr.resolve_against(jsi_document).equal?(jsi_ptr)
|
|
236
|
+
@jsi_ptr = jsi_ptr
|
|
237
|
+
#chkbug fail(Bug) unless jsi_indicated_schemas.is_a?(SchemaSet)
|
|
238
|
+
@jsi_indicated_schemas = jsi_indicated_schemas
|
|
239
|
+
self.jsi_base_uri = jsi_base_uri
|
|
129
240
|
self.jsi_schema_resource_ancestors = jsi_schema_resource_ancestors
|
|
130
|
-
self.
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
@jsi_root_node = jsi_root_node
|
|
139
|
-
end
|
|
140
|
-
|
|
241
|
+
self.jsi_schema_dynamic_anchor_map = jsi_schema_dynamic_anchor_map
|
|
242
|
+
#chkbug fail(Bug) if jsi_root_node && jsi_dynamic_root_map
|
|
243
|
+
@jsi_dynamic_root_map = jsi_dynamic_root_map || (jsi_root_node ? jsi_root_node.jsi_dynamic_root_map : jsi_memomap(&method(:jsi_dynamic_root_compute)))
|
|
244
|
+
@jsi_root_node = jsi_root_node || self
|
|
245
|
+
@root_rel_ptr = @jsi_ptr.relative_to(@jsi_root_node.jsi_ptr)
|
|
246
|
+
|
|
247
|
+
# @memos does not freeze if/when the node freezes
|
|
248
|
+
@memos = {}
|
|
141
249
|
jsi_memomaps_initialize
|
|
142
250
|
jsi_mutability_initialize
|
|
143
251
|
|
|
@@ -150,7 +258,7 @@ module JSI
|
|
|
150
258
|
|
|
151
259
|
# @!method jsi_schemas
|
|
152
260
|
# The set of schemas that describe this instance.
|
|
153
|
-
# These are the applicator schemas that apply to this instance, the result of
|
|
261
|
+
# These are the applicator schemas that apply to this instance, the result of in-place application
|
|
154
262
|
# of our {#jsi_indicated_schemas}.
|
|
155
263
|
# @return [JSI::SchemaSet]
|
|
156
264
|
# note: defined on subclasses by JSI::SchemaClasses.class_for_schemas
|
|
@@ -163,13 +271,20 @@ module JSI
|
|
|
163
271
|
# @return [JSI::Ptr]
|
|
164
272
|
attr_reader :jsi_ptr
|
|
165
273
|
|
|
166
|
-
#
|
|
167
|
-
#
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
274
|
+
# See {SchemaSet#new_jsi} param `registry`
|
|
275
|
+
# @return [Registry, nil]
|
|
276
|
+
def jsi_registry
|
|
277
|
+
jsi_conf.registry
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
attr_reader(:jsi_dynamic_root_map)
|
|
281
|
+
protected(:jsi_dynamic_root_map)
|
|
282
|
+
|
|
283
|
+
# @return [Base::Conf]
|
|
284
|
+
attr_reader(:jsi_conf)
|
|
171
285
|
|
|
172
|
-
#
|
|
286
|
+
# The root ancestor of this node. This is typically the document root, though
|
|
287
|
+
# it can be a different resource root when dynamic scope is overridden.
|
|
173
288
|
# @return [JSI::Base]
|
|
174
289
|
attr_reader :jsi_root_node
|
|
175
290
|
|
|
@@ -185,12 +300,12 @@ module JSI
|
|
|
185
300
|
jsi_node_content
|
|
186
301
|
end
|
|
187
302
|
|
|
188
|
-
#
|
|
303
|
+
# The schemas indicated as describing this instance, prior to in-place application.
|
|
189
304
|
#
|
|
190
|
-
#
|
|
305
|
+
# This is different from {#jsi_schemas}, which are the in-place applicator schemas
|
|
191
306
|
# which describe this instance. for most purposes, `#jsi_schemas` is more relevant.
|
|
192
307
|
#
|
|
193
|
-
# `jsi_indicated_schemas` does not include
|
|
308
|
+
# `jsi_indicated_schemas` does not include in-place applicator schemas, such as the
|
|
194
309
|
# subschemas of `allOf`, whereas `#jsi_schemas` does.
|
|
195
310
|
#
|
|
196
311
|
# this does include indicated schemas which do not apply themselves, such as `$ref`
|
|
@@ -209,7 +324,22 @@ module JSI
|
|
|
209
324
|
# @yield [JSI::Base] each descendent node, starting with self
|
|
210
325
|
# @return [nil, Enumerator] an Enumerator if invoked without a block; otherwise nil
|
|
211
326
|
def jsi_each_descendent_node(propertyNames: false, &block)
|
|
212
|
-
|
|
327
|
+
unless block
|
|
328
|
+
return to_enum(__method__, propertyNames: propertyNames) do
|
|
329
|
+
# size
|
|
330
|
+
Util.ycomb do |rec|
|
|
331
|
+
proc do |node|
|
|
332
|
+
if node.respond_to?(:to_hash)
|
|
333
|
+
node.to_hash.inject(1) { |c, (k, child)| c + rec[child] + (propertyNames ? rec[k] : 0) }
|
|
334
|
+
elsif node.respond_to?(:to_ary)
|
|
335
|
+
node.to_ary.inject(1) { |c, child| c + rec[child] }
|
|
336
|
+
else
|
|
337
|
+
1
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
end[jsi_node_content]
|
|
341
|
+
end
|
|
342
|
+
end
|
|
213
343
|
|
|
214
344
|
yield self
|
|
215
345
|
|
|
@@ -226,6 +356,32 @@ module JSI
|
|
|
226
356
|
nil
|
|
227
357
|
end
|
|
228
358
|
|
|
359
|
+
# yields each descendent of this node that is a JSI Schema
|
|
360
|
+
# @yield [Base + Schema]
|
|
361
|
+
# @return [nil, Enumerator] an Enumerator if invoked without a block; otherwise nil
|
|
362
|
+
def jsi_each_descendent_schema(&block)
|
|
363
|
+
return(to_enum(__method__)) unless block_given?
|
|
364
|
+
|
|
365
|
+
# note: this never yields self; if self is a Schema, Schema#jsi_each_descendent_schema overrides this method
|
|
366
|
+
jsi_each_child_token do |token|
|
|
367
|
+
jsi_child_node(token).jsi_each_descendent_schema(&block)
|
|
368
|
+
end
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
# yields each descendent of this node within the same resource that is a Schema
|
|
372
|
+
# @yield [Schema]
|
|
373
|
+
def jsi_each_descendent_schema_same_resource(&block)
|
|
374
|
+
return(to_enum(__method__)) unless block_given?
|
|
375
|
+
|
|
376
|
+
jsi_each_child_token do |token|
|
|
377
|
+
child = jsi_child_node(token)
|
|
378
|
+
if !child.jsi_is_resource_root?
|
|
379
|
+
# note: if child is a Schema, Schema#jsi_each_descendent_schema_same_resource overrides Base
|
|
380
|
+
child.jsi_each_descendent_schema_same_resource(&block)
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
|
|
229
385
|
# recursively selects descendent nodes of this JSI, returning a modified copy of self containing only
|
|
230
386
|
# descendent nodes for which the given block had a true-ish result.
|
|
231
387
|
#
|
|
@@ -286,24 +442,24 @@ module JSI
|
|
|
286
442
|
end
|
|
287
443
|
end
|
|
288
444
|
|
|
289
|
-
#
|
|
445
|
+
# JSI nodes above this one in the document.
|
|
290
446
|
#
|
|
291
447
|
# @return [Array<JSI::Base>]
|
|
292
448
|
def jsi_parent_nodes
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
parent
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
449
|
+
parent_nodes = []
|
|
450
|
+
ptr = @root_rel_ptr
|
|
451
|
+
while !ptr.root?
|
|
452
|
+
ptr = ptr.parent
|
|
453
|
+
parent_nodes.push(jsi_root_node.jsi_descendent_node(ptr))
|
|
454
|
+
end
|
|
455
|
+
parent_nodes.freeze
|
|
300
456
|
end
|
|
301
457
|
|
|
302
458
|
# the immediate parent of this JSI. nil if there is no parent.
|
|
303
459
|
#
|
|
304
460
|
# @return [JSI::Base, nil]
|
|
305
461
|
def jsi_parent_node
|
|
306
|
-
|
|
462
|
+
@root_rel_ptr.root? ? nil : jsi_root_node.jsi_descendent_node(@root_rel_ptr.parent)
|
|
307
463
|
end
|
|
308
464
|
|
|
309
465
|
# ancestor JSI instances from this node up to the root. this node itself is always its own first ancestor.
|
|
@@ -314,7 +470,7 @@ module JSI
|
|
|
314
470
|
ancestor = jsi_root_node
|
|
315
471
|
ancestors << ancestor
|
|
316
472
|
|
|
317
|
-
|
|
473
|
+
@root_rel_ptr.tokens.each do |token|
|
|
318
474
|
ancestor = ancestor.jsi_child_node(token)
|
|
319
475
|
ancestors << ancestor
|
|
320
476
|
end
|
|
@@ -326,11 +482,11 @@ module JSI
|
|
|
326
482
|
# @param ptr [JSI::Ptr, #to_ary]
|
|
327
483
|
# @return [JSI::Base]
|
|
328
484
|
def jsi_descendent_node(ptr)
|
|
329
|
-
|
|
330
|
-
|
|
485
|
+
tokens = Ptr.ary_ptr(ptr).resolve_against(jsi_node_content).tokens
|
|
486
|
+
tokens.inject(self, &:jsi_child_node)
|
|
331
487
|
end
|
|
332
488
|
|
|
333
|
-
#
|
|
489
|
+
# The descendent node at the given {Ptr}, token array, or pointer string.
|
|
334
490
|
#
|
|
335
491
|
# Note that, though more convenient to type, using an operator whose meaning may not be intuitive
|
|
336
492
|
# to a reader could impair readability of code.
|
|
@@ -339,13 +495,14 @@ module JSI
|
|
|
339
495
|
#
|
|
340
496
|
# my_jsi / ['foo', 'bar']
|
|
341
497
|
# my_jsi / %w(foo bar)
|
|
498
|
+
# my_jsi / '/foo/bar'
|
|
342
499
|
# my_schema / JSI::Ptr['additionalProperties']
|
|
343
500
|
# my_schema / %w(properties foo items additionalProperties)
|
|
344
501
|
#
|
|
345
|
-
# @param
|
|
502
|
+
# @param ptr [JSI::Ptr, #to_ary, #to_str]
|
|
346
503
|
# @return (see #jsi_descendent_node)
|
|
347
504
|
def /(ptr)
|
|
348
|
-
jsi_descendent_node(ptr)
|
|
505
|
+
jsi_descendent_node(ptr.respond_to?(:to_str) ? Ptr.from_pointer(ptr) : ptr)
|
|
349
506
|
end
|
|
350
507
|
|
|
351
508
|
# yields each token (array index or hash key) identifying a child node.
|
|
@@ -367,11 +524,20 @@ module JSI
|
|
|
367
524
|
#
|
|
368
525
|
# @param token [String, Integer]
|
|
369
526
|
# @return [Boolean]
|
|
370
|
-
def
|
|
527
|
+
def jsi_child_token_present?(token)
|
|
371
528
|
# note: overridden by Base::HashNode, Base::ArrayNode
|
|
372
529
|
false
|
|
373
530
|
end
|
|
374
531
|
|
|
532
|
+
# @api private
|
|
533
|
+
# @return [nil]
|
|
534
|
+
def jsi_child_ensure_present(token)
|
|
535
|
+
if !jsi_child_token_present?(token)
|
|
536
|
+
raise(ChildNotPresent, -"token does not identify a child that is present: #{token.inspect}\nself = #{pretty_inspect.chomp}")
|
|
537
|
+
end
|
|
538
|
+
nil
|
|
539
|
+
end
|
|
540
|
+
|
|
375
541
|
# The child of the {#jsi_node_content} identified by the given token,
|
|
376
542
|
# or `nil` if the token does not identify an existing child.
|
|
377
543
|
#
|
|
@@ -386,32 +552,36 @@ module JSI
|
|
|
386
552
|
end
|
|
387
553
|
|
|
388
554
|
# A child JSI node, or the child of our {#jsi_instance}, identified by the given token.
|
|
389
|
-
# The token must identify an existing child; behavior if the child does not exist is undefined.
|
|
390
555
|
#
|
|
391
556
|
# @param token (see Base#[])
|
|
392
557
|
# @param as_jsi (see Base#[])
|
|
393
558
|
# @return [JSI::Base, Object]
|
|
559
|
+
# @raise [Base::ChildNotPresent]
|
|
394
560
|
def jsi_child(token, as_jsi: )
|
|
561
|
+
jsi_child_as_jsi(jsi_child_node(token), as_jsi)
|
|
562
|
+
end
|
|
563
|
+
private :jsi_child # internals for #[] but idk, could be public
|
|
564
|
+
|
|
565
|
+
# @param token An array index or Hash/object property name identifying a present child of this node
|
|
566
|
+
# @return [JSI::Base]
|
|
567
|
+
# @raise [Base::ChildNotPresent]
|
|
568
|
+
def jsi_child_node(token)
|
|
569
|
+
@child_node_by_token_map[token: token]
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
private def jsi_child_node_by_token_compute(token: )
|
|
573
|
+
jsi_child_ensure_present(token)
|
|
574
|
+
|
|
395
575
|
child_content = jsi_node_content_child(token)
|
|
396
576
|
|
|
397
577
|
child_indicated_schemas = @child_indicated_schemas_map[token: token, content: jsi_node_content]
|
|
398
578
|
child_applied_schemas = @child_applied_schemas_map[token: token, child_indicated_schemas: child_indicated_schemas, child_content: child_content]
|
|
399
|
-
|
|
400
|
-
jsi_child_as_jsi(child_content, child_applied_schemas, as_jsi) do
|
|
401
|
-
@child_node_map[
|
|
579
|
+
@child_node_map[
|
|
402
580
|
token: token,
|
|
403
581
|
child_indicated_schemas: child_indicated_schemas,
|
|
404
582
|
child_applied_schemas: child_applied_schemas,
|
|
405
583
|
includes: SchemaClasses.includes_for(child_content),
|
|
406
|
-
|
|
407
|
-
end
|
|
408
|
-
end
|
|
409
|
-
private :jsi_child # internals for #[] but idk, could be public
|
|
410
|
-
|
|
411
|
-
# @param token (see Base#[])
|
|
412
|
-
# @return [JSI::Base]
|
|
413
|
-
protected def jsi_child_node(token)
|
|
414
|
-
jsi_child(token, as_jsi: true)
|
|
584
|
+
]
|
|
415
585
|
end
|
|
416
586
|
|
|
417
587
|
# A default value for a child of this node identified by the given token, if schemas describing
|
|
@@ -433,41 +603,42 @@ module JSI
|
|
|
433
603
|
|
|
434
604
|
defaults = Set.new
|
|
435
605
|
child_applied_schemas.each do |child_schema|
|
|
436
|
-
|
|
437
|
-
defaults << child_schema.jsi_node_content['default']
|
|
438
|
-
end
|
|
606
|
+
defaults.merge(child_schema.dialect_invoke_each(:default))
|
|
439
607
|
end
|
|
440
608
|
|
|
441
609
|
if defaults.size == 1
|
|
442
610
|
# use the default value
|
|
443
|
-
|
|
611
|
+
default_child_node =
|
|
444
612
|
jsi_modified_copy do |i|
|
|
445
613
|
i.dup.tap { |i_dup| i_dup[token] = defaults.first }
|
|
446
614
|
end.jsi_child_node(token)
|
|
447
|
-
|
|
615
|
+
jsi_child_as_jsi(default_child_node, as_jsi)
|
|
448
616
|
else
|
|
449
617
|
child_content
|
|
450
618
|
end
|
|
451
619
|
end
|
|
452
620
|
private :jsi_default_child # internals for #[] but idk, could be public
|
|
453
621
|
|
|
454
|
-
#
|
|
622
|
+
# Returns a child or children identified by param `token`.
|
|
623
|
+
#
|
|
624
|
+
# @param token [String, Integer, Range, Object] Identifies the child or children to return.
|
|
625
|
+
# Typically an array index or hash key (JSON object property name) of the instance.
|
|
626
|
+
#
|
|
627
|
+
# For an array instance, this may also be a Range (in which case an Array of children is returned)
|
|
628
|
+
# or a negative index; these behave as Array#[] does.
|
|
629
|
+
# @param as_jsi [:auto, true, false] (default is `:auto` or {Base::Conf#child_as_jsi conf child_as_jsi})
|
|
455
630
|
#
|
|
456
|
-
# @param token [String, Integer, Object] an array index or hash key (JSON object property name)
|
|
457
|
-
# of the instance identifying the child value
|
|
458
|
-
# @param as_jsi [:auto, true, false] (default is `:auto`)
|
|
459
631
|
# Whether to return the child as a JSI. One of:
|
|
460
632
|
#
|
|
461
633
|
# - `:auto`: By default a JSI will be returned when either:
|
|
462
634
|
#
|
|
463
|
-
# - the
|
|
464
|
-
# - the
|
|
635
|
+
# - the child is an array or hash
|
|
636
|
+
# - the child is a schema (including true/false schemas)
|
|
465
637
|
#
|
|
466
638
|
# The plain content is returned when it is a simple type.
|
|
467
639
|
#
|
|
468
|
-
# - true: the result
|
|
469
|
-
#
|
|
470
|
-
# - false: the result value will always be the plain instance.
|
|
640
|
+
# - true: the result will always be returned as a JSI.
|
|
641
|
+
# - false: the result will always be the node's content.
|
|
471
642
|
#
|
|
472
643
|
# note that nil is returned (regardless of as_jsi) when there is no value to return because the token
|
|
473
644
|
# is not a hash key or array index of the instance and no default value applies.
|
|
@@ -484,15 +655,26 @@ module JSI
|
|
|
484
655
|
#
|
|
485
656
|
# if the child instance's schemas do not indicate a single default value (that is, if zero or multiple
|
|
486
657
|
# defaults are specified across those schemas), nil is returned.
|
|
487
|
-
#
|
|
488
|
-
# unspecified behavior.)
|
|
489
|
-
# @return [JSI::Base, Object, nil] the child value identified by the subscript token
|
|
658
|
+
# @return [Base, Object, Array, nil] the child or children identified by `token`
|
|
490
659
|
def [](token, as_jsi: jsi_child_as_jsi_default, use_default: jsi_child_use_default_default)
|
|
660
|
+
raise(BlockGivenError) if block_given?
|
|
491
661
|
# note: overridden by Base::HashNode, Base::ArrayNode
|
|
492
662
|
jsi_simple_node_child_error(token)
|
|
493
663
|
end
|
|
494
664
|
|
|
665
|
+
# When accessing this node as a child (from a parent's {#[]} or a property reader), should the result
|
|
666
|
+
# by default be a JSI node (this node), or its node content?
|
|
667
|
+
# This default may be overridden using the `as_jsi` parameter calling the parent's {#[]}.
|
|
668
|
+
# {Base::Conf Configurable} using {Base::Conf#child_as_jsi `child_as_jsi`}.
|
|
669
|
+
# @return [Boolean]
|
|
670
|
+
def jsi_as_child_default_as_jsi
|
|
671
|
+
# base default is false, for simple types. overridden by complex types (HashNode, ArrayNode), Schema, and others.
|
|
672
|
+
jsi_conf.child_as_jsi
|
|
673
|
+
end
|
|
674
|
+
|
|
495
675
|
# The default value for the param `as_jsi` of {#[]}, controlling whether a child is returned as a JSI instance.
|
|
676
|
+
# @deprecated after v0.8. This is the parent node's preference whether its children are returned as JSIs, but it
|
|
677
|
+
# is better for a child to indicate whether it should be a JSI by overriding {#jsi_as_child_default_as_jsi}.
|
|
496
678
|
# @return [:auto, true, false] a valid value of the `as_jsi` param of {#[]}
|
|
497
679
|
def jsi_child_as_jsi_default
|
|
498
680
|
:auto
|
|
@@ -500,33 +682,28 @@ module JSI
|
|
|
500
682
|
|
|
501
683
|
# The default value for the param `use_default` of {#[]}, controlling whether a schema default value is
|
|
502
684
|
# returned when a token refers to a child that is not in the document.
|
|
685
|
+
# {Base::Conf Configurable} using {Base::Conf#child_use_default `child_use_default`}.
|
|
503
686
|
# @return [true, false] a valid value of the `use_default` param of {#[]}
|
|
504
687
|
def jsi_child_use_default_default
|
|
505
|
-
|
|
688
|
+
jsi_conf.child_use_default
|
|
506
689
|
end
|
|
507
690
|
|
|
508
|
-
#
|
|
509
|
-
#
|
|
691
|
+
# Assigns a child identified by the given token to the given value.
|
|
692
|
+
# If the given value is a JSI node, its content is used; its {#jsi_schemas} are not.
|
|
510
693
|
#
|
|
511
|
-
# @param token [String, Integer, Object] token identifying the
|
|
694
|
+
# @param token [String, Integer, Object] token identifying the child to assign
|
|
512
695
|
# @param value [JSI::Base, Object] the value to be assigned
|
|
513
696
|
def []=(token, value)
|
|
514
697
|
unless jsi_array? || jsi_hash?
|
|
515
698
|
jsi_simple_node_child_error(token)
|
|
516
699
|
end
|
|
517
700
|
if value.is_a?(Base)
|
|
518
|
-
self[token] = value.
|
|
701
|
+
self[token] = value.jsi_node_content
|
|
519
702
|
else
|
|
520
|
-
|
|
703
|
+
jsi_node_content[token] = value
|
|
521
704
|
end
|
|
522
705
|
end
|
|
523
706
|
|
|
524
|
-
# the set of JSI schema modules corresponding to the schemas that describe this JSI
|
|
525
|
-
# @return [Set<Module>]
|
|
526
|
-
def jsi_schema_modules
|
|
527
|
-
Util.ensure_module_set(jsi_schemas.map(&:jsi_schema_module))
|
|
528
|
-
end
|
|
529
|
-
|
|
530
707
|
# Is this JSI described by the given schema (or schema module)?
|
|
531
708
|
#
|
|
532
709
|
# @param schema [Schema, SchemaModule]
|
|
@@ -535,7 +712,7 @@ module JSI
|
|
|
535
712
|
if schema.is_a?(Schema)
|
|
536
713
|
jsi_schemas.include?(schema)
|
|
537
714
|
elsif schema.is_a?(SchemaModule)
|
|
538
|
-
|
|
715
|
+
jsi_schemas.include?(schema.schema)
|
|
539
716
|
else
|
|
540
717
|
raise(TypeError, "expected a Schema or Schema Module; got: #{schema.pretty_inspect.chomp}")
|
|
541
718
|
end
|
|
@@ -556,17 +733,37 @@ module JSI
|
|
|
556
733
|
#
|
|
557
734
|
# @yield [Object] this JSI's instance. the block should result
|
|
558
735
|
# in a nondestructively modified copy of this.
|
|
559
|
-
# @return [
|
|
560
|
-
def jsi_modified_copy(&block)
|
|
736
|
+
# @return [Base] the modified copy of self
|
|
737
|
+
def jsi_modified_copy(**conf_kw, &block)
|
|
561
738
|
modified_document = @jsi_ptr.modified_document_copy(@jsi_document, &block)
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
739
|
+
|
|
740
|
+
conf = jsi_conf.merge(**conf_kw)
|
|
741
|
+
|
|
742
|
+
modified_document = conf.to_immutable.call(modified_document) if !jsi_mutable? && conf.to_immutable
|
|
743
|
+
|
|
744
|
+
root_content = jsi_root_node.jsi_ptr.evaluate(modified_document)
|
|
745
|
+
|
|
746
|
+
root_applied_schemas = SchemaSet.build do |y|
|
|
747
|
+
c = y.method(:yield) # TODO drop c, just pass y, when all supported Enumerator::Yielder.method_defined?(:to_proc)
|
|
748
|
+
jsi_root_node.jsi_indicated_schemas.each do |is|
|
|
749
|
+
is.each_inplace_applicator_schema(root_content, &c)
|
|
750
|
+
end
|
|
751
|
+
end
|
|
752
|
+
|
|
753
|
+
root_class = JSI::SchemaClasses.class_for_schemas(root_applied_schemas,
|
|
754
|
+
includes: SchemaClasses.includes_for(root_content),
|
|
566
755
|
mutable: jsi_mutable?,
|
|
567
|
-
to_immutable: jsi_content_to_immutable,
|
|
568
756
|
)
|
|
569
|
-
modified_jsi_root_node.
|
|
757
|
+
modified_jsi_root_node = root_class.new(
|
|
758
|
+
jsi_document: modified_document,
|
|
759
|
+
jsi_ptr: jsi_root_node.jsi_ptr,
|
|
760
|
+
jsi_indicated_schemas: jsi_root_node.jsi_indicated_schemas,
|
|
761
|
+
jsi_base_uri: jsi_root_node.jsi_base_uri,
|
|
762
|
+
jsi_schema_dynamic_anchor_map: jsi_root_node.jsi_schema_dynamic_anchor_map,
|
|
763
|
+
jsi_conf: conf,
|
|
764
|
+
).send(:jsi_initialized)
|
|
765
|
+
|
|
766
|
+
modified_jsi_root_node.jsi_descendent_node(@root_rel_ptr)
|
|
570
767
|
end
|
|
571
768
|
|
|
572
769
|
# Is the instance an array?
|
|
@@ -601,7 +798,7 @@ module JSI
|
|
|
601
798
|
|
|
602
799
|
# validates this JSI's instance against its schemas
|
|
603
800
|
#
|
|
604
|
-
# @return [JSI::Validation::
|
|
801
|
+
# @return [JSI::Validation::Result::Full]
|
|
605
802
|
def jsi_validate
|
|
606
803
|
jsi_indicated_schemas.instance_validate(self)
|
|
607
804
|
end
|
|
@@ -612,6 +809,15 @@ module JSI
|
|
|
612
809
|
jsi_indicated_schemas.instance_valid?(self)
|
|
613
810
|
end
|
|
614
811
|
|
|
812
|
+
# Asserts that this JSI is valid against its schemas.
|
|
813
|
+
# {JSI::Invalid} is raised if it is not.
|
|
814
|
+
#
|
|
815
|
+
# @raise [Invalid]
|
|
816
|
+
# @return [nil]
|
|
817
|
+
def jsi_valid!
|
|
818
|
+
jsi_validate.valid!
|
|
819
|
+
end
|
|
820
|
+
|
|
615
821
|
# queries this JSI using the [JMESPath Ruby](https://rubygems.org/gems/jmespath) gem.
|
|
616
822
|
# see [https://jmespath.org/](https://jmespath.org/) to learn the JMESPath query language.
|
|
617
823
|
#
|
|
@@ -629,41 +835,161 @@ module JSI
|
|
|
629
835
|
JMESPath.search(expression, self, **runtime_options)
|
|
630
836
|
end
|
|
631
837
|
|
|
632
|
-
|
|
633
|
-
|
|
838
|
+
# The nearest ancestor (including this node) that is a resource root.
|
|
839
|
+
#
|
|
840
|
+
# A resource root is a schema with an absolute URI, or the {#jsi_root_node document's root node}
|
|
841
|
+
# (which might not be a schema and might not have an absolute URI).
|
|
842
|
+
# @return [Base]
|
|
843
|
+
def jsi_resource_root
|
|
844
|
+
super || jsi_root_node
|
|
634
845
|
end
|
|
635
846
|
|
|
636
|
-
#
|
|
637
|
-
# @return [
|
|
638
|
-
def
|
|
639
|
-
|
|
847
|
+
# @private
|
|
848
|
+
# @return [URI, nil]
|
|
849
|
+
def jsi_root_uri
|
|
850
|
+
jsi_conf.root_uri
|
|
640
851
|
end
|
|
641
852
|
|
|
642
|
-
|
|
643
|
-
|
|
853
|
+
# @private
|
|
854
|
+
# @return [SchemaModule::Connection]
|
|
855
|
+
def jsi_schema_module_connection
|
|
856
|
+
raise(BlockGivenError) if block_given?
|
|
857
|
+
@memos.fetch(:schema_module_connection) do
|
|
858
|
+
if is_a?(Schema)
|
|
859
|
+
raise(TypeError, "mutable schema may not have a schema module: #{self}") if jsi_mutable?
|
|
860
|
+
module_class = SchemaModule
|
|
861
|
+
else
|
|
862
|
+
raise(TypeError, "mutable JSI may not have a SchemaModule::Connection: #{self}") if jsi_mutable?
|
|
863
|
+
module_class = SchemaModule::Connection
|
|
864
|
+
end
|
|
865
|
+
@memos[:schema_module_connection] = module_class.new(self)
|
|
866
|
+
end
|
|
644
867
|
end
|
|
645
868
|
|
|
646
|
-
#
|
|
869
|
+
# @private experimental
|
|
870
|
+
# a callback to be overridden (remember to call super), called with our #jsi_schema_module_connection when that is created
|
|
871
|
+
# @param mod [SchemaModule::Connection]
|
|
647
872
|
# @return [void]
|
|
873
|
+
def jsi_schema_module_connection_created(mod)
|
|
874
|
+
end
|
|
875
|
+
|
|
876
|
+
# @private
|
|
877
|
+
# @return [Boolean]
|
|
878
|
+
def jsi_schema_module_connection_defined?
|
|
879
|
+
@memos.key?(:schema_module_connection)
|
|
880
|
+
end
|
|
881
|
+
|
|
882
|
+
# @param dynamic_anchor_map [Schema::DynamicAnchorMap]
|
|
883
|
+
# @return [Base]
|
|
884
|
+
private def jsi_dynamic_root_descendent(dynamic_anchor_map)
|
|
885
|
+
root = jsi_dynamic_root_map[
|
|
886
|
+
ptr: jsi_resource_root.jsi_ptr,
|
|
887
|
+
dynamic_anchor_map: dynamic_anchor_map,
|
|
888
|
+
]
|
|
889
|
+
root.jsi_descendent_node(jsi_ptr.relative_to(jsi_resource_root.jsi_ptr))
|
|
890
|
+
end
|
|
891
|
+
|
|
892
|
+
# This instantiates a new root node (its #jsi_root_node is itself).
|
|
893
|
+
# When the resource at `ptr` is not the document root, the new root node has the
|
|
894
|
+
# unusual property that its jsi_ptr is not the root ptr; it is the given `ptr`.
|
|
895
|
+
# (Calling this a 'root node' is questionable, but that is the name we use.)
|
|
896
|
+
# From the new root node, no JSI node represents locations in the document above it.
|
|
897
|
+
# @param ptr [Ptr]
|
|
898
|
+
# @param dynamic_anchor_map [Schema::DynamicAnchorMap]
|
|
899
|
+
# @return [Base]
|
|
900
|
+
private def jsi_dynamic_root_compute(ptr: , dynamic_anchor_map: )
|
|
901
|
+
# self is always the originally instantiated root node (with jsi_ptr = Ptr[])
|
|
902
|
+
resource_root = jsi_descendent_node(ptr)
|
|
903
|
+
if resource_root.jsi_schema_dynamic_anchor_map == dynamic_anchor_map
|
|
904
|
+
return resource_root
|
|
905
|
+
end
|
|
906
|
+
|
|
907
|
+
resource_root.jsi_dynamic_root_instantiate(
|
|
908
|
+
jsi_document: resource_root.jsi_document,
|
|
909
|
+
jsi_ptr: ptr,
|
|
910
|
+
jsi_base_uri: resource_root.jsi_base_uri,
|
|
911
|
+
#jsi_schema_resource_ancestors: none (new root),
|
|
912
|
+
jsi_schema_dynamic_anchor_map: dynamic_anchor_map,
|
|
913
|
+
jsi_dynamic_root_map: jsi_dynamic_root_map,
|
|
914
|
+
jsi_conf: resource_root.jsi_conf,
|
|
915
|
+
)
|
|
916
|
+
end
|
|
917
|
+
|
|
918
|
+
protected def jsi_dynamic_root_instantiate(**kw)
|
|
919
|
+
# self is a resource root being instantiated with overridden dynamic scope
|
|
920
|
+
self.class.new(
|
|
921
|
+
jsi_indicated_schemas: jsi_indicated_schemas,
|
|
922
|
+
**kw,
|
|
923
|
+
).send(:jsi_initialized)
|
|
924
|
+
end
|
|
925
|
+
|
|
926
|
+
# A JSI whose node content is a duplicate of this JSI's (using its #dup).
|
|
927
|
+
#
|
|
928
|
+
# Note that immutable JSIs are not made mutable with #dup.
|
|
929
|
+
# The content's #dup may return an unfrozen copy, but instantiating a modified
|
|
930
|
+
# copy of this JSI involves transforming the content to immutable again
|
|
931
|
+
# (using {Base::Conf conf} {Base::Conf#to_immutable to_immutable}).
|
|
932
|
+
# @return [Base]
|
|
933
|
+
def dup
|
|
934
|
+
jsi_modified_copy(&:dup)
|
|
935
|
+
end
|
|
936
|
+
|
|
937
|
+
# Prints a string indicating this JSI's schemas, briefly, and its content.
|
|
938
|
+
#
|
|
939
|
+
# If described by a schema with a named schema module, that is shown.
|
|
940
|
+
# The number of schemas describing this JSI is indicated.
|
|
941
|
+
#
|
|
942
|
+
# If this JSI is a simple type, the node's content is inspected; if complex, its children are inspected.
|
|
648
943
|
def pretty_print(q)
|
|
649
|
-
q
|
|
650
|
-
q.text jsi_object_group_text.join(' ')
|
|
651
|
-
q.group(2) {
|
|
652
|
-
q.breakable ' '
|
|
944
|
+
jsi_pp_object_group(q, jsi_object_group_text) do
|
|
653
945
|
q.pp jsi_instance
|
|
654
|
-
|
|
655
|
-
q.breakable ''
|
|
656
|
-
q.text '>'
|
|
946
|
+
end
|
|
657
947
|
end
|
|
658
948
|
|
|
659
949
|
# @private
|
|
660
950
|
# @return [Array<String>]
|
|
661
951
|
def jsi_object_group_text
|
|
662
|
-
|
|
952
|
+
jsi_schemas = self.jsi_schemas || Util::EMPTY_SET # for debug during MSN initialize, may not be set yet
|
|
953
|
+
schemas_priorities = jsi_schemas.each_with_index.map do |schema, i|
|
|
954
|
+
priority = [
|
|
955
|
+
schema.describes_schema? ? 0 : 1,
|
|
956
|
+
|
|
957
|
+
schema.jsi_schema_module_name ?
|
|
958
|
+
0
|
|
959
|
+
: schema.jsi_schema_module_name_from_ancestor ?
|
|
960
|
+
1
|
|
961
|
+
: schema.jsi_resource_uri ?
|
|
962
|
+
2
|
|
963
|
+
: schema.schema_uri ?
|
|
964
|
+
3
|
|
965
|
+
: 5,
|
|
966
|
+
|
|
967
|
+
!schema.respond_to?(:to_hash) ?
|
|
968
|
+
# boolean
|
|
969
|
+
9
|
|
970
|
+
: schema.empty? ?
|
|
971
|
+
8
|
|
972
|
+
: schema.all? { |k, _| k == '$ref' || k == '$dynamicRef' } ?
|
|
973
|
+
7
|
|
974
|
+
: 5,
|
|
975
|
+
]
|
|
976
|
+
[priority, i, schema]
|
|
977
|
+
end.sort
|
|
978
|
+
|
|
979
|
+
schema_names = []
|
|
980
|
+
schemas_priorities.each do |(priority, _idx, schema)|
|
|
981
|
+
if priority[0] == 0 || (priority == schemas_priorities.first.first && schema_names.size < 2)
|
|
982
|
+
name = schema.jsi_schema_identifier(required: priority[0] == 0)
|
|
983
|
+
schema_names << name if name
|
|
984
|
+
end
|
|
985
|
+
end
|
|
986
|
+
|
|
663
987
|
if schema_names.empty?
|
|
664
|
-
|
|
988
|
+
schemas_txt = -"*#{self.jsi_schemas ? jsi_schemas.size : '?'}"
|
|
989
|
+
elsif schema_names.size == jsi_schemas.size
|
|
990
|
+
schemas_txt = -" (#{schema_names.join(' + ')})"
|
|
665
991
|
else
|
|
666
|
-
|
|
992
|
+
schemas_txt = -" (#{schema_names.join(' + ')} + #{jsi_schemas.size - schema_names.size})"
|
|
667
993
|
end
|
|
668
994
|
|
|
669
995
|
if (is_a?(ArrayNode) || is_a?(HashNode)) && ![Array, Hash].include?(jsi_node_content.class)
|
|
@@ -677,8 +1003,9 @@ module JSI
|
|
|
677
1003
|
end
|
|
678
1004
|
|
|
679
1005
|
[
|
|
680
|
-
|
|
1006
|
+
-"JSI#{is_a?(MetaSchemaNode) ? ":MSN" : ""}#{schemas_txt}",
|
|
681
1007
|
is_a?(Schema::MetaSchema) ? "Meta-Schema" : is_a?(Schema) ? "Schema" : nil,
|
|
1008
|
+
is_a?(Schema) && !jsi_schema_dynamic_anchor_map.empty? ? jsi_schema_dynamic_anchor_map.anchor_schemas_identifier : nil,
|
|
682
1009
|
*content_txt,
|
|
683
1010
|
].compact.freeze
|
|
684
1011
|
end
|
|
@@ -693,35 +1020,50 @@ module JSI
|
|
|
693
1020
|
# Calls {Util.to_json} with the instance and any given options.
|
|
694
1021
|
# @return [String]
|
|
695
1022
|
def to_json(options = {})
|
|
696
|
-
Util.to_json(jsi_instance,
|
|
1023
|
+
Util.to_json(jsi_instance, options)
|
|
1024
|
+
end
|
|
1025
|
+
|
|
1026
|
+
# Psych/YAML .dump calls this method; dumping a JSI as YAML will dump its instance.
|
|
1027
|
+
# @param coder [Psych::Coder]
|
|
1028
|
+
def encode_with(coder)
|
|
1029
|
+
coder.represent_object(nil, jsi_node_content)
|
|
1030
|
+
nil
|
|
697
1031
|
end
|
|
698
1032
|
|
|
699
1033
|
# see {Util::Private::FingerprintHash}
|
|
700
1034
|
# @api private
|
|
701
1035
|
def jsi_fingerprint
|
|
1036
|
+
jsi_fingerprint_no_schemas.merge({
|
|
1037
|
+
jsi_schemas: jsi_schemas,
|
|
1038
|
+
}).freeze
|
|
1039
|
+
end
|
|
1040
|
+
|
|
1041
|
+
private def jsi_fingerprint_no_schemas
|
|
702
1042
|
{
|
|
703
1043
|
class: JSI::Base,
|
|
704
|
-
jsi_schemas: jsi_schemas,
|
|
705
1044
|
jsi_document: jsi_document,
|
|
706
1045
|
jsi_ptr: jsi_ptr,
|
|
707
1046
|
# for instances in documents with schemas:
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
1047
|
+
jsi_base_uri: jsi_base_uri,
|
|
1048
|
+
jsi_root_uri: jsi_conf.root_uri,
|
|
1049
|
+
# different dynamic anchor map means dynamic references may resolve to different resources so must not be equal
|
|
1050
|
+
jsi_schema_dynamic_anchor_map: jsi_schema_dynamic_anchor_map,
|
|
1051
|
+
**jsi_conf.for_fingerprint,
|
|
711
1052
|
}.freeze
|
|
712
1053
|
end
|
|
713
1054
|
|
|
1055
|
+
# @private
|
|
1056
|
+
def jsi_next_schema_dynamic_anchor_map
|
|
1057
|
+
jsi_schema_dynamic_anchor_map
|
|
1058
|
+
end
|
|
1059
|
+
|
|
714
1060
|
private
|
|
715
1061
|
|
|
716
|
-
|
|
717
|
-
@child_indicated_schemas_map = jsi_memomap(key_by: proc { |i| i[:token] }, &method(:jsi_child_indicated_schemas_compute))
|
|
718
|
-
@child_applied_schemas_map = jsi_memomap(key_by: proc { |i| i[:token] }, &method(:jsi_child_applied_schemas_compute))
|
|
719
|
-
@child_node_map = jsi_memomap(key_by: proc { |i| i[:token] }, &method(:jsi_child_node_compute))
|
|
720
|
-
end
|
|
1062
|
+
BY_TOKEN = proc { |i| i[:token] }
|
|
721
1063
|
|
|
722
|
-
def
|
|
723
|
-
|
|
724
|
-
@
|
|
1064
|
+
def jsi_memomaps_initialize
|
|
1065
|
+
@child_indicated_schemas_map = jsi_memomap(key_by: BY_TOKEN, &method(:jsi_child_indicated_schemas_compute))
|
|
1066
|
+
@child_applied_schemas_map = jsi_memomap(key_by: BY_TOKEN, &method(:jsi_child_applied_schemas_compute))
|
|
725
1067
|
end
|
|
726
1068
|
|
|
727
1069
|
def jsi_child_node_compute(token: , child_indicated_schemas: , child_applied_schemas: , includes: )
|
|
@@ -729,40 +1071,70 @@ module JSI
|
|
|
729
1071
|
includes: includes,
|
|
730
1072
|
mutable: jsi_mutable?,
|
|
731
1073
|
)
|
|
732
|
-
jsi_class.new(
|
|
1074
|
+
jsi_class.new(
|
|
1075
|
+
jsi_document: @jsi_document,
|
|
733
1076
|
jsi_ptr: @jsi_ptr[token],
|
|
734
1077
|
jsi_indicated_schemas: child_indicated_schemas,
|
|
735
|
-
|
|
1078
|
+
jsi_base_uri: jsi_next_base_uri,
|
|
736
1079
|
jsi_schema_resource_ancestors: is_a?(Schema) ? jsi_subschema_resource_ancestors : jsi_schema_resource_ancestors,
|
|
737
|
-
|
|
738
|
-
jsi_content_to_immutable: @jsi_content_to_immutable,
|
|
1080
|
+
jsi_schema_dynamic_anchor_map: jsi_child_dynamic_anchor_map(token: token, child_schemas: child_applied_schemas),
|
|
739
1081
|
jsi_root_node: @jsi_root_node,
|
|
740
|
-
)
|
|
1082
|
+
).send(:jsi_initialized)
|
|
1083
|
+
end
|
|
1084
|
+
|
|
1085
|
+
def jsi_child_dynamic_anchor_map(token: , child_schemas: )
|
|
1086
|
+
metaschema = child_schemas.detect(&:describes_schema?)
|
|
1087
|
+
# note: no memoized dialect.bootstrap_schema; this is only used once.
|
|
1088
|
+
if metaschema && MetaSchemaNode::BootstrapSchema.new(dialect: metaschema.dialect, jsi_document: jsi_document, jsi_ptr: jsi_ptr[token], jsi_base_uri: jsi_next_base_uri).jsi_is_resource_root?
|
|
1089
|
+
# if the child is a resource root, compute dynamic scope for it.
|
|
1090
|
+
# TODO if without_node removes the child, that probably means the child is equal to a descendent
|
|
1091
|
+
# of another dynamic_root. not sure if this should find/return that child - on one hand
|
|
1092
|
+
# instantiating multiple equal nodes via different dynamic scopes isn't ideal; on the other
|
|
1093
|
+
# hand having child_node return a node with a different jsi_root_node may also be problematic.
|
|
1094
|
+
jsi_next_schema_dynamic_anchor_map.without_node(self, ptr: jsi_ptr[token])
|
|
1095
|
+
else
|
|
1096
|
+
# child is not a resource, has the same dynamic scope as self.
|
|
1097
|
+
jsi_schema_dynamic_anchor_map
|
|
1098
|
+
end
|
|
741
1099
|
end
|
|
742
1100
|
|
|
743
1101
|
def jsi_child_indicated_schemas_compute(token: , content: )
|
|
744
|
-
jsi_schemas.
|
|
1102
|
+
if jsi_schemas.any?(&:application_requires_evaluated)
|
|
1103
|
+
# if application_requires_evaluated, in-place application needs to collect token evaluation
|
|
1104
|
+
# recursively to inform child application, so must be recomputed.
|
|
1105
|
+
jsi_indicated_schemas.each_yield_set do |is, y|
|
|
1106
|
+
is.each_inplace_child_applicator_schema(token, content,
|
|
1107
|
+
collect_evaluated_validate: jsi_conf.application_collect_evaluated_validate,
|
|
1108
|
+
&y
|
|
1109
|
+
)
|
|
1110
|
+
end
|
|
1111
|
+
else
|
|
1112
|
+
# if token evaluation does not need to be collected, use our already-computed #jsi_schemas.
|
|
1113
|
+
jsi_schemas.each_yield_set do |s, y|
|
|
1114
|
+
s.each_child_applicator_schema(token, content, &y)
|
|
1115
|
+
end
|
|
1116
|
+
end
|
|
745
1117
|
end
|
|
746
1118
|
|
|
747
1119
|
def jsi_child_applied_schemas_compute(token: , child_indicated_schemas: , child_content: )
|
|
748
|
-
child_indicated_schemas.
|
|
1120
|
+
child_indicated_schemas.each_yield_set do |cis, y|
|
|
1121
|
+
cis.each_inplace_applicator_schema(child_content, &y)
|
|
1122
|
+
end
|
|
749
1123
|
end
|
|
750
1124
|
|
|
751
|
-
def jsi_child_as_jsi(
|
|
1125
|
+
def jsi_child_as_jsi(child_node, as_jsi)
|
|
752
1126
|
if [true, false].include?(as_jsi)
|
|
753
1127
|
child_as_jsi = as_jsi
|
|
754
1128
|
elsif as_jsi == :auto
|
|
755
|
-
|
|
756
|
-
child_is_schema = child_schemas.any?(&:describes_schema?)
|
|
757
|
-
child_as_jsi = child_is_complex || child_is_schema
|
|
1129
|
+
child_as_jsi = child_node.jsi_as_child_default_as_jsi
|
|
758
1130
|
else
|
|
759
1131
|
raise(ArgumentError, "as_jsi must be one of: :auto, true, false")
|
|
760
1132
|
end
|
|
761
1133
|
|
|
762
1134
|
if child_as_jsi
|
|
763
|
-
|
|
1135
|
+
child_node
|
|
764
1136
|
else
|
|
765
|
-
|
|
1137
|
+
child_node.jsi_node_content
|
|
766
1138
|
end
|
|
767
1139
|
end
|
|
768
1140
|
|
|
@@ -773,5 +1145,16 @@ module JSI
|
|
|
773
1145
|
"instance: #{jsi_instance.pretty_inspect.chomp}",
|
|
774
1146
|
].join("\n"))
|
|
775
1147
|
end
|
|
1148
|
+
|
|
1149
|
+
# Note that #initialize does not invoke the jsi_conf.after_initialize callback, because
|
|
1150
|
+
# the node is not finished initializing when #initialize returns; other included modules
|
|
1151
|
+
# or subclasses with #initialize methods calling super still need to finish.
|
|
1152
|
+
# Places where Base is directly instantiated must call #jsi_initialized.
|
|
1153
|
+
# @return [self]
|
|
1154
|
+
def jsi_initialized
|
|
1155
|
+
#chkbug fail if defined?(super) # this should be the last jsi_initialized in class ancestry
|
|
1156
|
+
jsi_conf.after_initialize.call(self) if jsi_conf.after_initialize
|
|
1157
|
+
self
|
|
1158
|
+
end
|
|
776
1159
|
end
|
|
777
1160
|
end
|