jsi 0.4.0 → 0.6.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/.yardopts +1 -1
- data/CHANGELOG.md +15 -0
- data/README.md +105 -38
- data/lib/jsi/base.rb +349 -155
- data/lib/jsi/jsi_coder.rb +5 -4
- data/lib/jsi/metaschema_node/bootstrap_schema.rb +100 -0
- data/lib/jsi/metaschema_node.rb +156 -129
- data/lib/jsi/pathed_node.rb +47 -49
- data/lib/jsi/ptr.rb +292 -0
- data/lib/jsi/schema/application/child_application/contains.rb +16 -0
- data/lib/jsi/schema/application/child_application/draft04.rb +22 -0
- data/lib/jsi/schema/application/child_application/draft06.rb +29 -0
- data/lib/jsi/schema/application/child_application/draft07.rb +29 -0
- data/lib/jsi/schema/application/child_application/items.rb +18 -0
- data/lib/jsi/schema/application/child_application/properties.rb +25 -0
- data/lib/jsi/schema/application/child_application.rb +40 -0
- data/lib/jsi/schema/application/draft04.rb +8 -0
- data/lib/jsi/schema/application/draft06.rb +8 -0
- data/lib/jsi/schema/application/draft07.rb +8 -0
- data/lib/jsi/schema/application/inplace_application/dependencies.rb +28 -0
- data/lib/jsi/schema/application/inplace_application/draft04.rb +26 -0
- data/lib/jsi/schema/application/inplace_application/draft06.rb +27 -0
- data/lib/jsi/schema/application/inplace_application/draft07.rb +33 -0
- data/lib/jsi/schema/application/inplace_application/ifthenelse.rb +20 -0
- data/lib/jsi/schema/application/inplace_application/ref.rb +18 -0
- data/lib/jsi/schema/application/inplace_application/someof.rb +29 -0
- data/lib/jsi/schema/application/inplace_application.rb +46 -0
- data/lib/jsi/schema/application.rb +12 -0
- data/lib/jsi/schema/draft04.rb +14 -0
- data/lib/jsi/schema/draft06.rb +14 -0
- data/lib/jsi/schema/draft07.rb +14 -0
- data/lib/jsi/schema/issue.rb +36 -0
- data/lib/jsi/schema/ref.rb +159 -0
- data/lib/jsi/schema/schema_ancestor_node.rb +119 -0
- data/lib/jsi/schema/validation/array.rb +69 -0
- data/lib/jsi/schema/validation/const.rb +20 -0
- data/lib/jsi/schema/validation/contains.rb +25 -0
- data/lib/jsi/schema/validation/core.rb +39 -0
- data/lib/jsi/schema/validation/dependencies.rb +49 -0
- data/lib/jsi/schema/validation/draft04/minmax.rb +91 -0
- data/lib/jsi/schema/validation/draft04.rb +112 -0
- data/lib/jsi/schema/validation/draft06.rb +122 -0
- data/lib/jsi/schema/validation/draft07.rb +159 -0
- data/lib/jsi/schema/validation/enum.rb +25 -0
- data/lib/jsi/schema/validation/ifthenelse.rb +46 -0
- data/lib/jsi/schema/validation/items.rb +54 -0
- data/lib/jsi/schema/validation/not.rb +20 -0
- data/lib/jsi/schema/validation/numeric.rb +121 -0
- data/lib/jsi/schema/validation/object.rb +45 -0
- data/lib/jsi/schema/validation/pattern.rb +34 -0
- data/lib/jsi/schema/validation/properties.rb +101 -0
- data/lib/jsi/schema/validation/property_names.rb +32 -0
- data/lib/jsi/schema/validation/ref.rb +40 -0
- data/lib/jsi/schema/validation/required.rb +27 -0
- data/lib/jsi/schema/validation/someof.rb +90 -0
- data/lib/jsi/schema/validation/string.rb +47 -0
- data/lib/jsi/schema/validation/type.rb +49 -0
- data/lib/jsi/schema/validation.rb +51 -0
- data/lib/jsi/schema.rb +486 -133
- data/lib/jsi/schema_classes.rb +157 -42
- data/lib/jsi/schema_registry.rb +141 -0
- data/lib/jsi/schema_set.rb +141 -0
- data/lib/jsi/simple_wrap.rb +2 -2
- data/lib/jsi/typelike_modules.rb +52 -37
- data/lib/jsi/util/attr_struct.rb +106 -0
- data/lib/jsi/util.rb +141 -25
- data/lib/jsi/validation/error.rb +34 -0
- data/lib/jsi/validation/result.rb +210 -0
- data/lib/jsi/validation.rb +15 -0
- data/lib/jsi/version.rb +3 -1
- data/lib/jsi.rb +55 -9
- data/lib/schemas/json-schema.org/draft-04/schema.rb +8 -3
- data/lib/schemas/json-schema.org/draft-06/schema.rb +8 -3
- data/lib/schemas/json-schema.org/draft-07/schema.rb +12 -0
- data/readme.rb +138 -0
- data/{resources}/schemas/json-schema.org/draft-04/schema.json +149 -0
- data/{resources}/schemas/json-schema.org/draft-06/schema.json +154 -0
- data/{resources}/schemas/json-schema.org/draft-07/schema.json +168 -0
- metadata +69 -118
- data/.simplecov +0 -3
- data/Rakefile.rb +0 -9
- data/jsi.gemspec +0 -28
- data/lib/jsi/base/to_rb.rb +0 -128
- data/lib/jsi/json/node.rb +0 -203
- data/lib/jsi/json/pointer.rb +0 -419
- data/lib/jsi/json-schema-fragments.rb +0 -61
- data/lib/jsi/json.rb +0 -10
- data/resources/icons/AGPL-3.0.png +0 -0
- data/test/base_array_test.rb +0 -323
- data/test/base_hash_test.rb +0 -337
- data/test/base_test.rb +0 -486
- data/test/jsi_coder_test.rb +0 -85
- data/test/jsi_json_arraynode_test.rb +0 -150
- data/test/jsi_json_hashnode_test.rb +0 -132
- data/test/jsi_json_node_test.rb +0 -257
- data/test/jsi_json_pointer_test.rb +0 -102
- data/test/jsi_test.rb +0 -11
- data/test/jsi_typelike_as_json_test.rb +0 -53
- data/test/metaschema_node_test.rb +0 -19
- data/test/schema_module_test.rb +0 -21
- data/test/schema_test.rb +0 -208
- data/test/spreedly_openapi_test.rb +0 -8
- data/test/test_helper.rb +0 -97
- data/test/util_test.rb +0 -62
data/lib/jsi/pathed_node.rb
CHANGED
|
@@ -1,31 +1,21 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module JSI
|
|
4
|
-
#
|
|
5
|
-
# - #node_document [Object] returning the document
|
|
6
|
-
# - #node_ptr [JSI::JSON::Pointer] returning a pointer for the node path in the document
|
|
7
|
-
# - #document_root_node [JSI::PathedNode] returning a PathedNode pointing at the document root
|
|
8
|
-
# - #parent_node [JSI::PathedNode] returning the parent node of this PathedNode
|
|
9
|
-
# - #deref [JSI::PathedNode] following a $ref
|
|
4
|
+
# this module represents a node in a document.
|
|
10
5
|
#
|
|
11
|
-
#
|
|
6
|
+
# including class MUST define
|
|
12
7
|
#
|
|
13
|
-
#
|
|
8
|
+
# - `#jsi_document` [Object] the document
|
|
9
|
+
# - `#jsi_ptr` [JSI::Ptr] a pointer to the node in the document
|
|
14
10
|
module PathedNode
|
|
15
|
-
#
|
|
16
|
-
def
|
|
17
|
-
content =
|
|
11
|
+
# the content of this node
|
|
12
|
+
def jsi_node_content
|
|
13
|
+
content = jsi_ptr.evaluate(jsi_document)
|
|
18
14
|
content
|
|
19
15
|
end
|
|
20
|
-
|
|
21
|
-
# @yield [JSI::JSON::Pointer] if a block is given (optional), this will yield a deref'd pointer
|
|
22
|
-
# @return [JSI::JSON::Pointer] our node_ptr, derefed against our node_document
|
|
23
|
-
def node_ptr_deref(&block)
|
|
24
|
-
node_ptr.deref(node_document, &block)
|
|
25
|
-
end
|
|
26
16
|
end
|
|
27
17
|
|
|
28
|
-
# module extending a {JSI::PathedNode} object when its
|
|
18
|
+
# module extending a {JSI::PathedNode} object when its jsi_node_content is Hash-like (responds to #to_hash)
|
|
29
19
|
module PathedHashNode
|
|
30
20
|
# yields each hash key and value of this node.
|
|
31
21
|
#
|
|
@@ -34,41 +24,45 @@ module JSI
|
|
|
34
24
|
#
|
|
35
25
|
# returns an Enumerator if no block is given.
|
|
36
26
|
#
|
|
27
|
+
# @param a arguments are passed to `#[]`
|
|
37
28
|
# @yield [Object, Object] each key and value of this hash node
|
|
38
|
-
# @return [self, Enumerator]
|
|
39
|
-
def each(&block)
|
|
40
|
-
return to_enum(__method__) {
|
|
29
|
+
# @return [self, Enumerator] an Enumerator if invoked without a block; otherwise self
|
|
30
|
+
def each(*a, &block)
|
|
31
|
+
return to_enum(__method__) { jsi_node_content_hash_pubsend(:size) } unless block
|
|
41
32
|
if block.arity > 1
|
|
42
|
-
|
|
33
|
+
jsi_node_content_hash_pubsend(:each_key) { |k| yield k, self[k, *a] }
|
|
43
34
|
else
|
|
44
|
-
|
|
35
|
+
jsi_node_content_hash_pubsend(:each_key) { |k| yield [k, self[k, *a]] }
|
|
45
36
|
end
|
|
46
37
|
self
|
|
47
38
|
end
|
|
48
39
|
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
|
|
52
|
-
|
|
40
|
+
# a hash in which each key is a key of the jsi_node_content hash and each value is the
|
|
41
|
+
# result of `self[key]`
|
|
42
|
+
# @param a arguments are passed to `#[]`
|
|
43
|
+
# @return [Hash]
|
|
44
|
+
def to_hash(*a)
|
|
45
|
+
{}.tap { |h| jsi_node_content_hash_pubsend(:each_key) { |k| h[k] = self[k, *a] } }
|
|
53
46
|
end
|
|
54
47
|
|
|
55
48
|
include Hashlike
|
|
56
49
|
|
|
50
|
+
# invokes the method with the given name on the jsi_node_content (if defined) or its #to_hash
|
|
57
51
|
# @param method_name [String, Symbol]
|
|
58
|
-
# @param
|
|
59
|
-
# @return [Object] the result of calling method method_name on the
|
|
60
|
-
def
|
|
61
|
-
if
|
|
62
|
-
|
|
52
|
+
# @param a arguments and block are passed to the invocation of method_name
|
|
53
|
+
# @return [Object] the result of calling method method_name on the jsi_node_content or its #to_hash
|
|
54
|
+
def jsi_node_content_hash_pubsend(method_name, *a, &b)
|
|
55
|
+
if jsi_node_content.respond_to?(method_name)
|
|
56
|
+
jsi_node_content.public_send(method_name, *a, &b)
|
|
63
57
|
else
|
|
64
|
-
|
|
58
|
+
jsi_node_content.to_hash.public_send(method_name, *a, &b)
|
|
65
59
|
end
|
|
66
60
|
end
|
|
67
61
|
|
|
68
62
|
# methods that don't look at the value; can skip the overhead of #[] (invoked by #to_hash)
|
|
69
63
|
SAFE_KEY_ONLY_METHODS.each do |method_name|
|
|
70
64
|
define_method(method_name) do |*a, &b|
|
|
71
|
-
|
|
65
|
+
jsi_node_content_hash_pubsend(method_name, *a, &b)
|
|
72
66
|
end
|
|
73
67
|
end
|
|
74
68
|
end
|
|
@@ -80,30 +74,34 @@ module JSI
|
|
|
80
74
|
#
|
|
81
75
|
# returns an Enumerator if no block is given.
|
|
82
76
|
#
|
|
77
|
+
# @param a arguments are passed to `#[]`
|
|
83
78
|
# @yield [Object] each element of this array node
|
|
84
|
-
# @return [self, Enumerator]
|
|
85
|
-
def each
|
|
86
|
-
return to_enum(__method__) {
|
|
87
|
-
|
|
79
|
+
# @return [self, Enumerator] an Enumerator if invoked without a block; otherwise self
|
|
80
|
+
def each(*a, &block)
|
|
81
|
+
return to_enum(__method__) { jsi_node_content_ary_pubsend(:size) } unless block
|
|
82
|
+
jsi_node_content_ary_pubsend(:each_index) { |i| yield(self[i, *a]) }
|
|
88
83
|
self
|
|
89
84
|
end
|
|
90
85
|
|
|
91
|
-
#
|
|
92
|
-
#
|
|
93
|
-
|
|
94
|
-
|
|
86
|
+
# an array, the same size as the jsi_node_content, in which the element at each index is the
|
|
87
|
+
# result of `self[index]`
|
|
88
|
+
# @param a arguments are passed to `#[]`
|
|
89
|
+
# @return [Array]
|
|
90
|
+
def to_ary(*a)
|
|
91
|
+
to_a(*a)
|
|
95
92
|
end
|
|
96
93
|
|
|
97
94
|
include Arraylike
|
|
98
95
|
|
|
96
|
+
# invokes the method with the given name on the jsi_node_content (if defined) or its #to_ary
|
|
99
97
|
# @param method_name [String, Symbol]
|
|
100
|
-
# @param
|
|
101
|
-
# @return [Object] the result of calling method method_name on the
|
|
102
|
-
def
|
|
103
|
-
if
|
|
104
|
-
|
|
98
|
+
# @param a arguments and block are passed to the invocation of method_name
|
|
99
|
+
# @return [Object] the result of calling method method_name on the jsi_node_content or its #to_ary
|
|
100
|
+
def jsi_node_content_ary_pubsend(method_name, *a, &b)
|
|
101
|
+
if jsi_node_content.respond_to?(method_name)
|
|
102
|
+
jsi_node_content.public_send(method_name, *a, &b)
|
|
105
103
|
else
|
|
106
|
-
|
|
104
|
+
jsi_node_content.to_ary.public_send(method_name, *a, &b)
|
|
107
105
|
end
|
|
108
106
|
end
|
|
109
107
|
|
|
@@ -111,7 +109,7 @@ module JSI
|
|
|
111
109
|
# we override these methods from Arraylike
|
|
112
110
|
SAFE_INDEX_ONLY_METHODS.each do |method_name|
|
|
113
111
|
define_method(method_name) do |*a, &b|
|
|
114
|
-
|
|
112
|
+
jsi_node_content_ary_pubsend(method_name, *a, &b)
|
|
115
113
|
end
|
|
116
114
|
end
|
|
117
115
|
end
|
data/lib/jsi/ptr.rb
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
# a representation to work with JSON Pointer, as described by RFC 6901 https://tools.ietf.org/html/rfc6901
|
|
5
|
+
#
|
|
6
|
+
# a pointer is a sequence of tokens pointing to a node in a document.
|
|
7
|
+
class Ptr
|
|
8
|
+
class Error < StandardError
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# raised when attempting to parse a JSON Pointer string with invalid syntax
|
|
12
|
+
class PointerSyntaxError < Error
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# raised when a pointer refers to a path in a document that could not be resolved
|
|
16
|
+
class ResolutionError < Error
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# instantiates a pointer or returns the given pointer
|
|
20
|
+
# @param ary_ptr [#to_ary, JSI::Ptr] an array of tokens, or a pointer
|
|
21
|
+
# @return [JSI::Ptr]
|
|
22
|
+
def self.ary_ptr(ary_ptr)
|
|
23
|
+
if ary_ptr.is_a?(Ptr)
|
|
24
|
+
ary_ptr
|
|
25
|
+
else
|
|
26
|
+
new(ary_ptr)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# instantiates a pointer from the given tokens.
|
|
31
|
+
#
|
|
32
|
+
# JSI::Ptr[]
|
|
33
|
+
#
|
|
34
|
+
# instantes a root pointer.
|
|
35
|
+
#
|
|
36
|
+
# JSI::Ptr['a', 'b']
|
|
37
|
+
# JSI::Ptr['a']['b']
|
|
38
|
+
#
|
|
39
|
+
# are both ways to instantiate a pointer with tokens ['a', 'b']. the latter example chains the
|
|
40
|
+
# class .[] method with the instance #[] method.
|
|
41
|
+
#
|
|
42
|
+
# @param tokens any number of tokens
|
|
43
|
+
# @return [JSI::Ptr]
|
|
44
|
+
def self.[](*tokens)
|
|
45
|
+
new(tokens)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# parse a URI-escaped fragment and instantiate as a JSI::Ptr
|
|
49
|
+
#
|
|
50
|
+
# JSI::Ptr.from_fragment('/foo/bar')
|
|
51
|
+
# => JSI::Ptr["foo", "bar"]
|
|
52
|
+
#
|
|
53
|
+
# with URI escaping:
|
|
54
|
+
#
|
|
55
|
+
# JSI::Ptr.from_fragment('/foo%20bar')
|
|
56
|
+
# => JSI::Ptr["foo bar"]
|
|
57
|
+
#
|
|
58
|
+
# @param fragment [String] a fragment containing a pointer
|
|
59
|
+
# @return [JSI::Ptr]
|
|
60
|
+
# @raise [JSI::Ptr::PointerSyntaxError] when the fragment does not contain a pointer with
|
|
61
|
+
# valid pointer syntax
|
|
62
|
+
def self.from_fragment(fragment)
|
|
63
|
+
from_pointer(Addressable::URI.unescape(fragment))
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# parse a pointer string and instantiate as a JSI::Ptr
|
|
67
|
+
#
|
|
68
|
+
# JSI::Ptr.from_pointer('/foo')
|
|
69
|
+
# => JSI::Ptr["foo"]
|
|
70
|
+
#
|
|
71
|
+
# JSI::Ptr.from_pointer('/foo~0bar/baz~1qux')
|
|
72
|
+
# => JSI::Ptr["foo~bar", "baz/qux"]
|
|
73
|
+
#
|
|
74
|
+
# @param pointer_string [String] a pointer string
|
|
75
|
+
# @return [JSI::Ptr]
|
|
76
|
+
# @raise [JSI::Ptr::PointerSyntaxError] when the pointer_string does not have valid pointer syntax
|
|
77
|
+
def self.from_pointer(pointer_string)
|
|
78
|
+
tokens = pointer_string.split('/', -1).map! do |piece|
|
|
79
|
+
piece.gsub('~1', '/').gsub('~0', '~')
|
|
80
|
+
end
|
|
81
|
+
if tokens[0] == ''
|
|
82
|
+
new(tokens[1..-1])
|
|
83
|
+
elsif tokens.empty?
|
|
84
|
+
new(tokens)
|
|
85
|
+
else
|
|
86
|
+
raise(PointerSyntaxError, "Invalid pointer syntax in #{pointer_string.inspect}: pointer must begin with /")
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# initializes a JSI::Ptr from the given tokens.
|
|
91
|
+
#
|
|
92
|
+
# @param tokens [Array<Object>]
|
|
93
|
+
def initialize(tokens)
|
|
94
|
+
unless tokens.respond_to?(:to_ary)
|
|
95
|
+
raise(TypeError, "tokens must be an array. got: #{tokens.inspect}")
|
|
96
|
+
end
|
|
97
|
+
@tokens = tokens.to_ary.map(&:freeze).freeze
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
attr_reader :tokens
|
|
101
|
+
|
|
102
|
+
# @private @deprecated
|
|
103
|
+
alias_method :reference_tokens, :tokens
|
|
104
|
+
|
|
105
|
+
# takes a root json document and evaluates this pointer through the document, returning the value
|
|
106
|
+
# pointed to by this pointer.
|
|
107
|
+
#
|
|
108
|
+
# @param document [#to_ary, #to_hash] the document against which we will evaluate this pointer
|
|
109
|
+
# @param a arguments are passed to each invocation of `#[]`
|
|
110
|
+
# @return [Object] the content of the document pointed to by this pointer
|
|
111
|
+
# @raise [JSI::Ptr::ResolutionError] the document does not contain the path this pointer references
|
|
112
|
+
def evaluate(document, *a)
|
|
113
|
+
res = tokens.inject(document) do |value, token|
|
|
114
|
+
_, child = node_subscript_token_child(value, token, *a)
|
|
115
|
+
child
|
|
116
|
+
end
|
|
117
|
+
res
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# the pointer string representation of this pointer
|
|
121
|
+
# @return [String]
|
|
122
|
+
def pointer
|
|
123
|
+
tokens.map { |t| '/' + t.to_s.gsub('~', '~0').gsub('/', '~1') }.join('')
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# the fragment string representation of this pointer
|
|
127
|
+
# @return [String]
|
|
128
|
+
def fragment
|
|
129
|
+
Addressable::URI.escape(pointer)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# a URI consisting of a fragment containing this pointer's fragment string representation
|
|
133
|
+
# @return [Addressable::URI]
|
|
134
|
+
def uri
|
|
135
|
+
Addressable::URI.new(fragment: fragment)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# whether this pointer is empty, i.e. it has no tokens
|
|
139
|
+
# @return [Boolean]
|
|
140
|
+
def empty?
|
|
141
|
+
tokens.empty?
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# whether this is a root pointer, indicated by an empty array of tokens
|
|
145
|
+
# @return [Boolean]
|
|
146
|
+
alias_method :root?, :empty?
|
|
147
|
+
|
|
148
|
+
# pointer to the parent of where this pointer points
|
|
149
|
+
# @return [JSI::Ptr]
|
|
150
|
+
# @raise [JSI::Ptr::Error] if this pointer has no parent (points to the root)
|
|
151
|
+
def parent
|
|
152
|
+
if root?
|
|
153
|
+
raise(Ptr::Error, "cannot access parent of root pointer: #{pretty_inspect.chomp}")
|
|
154
|
+
else
|
|
155
|
+
Ptr.new(tokens[0...-1])
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# whether this pointer contains the other_ptr - that is, whether this pointer is an ancestor
|
|
160
|
+
# of `other_ptr`, a child pointer. `contains?` is inclusive; a pointer does contain itself.
|
|
161
|
+
# @return [Boolean]
|
|
162
|
+
def contains?(other_ptr)
|
|
163
|
+
self.tokens == other_ptr.tokens[0...self.tokens.size]
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# part of this pointer relative to the given ancestor_ptr
|
|
167
|
+
# @return [JSI::Ptr]
|
|
168
|
+
# @raise [JSI::Ptr::Error] if the given ancestor_ptr is not an ancestor of this pointer
|
|
169
|
+
def ptr_relative_to(ancestor_ptr)
|
|
170
|
+
unless ancestor_ptr.contains?(self)
|
|
171
|
+
raise(Error, "ancestor_ptr #{ancestor_ptr.inspect} is not ancestor of #{inspect}")
|
|
172
|
+
end
|
|
173
|
+
Ptr.new(tokens[ancestor_ptr.tokens.size..-1])
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# a pointer with the tokens of this one plus the given `ptr`'s.
|
|
177
|
+
# @param ptr [JSI::Ptr, #to_ary]
|
|
178
|
+
# @return [JSI::Ptr]
|
|
179
|
+
def +(ptr)
|
|
180
|
+
if ptr.is_a?(Ptr)
|
|
181
|
+
ptr_tokens = ptr.tokens
|
|
182
|
+
elsif ptr.respond_to?(:to_ary)
|
|
183
|
+
ptr_tokens = ptr
|
|
184
|
+
else
|
|
185
|
+
raise(TypeError, "ptr must be a JSI::Ptr or Array of tokens; got: #{ptr.inspect}")
|
|
186
|
+
end
|
|
187
|
+
Ptr.new(self.tokens + ptr_tokens)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# a pointer consisting of the first `n` of our tokens
|
|
191
|
+
# @param n [Integer]
|
|
192
|
+
# @return [JSI::Ptr]
|
|
193
|
+
# @raise [ArgumentError] if n is not between 0 and the size of our tokens
|
|
194
|
+
def take(n)
|
|
195
|
+
unless (0..tokens.size).include?(n)
|
|
196
|
+
raise(ArgumentError, "n not in range (0..#{tokens.size}): #{n.inspect}")
|
|
197
|
+
end
|
|
198
|
+
Ptr.new(tokens.take(n))
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# appends the given token to this pointer's tokens and returns the result
|
|
202
|
+
#
|
|
203
|
+
# @param token [Object]
|
|
204
|
+
# @return [JSI::Ptr] pointer to a child node of this pointer with the given token
|
|
205
|
+
def [](token)
|
|
206
|
+
Ptr.new(tokens + [token])
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# takes a document and a block. the block is yielded the content of the given document at this
|
|
210
|
+
# pointer's location. the block must result a modified copy of that content (and MUST NOT modify
|
|
211
|
+
# the object it is given). this modified copy of that content is incorporated into a modified copy
|
|
212
|
+
# of the given document, which is then returned. the structure and contents of the document outside
|
|
213
|
+
# the path pointed to by this pointer is not modified.
|
|
214
|
+
#
|
|
215
|
+
# @param document [Object] the document to apply this pointer to
|
|
216
|
+
# @yield [Object] the content this pointer applies to in the given document
|
|
217
|
+
# the block must result in the new content which will be placed in the modified document copy.
|
|
218
|
+
# @return [Object] a copy of the given document, with the content this pointer applies to
|
|
219
|
+
# replaced by the result of the block
|
|
220
|
+
def modified_document_copy(document, &block)
|
|
221
|
+
# we need to preserve the rest of the document, but modify the content at our path.
|
|
222
|
+
#
|
|
223
|
+
# this is actually a bit tricky. we can't modify the original document, obviously.
|
|
224
|
+
# we could do a deep copy, but that's expensive. instead, we make a copy of each array
|
|
225
|
+
# or hash in the path above the node we point to. this node's content is modified by the
|
|
226
|
+
# caller, and that is recursively merged up to the document root.
|
|
227
|
+
if empty?
|
|
228
|
+
Typelike.modified_copy(document, &block)
|
|
229
|
+
else
|
|
230
|
+
car = tokens[0]
|
|
231
|
+
cdr = Ptr.new(tokens[1..-1])
|
|
232
|
+
token, document_child = node_subscript_token_child(document, car)
|
|
233
|
+
modified_document_child = cdr.modified_document_copy(document_child, &block)
|
|
234
|
+
if modified_document_child.object_id == document_child.object_id
|
|
235
|
+
document
|
|
236
|
+
else
|
|
237
|
+
modified_document = document.respond_to?(:[]=) ? document.dup :
|
|
238
|
+
document.respond_to?(:to_hash) ? document.to_hash.dup :
|
|
239
|
+
document.respond_to?(:to_ary) ? document.to_ary.dup :
|
|
240
|
+
raise(Bug) # not possible; node_subscript_token_child would have raised
|
|
241
|
+
modified_document[token] = modified_document_child
|
|
242
|
+
modified_document
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# a string representation of this pointer
|
|
248
|
+
# @return [String]
|
|
249
|
+
def inspect
|
|
250
|
+
"#{self.class.name}[#{tokens.map(&:inspect).join(", ")}]"
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
alias_method :to_s, :inspect
|
|
254
|
+
|
|
255
|
+
# pointers are equal if the tokens are equal
|
|
256
|
+
def jsi_fingerprint
|
|
257
|
+
{class: Ptr, tokens: tokens}
|
|
258
|
+
end
|
|
259
|
+
include Util::FingerprintHash
|
|
260
|
+
|
|
261
|
+
private
|
|
262
|
+
|
|
263
|
+
def node_subscript_token_child(value, token, *a)
|
|
264
|
+
if value.respond_to?(:to_ary)
|
|
265
|
+
if token.is_a?(String) && token =~ /\A\d|[1-9]\d+\z/
|
|
266
|
+
token = token.to_i
|
|
267
|
+
elsif token == '-'
|
|
268
|
+
# per rfc6901, - refers "to the (nonexistent) member after the last array element" and is
|
|
269
|
+
# expected to raise an error condition.
|
|
270
|
+
raise(ResolutionError, "Invalid resolution: #{token.inspect} refers to a nonexistent element in array #{value.inspect}")
|
|
271
|
+
end
|
|
272
|
+
unless token.is_a?(Integer)
|
|
273
|
+
raise(ResolutionError, "Invalid resolution: #{token.inspect} is not an integer and cannot be resolved in array #{value.inspect}")
|
|
274
|
+
end
|
|
275
|
+
unless (0...(value.respond_to?(:size) ? value : value.to_ary).size).include?(token)
|
|
276
|
+
raise(ResolutionError, "Invalid resolution: #{token.inspect} is not a valid index of #{value.inspect}")
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
child = (value.respond_to?(:[]) ? value : value.to_ary)[token, *a]
|
|
280
|
+
elsif value.respond_to?(:to_hash)
|
|
281
|
+
unless (value.respond_to?(:key?) ? value : value.to_hash).key?(token)
|
|
282
|
+
raise(ResolutionError, "Invalid resolution: #{token.inspect} is not a valid key of #{value.inspect}")
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
child = (value.respond_to?(:[]) ? value : value.to_hash)[token, *a]
|
|
286
|
+
else
|
|
287
|
+
raise(ResolutionError, "Invalid resolution: #{token.inspect} cannot be resolved in #{value.inspect}")
|
|
288
|
+
end
|
|
289
|
+
[token, child]
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema::Application::ChildApplication::Contains
|
|
5
|
+
# @private
|
|
6
|
+
def internal_applicate_contains(idx, instance, &block)
|
|
7
|
+
if schema_content.key?('contains')
|
|
8
|
+
contains_schema = subschema(['contains'])
|
|
9
|
+
|
|
10
|
+
if contains_schema.instance_valid?(instance[idx])
|
|
11
|
+
yield contains_schema
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema::Application::ChildApplication::Draft04
|
|
5
|
+
include Schema::Application::ChildApplication
|
|
6
|
+
include Schema::Application::ChildApplication::Items
|
|
7
|
+
include Schema::Application::ChildApplication::Properties
|
|
8
|
+
|
|
9
|
+
# @private
|
|
10
|
+
def internal_child_applicate_keywords(token, instance, &block)
|
|
11
|
+
if instance.respond_to?(:to_ary)
|
|
12
|
+
# 5.3.1. additionalItems and items
|
|
13
|
+
internal_applicate_items(token, &block)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
if instance.respond_to?(:to_hash)
|
|
17
|
+
# 5.4.4. additionalProperties, properties and patternProperties
|
|
18
|
+
internal_applicate_properties(token, &block)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema::Application::ChildApplication::Draft06
|
|
5
|
+
include Schema::Application::ChildApplication
|
|
6
|
+
include Schema::Application::ChildApplication::Items
|
|
7
|
+
include Schema::Application::ChildApplication::Contains
|
|
8
|
+
include Schema::Application::ChildApplication::Properties
|
|
9
|
+
|
|
10
|
+
# @private
|
|
11
|
+
def internal_child_applicate_keywords(token, instance, &block)
|
|
12
|
+
if instance.respond_to?(:to_ary)
|
|
13
|
+
# json-schema-validation 6.9. items
|
|
14
|
+
# json-schema-validation 6.10. additionalItems
|
|
15
|
+
internal_applicate_items(token, &block)
|
|
16
|
+
|
|
17
|
+
# json-schema-validation 6.14. contains
|
|
18
|
+
internal_applicate_contains(token, instance, &block)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
if instance.respond_to?(:to_hash)
|
|
22
|
+
# json-schema-validation 6.18. properties
|
|
23
|
+
# json-schema-validation 6.19. patternProperties
|
|
24
|
+
# json-schema-validation 6.20. additionalProperties
|
|
25
|
+
internal_applicate_properties(token, &block)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema::Application::ChildApplication::Draft07
|
|
5
|
+
include Schema::Application::ChildApplication
|
|
6
|
+
include Schema::Application::ChildApplication::Items
|
|
7
|
+
include Schema::Application::ChildApplication::Contains
|
|
8
|
+
include Schema::Application::ChildApplication::Properties
|
|
9
|
+
|
|
10
|
+
# @private
|
|
11
|
+
def internal_child_applicate_keywords(token, instance, &block)
|
|
12
|
+
if instance.respond_to?(:to_ary)
|
|
13
|
+
# 6.4.1. items
|
|
14
|
+
# 6.4.2. additionalItems
|
|
15
|
+
internal_applicate_items(token, &block)
|
|
16
|
+
|
|
17
|
+
# 6.4.6. contains
|
|
18
|
+
internal_applicate_contains(token, instance, &block)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
if instance.respond_to?(:to_hash)
|
|
22
|
+
# 6.5.4. properties
|
|
23
|
+
# 6.5.5. patternProperties
|
|
24
|
+
# 6.5.6. additionalProperties
|
|
25
|
+
internal_applicate_properties(token, &block)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema::Application::ChildApplication::Items
|
|
5
|
+
# @private
|
|
6
|
+
def internal_applicate_items(idx, &block)
|
|
7
|
+
if schema_content['items'].respond_to?(:to_ary)
|
|
8
|
+
if schema_content['items'].each_index.to_a.include?(idx)
|
|
9
|
+
yield subschema(['items', idx])
|
|
10
|
+
elsif schema_content.key?('additionalItems')
|
|
11
|
+
yield subschema(['additionalItems'])
|
|
12
|
+
end
|
|
13
|
+
elsif schema_content.key?('items')
|
|
14
|
+
yield subschema(['items'])
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema::Application::ChildApplication::Properties
|
|
5
|
+
# @private
|
|
6
|
+
def internal_applicate_properties(property_name, &block)
|
|
7
|
+
apply_additional = true
|
|
8
|
+
if schema_content.key?('properties') && schema_content['properties'].respond_to?(:to_hash) && schema_content['properties'].key?(property_name)
|
|
9
|
+
apply_additional = false
|
|
10
|
+
yield subschema(['properties', property_name])
|
|
11
|
+
end
|
|
12
|
+
if schema_content['patternProperties'].respond_to?(:to_hash)
|
|
13
|
+
schema_content['patternProperties'].each_key do |pattern|
|
|
14
|
+
if pattern.respond_to?(:to_str) && property_name.to_s =~ Regexp.new(pattern) # TODO map pattern to ruby syntax
|
|
15
|
+
apply_additional = false
|
|
16
|
+
yield subschema(['patternProperties', pattern])
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
if apply_additional && schema_content.key?('additionalProperties')
|
|
21
|
+
yield subschema(['additionalProperties'])
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema::Application::ChildApplication
|
|
5
|
+
autoload :Draft04, 'jsi/schema/application/child_application/draft04'
|
|
6
|
+
autoload :Draft06, 'jsi/schema/application/child_application/draft06'
|
|
7
|
+
autoload :Draft07, 'jsi/schema/application/child_application/draft07'
|
|
8
|
+
|
|
9
|
+
autoload :Items, 'jsi/schema/application/child_application/items'
|
|
10
|
+
autoload :Contains, 'jsi/schema/application/child_application/contains'
|
|
11
|
+
autoload :Properties, 'jsi/schema/application/child_application/properties'
|
|
12
|
+
|
|
13
|
+
# a set of child applicator subschemas of this schema which apply to the child of the given instance
|
|
14
|
+
# on the given token.
|
|
15
|
+
#
|
|
16
|
+
# @param token [Object] the array index or object property name for the child instance
|
|
17
|
+
# @param instance [Object] the instance to check any child applicators against
|
|
18
|
+
# @return [JSI::SchemaSet] child applicator subschemas of this schema for the given token
|
|
19
|
+
# of the instance
|
|
20
|
+
def child_applicator_schemas(token, instance)
|
|
21
|
+
SchemaSet.new(each_child_applicator_schema(token, instance))
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# yields each child applicator subschema (from properties, items, etc.) which applies to the child of
|
|
25
|
+
# the given instance on the given token.
|
|
26
|
+
#
|
|
27
|
+
# @param (see #child_applicator_schemas)
|
|
28
|
+
# @yield [JSI::Schema]
|
|
29
|
+
# @return [nil, Enumerator] an Enumerator if invoked without a block; otherwise nil
|
|
30
|
+
def each_child_applicator_schema(token, instance, &block)
|
|
31
|
+
return to_enum(__method__, token, instance) unless block
|
|
32
|
+
|
|
33
|
+
if schema_content.respond_to?(:to_hash)
|
|
34
|
+
internal_child_applicate_keywords(token, instance, &block)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
nil
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|