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.
Files changed (105) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -1
  3. data/CHANGELOG.md +15 -0
  4. data/README.md +105 -38
  5. data/lib/jsi/base.rb +349 -155
  6. data/lib/jsi/jsi_coder.rb +5 -4
  7. data/lib/jsi/metaschema_node/bootstrap_schema.rb +100 -0
  8. data/lib/jsi/metaschema_node.rb +156 -129
  9. data/lib/jsi/pathed_node.rb +47 -49
  10. data/lib/jsi/ptr.rb +292 -0
  11. data/lib/jsi/schema/application/child_application/contains.rb +16 -0
  12. data/lib/jsi/schema/application/child_application/draft04.rb +22 -0
  13. data/lib/jsi/schema/application/child_application/draft06.rb +29 -0
  14. data/lib/jsi/schema/application/child_application/draft07.rb +29 -0
  15. data/lib/jsi/schema/application/child_application/items.rb +18 -0
  16. data/lib/jsi/schema/application/child_application/properties.rb +25 -0
  17. data/lib/jsi/schema/application/child_application.rb +40 -0
  18. data/lib/jsi/schema/application/draft04.rb +8 -0
  19. data/lib/jsi/schema/application/draft06.rb +8 -0
  20. data/lib/jsi/schema/application/draft07.rb +8 -0
  21. data/lib/jsi/schema/application/inplace_application/dependencies.rb +28 -0
  22. data/lib/jsi/schema/application/inplace_application/draft04.rb +26 -0
  23. data/lib/jsi/schema/application/inplace_application/draft06.rb +27 -0
  24. data/lib/jsi/schema/application/inplace_application/draft07.rb +33 -0
  25. data/lib/jsi/schema/application/inplace_application/ifthenelse.rb +20 -0
  26. data/lib/jsi/schema/application/inplace_application/ref.rb +18 -0
  27. data/lib/jsi/schema/application/inplace_application/someof.rb +29 -0
  28. data/lib/jsi/schema/application/inplace_application.rb +46 -0
  29. data/lib/jsi/schema/application.rb +12 -0
  30. data/lib/jsi/schema/draft04.rb +14 -0
  31. data/lib/jsi/schema/draft06.rb +14 -0
  32. data/lib/jsi/schema/draft07.rb +14 -0
  33. data/lib/jsi/schema/issue.rb +36 -0
  34. data/lib/jsi/schema/ref.rb +159 -0
  35. data/lib/jsi/schema/schema_ancestor_node.rb +119 -0
  36. data/lib/jsi/schema/validation/array.rb +69 -0
  37. data/lib/jsi/schema/validation/const.rb +20 -0
  38. data/lib/jsi/schema/validation/contains.rb +25 -0
  39. data/lib/jsi/schema/validation/core.rb +39 -0
  40. data/lib/jsi/schema/validation/dependencies.rb +49 -0
  41. data/lib/jsi/schema/validation/draft04/minmax.rb +91 -0
  42. data/lib/jsi/schema/validation/draft04.rb +112 -0
  43. data/lib/jsi/schema/validation/draft06.rb +122 -0
  44. data/lib/jsi/schema/validation/draft07.rb +159 -0
  45. data/lib/jsi/schema/validation/enum.rb +25 -0
  46. data/lib/jsi/schema/validation/ifthenelse.rb +46 -0
  47. data/lib/jsi/schema/validation/items.rb +54 -0
  48. data/lib/jsi/schema/validation/not.rb +20 -0
  49. data/lib/jsi/schema/validation/numeric.rb +121 -0
  50. data/lib/jsi/schema/validation/object.rb +45 -0
  51. data/lib/jsi/schema/validation/pattern.rb +34 -0
  52. data/lib/jsi/schema/validation/properties.rb +101 -0
  53. data/lib/jsi/schema/validation/property_names.rb +32 -0
  54. data/lib/jsi/schema/validation/ref.rb +40 -0
  55. data/lib/jsi/schema/validation/required.rb +27 -0
  56. data/lib/jsi/schema/validation/someof.rb +90 -0
  57. data/lib/jsi/schema/validation/string.rb +47 -0
  58. data/lib/jsi/schema/validation/type.rb +49 -0
  59. data/lib/jsi/schema/validation.rb +51 -0
  60. data/lib/jsi/schema.rb +486 -133
  61. data/lib/jsi/schema_classes.rb +157 -42
  62. data/lib/jsi/schema_registry.rb +141 -0
  63. data/lib/jsi/schema_set.rb +141 -0
  64. data/lib/jsi/simple_wrap.rb +2 -2
  65. data/lib/jsi/typelike_modules.rb +52 -37
  66. data/lib/jsi/util/attr_struct.rb +106 -0
  67. data/lib/jsi/util.rb +141 -25
  68. data/lib/jsi/validation/error.rb +34 -0
  69. data/lib/jsi/validation/result.rb +210 -0
  70. data/lib/jsi/validation.rb +15 -0
  71. data/lib/jsi/version.rb +3 -1
  72. data/lib/jsi.rb +55 -9
  73. data/lib/schemas/json-schema.org/draft-04/schema.rb +8 -3
  74. data/lib/schemas/json-schema.org/draft-06/schema.rb +8 -3
  75. data/lib/schemas/json-schema.org/draft-07/schema.rb +12 -0
  76. data/readme.rb +138 -0
  77. data/{resources}/schemas/json-schema.org/draft-04/schema.json +149 -0
  78. data/{resources}/schemas/json-schema.org/draft-06/schema.json +154 -0
  79. data/{resources}/schemas/json-schema.org/draft-07/schema.json +168 -0
  80. metadata +69 -118
  81. data/.simplecov +0 -3
  82. data/Rakefile.rb +0 -9
  83. data/jsi.gemspec +0 -28
  84. data/lib/jsi/base/to_rb.rb +0 -128
  85. data/lib/jsi/json/node.rb +0 -203
  86. data/lib/jsi/json/pointer.rb +0 -419
  87. data/lib/jsi/json-schema-fragments.rb +0 -61
  88. data/lib/jsi/json.rb +0 -10
  89. data/resources/icons/AGPL-3.0.png +0 -0
  90. data/test/base_array_test.rb +0 -323
  91. data/test/base_hash_test.rb +0 -337
  92. data/test/base_test.rb +0 -486
  93. data/test/jsi_coder_test.rb +0 -85
  94. data/test/jsi_json_arraynode_test.rb +0 -150
  95. data/test/jsi_json_hashnode_test.rb +0 -132
  96. data/test/jsi_json_node_test.rb +0 -257
  97. data/test/jsi_json_pointer_test.rb +0 -102
  98. data/test/jsi_test.rb +0 -11
  99. data/test/jsi_typelike_as_json_test.rb +0 -53
  100. data/test/metaschema_node_test.rb +0 -19
  101. data/test/schema_module_test.rb +0 -21
  102. data/test/schema_test.rb +0 -208
  103. data/test/spreedly_openapi_test.rb +0 -8
  104. data/test/test_helper.rb +0 -97
  105. data/test/util_test.rb +0 -62
