json_schemer 0.2.18 → 2.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +7 -7
- data/CHANGELOG.md +102 -0
- data/Gemfile.lock +30 -10
- data/README.md +402 -6
- data/bin/hostname_character_classes +42 -0
- data/bin/rake +29 -0
- data/exe/json_schemer +62 -0
- data/json_schemer.gemspec +9 -12
- data/lib/json_schemer/cached_resolver.rb +16 -0
- data/lib/json_schemer/configuration.rb +31 -0
- data/lib/json_schemer/content.rb +18 -0
- data/lib/json_schemer/draft201909/meta.rb +320 -0
- data/lib/json_schemer/draft201909/vocab/applicator.rb +104 -0
- data/lib/json_schemer/draft201909/vocab/core.rb +45 -0
- data/lib/json_schemer/draft201909/vocab.rb +31 -0
- data/lib/json_schemer/draft202012/meta.rb +364 -0
- data/lib/json_schemer/draft202012/vocab/applicator.rb +382 -0
- data/lib/json_schemer/draft202012/vocab/content.rb +52 -0
- data/lib/json_schemer/draft202012/vocab/core.rb +160 -0
- data/lib/json_schemer/draft202012/vocab/format_annotation.rb +23 -0
- data/lib/json_schemer/draft202012/vocab/format_assertion.rb +23 -0
- data/lib/json_schemer/draft202012/vocab/meta_data.rb +30 -0
- data/lib/json_schemer/draft202012/vocab/unevaluated.rb +104 -0
- data/lib/json_schemer/draft202012/vocab/validation.rb +286 -0
- data/lib/json_schemer/draft202012/vocab.rb +105 -0
- data/lib/json_schemer/draft4/meta.rb +161 -0
- data/lib/json_schemer/draft4/vocab/validation.rb +39 -0
- data/lib/json_schemer/draft4/vocab.rb +18 -0
- data/lib/json_schemer/draft6/meta.rb +172 -0
- data/lib/json_schemer/draft6/vocab.rb +16 -0
- data/lib/json_schemer/draft7/meta.rb +183 -0
- data/lib/json_schemer/draft7/vocab/validation.rb +69 -0
- data/lib/json_schemer/draft7/vocab.rb +30 -0
- data/lib/json_schemer/ecma_regexp.rb +51 -0
- data/lib/json_schemer/errors.rb +1 -0
- data/lib/json_schemer/format/duration.rb +23 -0
- data/lib/json_schemer/format/email.rb +56 -0
- data/lib/json_schemer/format/hostname.rb +58 -0
- data/lib/json_schemer/format/json_pointer.rb +18 -0
- data/lib/json_schemer/format/uri_template.rb +34 -0
- data/lib/json_schemer/format.rb +128 -109
- data/lib/json_schemer/keyword.rb +56 -0
- data/lib/json_schemer/location.rb +25 -0
- data/lib/json_schemer/openapi.rb +38 -0
- data/lib/json_schemer/openapi30/document.rb +1672 -0
- data/lib/json_schemer/openapi30/meta.rb +32 -0
- data/lib/json_schemer/openapi30/vocab/base.rb +18 -0
- data/lib/json_schemer/openapi30/vocab.rb +12 -0
- data/lib/json_schemer/openapi31/document.rb +1557 -0
- data/lib/json_schemer/openapi31/meta.rb +136 -0
- data/lib/json_schemer/openapi31/vocab/base.rb +127 -0
- data/lib/json_schemer/openapi31/vocab.rb +18 -0
- data/lib/json_schemer/output.rb +56 -0
- data/lib/json_schemer/result.rb +242 -0
- data/lib/json_schemer/schema.rb +424 -0
- data/lib/json_schemer/version.rb +1 -1
- data/lib/json_schemer.rb +243 -30
- metadata +141 -25
- data/lib/json_schemer/cached_ref_resolver.rb +0 -14
- data/lib/json_schemer/schema/base.rb +0 -658
- data/lib/json_schemer/schema/draft4.rb +0 -44
- data/lib/json_schemer/schema/draft6.rb +0 -25
- data/lib/json_schemer/schema/draft7.rb +0 -32
@@ -0,0 +1,136 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module JSONSchemer
|
3
|
+
module OpenAPI31
|
4
|
+
BASE_URI = URI('https://spec.openapis.org/oas/3.1/dialect/base')
|
5
|
+
# https://spec.openapis.org/oas/v3.1.0#data-types
|
6
|
+
FORMATS = {
|
7
|
+
'int32' => proc { |instance, _format| instance.is_a?(Integer) && instance.bit_length <= 32 },
|
8
|
+
'int64' => proc { |instance, _format| instance.is_a?(Integer) && instance.bit_length <= 64 },
|
9
|
+
'float' => proc { |instance, _format| instance.is_a?(Float) },
|
10
|
+
'double' => proc { |instance, _format| instance.is_a?(Float) },
|
11
|
+
'password' => proc { |_instance, _format| true }
|
12
|
+
}
|
13
|
+
SCHEMA = {
|
14
|
+
'$id' => 'https://spec.openapis.org/oas/3.1/dialect/base',
|
15
|
+
'$schema' => 'https://json-schema.org/draft/2020-12/schema',
|
16
|
+
|
17
|
+
'title' => 'OpenAPI 3.1 Schema Object Dialect',
|
18
|
+
'description' => 'A JSON Schema dialect describing schemas found in OpenAPI documents',
|
19
|
+
|
20
|
+
'$vocabulary' => {
|
21
|
+
'https://json-schema.org/draft/2020-12/vocab/core' => true,
|
22
|
+
'https://json-schema.org/draft/2020-12/vocab/applicator' => true,
|
23
|
+
'https://json-schema.org/draft/2020-12/vocab/unevaluated' => true,
|
24
|
+
'https://json-schema.org/draft/2020-12/vocab/validation' => true,
|
25
|
+
'https://json-schema.org/draft/2020-12/vocab/meta-data' => true,
|
26
|
+
'https://json-schema.org/draft/2020-12/vocab/format-annotation' => true,
|
27
|
+
'https://json-schema.org/draft/2020-12/vocab/content' => true,
|
28
|
+
'https://spec.openapis.org/oas/3.1/vocab/base' => false
|
29
|
+
},
|
30
|
+
|
31
|
+
'$dynamicAnchor' => 'meta',
|
32
|
+
|
33
|
+
'allOf' => [
|
34
|
+
{ '$ref' => 'https://json-schema.org/draft/2020-12/schema' },
|
35
|
+
{ '$ref' => 'https://spec.openapis.org/oas/3.1/meta/base' }
|
36
|
+
]
|
37
|
+
}
|
38
|
+
|
39
|
+
|
40
|
+
module Meta
|
41
|
+
BASE = {
|
42
|
+
'$id' => 'https://spec.openapis.org/oas/3.1/meta/base',
|
43
|
+
'$schema' => 'https://json-schema.org/draft/2020-12/schema',
|
44
|
+
|
45
|
+
'title' => 'OAS Base vocabulary',
|
46
|
+
'description' => 'A JSON Schema Vocabulary used in the OpenAPI Schema Dialect',
|
47
|
+
|
48
|
+
'$vocabulary' => {
|
49
|
+
'https://spec.openapis.org/oas/3.1/vocab/base' => true
|
50
|
+
},
|
51
|
+
|
52
|
+
'$dynamicAnchor' => 'meta',
|
53
|
+
|
54
|
+
'type' => ['object', 'boolean'],
|
55
|
+
'properties' => {
|
56
|
+
'example' => true,
|
57
|
+
'discriminator' => { '$ref' => '#/$defs/discriminator' },
|
58
|
+
'externalDocs' => { '$ref' => '#/$defs/external-docs' },
|
59
|
+
'xml' => { '$ref' => '#/$defs/xml' }
|
60
|
+
},
|
61
|
+
|
62
|
+
'$defs' => {
|
63
|
+
'extensible' => {
|
64
|
+
'patternProperties' => {
|
65
|
+
'^x-' => true
|
66
|
+
}
|
67
|
+
},
|
68
|
+
|
69
|
+
'discriminator' => {
|
70
|
+
'$ref' => '#/$defs/extensible',
|
71
|
+
'type' => 'object',
|
72
|
+
'properties' => {
|
73
|
+
'propertyName' => {
|
74
|
+
'type' => 'string'
|
75
|
+
},
|
76
|
+
'mapping' => {
|
77
|
+
'type' => 'object',
|
78
|
+
'additionalProperties' => {
|
79
|
+
'type' => 'string'
|
80
|
+
}
|
81
|
+
}
|
82
|
+
},
|
83
|
+
'required' => ['propertyName'],
|
84
|
+
'unevaluatedProperties' => false
|
85
|
+
},
|
86
|
+
|
87
|
+
'external-docs' => {
|
88
|
+
'$ref' => '#/$defs/extensible',
|
89
|
+
'type' => 'object',
|
90
|
+
'properties' => {
|
91
|
+
'url' => {
|
92
|
+
'type' => 'string',
|
93
|
+
'format' => 'uri-reference'
|
94
|
+
},
|
95
|
+
'description' => {
|
96
|
+
'type' => 'string'
|
97
|
+
}
|
98
|
+
},
|
99
|
+
'required' => ['url'],
|
100
|
+
'unevaluatedProperties' => false
|
101
|
+
},
|
102
|
+
|
103
|
+
'xml' => {
|
104
|
+
'$ref' => '#/$defs/extensible',
|
105
|
+
'type' => 'object',
|
106
|
+
'properties' => {
|
107
|
+
'name' => {
|
108
|
+
'type' => 'string'
|
109
|
+
},
|
110
|
+
'namespace' => {
|
111
|
+
'type' => 'string',
|
112
|
+
'format' => 'uri'
|
113
|
+
},
|
114
|
+
'prefix' => {
|
115
|
+
'type' => 'string'
|
116
|
+
},
|
117
|
+
'attribute' => {
|
118
|
+
'type' => 'boolean'
|
119
|
+
},
|
120
|
+
'wrapped' => {
|
121
|
+
'type' => 'boolean'
|
122
|
+
}
|
123
|
+
},
|
124
|
+
'unevaluatedProperties' => false
|
125
|
+
}
|
126
|
+
}
|
127
|
+
}
|
128
|
+
|
129
|
+
|
130
|
+
SCHEMAS = Draft202012::Meta::SCHEMAS.merge(
|
131
|
+
Draft202012::BASE_URI => Draft202012::SCHEMA,
|
132
|
+
URI('https://spec.openapis.org/oas/3.1/meta/base') => BASE
|
133
|
+
)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module JSONSchemer
|
3
|
+
module OpenAPI31
|
4
|
+
module Vocab
|
5
|
+
module Base
|
6
|
+
class AllOf < Draft202012::Vocab::Applicator::AllOf
|
7
|
+
attr_accessor :skip_ref_once
|
8
|
+
|
9
|
+
def validate(instance, instance_location, keyword_location, context)
|
10
|
+
nested = []
|
11
|
+
parsed.each_with_index do |subschema, index|
|
12
|
+
if ref_schema = subschema.parsed['$ref']&.ref_schema
|
13
|
+
next if skip_ref_once == ref_schema.absolute_keyword_location
|
14
|
+
ref_schema.parsed['discriminator']&.skip_ref_once = schema.absolute_keyword_location
|
15
|
+
end
|
16
|
+
nested << subschema.validate_instance(instance, instance_location, join_location(keyword_location, index.to_s), context)
|
17
|
+
end
|
18
|
+
result(instance, instance_location, keyword_location, nested.all?(&:valid), nested)
|
19
|
+
ensure
|
20
|
+
self.skip_ref_once = nil
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class AnyOf < Draft202012::Vocab::Applicator::AnyOf
|
25
|
+
def validate(*)
|
26
|
+
schema.parsed.key?('discriminator') ? nil : super
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class OneOf < Draft202012::Vocab::Applicator::OneOf
|
31
|
+
def validate(*)
|
32
|
+
schema.parsed.key?('discriminator') ? nil : super
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class Discriminator < Keyword
|
37
|
+
# https://spec.openapis.org/oas/v3.1.0#components-object
|
38
|
+
FIXED_FIELD_REGEX = /\A[a-zA-Z0-9\.\-_]+$\z/
|
39
|
+
|
40
|
+
attr_accessor :skip_ref_once
|
41
|
+
|
42
|
+
def error(formatted_instance_location:, **)
|
43
|
+
"value at #{formatted_instance_location} does not match `discriminator` schema"
|
44
|
+
end
|
45
|
+
|
46
|
+
def mapping
|
47
|
+
@mapping ||= value['mapping'] || {}
|
48
|
+
end
|
49
|
+
|
50
|
+
def subschemas_by_property_value
|
51
|
+
@subschemas_by_property_value ||= if schema.parsed.key?('anyOf') || schema.parsed.key?('oneOf')
|
52
|
+
subschemas = schema.parsed['anyOf']&.parsed || []
|
53
|
+
subschemas += schema.parsed['oneOf']&.parsed || []
|
54
|
+
|
55
|
+
subschemas_by_ref = {}
|
56
|
+
subschemas_by_schema_name = {}
|
57
|
+
|
58
|
+
subschemas.each do |subschema|
|
59
|
+
subschema_ref = subschema.parsed.fetch('$ref').parsed
|
60
|
+
subschemas_by_ref[subschema_ref] = subschema
|
61
|
+
|
62
|
+
if subschema_ref.start_with?('#/components/schemas/')
|
63
|
+
schema_name = subschema_ref.delete_prefix('#/components/schemas/')
|
64
|
+
subschemas_by_schema_name[schema_name] = subschema if FIXED_FIELD_REGEX.match?(schema_name)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
explicit_mapping = mapping.transform_values do |schema_name_or_ref|
|
69
|
+
subschemas_by_schema_name.fetch(schema_name_or_ref) { subschemas_by_ref.fetch(schema_name_or_ref) }
|
70
|
+
end
|
71
|
+
|
72
|
+
implicit_mapping = subschemas_by_schema_name.reject do |_schema_name, subschema|
|
73
|
+
explicit_mapping.value?(subschema)
|
74
|
+
end
|
75
|
+
|
76
|
+
implicit_mapping.merge(explicit_mapping)
|
77
|
+
else
|
78
|
+
Hash.new do |hash, property_value|
|
79
|
+
schema_name_or_ref = mapping.fetch(property_value, property_value)
|
80
|
+
|
81
|
+
subschema = nil
|
82
|
+
|
83
|
+
if FIXED_FIELD_REGEX.match?(schema_name_or_ref)
|
84
|
+
subschema = begin
|
85
|
+
schema.ref("#/components/schemas/#{schema_name_or_ref}")
|
86
|
+
rescue InvalidRefPointer
|
87
|
+
nil
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
subschema ||= begin
|
92
|
+
schema.ref(schema_name_or_ref)
|
93
|
+
rescue InvalidRefResolution, UnknownRef
|
94
|
+
nil
|
95
|
+
end
|
96
|
+
|
97
|
+
hash[property_value] = subschema
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def validate(instance, instance_location, keyword_location, context)
|
103
|
+
return result(instance, instance_location, keyword_location, true) unless instance.is_a?(Hash)
|
104
|
+
|
105
|
+
property_name = value.fetch('propertyName')
|
106
|
+
|
107
|
+
return result(instance, instance_location, keyword_location, false) unless instance.key?(property_name)
|
108
|
+
|
109
|
+
property_value = instance.fetch(property_name)
|
110
|
+
subschema = subschemas_by_property_value[property_value]
|
111
|
+
|
112
|
+
return result(instance, instance_location, keyword_location, false) unless subschema
|
113
|
+
|
114
|
+
return if skip_ref_once == subschema.absolute_keyword_location
|
115
|
+
subschema.parsed['allOf']&.skip_ref_once = schema.absolute_keyword_location
|
116
|
+
|
117
|
+
subschema_result = subschema.validate_instance(instance, instance_location, keyword_location, context)
|
118
|
+
|
119
|
+
result(instance, instance_location, keyword_location, subschema_result.valid, subschema_result.nested)
|
120
|
+
ensure
|
121
|
+
self.skip_ref_once = nil
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module JSONSchemer
|
3
|
+
module OpenAPI31
|
4
|
+
module Vocab
|
5
|
+
# https://spec.openapis.org/oas/latest.html#schema-object
|
6
|
+
BASE = {
|
7
|
+
# https://spec.openapis.org/oas/latest.html#discriminator-object
|
8
|
+
'discriminator' => Base::Discriminator,
|
9
|
+
'allOf' => Base::AllOf,
|
10
|
+
'anyOf' => Base::AnyOf,
|
11
|
+
'oneOf' => Base::OneOf
|
12
|
+
# 'xml' => Base::Xml,
|
13
|
+
# 'externalDocs' => Base::ExternalDocs,
|
14
|
+
# 'example' => Base::Example
|
15
|
+
}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module JSONSchemer
|
3
|
+
module Output
|
4
|
+
FRAGMENT_ENCODE_REGEX = /[^\w?\/:@\-.~!$&'()*+,;=]/
|
5
|
+
|
6
|
+
attr_reader :keyword, :schema
|
7
|
+
|
8
|
+
def x_error
|
9
|
+
return @x_error if defined?(@x_error)
|
10
|
+
@x_error = schema.parsed['x-error']&.message(error_key)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def result(instance, instance_location, keyword_location, valid, nested = nil, type: nil, annotation: nil, details: nil, ignore_nested: false)
|
16
|
+
Result.new(self, instance, instance_location, keyword_location, valid, nested, type, annotation, details, ignore_nested, valid ? 'annotations' : 'errors')
|
17
|
+
end
|
18
|
+
|
19
|
+
def escaped_keyword
|
20
|
+
@escaped_keyword ||= Location.escape_json_pointer_token(keyword)
|
21
|
+
end
|
22
|
+
|
23
|
+
def join_location(location, keyword)
|
24
|
+
Location.join(location, keyword)
|
25
|
+
end
|
26
|
+
|
27
|
+
def fragment_encode(location)
|
28
|
+
Format.percent_encode(location, FRAGMENT_ENCODE_REGEX)
|
29
|
+
end
|
30
|
+
|
31
|
+
# :nocov:
|
32
|
+
if Symbol.method_defined?(:name)
|
33
|
+
def stringify(key)
|
34
|
+
key.is_a?(Symbol) ? key.name : key.to_s
|
35
|
+
end
|
36
|
+
else
|
37
|
+
def stringify(key)
|
38
|
+
key.to_s
|
39
|
+
end
|
40
|
+
end
|
41
|
+
# :nocov:
|
42
|
+
|
43
|
+
def deep_stringify_keys(obj)
|
44
|
+
case obj
|
45
|
+
when Hash
|
46
|
+
obj.each_with_object({}) do |(key, value), out|
|
47
|
+
out[stringify(key)] = deep_stringify_keys(value)
|
48
|
+
end
|
49
|
+
when Array
|
50
|
+
obj.map { |item| deep_stringify_keys(item) }
|
51
|
+
else
|
52
|
+
obj
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,242 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module JSONSchemer
|
3
|
+
CATCHALL = '*'
|
4
|
+
I18N_SEPARATOR = "\x1F" # unit separator
|
5
|
+
I18N_SCOPE = 'json_schemer'
|
6
|
+
I18N_ERRORS_SCOPE = "#{I18N_SCOPE}#{I18N_SEPARATOR}errors"
|
7
|
+
X_ERROR_REGEX = /%\{(instance|instanceLocation|keywordLocation|absoluteKeywordLocation)\}/
|
8
|
+
CLASSIC_ERROR_TYPES = Hash.new do |hash, klass|
|
9
|
+
hash[klass] = klass.name.rpartition('::').last.sub(/\A[[:alpha:]]/, &:downcase)
|
10
|
+
end
|
11
|
+
|
12
|
+
Result = Struct.new(:source, :instance, :instance_location, :keyword_location, :valid, :nested, :type, :annotation, :details, :ignore_nested, :nested_key) do
|
13
|
+
def output(output_format)
|
14
|
+
case output_format
|
15
|
+
when 'classic'
|
16
|
+
classic
|
17
|
+
when 'flag'
|
18
|
+
flag
|
19
|
+
when 'basic'
|
20
|
+
basic
|
21
|
+
when 'detailed'
|
22
|
+
detailed
|
23
|
+
when 'verbose'
|
24
|
+
verbose
|
25
|
+
else
|
26
|
+
raise UnknownOutputFormat, output_format
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def error
|
31
|
+
return @error if defined?(@error)
|
32
|
+
if source.x_error
|
33
|
+
# not using sprintf because it warns: "too many arguments for format string"
|
34
|
+
@error = source.x_error.gsub(
|
35
|
+
X_ERROR_REGEX,
|
36
|
+
'%{instance}' => instance,
|
37
|
+
'%{instanceLocation}' => Location.resolve(instance_location),
|
38
|
+
'%{keywordLocation}' => Location.resolve(keyword_location),
|
39
|
+
'%{absoluteKeywordLocation}' => source.absolute_keyword_location
|
40
|
+
)
|
41
|
+
@x_error = true
|
42
|
+
else
|
43
|
+
resolved_instance_location = Location.resolve(instance_location)
|
44
|
+
formatted_instance_location = resolved_instance_location.empty? ? 'root' : "`#{resolved_instance_location}`"
|
45
|
+
@error = source.error(:formatted_instance_location => formatted_instance_location, :details => details)
|
46
|
+
if i18n?
|
47
|
+
begin
|
48
|
+
@error = i18n!
|
49
|
+
@i18n = true
|
50
|
+
rescue I18n::MissingTranslationData
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
@error
|
55
|
+
end
|
56
|
+
|
57
|
+
def i18n?
|
58
|
+
return @@i18n if defined?(@@i18n)
|
59
|
+
@@i18n = defined?(I18n) && I18n.exists?(I18N_SCOPE)
|
60
|
+
end
|
61
|
+
|
62
|
+
def i18n!
|
63
|
+
base_uri_str = source.schema.base_uri.to_s
|
64
|
+
meta_schema_base_uri_str = source.schema.meta_schema.base_uri.to_s
|
65
|
+
resolved_keyword_location = Location.resolve(keyword_location)
|
66
|
+
error_key = source.error_key
|
67
|
+
I18n.translate!(
|
68
|
+
source.absolute_keyword_location,
|
69
|
+
:default => [
|
70
|
+
"#{base_uri_str}#{I18N_SEPARATOR}##{resolved_keyword_location}",
|
71
|
+
"##{resolved_keyword_location}",
|
72
|
+
"#{base_uri_str}#{I18N_SEPARATOR}#{error_key}",
|
73
|
+
"#{base_uri_str}#{I18N_SEPARATOR}#{CATCHALL}",
|
74
|
+
"#{meta_schema_base_uri_str}#{I18N_SEPARATOR}#{error_key}",
|
75
|
+
"#{meta_schema_base_uri_str}#{I18N_SEPARATOR}#{CATCHALL}",
|
76
|
+
error_key,
|
77
|
+
CATCHALL
|
78
|
+
].map!(&:to_sym),
|
79
|
+
:separator => I18N_SEPARATOR,
|
80
|
+
:scope => I18N_ERRORS_SCOPE,
|
81
|
+
:instance => instance,
|
82
|
+
:instanceLocation => Location.resolve(instance_location),
|
83
|
+
:keywordLocation => resolved_keyword_location,
|
84
|
+
:absoluteKeywordLocation => source.absolute_keyword_location
|
85
|
+
)
|
86
|
+
end
|
87
|
+
|
88
|
+
def to_output_unit
|
89
|
+
out = {
|
90
|
+
'valid' => valid,
|
91
|
+
'keywordLocation' => Location.resolve(keyword_location),
|
92
|
+
'absoluteKeywordLocation' => source.absolute_keyword_location,
|
93
|
+
'instanceLocation' => Location.resolve(instance_location)
|
94
|
+
}
|
95
|
+
if valid
|
96
|
+
out['annotation'] = annotation if annotation
|
97
|
+
else
|
98
|
+
out['error'] = error
|
99
|
+
out['x-error'] = true if @x_error
|
100
|
+
out['i18n'] = true if @i18n
|
101
|
+
end
|
102
|
+
out
|
103
|
+
end
|
104
|
+
|
105
|
+
def to_classic
|
106
|
+
schema = source.schema
|
107
|
+
out = {
|
108
|
+
'data' => instance,
|
109
|
+
'data_pointer' => Location.resolve(instance_location),
|
110
|
+
'schema' => schema.value,
|
111
|
+
'schema_pointer' => schema.schema_pointer,
|
112
|
+
'root_schema' => schema.root.value,
|
113
|
+
'type' => type || CLASSIC_ERROR_TYPES[source.class]
|
114
|
+
}
|
115
|
+
out['error'] = error
|
116
|
+
out['x-error'] = true if @x_error
|
117
|
+
out['i18n'] = true if @i18n
|
118
|
+
out['details'] = details if details
|
119
|
+
out
|
120
|
+
end
|
121
|
+
|
122
|
+
def flag
|
123
|
+
{ 'valid' => valid }
|
124
|
+
end
|
125
|
+
|
126
|
+
def basic
|
127
|
+
out = to_output_unit
|
128
|
+
if nested&.any?
|
129
|
+
out[nested_key] = Enumerator.new do |yielder|
|
130
|
+
results = [self]
|
131
|
+
while result = results.pop
|
132
|
+
if result.ignore_nested || !result.nested&.any?
|
133
|
+
yielder << result.to_output_unit
|
134
|
+
else
|
135
|
+
previous_results_size = results.size
|
136
|
+
result.nested.reverse_each do |nested_result|
|
137
|
+
results << nested_result if nested_result.valid == valid
|
138
|
+
end
|
139
|
+
yielder << result.to_output_unit unless (results.size - previous_results_size) == 1
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
out
|
145
|
+
end
|
146
|
+
|
147
|
+
def detailed
|
148
|
+
return to_output_unit if ignore_nested || !nested&.any?
|
149
|
+
matching_results = nested.select { |nested_result| nested_result.valid == valid }
|
150
|
+
if matching_results.size == 1
|
151
|
+
matching_results.first.detailed
|
152
|
+
else
|
153
|
+
out = to_output_unit
|
154
|
+
if matching_results.any?
|
155
|
+
out[nested_key] = Enumerator.new do |yielder|
|
156
|
+
matching_results.each { |nested_result| yielder << nested_result.detailed }
|
157
|
+
end
|
158
|
+
end
|
159
|
+
out
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def verbose
|
164
|
+
out = to_output_unit
|
165
|
+
if nested&.any?
|
166
|
+
out[nested_key] = Enumerator.new do |yielder|
|
167
|
+
nested.each { |nested_result| yielder << nested_result.verbose }
|
168
|
+
end
|
169
|
+
end
|
170
|
+
out
|
171
|
+
end
|
172
|
+
|
173
|
+
def classic
|
174
|
+
Enumerator.new do |yielder|
|
175
|
+
unless valid
|
176
|
+
results = [self]
|
177
|
+
while result = results.pop
|
178
|
+
if result.ignore_nested || !result.nested&.any?
|
179
|
+
yielder << result.to_classic
|
180
|
+
else
|
181
|
+
previous_results_size = results.size
|
182
|
+
result.nested.reverse_each do |nested_result|
|
183
|
+
results << nested_result if nested_result.valid == valid
|
184
|
+
end
|
185
|
+
yielder << result.to_classic if (results.size - previous_results_size) == 0
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def insert_property_defaults(context)
|
193
|
+
instance_locations = {}
|
194
|
+
instance_locations.compare_by_identity
|
195
|
+
|
196
|
+
results = [[self, true]]
|
197
|
+
while (result, valid = results.pop)
|
198
|
+
next if result.source.is_a?(Schema::NOT_KEYWORD_CLASS)
|
199
|
+
|
200
|
+
valid &&= result.valid
|
201
|
+
result.nested&.each { |nested_result| results << [nested_result, valid] }
|
202
|
+
|
203
|
+
if result.source.is_a?(Schema::PROPERTIES_KEYWORD_CLASS) && result.instance.is_a?(Hash)
|
204
|
+
result.source.parsed.each do |property, schema|
|
205
|
+
next if result.instance.key?(property)
|
206
|
+
next unless default = default_keyword_instance(schema)
|
207
|
+
instance_location = Location.join(result.instance_location, property)
|
208
|
+
keyword_location = Location.join(Location.join(result.keyword_location, property), default.keyword)
|
209
|
+
default_result = default.validate(nil, instance_location, keyword_location, nil)
|
210
|
+
instance_locations[result.instance_location] ||= {}
|
211
|
+
instance_locations[result.instance_location][property] ||= []
|
212
|
+
instance_locations[result.instance_location][property] << [default_result, valid]
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
inserted = false
|
218
|
+
|
219
|
+
instance_locations.each do |instance_location, properties|
|
220
|
+
original_instance = context.original_instance(instance_location)
|
221
|
+
properties.each do |property, results_with_tree_validity|
|
222
|
+
property_inserted = yield(original_instance, property, results_with_tree_validity)
|
223
|
+
inserted ||= (property_inserted != false)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
inserted
|
228
|
+
end
|
229
|
+
|
230
|
+
private
|
231
|
+
|
232
|
+
def default_keyword_instance(schema)
|
233
|
+
schema.parsed.fetch('default') do
|
234
|
+
schema.parsed.find do |_keyword, keyword_instance|
|
235
|
+
next unless keyword_instance.respond_to?(:ref_schema)
|
236
|
+
next unless default = default_keyword_instance(keyword_instance.ref_schema)
|
237
|
+
break default
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|