json_skooma 0.1.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 +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
|