@@ -1,31 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JSI
4
- # including class MUST define
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
- # given these, this module represents the node in the document at the path.
6
+ # including class MUST define
12
7
  #
13
- # the node content (#node_content) is the result of evaluating the node document at the path.
8
+ # - `#jsi_document` [Object] the document
9
+ # - `#jsi_ptr` [JSI::Ptr] a pointer to the node in the document
14
10
  module PathedNode
15
- # @return [Object] the content of this node
16
- def node_content
17
- content = node_ptr.evaluate(node_document)
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 node_content is Hash-like (responds to #to_hash)
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__) { node_content_hash_pubsend(:size) } unless block_given?
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
- node_content_hash_pubsend(:each_key) { |k| yield k, self[k] }
33
+ jsi_node_content_hash_pubsend(:each_key) { |k| yield k, self[k, *a] }
43
34
  else
44
- node_content_hash_pubsend(:each_key) { |k| yield [k, self[k]] }
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
- # @return [Hash] a hash in which each key is a key of the node_content hash and
50
- # each value is the result of self[key] (see #[]).
51
- def to_hash
52
- {}.tap { |h| each_key { |k| h[k] = self[k] } }
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 *a, &b are passed to the invocation of method_name
59
- # @return [Object] the result of calling method method_name on the node_content or its #to_hash
60
- def node_content_hash_pubsend(method_name, *a, &b)
61
- if node_content.respond_to?(method_name)
62
- node_content.public_send(method_name, *a, &b)
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
- node_content.to_hash.public_send(method_name, *a, &b)
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
- node_content_hash_pubsend(method_name, *a, &b)
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__) { node_content_ary_pubsend(:size) } unless block_given?
87
- node_content_ary_pubsend(:each_index) { |i| yield(self[i]) }
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
- # @return [Array] an array, the same size as the node_content, in which the
92
- # element at each index is the result of self[index] (see #[])
93
- def to_ary
94
- to_a
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 *a, &b are passed to the invocation of method_name
101
- # @return [Object] the result of calling method method_name on the node_content or its #to_ary
102
- def node_content_ary_pubsend(method_name, *a, &b)
103
- if node_content.respond_to?(method_name)
104
- node_content.public_send(method_name, *a, &b)
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
- node_content.to_ary.public_send(method_name, *a, &b)
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
- node_content_ary_pubsend(method_name, *a, &b)
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
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSI
4
+ module Schema::Application::Draft04
5
+ include Schema::Application::InplaceApplication::Draft04
6
+ include Schema::Application::ChildApplication::Draft04
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSI
4
+ module Schema::Application::Draft06
5
+ include Schema::Application::InplaceApplication::Draft06
6
+ include Schema::Application::ChildApplication::Draft06
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSI
4
+ module Schema::Application::Draft07
5
+ include Schema::Application::InplaceApplication::Draft07
6
+ include Schema::Application::ChildApplication::Draft07
7
+ end
8
+ end