json_skooma 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +22 -0
- data/LICENSE.txt +21 -0
- data/README.md +107 -0
- data/data/draft-2019-09/README.md +60 -0
- data/data/draft-2019-09/hyper-schema.json +26 -0
- data/data/draft-2019-09/links.json +91 -0
- data/data/draft-2019-09/meta/applicator.json +56 -0
- data/data/draft-2019-09/meta/content.json +17 -0
- data/data/draft-2019-09/meta/core.json +57 -0
- data/data/draft-2019-09/meta/format.json +14 -0
- data/data/draft-2019-09/meta/hyper-schema.json +29 -0
- data/data/draft-2019-09/meta/meta-data.json +37 -0
- data/data/draft-2019-09/meta/validation.json +98 -0
- data/data/draft-2019-09/output/hyper-schema.json +62 -0
- data/data/draft-2019-09/output/schema.json +86 -0
- data/data/draft-2019-09/output/verbose-example.json +130 -0
- data/data/draft-2019-09/schema.json +42 -0
- data/data/draft-2020-12/README.md +89 -0
- data/data/draft-2020-12/adr/README.md +15 -0
- data/data/draft-2020-12/archive/hyper-schema.json +28 -0
- data/data/draft-2020-12/archive/links.json +93 -0
- data/data/draft-2020-12/archive/meta/hyper-schema.json +30 -0
- data/data/draft-2020-12/hyper-schema.json +27 -0
- data/data/draft-2020-12/links.json +85 -0
- data/data/draft-2020-12/meta/applicator.json +48 -0
- data/data/draft-2020-12/meta/content.json +17 -0
- data/data/draft-2020-12/meta/core.json +51 -0
- data/data/draft-2020-12/meta/format-annotation.json +14 -0
- data/data/draft-2020-12/meta/format-assertion.json +14 -0
- data/data/draft-2020-12/meta/hyper-schema.json +29 -0
- data/data/draft-2020-12/meta/meta-data.json +37 -0
- data/data/draft-2020-12/meta/unevaluated.json +15 -0
- data/data/draft-2020-12/meta/validation.json +98 -0
- data/data/draft-2020-12/output/hyper-schema.json +62 -0
- data/data/draft-2020-12/output/schema.json +96 -0
- data/data/draft-2020-12/output/verbose-example.json +130 -0
- data/data/draft-2020-12/schema.json +58 -0
- data/lib/json_skooma/dialects/draft201909.rb +137 -0
- data/lib/json_skooma/dialects/draft202012.rb +146 -0
- data/lib/json_skooma/formatters.rb +135 -0
- data/lib/json_skooma/inflector.rb +13 -0
- data/lib/json_skooma/json_node.rb +100 -0
- data/lib/json_skooma/json_pointer.rb +79 -0
- data/lib/json_skooma/json_schema.rb +176 -0
- data/lib/json_skooma/keywords/applicator/additional_properties.rb +37 -0
- data/lib/json_skooma/keywords/applicator/all_of.rb +25 -0
- data/lib/json_skooma/keywords/applicator/any_of.rb +26 -0
- data/lib/json_skooma/keywords/applicator/contains.rb +31 -0
- data/lib/json_skooma/keywords/applicator/dependent_schemas.rb +35 -0
- data/lib/json_skooma/keywords/applicator/else.rb +22 -0
- data/lib/json_skooma/keywords/applicator/if.rb +17 -0
- data/lib/json_skooma/keywords/applicator/items.rb +36 -0
- data/lib/json_skooma/keywords/applicator/not.rb +19 -0
- data/lib/json_skooma/keywords/applicator/one_of.rb +35 -0
- data/lib/json_skooma/keywords/applicator/pattern_properties.rb +46 -0
- data/lib/json_skooma/keywords/applicator/prefix_items.rb +31 -0
- data/lib/json_skooma/keywords/applicator/properties.rb +34 -0
- data/lib/json_skooma/keywords/applicator/property_names.rb +25 -0
- data/lib/json_skooma/keywords/applicator/then.rb +22 -0
- data/lib/json_skooma/keywords/base.rb +74 -0
- data/lib/json_skooma/keywords/base_annotation.rb +12 -0
- data/lib/json_skooma/keywords/content/content_encoding.rb +12 -0
- data/lib/json_skooma/keywords/content/content_media_type.rb +12 -0
- data/lib/json_skooma/keywords/content/content_schema.rb +19 -0
- data/lib/json_skooma/keywords/core/anchor.rb +22 -0
- data/lib/json_skooma/keywords/core/comment.rb +12 -0
- data/lib/json_skooma/keywords/core/defs.rb +13 -0
- data/lib/json_skooma/keywords/core/dynamic_anchor.rb +22 -0
- data/lib/json_skooma/keywords/core/dynamic_ref.rb +67 -0
- data/lib/json_skooma/keywords/core/id.rb +28 -0
- data/lib/json_skooma/keywords/core/ref.rb +35 -0
- data/lib/json_skooma/keywords/core/schema.rb +26 -0
- data/lib/json_skooma/keywords/core/vocabulary.rb +34 -0
- data/lib/json_skooma/keywords/draft_2019_09/additional_items.rb +40 -0
- data/lib/json_skooma/keywords/draft_2019_09/items.rb +41 -0
- data/lib/json_skooma/keywords/draft_2019_09/recursive_anchor.rb +12 -0
- data/lib/json_skooma/keywords/draft_2019_09/recursive_ref.rb +46 -0
- data/lib/json_skooma/keywords/draft_2019_09/unevaluated_items.rb +56 -0
- data/lib/json_skooma/keywords/format_annotation/format.rb +27 -0
- data/lib/json_skooma/keywords/meta_data/default.rb +11 -0
- data/lib/json_skooma/keywords/meta_data/deprecated.rb +11 -0
- data/lib/json_skooma/keywords/meta_data/description.rb +11 -0
- data/lib/json_skooma/keywords/meta_data/examples.rb +11 -0
- data/lib/json_skooma/keywords/meta_data/read_only.rb +11 -0
- data/lib/json_skooma/keywords/meta_data/title.rb +11 -0
- data/lib/json_skooma/keywords/meta_data/write_only.rb +11 -0
- data/lib/json_skooma/keywords/unevaluated/unevaluated_items.rb +59 -0
- data/lib/json_skooma/keywords/unevaluated/unevaluated_properties.rb +43 -0
- data/lib/json_skooma/keywords/unknown.rb +21 -0
- data/lib/json_skooma/keywords/validation/const.rb +17 -0
- data/lib/json_skooma/keywords/validation/dependent_required.rb +24 -0
- data/lib/json_skooma/keywords/validation/enum.rb +19 -0
- data/lib/json_skooma/keywords/validation/exclusive_maximum.rb +18 -0
- data/lib/json_skooma/keywords/validation/exclusive_minimum.rb +18 -0
- data/lib/json_skooma/keywords/validation/max_contains.rb +24 -0
- data/lib/json_skooma/keywords/validation/max_items.rb +18 -0
- data/lib/json_skooma/keywords/validation/max_length.rb +18 -0
- data/lib/json_skooma/keywords/validation/max_properties.rb +18 -0
- data/lib/json_skooma/keywords/validation/maximum.rb +18 -0
- data/lib/json_skooma/keywords/validation/min_contains.rb +31 -0
- data/lib/json_skooma/keywords/validation/min_items.rb +18 -0
- data/lib/json_skooma/keywords/validation/min_length.rb +18 -0
- data/lib/json_skooma/keywords/validation/min_properties.rb +18 -0
- data/lib/json_skooma/keywords/validation/minimum.rb +18 -0
- data/lib/json_skooma/keywords/validation/multiple_of.rb +20 -0
- data/lib/json_skooma/keywords/validation/pattern.rb +23 -0
- data/lib/json_skooma/keywords/validation/required.rb +19 -0
- data/lib/json_skooma/keywords/validation/type.rb +26 -0
- data/lib/json_skooma/keywords/validation/unique_items.rb +20 -0
- data/lib/json_skooma/keywords/value_schemas.rb +87 -0
- data/lib/json_skooma/memoizable.rb +21 -0
- data/lib/json_skooma/metaschema.rb +32 -0
- data/lib/json_skooma/registry.rb +130 -0
- data/lib/json_skooma/result.rb +125 -0
- data/lib/json_skooma/sources.rb +55 -0
- data/lib/json_skooma/validators/base.rb +31 -0
- data/lib/json_skooma/validators/date.rb +18 -0
- data/lib/json_skooma/validators/date_time.rb +24 -0
- data/lib/json_skooma/validators/duration.rb +25 -0
- data/lib/json_skooma/validators/email.rb +36 -0
- data/lib/json_skooma/validators/hostname.rb +17 -0
- data/lib/json_skooma/validators/idn_email.rb +30 -0
- data/lib/json_skooma/validators/idn_hostname.rb +15 -0
- data/lib/json_skooma/validators/ipv4.rb +20 -0
- data/lib/json_skooma/validators/ipv6.rb +16 -0
- data/lib/json_skooma/validators/iri.rb +47 -0
- data/lib/json_skooma/validators/iri_reference.rb +15 -0
- data/lib/json_skooma/validators/json_pointer.rb +19 -0
- data/lib/json_skooma/validators/regex.rb +15 -0
- data/lib/json_skooma/validators/relative_json_pointer.rb +18 -0
- data/lib/json_skooma/validators/time.rb +32 -0
- data/lib/json_skooma/validators/uri.rb +60 -0
- data/lib/json_skooma/validators/uri_reference.rb +15 -0
- data/lib/json_skooma/validators/uri_template.rb +26 -0
- data/lib/json_skooma/validators/uuid.rb +15 -0
- data/lib/json_skooma/validators.rb +17 -0
- data/lib/json_skooma/version.rb +5 -0
- data/lib/json_skooma/vocabulary.rb +12 -0
- data/lib/json_skooma.rb +39 -0
- metadata +244 -0
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "delegate"
|
4
|
+
|
5
|
+
module JSONSkooma
|
6
|
+
class JSONNode < SimpleDelegator
|
7
|
+
extend Memoizable
|
8
|
+
|
9
|
+
attr_reader :parent, :root, :key, :type
|
10
|
+
|
11
|
+
def initialize(value, key: nil, parent: nil, item_class: JSONNode, **item_params)
|
12
|
+
@key = key
|
13
|
+
@parent = parent
|
14
|
+
@root = parent&.root || self
|
15
|
+
@item_class = item_class
|
16
|
+
@item_params = item_params
|
17
|
+
@type, data = parse_value(value)
|
18
|
+
|
19
|
+
super(data)
|
20
|
+
end
|
21
|
+
|
22
|
+
def [](key)
|
23
|
+
case type
|
24
|
+
when "array"
|
25
|
+
super(key.to_i)
|
26
|
+
when "object"
|
27
|
+
super(key.to_s)
|
28
|
+
else
|
29
|
+
raise Error, "Cannot get key #{key} from #{__getobj__.class} in #{path}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def value
|
34
|
+
case type
|
35
|
+
when "array"
|
36
|
+
map(&:value)
|
37
|
+
when "object"
|
38
|
+
transform_values(&:value)
|
39
|
+
else
|
40
|
+
__getobj__
|
41
|
+
end
|
42
|
+
end
|
43
|
+
memoize :value
|
44
|
+
|
45
|
+
def path
|
46
|
+
result = []
|
47
|
+
each_parent { |node| result.unshift(node.key) }
|
48
|
+
JSONPointer.new(result)
|
49
|
+
end
|
50
|
+
memoize :path
|
51
|
+
|
52
|
+
def ==(other)
|
53
|
+
return super(other.__getobj__) if other.is_a?(self.class)
|
54
|
+
super
|
55
|
+
end
|
56
|
+
|
57
|
+
def !=(other)
|
58
|
+
return super(other.__getobj__) if other.is_a?(self.class)
|
59
|
+
super
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def each_parent
|
65
|
+
node = self
|
66
|
+
|
67
|
+
while node.parent
|
68
|
+
yield node
|
69
|
+
node = node.parent
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def parse_value(value)
|
74
|
+
case value
|
75
|
+
when true, false
|
76
|
+
["boolean", value]
|
77
|
+
when String
|
78
|
+
["string", value]
|
79
|
+
when Integer, Float
|
80
|
+
["number", value]
|
81
|
+
when nil
|
82
|
+
["null", value]
|
83
|
+
when Hash
|
84
|
+
["object", map_object_value(value)]
|
85
|
+
when Array
|
86
|
+
["array", map_array_value(value)]
|
87
|
+
else
|
88
|
+
raise Error, "Unknown JSON type: #{value.class}"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def map_array_value(value)
|
93
|
+
value.map.with_index { |v, i| @item_class.new(v, key: i.to_s, parent: self, **@item_params) }
|
94
|
+
end
|
95
|
+
|
96
|
+
def map_object_value(value)
|
97
|
+
value.map { |k, v| [k.to_s, @item_class.new(v, key: k.to_s, parent: self, **@item_params)] }.to_h
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "hana"
|
4
|
+
require "cgi"
|
5
|
+
|
6
|
+
module JSONSkooma
|
7
|
+
class JSONPointer < Hana::Pointer
|
8
|
+
extend Memoizable
|
9
|
+
ESC_REGEX = /[\/^~]/.freeze
|
10
|
+
ESC2 = {"^" => "^^", "~" => "~0", "/" => "~1"}.freeze
|
11
|
+
ESCAPE_REGEX = /([^ a-zA-Z0-9_.\-~\/!$&'()*+,;=]+)/.freeze
|
12
|
+
|
13
|
+
def self.new_root(path)
|
14
|
+
return [""] if path == ""
|
15
|
+
|
16
|
+
parts = ((path.include?("%") || path.include?("+")) ? CGI.unescape(path) : path).split(/(?<!\^)\//).each { |part|
|
17
|
+
part.gsub!(/\^[\/^]|~[01]/) { |m| ESC[m] }
|
18
|
+
}
|
19
|
+
|
20
|
+
parts.push("") if path.end_with?("/")
|
21
|
+
|
22
|
+
new(parts)
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(path)
|
26
|
+
if path.is_a?(Array)
|
27
|
+
@path = path
|
28
|
+
else
|
29
|
+
super CGI.unescape(path)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def child(key)
|
34
|
+
self.class.new(@path.dup.push(key))
|
35
|
+
end
|
36
|
+
|
37
|
+
def <<(part)
|
38
|
+
case part
|
39
|
+
when Array
|
40
|
+
@path.concat(part.map(&:to_s))
|
41
|
+
when JSONPointer
|
42
|
+
@path.concat(part.path)
|
43
|
+
else
|
44
|
+
@path << part.to_s
|
45
|
+
end
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_s
|
50
|
+
return "" if @path == []
|
51
|
+
return "/" if @path == [""]
|
52
|
+
|
53
|
+
"/" + @path.map(&method(:escape)).join("/")
|
54
|
+
end
|
55
|
+
|
56
|
+
memoize :to_s
|
57
|
+
|
58
|
+
def ==(other)
|
59
|
+
return super unless other.is_a?(self.class)
|
60
|
+
other.path == path
|
61
|
+
end
|
62
|
+
|
63
|
+
protected
|
64
|
+
|
65
|
+
attr_reader :path
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def escape(part)
|
70
|
+
string = part.gsub(ESC_REGEX) { |m| ESC2[m] }
|
71
|
+
encoding = string.encoding
|
72
|
+
string.b.gsub!(ESCAPE_REGEX) do |m|
|
73
|
+
"%" + m.unpack("H2" * m.bytesize).join("%").upcase
|
74
|
+
end
|
75
|
+
string.tr!(" ", "+")
|
76
|
+
string.force_encoding(encoding)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JSONSkooma
|
4
|
+
class JSONSchema < JSONNode
|
5
|
+
extend Memoizable
|
6
|
+
|
7
|
+
attr_reader :uri, :cache_id, :registry
|
8
|
+
|
9
|
+
attr_writer :metaschema_uri
|
10
|
+
|
11
|
+
def initialize(value, registry: Registry::DEFAULT_NAME, cache_id: "default", uri: nil, metaschema_uri: nil, parent: nil, key: nil)
|
12
|
+
super(value, parent: parent, key: key)
|
13
|
+
|
14
|
+
@keywords = {}
|
15
|
+
|
16
|
+
@cache_id = cache_id
|
17
|
+
@registry = Registry[registry]
|
18
|
+
self.metaschema_uri = metaschema_uri.is_a?(String) ? URI.parse(metaschema_uri) : metaschema_uri
|
19
|
+
self.uri = uri.is_a?(String) ? URI.parse(uri) : uri
|
20
|
+
|
21
|
+
return if type != "object"
|
22
|
+
|
23
|
+
self.uri ||= URI("urn:uuid:#{SecureRandom.uuid}") if parent.nil?
|
24
|
+
resolve_keywords(value.transform_keys(&:to_s))
|
25
|
+
resolve_references if parent.nil?
|
26
|
+
end
|
27
|
+
|
28
|
+
def evaluate(instance, result = nil)
|
29
|
+
instance = JSONSkooma::JSONNode.new(instance) unless instance.is_a?(JSONNode)
|
30
|
+
|
31
|
+
result ||= Result.new(self, instance)
|
32
|
+
case value
|
33
|
+
when true
|
34
|
+
# do nothing
|
35
|
+
when false
|
36
|
+
result.failure("The instance is disallowed by a boolean false schema")
|
37
|
+
else
|
38
|
+
@keywords.each do |key, keyword|
|
39
|
+
next if keyword.static || !keyword.instance_types.include?(instance.type)
|
40
|
+
|
41
|
+
result.call(instance, key, self) do |subresult|
|
42
|
+
keyword.evaluate(instance, subresult)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
if result.children.any? { |_, child| !child.passed? && child.instance.path == instance.path }
|
47
|
+
result.failure
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
result
|
52
|
+
end
|
53
|
+
|
54
|
+
def validate
|
55
|
+
metaschema.evaluate(self)
|
56
|
+
end
|
57
|
+
|
58
|
+
def parent_schema
|
59
|
+
node = parent
|
60
|
+
while node
|
61
|
+
return node if node.is_a?(JSONSchema)
|
62
|
+
|
63
|
+
node = node.parent
|
64
|
+
end
|
65
|
+
end
|
66
|
+
memoize :parent_schema
|
67
|
+
|
68
|
+
def uri=(uri)
|
69
|
+
return if @uri == uri
|
70
|
+
|
71
|
+
@base_uri = nil
|
72
|
+
@registry.delete_schema(@uri, cache_id: @cache_id) if @uri
|
73
|
+
@uri = uri
|
74
|
+
@registry.add_schema(@uri, self, cache_id: @cache_id) if @uri
|
75
|
+
end
|
76
|
+
|
77
|
+
def metaschema
|
78
|
+
raise RegistryError, "The schema's metaschema URI has not been set" if metaschema_uri.nil?
|
79
|
+
|
80
|
+
@metaschema ||= @registry.metaschema(metaschema_uri)
|
81
|
+
end
|
82
|
+
|
83
|
+
def metaschema_uri
|
84
|
+
@metaschema_uri || parent_schema&.metaschema_uri
|
85
|
+
end
|
86
|
+
memoize :metaschema_uri
|
87
|
+
|
88
|
+
def base_uri
|
89
|
+
return parent_schema&.base_uri unless uri
|
90
|
+
|
91
|
+
@base_uri ||= uri.dup.tap { |u| u.fragment = nil }
|
92
|
+
end
|
93
|
+
|
94
|
+
def canonical_uri
|
95
|
+
return uri if uri
|
96
|
+
|
97
|
+
keys = []
|
98
|
+
node = self
|
99
|
+
while node.parent
|
100
|
+
keys.unshift(node.key)
|
101
|
+
node = node.parent
|
102
|
+
|
103
|
+
if node.is_a?(JSONSchema) && node.uri
|
104
|
+
fragment = JSONPointer.new(node.uri.fragment || "") << keys
|
105
|
+
return node.uri.dup.tap { |u| u.fragment = fragment.to_s }
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
memoize :canonical_uri
|
110
|
+
|
111
|
+
def resolve_references
|
112
|
+
@keywords.each_value { |kw| kw.resolve }
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
def resolve_keywords(value)
|
118
|
+
bootstrap(value)
|
119
|
+
|
120
|
+
kw_classes = value.keys
|
121
|
+
.reject { |k| @keywords.key?(k) }
|
122
|
+
.map { |k| [k, kw_class(k)] }
|
123
|
+
.to_h
|
124
|
+
|
125
|
+
dependencies_in_order(kw_classes) do |kw_class|
|
126
|
+
add_keyword(kw_class.new(self, value[kw_class.key]))
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def bootstrap(value)
|
131
|
+
bootstrap_kw_classes = {
|
132
|
+
"$schema" => Keywords::Core::Schema,
|
133
|
+
"$id" => Keywords::Core::Id
|
134
|
+
}
|
135
|
+
|
136
|
+
bootstrap_kw_classes.each do |key, kw_class|
|
137
|
+
next unless value.key?(key)
|
138
|
+
|
139
|
+
add_keyword(kw_class.new(self, value[key]))
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def kw_class(k)
|
144
|
+
metaschema.kw_class(k)
|
145
|
+
end
|
146
|
+
|
147
|
+
def add_keyword(kw)
|
148
|
+
@keywords[kw.key] = kw
|
149
|
+
__getobj__[kw.key] = kw.json
|
150
|
+
end
|
151
|
+
|
152
|
+
def dependencies_in_order(kw_classes)
|
153
|
+
dependencies = kw_classes.map do |_, kw_class|
|
154
|
+
[kw_class, kw_class.depends_on.map { |dep| kw_classes[dep] }.compact]
|
155
|
+
end.to_h
|
156
|
+
|
157
|
+
while dependencies.any?
|
158
|
+
kw_class, _ = dependencies.find { |_, depclasses| depclasses.empty? }
|
159
|
+
dependencies.delete(kw_class)
|
160
|
+
dependencies.each { |_, deps| deps.delete(kw_class) }
|
161
|
+
yield kw_class
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def parse_value(value, **options)
|
166
|
+
case value
|
167
|
+
when true, false
|
168
|
+
["boolean", value]
|
169
|
+
when Hash
|
170
|
+
["object", {}]
|
171
|
+
else
|
172
|
+
raise TypeError, "#{value} is not JSONSchema-compatible"
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JSONSkooma
|
4
|
+
module Keywords
|
5
|
+
module Applicator
|
6
|
+
class AdditionalProperties < Base
|
7
|
+
self.key = "additionalProperties"
|
8
|
+
self.instance_types = "object"
|
9
|
+
self.value_schema = :schema
|
10
|
+
self.depends_on = %w[properties patternProperties]
|
11
|
+
|
12
|
+
def evaluate(instance, result)
|
13
|
+
known_property_names = result.sibling(instance, "properties")&.schema_node&.keys || []
|
14
|
+
known_property_patterns = result.sibling(instance, "patternProperties")&.schema_node&.keys || []
|
15
|
+
|
16
|
+
annotation = []
|
17
|
+
error = []
|
18
|
+
|
19
|
+
instance.each do |name, item|
|
20
|
+
if !known_property_names.include?(name) && !known_property_patterns.any? { |pattern| Regexp.new(pattern).match?(name) }
|
21
|
+
if json.evaluate(item, result).passed?
|
22
|
+
annotation << name
|
23
|
+
else
|
24
|
+
error << name
|
25
|
+
# reset to success for the next iteration
|
26
|
+
result.success
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
return result.annotate(annotation) if error.empty?
|
31
|
+
|
32
|
+
result.failure(error)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JSONSkooma
|
4
|
+
module Keywords
|
5
|
+
module Applicator
|
6
|
+
class AllOf < Base
|
7
|
+
self.key = "allOf"
|
8
|
+
self.value_schema = :array_of_schemas
|
9
|
+
|
10
|
+
def evaluate(instance, result)
|
11
|
+
err_indices = []
|
12
|
+
json.each.with_index do |subschema, index|
|
13
|
+
result.call(instance, index.to_s) do |subresult|
|
14
|
+
subschema.evaluate(instance, subresult)
|
15
|
+
err_indices << index unless subresult.passed?
|
16
|
+
end
|
17
|
+
end
|
18
|
+
return if err_indices.empty?
|
19
|
+
|
20
|
+
result.failure("The instance is invalid against subschemas #{err_indices}")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JSONSkooma
|
4
|
+
module Keywords
|
5
|
+
module Applicator
|
6
|
+
class AnyOf < Base
|
7
|
+
self.key = "anyOf"
|
8
|
+
self.value_schema = :array_of_schemas
|
9
|
+
|
10
|
+
def evaluate(instance, result)
|
11
|
+
valid = false
|
12
|
+
json.each.with_index do |subschema, index|
|
13
|
+
result.call(instance, index.to_s) do |subresult|
|
14
|
+
subschema.evaluate(instance, subresult)
|
15
|
+
valid = true if subresult.passed?
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
return if valid
|
20
|
+
|
21
|
+
result.failure("The instance must be valid against at least one subschema")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JSONSkooma
|
4
|
+
module Keywords
|
5
|
+
module Applicator
|
6
|
+
class Contains < Base
|
7
|
+
self.key = "contains"
|
8
|
+
self.instance_types = "array"
|
9
|
+
self.value_schema = :schema
|
10
|
+
|
11
|
+
def evaluate(instance, result)
|
12
|
+
annotation = []
|
13
|
+
instance.each_with_index do |item, index|
|
14
|
+
if json.evaluate(item, result).passed?
|
15
|
+
annotation << index
|
16
|
+
else
|
17
|
+
result.success
|
18
|
+
end
|
19
|
+
end
|
20
|
+
result.annotate(annotation)
|
21
|
+
|
22
|
+
return if annotation.any?
|
23
|
+
|
24
|
+
result.failure(
|
25
|
+
"The array does not contain any element that is valid against the `#{key}` subschema"
|
26
|
+
)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JSONSkooma
|
4
|
+
module Keywords
|
5
|
+
module Applicator
|
6
|
+
class DependentSchemas < Base
|
7
|
+
self.key = "dependentSchemas"
|
8
|
+
self.instance_types = "object"
|
9
|
+
self.value_schema = :object_of_schemas
|
10
|
+
|
11
|
+
def evaluate(instance, result)
|
12
|
+
annotation = []
|
13
|
+
err_names = []
|
14
|
+
|
15
|
+
json.each do |name, subschema|
|
16
|
+
next unless instance.key?(name)
|
17
|
+
|
18
|
+
result.call(instance, name) do |subresult|
|
19
|
+
if subschema.evaluate(instance, subresult).passed?
|
20
|
+
annotation << name
|
21
|
+
else
|
22
|
+
err_names << name
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
return result.annotate(annotation) if err_names.none?
|
27
|
+
|
28
|
+
result.failure(
|
29
|
+
"Properties #{err_names} are invalid against the corresponding `dependentSchemas` subschemas"
|
30
|
+
)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JSONSkooma
|
4
|
+
module Keywords
|
5
|
+
module Applicator
|
6
|
+
class Else < Base
|
7
|
+
self.key = "else"
|
8
|
+
self.value_schema = :schema
|
9
|
+
self.depends_on = %w[if]
|
10
|
+
|
11
|
+
def evaluate(instance, result)
|
12
|
+
condition = result.sibling(instance, "if")
|
13
|
+
if condition && !condition.valid?
|
14
|
+
json.evaluate(instance, result)
|
15
|
+
else
|
16
|
+
result.discard
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JSONSkooma
|
4
|
+
module Keywords
|
5
|
+
module Applicator
|
6
|
+
class If < Base
|
7
|
+
self.key = "if"
|
8
|
+
self.value_schema = :schema
|
9
|
+
|
10
|
+
def evaluate(instance, result)
|
11
|
+
json.evaluate(instance, result)
|
12
|
+
result.skip_assertion
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JSONSkooma
|
4
|
+
module Keywords
|
5
|
+
module Applicator
|
6
|
+
class Items < Base
|
7
|
+
self.key = "items"
|
8
|
+
self.instance_types = %w[array]
|
9
|
+
self.value_schema = :schema
|
10
|
+
self.depends_on = %w[prefixItems]
|
11
|
+
|
12
|
+
def evaluate(instance, result)
|
13
|
+
prefix_items = result.sibling(instance, "prefixItems")
|
14
|
+
start_index = prefix_items&.schema_node&.length || 0
|
15
|
+
|
16
|
+
annotation = nil
|
17
|
+
error = []
|
18
|
+
instance.each_with_index do |item, index|
|
19
|
+
next if index < start_index
|
20
|
+
|
21
|
+
if json.evaluate(item, result).passed?
|
22
|
+
annotation = true
|
23
|
+
else
|
24
|
+
error << index
|
25
|
+
# reset to passed for the next iteration
|
26
|
+
result.success
|
27
|
+
end
|
28
|
+
end
|
29
|
+
return result.annotate(annotation) if error.empty?
|
30
|
+
|
31
|
+
result.failure(error)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JSONSkooma
|
4
|
+
module Keywords
|
5
|
+
module Applicator
|
6
|
+
class Not < Base
|
7
|
+
self.key = "not"
|
8
|
+
self.value_schema = :schema
|
9
|
+
|
10
|
+
def evaluate(instance, result)
|
11
|
+
json.evaluate(instance, result)
|
12
|
+
return result.success unless result.passed?
|
13
|
+
|
14
|
+
result.failure("The instance must not be valid against the subschema")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JSONSkooma
|
4
|
+
module Keywords
|
5
|
+
module Applicator
|
6
|
+
class OneOf < Base
|
7
|
+
self.key = "oneOf"
|
8
|
+
self.value_schema = :array_of_schemas
|
9
|
+
|
10
|
+
def evaluate(instance, result)
|
11
|
+
valid_indices = []
|
12
|
+
err_indices = []
|
13
|
+
|
14
|
+
json.each.with_index do |subschema, index|
|
15
|
+
result.call(instance, index.to_s) do |subresult|
|
16
|
+
subschema.evaluate(instance, subresult)
|
17
|
+
if subresult.passed?
|
18
|
+
valid_indices << index
|
19
|
+
else
|
20
|
+
err_indices << index
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
return if valid_indices.size == 1
|
26
|
+
|
27
|
+
result.failure(
|
28
|
+
"The instance must be valid against exactly one subschema; " \
|
29
|
+
"it is valid against #{valid_indices} and invalid against #{err_indices}'"
|
30
|
+
)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JSONSkooma
|
4
|
+
module Keywords
|
5
|
+
module Applicator
|
6
|
+
class PatternProperties < Base
|
7
|
+
self.key = "patternProperties"
|
8
|
+
self.instance_types = "object"
|
9
|
+
self.value_schema = :object_of_schemas
|
10
|
+
|
11
|
+
def initialize(parent_schema, value)
|
12
|
+
super
|
13
|
+
@patterned = json.map do |pattern, subschema|
|
14
|
+
[Regexp.new(pattern), pattern, subschema]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def evaluate(instance, result)
|
19
|
+
matched_names = Set.new
|
20
|
+
err_names = []
|
21
|
+
|
22
|
+
instance.each do |name, item|
|
23
|
+
@patterned.each do |regexp, pattern, subschema|
|
24
|
+
if regexp.match?(name)
|
25
|
+
result.call(item, pattern) do |subresult|
|
26
|
+
subschema.evaluate(item, subresult)
|
27
|
+
if subresult.passed?
|
28
|
+
matched_names << name
|
29
|
+
else
|
30
|
+
err_names << name
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
if err_names.any?
|
38
|
+
result.failure("Properties #{err_names} are invalid")
|
39
|
+
else
|
40
|
+
result.annotate(matched_names.to_a)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|