json_skooma 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -1
- data/README.md +39 -0
- data/lib/json_skooma/formatters.rb +7 -5
- data/lib/json_skooma/inflector.rb +1 -1
- data/lib/json_skooma/json_node.rb +22 -18
- data/lib/json_skooma/json_pointer.rb +11 -7
- data/lib/json_skooma/json_schema.rb +28 -17
- data/lib/json_skooma/keywords/applicator/additional_properties.rb +3 -2
- data/lib/json_skooma/keywords/applicator/all_of.rb +1 -1
- data/lib/json_skooma/keywords/applicator/pattern_properties.rb +1 -1
- data/lib/json_skooma/keywords/applicator/prefix_items.rb +1 -1
- data/lib/json_skooma/keywords/applicator/properties.rb +1 -1
- data/lib/json_skooma/keywords/applicator/property_names.rb +1 -1
- data/lib/json_skooma/keywords/core/ref.rb +1 -16
- data/lib/json_skooma/keywords/draft_2019_09/items.rb +1 -1
- data/lib/json_skooma/keywords/draft_2019_09/unevaluated_items.rb +5 -10
- data/lib/json_skooma/keywords/unevaluated/unevaluated_items.rb +7 -11
- data/lib/json_skooma/keywords/unevaluated/unevaluated_properties.rb +5 -4
- data/lib/json_skooma/keywords/validation/const.rb +1 -1
- data/lib/json_skooma/keywords/validation/enum.rb +1 -1
- data/lib/json_skooma/keywords/validation/minimum.rb +1 -1
- data/lib/json_skooma/keywords/validation/required.rb +2 -3
- data/lib/json_skooma/metaschema.rb +1 -1
- data/lib/json_skooma/result.rb +71 -33
- data/lib/json_skooma/validators/ipv4.rb +4 -7
- data/lib/json_skooma/validators/ipv6.rb +8 -6
- data/lib/json_skooma/validators/iri.rb +1 -1
- data/lib/json_skooma/validators/uri.rb +2 -7
- data/lib/json_skooma/version.rb +1 -1
- data/lib/json_skooma.rb +1 -1
- metadata +3 -4
- data/lib/json_skooma/memoizable.rb +0 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 41c787af86379c5c95a01c3180116d12854d11ca7def23569967d35352998da2
|
4
|
+
data.tar.gz: 4f3fbca4d35931fe884598c1c827e4434102c25b76973293af31b2af5ce62371
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bef3119c2f4de1880ebde8036ad6517154b82373c80896b7d3d97ce70cb0d39f5b4c458ca2836d8f23439dcde2965b62a61e02586d7d2dfc12892e1f64fef815
|
7
|
+
data.tar.gz: 5aadc6b0af540a0d7d5eacae5959f5051a7b932ce9d92bd049bd5348f6d67e1740bba94a9b51be325aace1cdd1c28bf0521a8d325f137f5207afa79b1dd4c0c3
|
data/CHANGELOG.md
CHANGED
@@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning].
|
|
7
7
|
|
8
8
|
## [Unreleased]
|
9
9
|
|
10
|
+
## [0.2.0] - 2023-10-23
|
11
|
+
|
12
|
+
### Added
|
13
|
+
|
14
|
+
- Add JSON Schema evaluation against a reference. ([@skryukov])
|
15
|
+
|
16
|
+
### Changed
|
17
|
+
|
18
|
+
- Optimized JSON Schema evaluation. ([@skryukov])
|
19
|
+
|
20
|
+
### Fixed
|
21
|
+
|
22
|
+
- Fix compatibility issues for Ruby < 3.1 and JRuby. ([@skryukov])
|
23
|
+
- Fix Zeitwerk eager loading. ([@skryukov])
|
24
|
+
|
10
25
|
## [0.1.0] - 2023-09-27
|
11
26
|
|
12
27
|
### Added
|
@@ -15,7 +30,8 @@ and this project adheres to [Semantic Versioning].
|
|
15
30
|
|
16
31
|
[@skryukov]: https://github.com/skryukov
|
17
32
|
|
18
|
-
[Unreleased]: https://github.com/skryukov/json_skooma/compare/v0.
|
33
|
+
[Unreleased]: https://github.com/skryukov/json_skooma/compare/v0.2.0...HEAD
|
34
|
+
[0.2.0]: https://github.com/skryukov/json_skooma/compare/v0.1.0...v0.2.0
|
19
35
|
[0.1.0]: https://github.com/skryukov/json_skooma/commits/v0.1.0
|
20
36
|
|
21
37
|
[Keep a Changelog]: https://keepachangelog.com/en/1.0.0/
|
data/README.md
CHANGED
@@ -3,6 +3,8 @@
|
|
3
3
|
[](https://rubygems.org/gems/json_skooma)
|
4
4
|
[](https://github.com/skryukov/json_skooma/actions/workflows/main.yml)
|
5
5
|
|
6
|
+
<img align="right" height="150" width="150" title="JSONSkooma logo" src="./assets/logo.svg">
|
7
|
+
|
6
8
|
JSONSkooma is a Ruby library for validating JSONs against JSON Schemas.
|
7
9
|
|
8
10
|
### Features
|
@@ -78,6 +80,43 @@ result.output(:basic)
|
|
78
80
|
# "The instance value \"Human\" must be equal to one of the elements in the defined enumeration: [\"Nord\", \"Khajiit\", \"Argonian\", \"Breton\", \"Redguard\", \"Dunmer\", \"Altmer\", \"Bosmer\", \"Orc\", \"Imperial\"]"}]}
|
79
81
|
```
|
80
82
|
|
83
|
+
### Evaluating against a reference
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
require "json_skooma"
|
87
|
+
|
88
|
+
# Create a registry to store schemas, vocabularies, dialects, etc.
|
89
|
+
JSONSkooma.create_registry("2020-12", assert_formats: true)
|
90
|
+
|
91
|
+
# Load a schema
|
92
|
+
schema_hash = {
|
93
|
+
"$schema" => "https://json-schema.org/draft/2020-12/schema",
|
94
|
+
"$defs" => {
|
95
|
+
"Foo": {
|
96
|
+
"type" => "object",
|
97
|
+
"properties" => {
|
98
|
+
"foo" => {"enum" => ["baz"]}
|
99
|
+
},
|
100
|
+
}
|
101
|
+
}
|
102
|
+
}
|
103
|
+
|
104
|
+
schema = JSONSkooma::JSONSchema.new(schema_hash)
|
105
|
+
|
106
|
+
result = schema.evaluate({foo: "bar"}, ref: "#/$defs/Foo")
|
107
|
+
|
108
|
+
result.valid? # => false
|
109
|
+
|
110
|
+
result.output(:basic)
|
111
|
+
# {"valid"=>false,
|
112
|
+
# "errors"=>
|
113
|
+
# [{"instanceLocation"=>"", "keywordLocation"=>"/properties", "absoluteKeywordLocation"=>"urn:uuid:cb8fb0a0-ce16-416f-b5ba-2a6531992be9#/$defs/Foo/properties", "error"=>"Properties [\"foo\"] are invalid"},
|
114
|
+
# {"instanceLocation"=>"/foo",
|
115
|
+
# "keywordLocation"=>"/properties/foo/enum",
|
116
|
+
# "absoluteKeywordLocation"=>"urn:uuid:cb8fb0a0-ce16-416f-b5ba-2a6531992be9#/$defs/Foo/properties/foo/enum",
|
117
|
+
# "error"=>"The instance value \"bar\" must be equal to one of the elements in the defined enumeration: [\"baz\"]"}]}
|
118
|
+
```
|
119
|
+
|
81
120
|
## Alternatives
|
82
121
|
|
83
122
|
- [json_schemer](https://github.com/davishmcclurg/json_schemer) – Draft 4, 6, 7, 2019-09 and 2020-12 compliant
|
@@ -43,7 +43,7 @@ module JSONSkooma
|
|
43
43
|
key = valid ? "annotation" : "error"
|
44
44
|
result << node_data(node, key) if node.public_send(key)
|
45
45
|
|
46
|
-
node.children.
|
46
|
+
node.children.each_children do |child|
|
47
47
|
collect_nodes(child, valid, result)
|
48
48
|
end
|
49
49
|
|
@@ -83,8 +83,9 @@ module JSONSkooma
|
|
83
83
|
child_key = valid ? "annotations" : "errors"
|
84
84
|
msg_key = valid ? "annotation" : "error"
|
85
85
|
|
86
|
-
child_data =
|
87
|
-
|
86
|
+
child_data = []
|
87
|
+
node.each_children do |child|
|
88
|
+
child_data << node_data(child, valid) if child.valid? == valid
|
88
89
|
end
|
89
90
|
|
90
91
|
if first || child_data.length > 1
|
@@ -121,8 +122,9 @@ module JSONSkooma
|
|
121
122
|
data[msg_key] = node.public_send(msg_key) if node.public_send(msg_key)
|
122
123
|
|
123
124
|
child_key = valid ? "annotations" : "errors"
|
124
|
-
child_data =
|
125
|
-
|
125
|
+
child_data = []
|
126
|
+
node.each_children do |child|
|
127
|
+
child_data << node_data(child)
|
126
128
|
end
|
127
129
|
data[child_key] = child_data if child_data.length > 0
|
128
130
|
|
@@ -4,14 +4,11 @@ require "delegate"
|
|
4
4
|
|
5
5
|
module JSONSkooma
|
6
6
|
class JSONNode < SimpleDelegator
|
7
|
-
|
8
|
-
|
9
|
-
attr_reader :parent, :root, :key, :type
|
7
|
+
attr_reader :parent, :key, :type
|
10
8
|
|
11
9
|
def initialize(value, key: nil, parent: nil, item_class: JSONNode, **item_params)
|
12
10
|
@key = key
|
13
11
|
@parent = parent
|
14
|
-
@root = parent&.root || self
|
15
12
|
@item_class = item_class
|
16
13
|
@item_params = item_params
|
17
14
|
@type, data = parse_value(value)
|
@@ -19,6 +16,10 @@ module JSONSkooma
|
|
19
16
|
super(data)
|
20
17
|
end
|
21
18
|
|
19
|
+
def root
|
20
|
+
@root ||= parent&.root || self
|
21
|
+
end
|
22
|
+
|
22
23
|
def [](key)
|
23
24
|
case type
|
24
25
|
when "array"
|
@@ -31,23 +32,24 @@ module JSONSkooma
|
|
31
32
|
end
|
32
33
|
|
33
34
|
def value
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
35
|
+
return @value if instance_variable_defined?(:@value)
|
36
|
+
|
37
|
+
@value =
|
38
|
+
case type
|
39
|
+
when "array"
|
40
|
+
map(&:value)
|
41
|
+
when "object"
|
42
|
+
transform_values(&:value)
|
43
|
+
else
|
44
|
+
__getobj__
|
45
|
+
end
|
42
46
|
end
|
43
|
-
memoize :value
|
44
47
|
|
45
48
|
def path
|
46
|
-
|
47
|
-
|
48
|
-
JSONPointer.new(
|
49
|
+
return @path if instance_variable_defined?(:@path)
|
50
|
+
|
51
|
+
@path = @parent.nil? ? JSONPointer.new([]) : @parent.path.child(@key)
|
49
52
|
end
|
50
|
-
memoize :path
|
51
53
|
|
52
54
|
def ==(other)
|
53
55
|
return super(other.__getobj__) if other.is_a?(self.class)
|
@@ -94,7 +96,9 @@ module JSONSkooma
|
|
94
96
|
end
|
95
97
|
|
96
98
|
def map_object_value(value)
|
97
|
-
value.
|
99
|
+
value.each_with_object({}) do |(k, v), h|
|
100
|
+
h[k.to_s] = @item_class.new(v, key: k.to_s, parent: self, **@item_params)
|
101
|
+
end
|
98
102
|
end
|
99
103
|
end
|
100
104
|
end
|
@@ -5,7 +5,6 @@ require "cgi"
|
|
5
5
|
|
6
6
|
module JSONSkooma
|
7
7
|
class JSONPointer < Hana::Pointer
|
8
|
-
extend Memoizable
|
9
8
|
ESC_REGEX = /[\/^~]/.freeze
|
10
9
|
ESC2 = {"^" => "^^", "~" => "~0", "/" => "~1"}.freeze
|
11
10
|
ESCAPE_REGEX = /([^ a-zA-Z0-9_.\-~\/!$&'()*+,;=]+)/.freeze
|
@@ -47,14 +46,19 @@ module JSONSkooma
|
|
47
46
|
end
|
48
47
|
|
49
48
|
def to_s
|
50
|
-
return
|
51
|
-
|
52
|
-
|
53
|
-
|
49
|
+
return @to_s if instance_variable_defined?(:@to_s)
|
50
|
+
|
51
|
+
@to_s =
|
52
|
+
case @path
|
53
|
+
when []
|
54
|
+
""
|
55
|
+
when [""]
|
56
|
+
"/"
|
57
|
+
else
|
58
|
+
"/" + @path.map(&method(:escape)).join("/")
|
59
|
+
end
|
54
60
|
end
|
55
61
|
|
56
|
-
memoize :to_s
|
57
|
-
|
58
62
|
def ==(other)
|
59
63
|
return super unless other.is_a?(self.class)
|
60
64
|
other.path == path
|
@@ -2,10 +2,7 @@
|
|
2
2
|
|
3
3
|
module JSONSkooma
|
4
4
|
class JSONSchema < JSONNode
|
5
|
-
extend Memoizable
|
6
|
-
|
7
5
|
attr_reader :uri, :cache_id, :registry
|
8
|
-
|
9
6
|
attr_writer :metaschema_uri
|
10
7
|
|
11
8
|
def initialize(value, registry: Registry::DEFAULT_NAME, cache_id: "default", uri: nil, metaschema_uri: nil, parent: nil, key: nil)
|
@@ -25,7 +22,9 @@ module JSONSkooma
|
|
25
22
|
resolve_references if parent.nil?
|
26
23
|
end
|
27
24
|
|
28
|
-
def evaluate(instance, result = nil)
|
25
|
+
def evaluate(instance, result = nil, ref: nil)
|
26
|
+
return resolve_ref(ref).evaluate(instance, result) if ref
|
27
|
+
|
29
28
|
instance = JSONSkooma::JSONNode.new(instance) unless instance.is_a?(JSONNode)
|
30
29
|
|
31
30
|
result ||= Result.new(self, instance)
|
@@ -43,7 +42,7 @@ module JSONSkooma
|
|
43
42
|
end
|
44
43
|
end
|
45
44
|
|
46
|
-
if result.children.any? { |_, child| !child.passed?
|
45
|
+
if result.children[instance.path]&.any? { |_, child| !child.passed? }
|
47
46
|
result.failure
|
48
47
|
end
|
49
48
|
end
|
@@ -51,19 +50,32 @@ module JSONSkooma
|
|
51
50
|
result
|
52
51
|
end
|
53
52
|
|
53
|
+
def resolve_uri(uri)
|
54
|
+
uri = URI.parse(uri)
|
55
|
+
return uri if uri.absolute?
|
56
|
+
return base_uri + uri if base_uri
|
57
|
+
|
58
|
+
raise Error, "No base URI against which to resolve uri `#{uri}`"
|
59
|
+
end
|
60
|
+
|
61
|
+
def resolve_ref(uri)
|
62
|
+
registry.schema(resolve_uri(uri), metaschema_uri: metaschema_uri, cache_id: cache_id)
|
63
|
+
end
|
64
|
+
|
54
65
|
def validate
|
55
66
|
metaschema.evaluate(self)
|
56
67
|
end
|
57
68
|
|
58
69
|
def parent_schema
|
70
|
+
return @parent_schema if instance_variable_defined?(:@parent_schema)
|
71
|
+
|
59
72
|
node = parent
|
60
73
|
while node
|
61
|
-
return node if node.is_a?(JSONSchema)
|
74
|
+
return @parent_schema = node if node.is_a?(JSONSchema)
|
62
75
|
|
63
76
|
node = node.parent
|
64
77
|
end
|
65
78
|
end
|
66
|
-
memoize :parent_schema
|
67
79
|
|
68
80
|
def uri=(uri)
|
69
81
|
return if @uri == uri
|
@@ -81,9 +93,8 @@ module JSONSkooma
|
|
81
93
|
end
|
82
94
|
|
83
95
|
def metaschema_uri
|
84
|
-
@metaschema_uri
|
96
|
+
@metaschema_uri ||= parent_schema&.metaschema_uri
|
85
97
|
end
|
86
|
-
memoize :metaschema_uri
|
87
98
|
|
88
99
|
def base_uri
|
89
100
|
return parent_schema&.base_uri unless uri
|
@@ -93,6 +104,7 @@ module JSONSkooma
|
|
93
104
|
|
94
105
|
def canonical_uri
|
95
106
|
return uri if uri
|
107
|
+
return @canonical_uri if instance_variable_defined?(:@canonical_uri)
|
96
108
|
|
97
109
|
keys = []
|
98
110
|
node = self
|
@@ -102,14 +114,13 @@ module JSONSkooma
|
|
102
114
|
|
103
115
|
if node.is_a?(JSONSchema) && node.uri
|
104
116
|
fragment = JSONPointer.new(node.uri.fragment || "") << keys
|
105
|
-
return node.uri.dup.tap { |u| u.fragment = fragment.to_s }
|
117
|
+
return @canonical_uri = node.uri.dup.tap { |u| u.fragment = fragment.to_s }
|
106
118
|
end
|
107
119
|
end
|
108
120
|
end
|
109
|
-
memoize :canonical_uri
|
110
121
|
|
111
122
|
def resolve_references
|
112
|
-
@keywords.each_value
|
123
|
+
@keywords.each_value(&:resolve)
|
113
124
|
end
|
114
125
|
|
115
126
|
private
|
@@ -150,19 +161,19 @@ module JSONSkooma
|
|
150
161
|
end
|
151
162
|
|
152
163
|
def dependencies_in_order(kw_classes)
|
153
|
-
dependencies = kw_classes.
|
154
|
-
[kw_class
|
155
|
-
end
|
164
|
+
dependencies = kw_classes.each_value.with_object({}) do |kw_class, res|
|
165
|
+
res[kw_class] = kw_class.depends_on.filter_map { |dep| kw_classes[dep] }
|
166
|
+
end
|
156
167
|
|
157
168
|
while dependencies.any?
|
158
169
|
kw_class, _ = dependencies.find { |_, depclasses| depclasses.empty? }
|
159
170
|
dependencies.delete(kw_class)
|
160
|
-
dependencies.
|
171
|
+
dependencies.each_value { |deps| deps.delete(kw_class) }
|
161
172
|
yield kw_class
|
162
173
|
end
|
163
174
|
end
|
164
175
|
|
165
|
-
def parse_value(value
|
176
|
+
def parse_value(value)
|
166
177
|
case value
|
167
178
|
when true, false
|
168
179
|
["boolean", value]
|
@@ -11,13 +11,14 @@ module JSONSkooma
|
|
11
11
|
|
12
12
|
def evaluate(instance, result)
|
13
13
|
known_property_names = result.sibling(instance, "properties")&.schema_node&.keys || []
|
14
|
-
known_property_patterns = result.sibling(instance, "patternProperties")&.schema_node&.keys || []
|
14
|
+
known_property_patterns = (result.sibling(instance, "patternProperties")&.schema_node&.keys || [])
|
15
|
+
.map { |pattern| Regexp.new(pattern) }
|
15
16
|
|
16
17
|
annotation = []
|
17
18
|
error = []
|
18
19
|
|
19
20
|
instance.each do |name, item|
|
20
|
-
if !known_property_names.include?(name) && !known_property_patterns.any? { |pattern|
|
21
|
+
if !known_property_names.include?(name) && !known_property_patterns.any? { |pattern| pattern.match?(name) }
|
21
22
|
if json.evaluate(item, result).passed?
|
22
23
|
annotation << name
|
23
24
|
else
|
@@ -11,7 +11,7 @@ module JSONSkooma
|
|
11
11
|
def evaluate(instance, result)
|
12
12
|
annotation = nil
|
13
13
|
error = []
|
14
|
-
instance.take(json.length).each_with_index do |item, index|
|
14
|
+
instance.take(json.value.length).each_with_index do |item, index|
|
15
15
|
annotation = index
|
16
16
|
result.call(item, index.to_s) do |subresult|
|
17
17
|
unless json[index].evaluate(item, subresult).passed?
|
@@ -7,28 +7,13 @@ module JSONSkooma
|
|
7
7
|
self.key = "$ref"
|
8
8
|
|
9
9
|
def resolve
|
10
|
-
|
11
|
-
@ref_schema = parent_schema.registry.schema(
|
12
|
-
uri,
|
13
|
-
metaschema_uri: parent_schema.metaschema_uri,
|
14
|
-
cache_id: parent_schema.cache_id
|
15
|
-
)
|
10
|
+
@ref_schema = parent_schema.resolve_ref(json)
|
16
11
|
end
|
17
12
|
|
18
13
|
def evaluate(instance, result)
|
19
14
|
@ref_schema.evaluate(instance, result)
|
20
15
|
result.ref_schema = @ref_schema
|
21
16
|
end
|
22
|
-
|
23
|
-
private
|
24
|
-
|
25
|
-
def resolve_uri
|
26
|
-
uri = URI.parse(json)
|
27
|
-
return uri if uri.absolute?
|
28
|
-
return parent_schema.base_uri + uri if parent_schema.base_uri
|
29
|
-
|
30
|
-
raise Error, "No base URI against which to resolve the `$ref` value `#{uri}`"
|
31
|
-
end
|
32
17
|
end
|
33
18
|
end
|
34
19
|
end
|
@@ -22,7 +22,7 @@ module JSONSkooma
|
|
22
22
|
annotation = nil
|
23
23
|
error = []
|
24
24
|
|
25
|
-
instance.take(json.length).each_with_index do |item, index|
|
25
|
+
instance.take(json.value.length).each_with_index do |item, index|
|
26
26
|
annotation = index
|
27
27
|
result.call(item, index.to_s) do |subresult|
|
28
28
|
unless json[index].evaluate(item, subresult).passed?
|
@@ -12,21 +12,16 @@ module JSONSkooma
|
|
12
12
|
if then else allOf anyOf oneOf not
|
13
13
|
]
|
14
14
|
|
15
|
+
LOOKUP_KEYWORDS = %w[items additionalItems unevaluatedItems].freeze
|
16
|
+
|
15
17
|
def evaluate(instance, result)
|
16
18
|
last_evaluated_item = -1
|
17
19
|
|
18
|
-
result.parent.collect_annotations(instance,
|
20
|
+
result.parent.collect_annotations(instance, keys: LOOKUP_KEYWORDS) do |node|
|
21
|
+
i = node.annotation
|
19
22
|
next result.discard if i == true
|
20
23
|
|
21
|
-
last_evaluated_item = i if i > last_evaluated_item
|
22
|
-
end
|
23
|
-
|
24
|
-
result.parent.collect_annotations(instance, "additionalItems") do |i|
|
25
|
-
next result.discard if i == true
|
26
|
-
end
|
27
|
-
|
28
|
-
result.parent.collect_annotations(instance, "unevaluatedItems") do |i|
|
29
|
-
next result.discard if i == true
|
24
|
+
last_evaluated_item = i if node.key == "items" && i > last_evaluated_item
|
30
25
|
end
|
31
26
|
|
32
27
|
annotation = nil
|
@@ -12,25 +12,21 @@ module JSONSkooma
|
|
12
12
|
if then else allOf anyOf oneOf not
|
13
13
|
]
|
14
14
|
|
15
|
+
LOOKUP_KEYWORDS = %w[items unevaluatedItems prefixItems contains].freeze
|
16
|
+
|
15
17
|
def evaluate(instance, result)
|
18
|
+
contains_indices = Set.new
|
16
19
|
last_evaluated_item = -1
|
17
20
|
|
18
|
-
result.parent.collect_annotations(instance,
|
19
|
-
next
|
20
|
-
|
21
|
-
last_evaluated_item = i if i > last_evaluated_item
|
22
|
-
end
|
21
|
+
result.parent.collect_annotations(instance, keys: LOOKUP_KEYWORDS) do |node|
|
22
|
+
next contains_indices += node.annotation if node.key == "contains"
|
23
23
|
|
24
|
-
|
24
|
+
i = node.annotation
|
25
25
|
next result.discard if i == true
|
26
|
-
end
|
27
26
|
|
28
|
-
|
29
|
-
next result.discard if i == true
|
27
|
+
last_evaluated_item = i if node.key == "prefixItems" && i > last_evaluated_item
|
30
28
|
end
|
31
29
|
|
32
|
-
contains_indices = Set.new
|
33
|
-
result.parent.collect_annotations(instance, "contains") { |i| contains_indices += Set.new(i) }
|
34
30
|
annotation = nil
|
35
31
|
error = []
|
36
32
|
|
@@ -12,12 +12,13 @@ module JSONSkooma
|
|
12
12
|
if then else dependentSchemas allOf anyOf oneOf not
|
13
13
|
]
|
14
14
|
|
15
|
+
LOOKUP_KEYWORDS = %w[properties patternProperties additionalProperties unevaluatedProperties].freeze
|
16
|
+
|
15
17
|
def evaluate(instance, result)
|
16
18
|
evaluated_names = Set.new
|
17
|
-
result.parent.collect_annotations(instance,
|
18
|
-
|
19
|
-
|
20
|
-
result.parent.collect_annotations(instance, "unevaluatedProperties") { |name| evaluated_names += Set.new(name) }
|
19
|
+
result.parent.collect_annotations(instance, keys: LOOKUP_KEYWORDS) do |node|
|
20
|
+
evaluated_names += node.annotation
|
21
|
+
end
|
21
22
|
|
22
23
|
annotation = []
|
23
24
|
error = []
|
@@ -9,7 +9,7 @@ module JSONSkooma
|
|
9
9
|
def evaluate(instance, result)
|
10
10
|
return if instance == json
|
11
11
|
|
12
|
-
result.failure("The instance value #{instance.value
|
12
|
+
result.failure("The instance value #{instance.value} must be equal to the defined constant #{json.value}")
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
@@ -10,7 +10,7 @@ module JSONSkooma
|
|
10
10
|
return if json.include?(instance)
|
11
11
|
|
12
12
|
result.failure(
|
13
|
-
"The instance value #{instance.value
|
13
|
+
"The instance value #{instance.value} must be equal to one of the elements in the defined enumeration: #{json.value.join(", ")}"
|
14
14
|
)
|
15
15
|
end
|
16
16
|
end
|
@@ -8,10 +8,9 @@ module JSONSkooma
|
|
8
8
|
self.instance_types = "object"
|
9
9
|
|
10
10
|
def evaluate(instance, result)
|
11
|
-
|
12
|
-
return if missing_keys.empty?
|
11
|
+
return if json.value.all? { |val| instance.key?(val) }
|
13
12
|
|
14
|
-
result.failure("The object is missing required properties #{json.value}")
|
13
|
+
result.failure("The object is missing required properties #{json.value.join(", ")}")
|
15
14
|
end
|
16
15
|
end
|
17
16
|
end
|
@@ -21,7 +21,7 @@ module JSONSkooma
|
|
21
21
|
def bootstrap(value)
|
22
22
|
super
|
23
23
|
|
24
|
-
kw = Keywords::Core::Vocabulary.new(self, value.fetch("$vocabulary"
|
24
|
+
kw = Keywords::Core::Vocabulary.new(self, value.fetch("$vocabulary") { default_vocabulary_urls })
|
25
25
|
add_keyword(kw) if value.key?("$vocabulary")
|
26
26
|
end
|
27
27
|
|
data/lib/json_skooma/result.rb
CHANGED
@@ -4,36 +4,68 @@ module JSONSkooma
|
|
4
4
|
class Result
|
5
5
|
attr_writer :ref_schema
|
6
6
|
|
7
|
-
attr_reader :
|
7
|
+
attr_reader :schema, :instance, :parent, :annotation, :error, :key
|
8
8
|
|
9
9
|
def initialize(schema, instance, parent: nil, key: nil)
|
10
|
-
|
11
|
-
|
10
|
+
reset_with(instance, key, parent, schema)
|
11
|
+
end
|
12
|
+
|
13
|
+
def children
|
14
|
+
@children ||= {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def each_children
|
18
|
+
children.each_value do |child|
|
19
|
+
child.each_value do |grandchild|
|
20
|
+
yield grandchild
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def path
|
26
|
+
@path ||= @parent.nil? ? JSONPointer.new([]) : parent.path.child(key)
|
27
|
+
end
|
28
|
+
|
29
|
+
def relative_path
|
30
|
+
@relative_path ||=
|
31
|
+
if @parent.nil?
|
32
|
+
JSONPointer.new([])
|
33
|
+
elsif schema.equal?(parent.schema)
|
34
|
+
parent.relative_path.child(key)
|
35
|
+
else
|
36
|
+
JSONPointer.new_root(key)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def root
|
41
|
+
@root ||= parent&.root || self
|
42
|
+
end
|
43
|
+
|
44
|
+
def reset_with(instance, key, parent, schema = nil)
|
45
|
+
@schema = schema if schema
|
12
46
|
@parent = parent
|
13
|
-
@root = parent&.root || self
|
14
47
|
@key = key
|
15
|
-
@children = {}
|
16
48
|
@valid = true
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
end
|
28
|
-
end
|
49
|
+
@instance = instance
|
50
|
+
|
51
|
+
@children = nil
|
52
|
+
@annotation = nil
|
53
|
+
@discard = false
|
54
|
+
@skip_assertion = false
|
55
|
+
@error = nil
|
56
|
+
@path = nil
|
57
|
+
@ref_schema = nil
|
58
|
+
@relative_path = nil
|
29
59
|
end
|
30
60
|
|
31
|
-
def call(instance, key, schema = nil
|
32
|
-
child =
|
61
|
+
def call(instance, key, schema = nil)
|
62
|
+
child = dup
|
63
|
+
child.reset_with(instance, key, self, schema)
|
33
64
|
|
34
65
|
yield child
|
35
66
|
|
36
|
-
|
67
|
+
return if child.discard?
|
68
|
+
(children[instance.path] ||= {})[key] = child
|
37
69
|
end
|
38
70
|
|
39
71
|
def schema_node
|
@@ -41,7 +73,7 @@ module JSONSkooma
|
|
41
73
|
end
|
42
74
|
|
43
75
|
def sibling(instance, key)
|
44
|
-
@parent
|
76
|
+
@parent&.children&.dig(instance.path, key)
|
45
77
|
end
|
46
78
|
|
47
79
|
def annotate(value)
|
@@ -86,34 +118,36 @@ module JSONSkooma
|
|
86
118
|
schema.canonical_uri.dup.tap { |u| u.fragment = path.to_s }
|
87
119
|
end
|
88
120
|
|
89
|
-
def collect_annotations(instance
|
121
|
+
def collect_annotations(instance, key: nil, keys: nil)
|
90
122
|
return if !valid? || discard?
|
91
123
|
|
92
124
|
if @annotation &&
|
93
125
|
(key.nil? || key == @key) &&
|
94
|
-
(
|
95
|
-
|
126
|
+
(keys.nil? || keys.include?(@key)) &&
|
127
|
+
(instance.path == @instance.path)
|
128
|
+
yield self
|
96
129
|
end
|
97
130
|
|
98
|
-
|
99
|
-
child.collect_annotations(instance, key) do |
|
100
|
-
yield
|
131
|
+
children[instance.path]&.each_value do |child|
|
132
|
+
child.collect_annotations(instance, key: key, keys: keys) do |node|
|
133
|
+
yield node
|
101
134
|
end
|
102
135
|
end
|
103
136
|
end
|
104
137
|
|
105
|
-
def collect_errors(instance: nil,
|
138
|
+
def collect_errors(instance, key: nil, keys: nil)
|
106
139
|
return if valid? || discard?
|
107
140
|
|
108
141
|
if @error &&
|
109
142
|
(key.nil? || key == @key) &&
|
110
|
-
(
|
111
|
-
|
143
|
+
(keys.nil? || keys.include?(@key)) &&
|
144
|
+
(instance.path == @instance.path)
|
145
|
+
yield self
|
112
146
|
end
|
113
147
|
|
114
|
-
|
115
|
-
child.collect_errors(instance, key) do |
|
116
|
-
yield
|
148
|
+
children[instance.path]&.each_value do |child|
|
149
|
+
child.collect_errors(instance, key: key, keys: keys) do |node|
|
150
|
+
yield node
|
117
151
|
end
|
118
152
|
end
|
119
153
|
end
|
@@ -121,5 +155,9 @@ module JSONSkooma
|
|
121
155
|
def output(format, **options)
|
122
156
|
Formatters[format].call(self, **options)
|
123
157
|
end
|
158
|
+
|
159
|
+
def to_s
|
160
|
+
output(:simple)
|
161
|
+
end
|
124
162
|
end
|
125
163
|
end
|
@@ -1,18 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "ipaddr"
|
4
|
-
|
5
3
|
module JSONSkooma
|
6
4
|
module Validators
|
7
5
|
class Ipv4 < Base
|
8
|
-
|
6
|
+
IPV4_ADDRESS = /((25[0-5]|(2[0-4]|1\d|[1-9])?\d)\.?\b){4}/
|
7
|
+
REGEXP = /\A#{IPV4_ADDRESS}\z/
|
9
8
|
|
10
9
|
class << self
|
11
10
|
def call(data)
|
12
|
-
|
13
|
-
raise FormatError, "must be a valid IPv4 address"
|
14
|
-
rescue IPAddr::Error => e
|
15
|
-
raise FormatError, e.message
|
11
|
+
match = REGEXP.match(data)
|
12
|
+
raise FormatError, "must be a valid IPv4 address" if match.nil?
|
16
13
|
end
|
17
14
|
end
|
18
15
|
end
|
@@ -1,15 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "ipaddr"
|
4
|
-
|
5
3
|
module JSONSkooma
|
6
4
|
module Validators
|
7
5
|
class Ipv6 < Base
|
6
|
+
H16 = /\h{1,4}/
|
7
|
+
LS32 = /(?:#{H16}:#{H16})|#{Ipv4::IPV4_ADDRESS}/
|
8
|
+
IPV6_ADDRESS = /(?:#{H16}:){6}#{LS32}|::(?:#{H16}:){5}#{LS32}|(?:#{H16})?::(?:#{H16}:){4}#{LS32}|(?:(?:#{H16}:){0,1}#{H16})?::(?:#{H16}:){3}#{LS32}|(?:(?:#{H16}:){0,2}#{H16})?::(?:#{H16}:){2}#{LS32}|(?:(?:#{H16}:){0,3}#{H16})?::(?:#{H16}:){1}#{LS32}|(?:(?:#{H16}:){0,4}#{H16})?::#{LS32}|(?:(?:#{H16}:){0,5}#{H16})?::#{H16}|(?:(?:#{H16}:){0,6}#{H16})?::/.freeze
|
9
|
+
|
10
|
+
REGEXP = /\A#{IPV6_ADDRESS}\z/
|
11
|
+
|
8
12
|
def call(data)
|
9
|
-
|
10
|
-
raise FormatError, "must be a valid IPv6 address"
|
11
|
-
rescue IPAddr::Error => e
|
12
|
-
raise FormatError, e.message
|
13
|
+
match = REGEXP.match(data)
|
14
|
+
raise FormatError, "must be a valid IPv6 address" if match.nil?
|
13
15
|
end
|
14
16
|
end
|
15
17
|
end
|
@@ -23,7 +23,7 @@ module JSONSkooma
|
|
23
23
|
IPATH_OLD = /#{IPATH_ABEMPTY}|#{IPATH_ABSOLUTE}|#{IPATH_NOSCHEME}|#{IPATH_ROOTLESS}|/.freeze
|
24
24
|
|
25
25
|
IREG_NAME = /(?:#{IUNRESERVED}|#{Uri::PCT_ENCODED}|#{Uri::SUB_DELIMS})*/.freeze
|
26
|
-
IHOST = /#{Uri::IP_LITERAL}|#{
|
26
|
+
IHOST = /#{Uri::IP_LITERAL}|#{Ipv4::IPV4_ADDRESS}|#{IREG_NAME}/.freeze
|
27
27
|
IUSERINFO = /(?:#{IUNRESERVED}|#{Uri::PCT_ENCODED}|#{Uri::SUB_DELIMS}|:)*/.freeze
|
28
28
|
IAUTHORITY = /(?:#{IUSERINFO}@)?#{IHOST}(?::#{Uri::PORT})?/.freeze
|
29
29
|
|
@@ -22,17 +22,12 @@ module JSONSkooma
|
|
22
22
|
PATH_ROOTLESS = /#{SEGMENT_NZ}(?:\/#{SEGMENT})*/.freeze
|
23
23
|
PATH = /(?:#{PATH_ABEMPTY}|#{PATH_ABSOLUTE}|#{PATH_NOSCHEME}|#{PATH_ROOTLESS})?/.freeze
|
24
24
|
|
25
|
-
IPV4_ADDRESS = /((25[0-5]|(2[0-4]|1\d|[1-9])?\d)\.?\b){4}/
|
26
|
-
H16 = /\h{1,4}/
|
27
|
-
LS32 = /(?:#{H16}:#{H16})|#{IPV4_ADDRESS}/
|
28
|
-
IPV6_ADDRESS = /(?:#{H16}:){6}#{LS32}|::(?:#{H16}:){5}#{LS32}|(?:#{H16})?::(?:#{H16}:){4}#{LS32}|(?:(?:#{H16}:){0,1}#{H16})?::(?:#{H16}:){3}#{LS32}|(?:(?:#{H16}:){0,2}#{H16})?::(?:#{H16}:){2}#{LS32}|(?:(?:#{H16}:){0,3}#{H16})?::(?:#{H16}:){1}#{LS32}|(?:(?:#{H16}:){0,4}#{H16})?::#{LS32}|(?:(?:#{H16}:){0,5}#{H16})?::#{H16}|(?:(?:#{H16}:){0,6}#{H16})?::/.freeze
|
29
|
-
|
30
25
|
IPV_FUTURE = /v\h+\.(?:#{UNRESERVED}|#{SUB_DELIMS}|:)+/.freeze
|
31
|
-
IP_LITERAL = /\[(?:#{IPV6_ADDRESS}|#{IPV_FUTURE})\]/.freeze
|
26
|
+
IP_LITERAL = /\[(?:#{Ipv6::IPV6_ADDRESS}|#{IPV_FUTURE})\]/.freeze
|
32
27
|
|
33
28
|
PORT = /\d*/.freeze
|
34
29
|
REG_NAME = /(?:#{UNRESERVED}|#{PCT_ENCODED}|#{SUB_DELIMS})*/.freeze
|
35
|
-
HOST = /(?:#{IP_LITERAL}|#{IPV4_ADDRESS}|#{REG_NAME})/.freeze
|
30
|
+
HOST = /(?:#{IP_LITERAL}|#{Ipv4::IPV4_ADDRESS}|#{REG_NAME})/.freeze
|
36
31
|
USERINFO = /(?:#{UNRESERVED}|#{PCT_ENCODED}|#{SUB_DELIMS}|:)*/.freeze
|
37
32
|
AUTHORITY = /(?:#{USERINFO}@)?#{HOST}(?::#{PORT})?/.freeze
|
38
33
|
|
data/lib/json_skooma/version.rb
CHANGED
data/lib/json_skooma.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: json_skooma
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Svyatoslav Kryukov
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-10-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: zeitwerk
|
@@ -68,7 +68,7 @@ dependencies:
|
|
68
68
|
version: '0.1'
|
69
69
|
description: I bring some sugar for your JSONs.
|
70
70
|
email:
|
71
|
-
-
|
71
|
+
- me@skryukov.dev
|
72
72
|
executables: []
|
73
73
|
extensions: []
|
74
74
|
extra_rdoc_files: []
|
@@ -184,7 +184,6 @@ files:
|
|
184
184
|
- lib/json_skooma/keywords/validation/type.rb
|
185
185
|
- lib/json_skooma/keywords/validation/unique_items.rb
|
186
186
|
- lib/json_skooma/keywords/value_schemas.rb
|
187
|
-
- lib/json_skooma/memoizable.rb
|
188
187
|
- lib/json_skooma/metaschema.rb
|
189
188
|
- lib/json_skooma/registry.rb
|
190
189
|
- lib/json_skooma/result.rb
|
@@ -1,21 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module JSONSkooma
|
4
|
-
module Memoizable
|
5
|
-
def self.included(base)
|
6
|
-
base.extend(Memoizable)
|
7
|
-
end
|
8
|
-
|
9
|
-
def memoize(method_name)
|
10
|
-
this = respond_to?(:prepend) ? self : singleton_class
|
11
|
-
var_name = "@memoized_#{method_name}"
|
12
|
-
this.prepend(Module.new do
|
13
|
-
define_method(method_name) do
|
14
|
-
return instance_variable_get(var_name) if instance_variable_defined?(var_name)
|
15
|
-
|
16
|
-
instance_variable_set(var_name, super())
|
17
|
-
end
|
18
|
-
end)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|