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
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema::Elements
|
|
5
|
+
CONTAINS_MINMAX = element_map do
|
|
6
|
+
Schema::Element.new(keywords: ['contains', 'minContains', 'maxContains']) do |element|
|
|
7
|
+
element.add_action(:subschema) do
|
|
8
|
+
if keyword?('contains')
|
|
9
|
+
#> The value of this keyword MUST be a valid JSON Schema.
|
|
10
|
+
cxt_yield(['contains'])
|
|
11
|
+
end
|
|
12
|
+
end # element.add_action(:subschema)
|
|
13
|
+
|
|
14
|
+
element.add_action(:child_applicate) do
|
|
15
|
+
if instance.respond_to?(:to_ary)
|
|
16
|
+
if keyword?('contains')
|
|
17
|
+
contains_schema = subschema(['contains'])
|
|
18
|
+
|
|
19
|
+
child_idx_valid = Hash.new { |h, i| h[i] = contains_schema.instance_valid?(instance[i]) }
|
|
20
|
+
|
|
21
|
+
if child_idx_valid[token]
|
|
22
|
+
child_schema_applicate(contains_schema)
|
|
23
|
+
else
|
|
24
|
+
minContains = keyword_value_numeric?('minContains') ? schema_content['minContains'] : 1
|
|
25
|
+
child_valid_count = instance.each_index.inject(0) { |n, i| n < minContains && child_idx_valid[i] ? n + 1 : n }
|
|
26
|
+
|
|
27
|
+
if child_valid_count < minContains
|
|
28
|
+
# invalid application: if contains_schema does not validate against `minContains` children,
|
|
29
|
+
# it applies to every child.
|
|
30
|
+
# note: this does not consider maxContains; a validation error from maxContains is not from any
|
|
31
|
+
# validation error of child application (which invalid application is intended to preserve), but
|
|
32
|
+
# rather from too few failures in child application. children that don't validate against contains
|
|
33
|
+
# are correctly not described by contains when contains validation failure comes from maxContains.
|
|
34
|
+
child_schema_applicate(contains_schema)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end # if instance.respond_to?(:to_ary)
|
|
39
|
+
end # element.add_action(:child_applicate)
|
|
40
|
+
|
|
41
|
+
element.add_action(:validate) do
|
|
42
|
+
if keyword?('contains')
|
|
43
|
+
# https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-01#name-contains
|
|
44
|
+
#> An array instance is valid against "contains" if at least one of its elements is valid against
|
|
45
|
+
#> the given schema, except when "minContains" is present and has a value of 0,
|
|
46
|
+
#> in which case an array instance MUST be considered valid against the "contains" keyword,
|
|
47
|
+
#> even if none of its elements is valid against the given schema.
|
|
48
|
+
if instance.respond_to?(:to_ary)
|
|
49
|
+
results = {}
|
|
50
|
+
instance.each_index do |i|
|
|
51
|
+
results[i] = child_subschema_validate(i, ['contains'])
|
|
52
|
+
end
|
|
53
|
+
child_valid_count = instance.each_index.count { |i| results[i].valid? }
|
|
54
|
+
|
|
55
|
+
minContains = keyword_value_numeric?('minContains') ? schema_content['minContains'] : nil
|
|
56
|
+
if minContains
|
|
57
|
+
# https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#name-mincontains
|
|
58
|
+
child_results_validate(
|
|
59
|
+
child_valid_count >= minContains,
|
|
60
|
+
'validation.keyword.contains.fewer_than_minContains',
|
|
61
|
+
"instance array contains fewer items that are valid against `contains` schema than `minContains` value",
|
|
62
|
+
keyword: 'contains',
|
|
63
|
+
child_results: results,
|
|
64
|
+
)
|
|
65
|
+
else
|
|
66
|
+
child_results_validate(
|
|
67
|
+
child_valid_count > 0,
|
|
68
|
+
'validation.keyword.contains.none',
|
|
69
|
+
"instance array does not contain any items that are valid against `contains` schema",
|
|
70
|
+
keyword: 'contains',
|
|
71
|
+
child_results: results,
|
|
72
|
+
)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
maxContains = keyword_value_numeric?('maxContains') ? schema_content['maxContains'] : nil
|
|
76
|
+
if maxContains
|
|
77
|
+
# https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#name-maxcontains
|
|
78
|
+
validate(
|
|
79
|
+
child_valid_count <= maxContains,
|
|
80
|
+
'validation.keyword.maxContains.more_than_maxContains',
|
|
81
|
+
"instance array contains more items that are valid against `contains` schema than `maxContains` value",
|
|
82
|
+
keyword: 'maxContains',
|
|
83
|
+
)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end # element.add_action(:validate)
|
|
88
|
+
end # Schema::Element.new
|
|
89
|
+
end # CONTAINS_MINMAX = element_map
|
|
90
|
+
end # module Schema::Elements
|
|
91
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema::Elements
|
|
5
|
+
CONTENT_ENCODING = element_map do
|
|
6
|
+
Schema::Element.new(keyword: 'contentEncoding') do |element|
|
|
7
|
+
end # Schema::Element.new
|
|
8
|
+
end # CONTENT_ENCODING = element_map
|
|
9
|
+
end # module Schema::Elements
|
|
10
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema::Elements
|
|
5
|
+
CONTENT_MEDIA_TYPE = element_map do
|
|
6
|
+
Schema::Element.new(keyword: 'contentMediaType') do |element|
|
|
7
|
+
end # Schema::Element.new
|
|
8
|
+
end # CONTENT_MEDIA_TYPE = element_map
|
|
9
|
+
end # module Schema::Elements
|
|
10
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema::Elements
|
|
5
|
+
CONTENT_SCHEMA = element_map do
|
|
6
|
+
Schema::Element.new(keyword: 'contentSchema') do |element|
|
|
7
|
+
element.add_action(:subschema) do
|
|
8
|
+
if keyword?('contentSchema')
|
|
9
|
+
#> The value of this property MUST be a valid JSON schema.
|
|
10
|
+
cxt_yield(['contentSchema'])
|
|
11
|
+
end
|
|
12
|
+
end # element.add_action(:subschema)
|
|
13
|
+
end # Schema::Element.new
|
|
14
|
+
end # CONTENT_SCHEMA = element_map
|
|
15
|
+
end # module Schema::Elements
|
|
16
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema::Elements
|
|
5
|
+
DEFAULT = element_map do
|
|
6
|
+
Schema::Element.new(keyword: 'default') do |element|
|
|
7
|
+
element.add_action(:default) { cxt_yield(schema_content['default']) if keyword?('default') }
|
|
8
|
+
end # Schema::Element.new
|
|
9
|
+
end # DEFAULT = element_map
|
|
10
|
+
end # module Schema::Elements
|
|
11
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema::Elements
|
|
5
|
+
DEFINITIONS = element_map do |keyword: |
|
|
6
|
+
Schema::Element.new(keyword: keyword) do |element|
|
|
7
|
+
element.add_action(:subschema) do
|
|
8
|
+
#> This keyword's value MUST be an object.
|
|
9
|
+
if keyword_value_hash?(keyword)
|
|
10
|
+
schema_content[keyword].each_key do |property_name|
|
|
11
|
+
#> Each member value of this object MUST be a valid JSON Schema.
|
|
12
|
+
cxt_yield([keyword, property_name])
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end # element.add_action(:subschema)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema::Elements
|
|
5
|
+
DEPENDENCIES = element_map do
|
|
6
|
+
Schema::Element.new(keyword: 'dependencies') do |element|
|
|
7
|
+
element.add_action(:subschema) do
|
|
8
|
+
#> This keyword's value MUST be an object.
|
|
9
|
+
if keyword_value_hash?('dependencies')
|
|
10
|
+
schema_content['dependencies'].each_pair do |property_name, dependency|
|
|
11
|
+
#> Each property specifies a dependency.
|
|
12
|
+
#> Each dependency value MUST be an array or a valid JSON Schema.
|
|
13
|
+
if !dependency.respond_to?(:to_ary)
|
|
14
|
+
cxt_yield(['dependencies', property_name])
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end # element.add_action(:subschema)
|
|
19
|
+
|
|
20
|
+
element.add_action(:described_object_property_names) do
|
|
21
|
+
next if !keyword_value_hash?('dependencies')
|
|
22
|
+
schema_content['dependencies'].each do |property_name, dependency|
|
|
23
|
+
cxt_yield(property_name)
|
|
24
|
+
if dependency.respond_to?(:to_ary)
|
|
25
|
+
dependency.each(&block)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
element.add_action(:inplace_application_requires_instance) do
|
|
31
|
+
next if !keyword_value_hash?('dependencies')
|
|
32
|
+
next if schema_content['dependencies'].each_value.all? { |dependency| dependency.respond_to?(:to_ary) }
|
|
33
|
+
cxt_yield(true)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
element.add_action(:inplace_applicate) do
|
|
37
|
+
next if !keyword_value_hash?('dependencies')
|
|
38
|
+
next if schema_content['dependencies'].each_value.all? { |dependency| dependency.respond_to?(:to_ary) }
|
|
39
|
+
next if !instance.respond_to?(:to_hash)
|
|
40
|
+
#> This keyword's value MUST be an object. Each property specifies a dependency. Each dependency
|
|
41
|
+
#> value MUST be an array or a valid JSON Schema.
|
|
42
|
+
schema_content['dependencies'].each_pair do |property_name, dependency|
|
|
43
|
+
if dependency.respond_to?(:to_ary)
|
|
44
|
+
# noop: array-form dependencies has no in-place applicator schema
|
|
45
|
+
else
|
|
46
|
+
# If the dependency value is a subschema, and the dependency key is a
|
|
47
|
+
# property in the instance, the entire instance must validate against
|
|
48
|
+
# the dependency value.
|
|
49
|
+
if instance.key?(property_name)
|
|
50
|
+
inplace_subschema_applicate(['dependencies', property_name])
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end # element.add_action(:inplace_applicate)
|
|
55
|
+
|
|
56
|
+
element.add_action(:validate) do
|
|
57
|
+
#> This keyword's value MUST be an object. Each property specifies a dependency. Each dependency
|
|
58
|
+
#> value MUST be an array or a valid JSON Schema.
|
|
59
|
+
next if !keyword_value_hash?('dependencies')
|
|
60
|
+
next if !instance.respond_to?(:to_hash)
|
|
61
|
+
schema_content['dependencies'].each_pair do |property_name, dependency|
|
|
62
|
+
if dependency.respond_to?(:to_ary)
|
|
63
|
+
# If the dependency value is an array, each element in the array, if
|
|
64
|
+
# any, MUST be a string, and MUST be unique. If the dependency key is
|
|
65
|
+
# a property in the instance, each of the items in the dependency value
|
|
66
|
+
# must be a property that exists in the instance.
|
|
67
|
+
if instance.respond_to?(:to_hash) && instance.key?(property_name)
|
|
68
|
+
missing_required = dependency.reject { |name| instance.key?(name) }.freeze
|
|
69
|
+
validate(
|
|
70
|
+
missing_required.empty?,
|
|
71
|
+
'validation.keyword.dependencies.dependent_required.missing_property_names',
|
|
72
|
+
'instance object does not contain all dependent required property names specified by `dependencies` value',
|
|
73
|
+
keyword: 'dependencies',
|
|
74
|
+
property_name: property_name,
|
|
75
|
+
missing_dependent_required_property_names: missing_required,
|
|
76
|
+
)
|
|
77
|
+
end
|
|
78
|
+
else
|
|
79
|
+
# If the dependency value is a subschema, and the dependency key is a
|
|
80
|
+
# property in the instance, the entire instance must validate against
|
|
81
|
+
# the dependency value.
|
|
82
|
+
if instance.key?(property_name)
|
|
83
|
+
dependency_result = inplace_subschema_validate(['dependencies', property_name])
|
|
84
|
+
inplace_results_validate(
|
|
85
|
+
dependency_result.valid?,
|
|
86
|
+
'validation.keyword.dependencies.dependent_schema.invalid',
|
|
87
|
+
'instance object is not valid against the schema corresponding to a matched property name specified by `dependencies` value',
|
|
88
|
+
keyword: 'dependencies',
|
|
89
|
+
results: [dependency_result],
|
|
90
|
+
property_name: property_name,
|
|
91
|
+
)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end # element.add_action(:validate)
|
|
96
|
+
end # Schema::Element.new
|
|
97
|
+
end # DEPENDENCIES = element_map
|
|
98
|
+
end # module Schema::Elements
|
|
99
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema::Elements
|
|
5
|
+
DEPENDENT_REQUIRED = element_map do
|
|
6
|
+
Schema::Element.new(keyword: 'dependentRequired') do |element|
|
|
7
|
+
element.add_action(:described_object_property_names) do
|
|
8
|
+
next if !keyword_value_hash?('dependentRequired')
|
|
9
|
+
schema_content['dependentRequired'].each do |property_name, dependent_property_names|
|
|
10
|
+
cxt_yield(property_name)
|
|
11
|
+
next if !dependent_property_names.respond_to?(:to_ary)
|
|
12
|
+
dependent_property_names.each(&block)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
element.add_action(:validate) do
|
|
17
|
+
#> The value of this keyword MUST be an object.
|
|
18
|
+
next if !keyword_value_hash?('dependentRequired')
|
|
19
|
+
next if !instance.respond_to?(:to_hash)
|
|
20
|
+
|
|
21
|
+
#> This keyword specifies properties that are required if a specific other property is
|
|
22
|
+
#> present. Their requirement is dependent on the presence of the other property.
|
|
23
|
+
#
|
|
24
|
+
#> Validation succeeds if, for each name that appears in both the instance and as a name
|
|
25
|
+
#> within this keyword's value, every item in the corresponding array is also the name of
|
|
26
|
+
#> a property in the instance.
|
|
27
|
+
missing_dependent_required = {}
|
|
28
|
+
schema_content['dependentRequired'].each do |property_name, dependent_property_names|
|
|
29
|
+
#> Properties in this object, if any, MUST be arrays.
|
|
30
|
+
next if !dependent_property_names.respond_to?(:to_ary)
|
|
31
|
+
if instance.key?(property_name)
|
|
32
|
+
missing_required = dependent_property_names.reject { |name| instance.key?(name) }
|
|
33
|
+
unless missing_required.empty?
|
|
34
|
+
missing_dependent_required[property_name] = missing_required
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
validate(
|
|
39
|
+
missing_dependent_required.empty?,
|
|
40
|
+
'validation.keyword.dependentRequired.missing_property_names',
|
|
41
|
+
"instance object does not contain all dependent required property names specified by `dependentRequired`",
|
|
42
|
+
keyword: 'dependentRequired',
|
|
43
|
+
missing_dependent_required: missing_dependent_required,
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema::Elements
|
|
5
|
+
DEPENDENT_SCHEMAS = element_map do
|
|
6
|
+
Schema::Element.new(keyword: 'dependentSchemas') do |element|
|
|
7
|
+
element.add_action(:subschema) do
|
|
8
|
+
#> This keyword's value MUST be an object.
|
|
9
|
+
next if !keyword_value_hash?('dependentSchemas')
|
|
10
|
+
|
|
11
|
+
#> Each value in the object MUST be a valid JSON Schema.
|
|
12
|
+
schema_content['dependentSchemas'].each_key do |property_name|
|
|
13
|
+
cxt_yield(['dependentSchemas', property_name])
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
element.add_action(:described_object_property_names) do
|
|
18
|
+
next if !keyword_value_hash?('dependentSchemas')
|
|
19
|
+
schema_content['dependentSchemas'].each_key(&block)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
element.add_action(:inplace_application_requires_instance) { cxt_yield(true) if keyword?('dependentSchemas') }
|
|
23
|
+
|
|
24
|
+
element.add_action(:inplace_applicate) do
|
|
25
|
+
#> This keyword's value MUST be an object.
|
|
26
|
+
next if !keyword_value_hash?('dependentSchemas')
|
|
27
|
+
next if !instance.respond_to?(:to_hash)
|
|
28
|
+
|
|
29
|
+
#> This keyword specifies subschemas that are evaluated if the
|
|
30
|
+
#> instance is an object and contains a certain property.
|
|
31
|
+
#
|
|
32
|
+
#> If the object key is a property in the instance, the entire instance must validate
|
|
33
|
+
#> against the subschema. Its use is dependent on the presence of the property.
|
|
34
|
+
schema_content['dependentSchemas'].each_key do |property_name|
|
|
35
|
+
if instance.key?(property_name)
|
|
36
|
+
inplace_subschema_applicate(['dependentSchemas', property_name])
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
element.add_action(:validate) do
|
|
42
|
+
#> This keyword's value MUST be an object.
|
|
43
|
+
next if !keyword_value_hash?('dependentSchemas')
|
|
44
|
+
next if !instance.respond_to?(:to_hash)
|
|
45
|
+
|
|
46
|
+
#> This keyword specifies subschemas that are evaluated if the
|
|
47
|
+
#> instance is an object and contains a certain property.
|
|
48
|
+
#
|
|
49
|
+
#> If the object key is a property in the instance, the entire instance must validate
|
|
50
|
+
#> against the subschema. Its use is dependent on the presence of the property.
|
|
51
|
+
results = {}
|
|
52
|
+
schema_content['dependentSchemas'].each_key do |property_name|
|
|
53
|
+
if instance.key?(property_name)
|
|
54
|
+
results[property_name] = inplace_subschema_validate(['dependentSchemas', property_name])
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
inplace_results_validate(
|
|
58
|
+
results.each_value.all?(&:valid?),
|
|
59
|
+
'validation.keyword.dependentSchemas.invalid',
|
|
60
|
+
"instance object is not valid against all schemas corresponding to matched property names specified by `dependentSchemas`",
|
|
61
|
+
keyword: 'dependentSchemas',
|
|
62
|
+
results: results.each_value,
|
|
63
|
+
dependentSchemas_properties_valid: results.inject({}) { |h, (k, r)| h.update({k => r.valid?}) }.freeze,
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema::Elements
|
|
5
|
+
DYNAMIC_REF = element_map do
|
|
6
|
+
Schema::Element.new(keyword: '$dynamicRef') do |element|
|
|
7
|
+
resolve_dynamicRef = proc do
|
|
8
|
+
next unless keyword_value_str?('$dynamicRef')
|
|
9
|
+
|
|
10
|
+
dynamic_anchor_map = schema.jsi_next_schema_dynamic_anchor_map
|
|
11
|
+
|
|
12
|
+
#> Resolved against the current URI base, it produces the URI used as the starting point for runtime resolution.
|
|
13
|
+
#> This initial resolution is safe to perform on schema load.
|
|
14
|
+
ref = schema.schema_ref(schema_content['$dynamicRef'])
|
|
15
|
+
|
|
16
|
+
initial_resolution = ref.resolve
|
|
17
|
+
|
|
18
|
+
#> If the initially resolved starting point URI includes a
|
|
19
|
+
#> fragment that was created by the "$dynamicAnchor" keyword,
|
|
20
|
+
resolve_dynamically = \
|
|
21
|
+
# did resolution resolve a fragment?
|
|
22
|
+
ref.ref_uri.fragment &&
|
|
23
|
+
# does the fragment correspond to a dynamicAnchor? (not a regular anchor, not a pointer)
|
|
24
|
+
initial_resolution.dialect_invoke_each(:dynamicAnchor).include?(ref.ref_uri.fragment) &&
|
|
25
|
+
# is the anchor in our dynamic_anchor_map?
|
|
26
|
+
dynamic_anchor_map.key?(ref.ref_uri.fragment)
|
|
27
|
+
if resolve_dynamically
|
|
28
|
+
#> the initial URI MUST be replaced by the URI (including the fragment)
|
|
29
|
+
#> for the outermost schema resource in the dynamic scope (Section 7.1)
|
|
30
|
+
#> that defines an identically named fragment with "$dynamicAnchor".
|
|
31
|
+
|
|
32
|
+
# our dynamic resolution doesn't use a stack of dynamic scope URIs.
|
|
33
|
+
# we replace the initially resolved resource with the resource from dynamic_anchor_map.
|
|
34
|
+
|
|
35
|
+
scope_schema, subptrs = dynamic_anchor_map[ref.ref_uri.fragment]
|
|
36
|
+
resolved_schema = subptrs.inject(scope_schema, &:subschema)
|
|
37
|
+
else
|
|
38
|
+
#> Otherwise, its behavior is identical to "$ref", and no runtime resolution is needed.
|
|
39
|
+
resolved_schema = initial_resolution
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
[resolved_schema.with_dynamic_scope_from(schema), ref]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
element.add_action(:inplace_applicate) do
|
|
46
|
+
resolved_schema, ref = *instance_exec(&resolve_dynamicRef) || next
|
|
47
|
+
inplace_schema_applicate(resolved_schema, ref: ref)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
element.add_action(:validate) do
|
|
51
|
+
resolved_schema, ref = *instance_exec(&resolve_dynamicRef) || next
|
|
52
|
+
ref_result = resolved_schema.internal_validate_instance(
|
|
53
|
+
instance_ptr,
|
|
54
|
+
instance_document,
|
|
55
|
+
validate_only: validate_only,
|
|
56
|
+
visited_refs: Util.add_visited_ref(visited_refs, ref),
|
|
57
|
+
)
|
|
58
|
+
inplace_results_validate(
|
|
59
|
+
ref_result.valid?,
|
|
60
|
+
'validation.keyword.$dynamicRef.invalid',
|
|
61
|
+
"instance is not valid against the schema referenced by `$dynamicRef`",
|
|
62
|
+
keyword: '$dynamicRef',
|
|
63
|
+
results: [ref_result],
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema::Elements
|
|
5
|
+
ENUM = element_map do
|
|
6
|
+
Schema::Element.new(keyword: 'enum') do |element|
|
|
7
|
+
element.add_action(:validate) do
|
|
8
|
+
if keyword?('enum')
|
|
9
|
+
value = schema_content['enum']
|
|
10
|
+
#> The value of this keyword MUST be an array.
|
|
11
|
+
if value.respond_to?(:to_ary)
|
|
12
|
+
# An instance validates successfully against this keyword if its value is equal to one of the
|
|
13
|
+
# elements in this keyword's array value.
|
|
14
|
+
validate(
|
|
15
|
+
value.include?(instance),
|
|
16
|
+
'validation.keyword.enum.none_equal',
|
|
17
|
+
"instance is not equal to any `enum` item",
|
|
18
|
+
keyword: 'enum',
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end # element.add_action(:validate)
|
|
23
|
+
end # Schema::Element.new
|
|
24
|
+
end # ENUM = element_map
|
|
25
|
+
end # module Schema::Elements
|
|
26
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema::Elements
|
|
5
|
+
ID = element_map do |keyword: , fragment_is_anchor: |
|
|
6
|
+
Schema::Element.new(keyword: keyword) do |element|
|
|
7
|
+
element.add_action(:id) { cxt_yield(schema_content[keyword]) if keyword_value_str?(keyword) }
|
|
8
|
+
|
|
9
|
+
element.add_action(:id_without_fragment) do
|
|
10
|
+
next if !keyword_value_str?(keyword)
|
|
11
|
+
id_without_fragment = Util.uri(schema_content[keyword]).merge(fragment: nil)
|
|
12
|
+
|
|
13
|
+
if !id_without_fragment.empty?
|
|
14
|
+
cxt_yield(id_without_fragment)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
if fragment_is_anchor
|
|
19
|
+
element.add_action(:anchor) do
|
|
20
|
+
next if !keyword_value_str?(keyword)
|
|
21
|
+
id_fragment = Util.uri(schema_content[keyword]).fragment
|
|
22
|
+
if id_fragment && !id_fragment.empty?
|
|
23
|
+
cxt_yield(id_fragment)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema::Elements
|
|
5
|
+
IF_THEN_ELSE = element_map do
|
|
6
|
+
Schema::Element.new(keywords: %w(if then else)) do |element|
|
|
7
|
+
element.add_action(:subschema) do
|
|
8
|
+
if keyword?('if')
|
|
9
|
+
#> This keyword's value MUST be a valid JSON Schema.
|
|
10
|
+
cxt_yield(['if'])
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
if keyword?('then')
|
|
14
|
+
#> This keyword's value MUST be a valid JSON Schema.
|
|
15
|
+
cxt_yield(['then'])
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
if keyword?('else')
|
|
19
|
+
#> This keyword's value MUST be a valid JSON Schema.
|
|
20
|
+
cxt_yield(['else'])
|
|
21
|
+
end
|
|
22
|
+
end # element.add_action(:subschema)
|
|
23
|
+
|
|
24
|
+
element.add_action(:inplace_application_requires_instance) { cxt_yield(true) if keyword?('if') }
|
|
25
|
+
|
|
26
|
+
element.add_action(:inplace_applicate) do
|
|
27
|
+
if keyword?('if')
|
|
28
|
+
if subschema(['if']).instance_valid?(instance)
|
|
29
|
+
if collect_evaluated
|
|
30
|
+
inplace_subschema_applicate(['if'], applicate: false)
|
|
31
|
+
end
|
|
32
|
+
if keyword?('then')
|
|
33
|
+
inplace_subschema_applicate(['then'])
|
|
34
|
+
end
|
|
35
|
+
else
|
|
36
|
+
if keyword?('else')
|
|
37
|
+
inplace_subschema_applicate(['else'])
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end # element.add_action(:inplace_applicate)
|
|
42
|
+
|
|
43
|
+
element.add_action(:validate) do
|
|
44
|
+
if keyword?('if')
|
|
45
|
+
# This keyword's value MUST be a valid JSON Schema.
|
|
46
|
+
# This validation outcome of this keyword's subschema has no direct effect on the overall validation
|
|
47
|
+
# result. Rather, it controls which of the "then" or "else" keywords are evaluated.
|
|
48
|
+
if_result = inplace_subschema_validate(['if'])
|
|
49
|
+
|
|
50
|
+
if if_result.valid?
|
|
51
|
+
result.evaluated_tokens.merge(if_result.evaluated_tokens)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
if if_result.valid?
|
|
55
|
+
if keyword?('then')
|
|
56
|
+
then_result = inplace_subschema_validate(['then'])
|
|
57
|
+
inplace_results_validate(
|
|
58
|
+
then_result.valid?,
|
|
59
|
+
'validation.keyword.then.invalid',
|
|
60
|
+
"instance is not valid against `then` schema after validating against `if` schema",
|
|
61
|
+
keyword: 'if',
|
|
62
|
+
results: [then_result],
|
|
63
|
+
)
|
|
64
|
+
end
|
|
65
|
+
else
|
|
66
|
+
if keyword?('else')
|
|
67
|
+
else_result = inplace_subschema_validate(['else'])
|
|
68
|
+
inplace_results_validate(
|
|
69
|
+
else_result.valid?,
|
|
70
|
+
'validation.keyword.else.invalid',
|
|
71
|
+
"instance is not valid against `else` schema after not validating against `if` schema",
|
|
72
|
+
keyword: 'if',
|
|
73
|
+
results: [else_result],
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end # element.add_action(:validate)
|
|
79
|
+
end # Schema::Element.new
|
|
80
|
+
end # IF_THEN_ELSE = element_map
|
|
81
|
+
end # module Schema::Elements
|
|
82
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema::Elements
|
|
5
|
+
INFO_BOOL = element_map do |keyword: |
|
|
6
|
+
Schema::Element.new(keyword: keyword) do |element|
|
|
7
|
+
end # Schema::Element.new
|
|
8
|
+
end # INFO_BOOL = element_map
|
|
9
|
+
end # module Schema::Elements
|
|
10
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema::Elements
|
|
5
|
+
INFO_STRING = element_map do |keyword: |
|
|
6
|
+
Schema::Element.new(keyword: keyword) do |element|
|
|
7
|
+
end # Schema::Element.new
|
|
8
|
+
end # INFO_STRING = element_map
|
|
9
|
+
end # module Schema::Elements
|
|
10
|
+
end
|