jsi-dev 0.0.0.pre.commonmarker

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +8 -0
  3. data/CHANGELOG.md +101 -0
  4. data/LICENSE.md +613 -0
  5. data/README.md +303 -0
  6. data/docs/glossary.md +281 -0
  7. data/jsi.gemspec +30 -0
  8. data/lib/jsi/base/node.rb +373 -0
  9. data/lib/jsi/base.rb +738 -0
  10. data/lib/jsi/jsi_coder.rb +92 -0
  11. data/lib/jsi/metaschema.rb +6 -0
  12. data/lib/jsi/metaschema_node/bootstrap_schema.rb +126 -0
  13. data/lib/jsi/metaschema_node.rb +262 -0
  14. data/lib/jsi/ptr.rb +314 -0
  15. data/lib/jsi/schema/application/child_application/contains.rb +25 -0
  16. data/lib/jsi/schema/application/child_application/draft04.rb +21 -0
  17. data/lib/jsi/schema/application/child_application/draft06.rb +28 -0
  18. data/lib/jsi/schema/application/child_application/draft07.rb +28 -0
  19. data/lib/jsi/schema/application/child_application/items.rb +18 -0
  20. data/lib/jsi/schema/application/child_application/properties.rb +25 -0
  21. data/lib/jsi/schema/application/child_application.rb +13 -0
  22. data/lib/jsi/schema/application/draft04.rb +8 -0
  23. data/lib/jsi/schema/application/draft06.rb +8 -0
  24. data/lib/jsi/schema/application/draft07.rb +8 -0
  25. data/lib/jsi/schema/application/inplace_application/dependencies.rb +28 -0
  26. data/lib/jsi/schema/application/inplace_application/draft04.rb +25 -0
  27. data/lib/jsi/schema/application/inplace_application/draft06.rb +26 -0
  28. data/lib/jsi/schema/application/inplace_application/draft07.rb +32 -0
  29. data/lib/jsi/schema/application/inplace_application/ifthenelse.rb +20 -0
  30. data/lib/jsi/schema/application/inplace_application/ref.rb +18 -0
  31. data/lib/jsi/schema/application/inplace_application/someof.rb +44 -0
  32. data/lib/jsi/schema/application/inplace_application.rb +14 -0
  33. data/lib/jsi/schema/application.rb +12 -0
  34. data/lib/jsi/schema/draft04.rb +13 -0
  35. data/lib/jsi/schema/draft06.rb +13 -0
  36. data/lib/jsi/schema/draft07.rb +13 -0
  37. data/lib/jsi/schema/issue.rb +36 -0
  38. data/lib/jsi/schema/ref.rb +183 -0
  39. data/lib/jsi/schema/schema_ancestor_node.rb +122 -0
  40. data/lib/jsi/schema/validation/array.rb +69 -0
  41. data/lib/jsi/schema/validation/const.rb +20 -0
  42. data/lib/jsi/schema/validation/contains.rb +25 -0
  43. data/lib/jsi/schema/validation/dependencies.rb +49 -0
  44. data/lib/jsi/schema/validation/draft04/minmax.rb +91 -0
  45. data/lib/jsi/schema/validation/draft04.rb +110 -0
  46. data/lib/jsi/schema/validation/draft06.rb +120 -0
  47. data/lib/jsi/schema/validation/draft07.rb +157 -0
  48. data/lib/jsi/schema/validation/enum.rb +25 -0
  49. data/lib/jsi/schema/validation/ifthenelse.rb +46 -0
  50. data/lib/jsi/schema/validation/items.rb +54 -0
  51. data/lib/jsi/schema/validation/not.rb +20 -0
  52. data/lib/jsi/schema/validation/numeric.rb +121 -0
  53. data/lib/jsi/schema/validation/object.rb +45 -0
  54. data/lib/jsi/schema/validation/pattern.rb +34 -0
  55. data/lib/jsi/schema/validation/properties.rb +101 -0
  56. data/lib/jsi/schema/validation/property_names.rb +32 -0
  57. data/lib/jsi/schema/validation/ref.rb +40 -0
  58. data/lib/jsi/schema/validation/required.rb +27 -0
  59. data/lib/jsi/schema/validation/someof.rb +90 -0
  60. data/lib/jsi/schema/validation/string.rb +47 -0
  61. data/lib/jsi/schema/validation/type.rb +49 -0
  62. data/lib/jsi/schema/validation.rb +49 -0
  63. data/lib/jsi/schema.rb +792 -0
  64. data/lib/jsi/schema_classes.rb +357 -0
  65. data/lib/jsi/schema_registry.rb +190 -0
  66. data/lib/jsi/schema_set.rb +219 -0
  67. data/lib/jsi/simple_wrap.rb +26 -0
  68. data/lib/jsi/util/private/attr_struct.rb +130 -0
  69. data/lib/jsi/util/private/memo_map.rb +75 -0
  70. data/lib/jsi/util/private.rb +202 -0
  71. data/lib/jsi/util/typelike.rb +225 -0
  72. data/lib/jsi/util.rb +227 -0
  73. data/lib/jsi/validation/error.rb +34 -0
  74. data/lib/jsi/validation/result.rb +212 -0
  75. data/lib/jsi/validation.rb +15 -0
  76. data/lib/jsi/version.rb +5 -0
  77. data/lib/jsi.rb +105 -0
  78. data/lib/schemas/json-schema.org/draft-04/schema.rb +169 -0
  79. data/lib/schemas/json-schema.org/draft-06/schema.rb +171 -0
  80. data/lib/schemas/json-schema.org/draft-07/schema.rb +198 -0
  81. data/readme.rb +138 -0
  82. data/{resources}/schemas/json-schema.org/draft-04/schema.json +149 -0
  83. data/{resources}/schemas/json-schema.org/draft-06/schema.json +154 -0
  84. data/{resources}/schemas/json-schema.org/draft-07/schema.json +168 -0
  85. metadata +155 -0
@@ -0,0 +1,373 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSI
4
+ module Base::Enumerable
5
+ include ::Enumerable
6
+
7
+ # an Array containing each item in this JSI.
8
+ #
9
+ # @param kw keyword arguments are passed to {Base#[]} - see its keyword params
10
+ # @return [Array]
11
+ def to_a(**kw)
12
+ # TODO remove eventually (keyword argument compatibility)
13
+ # discard when all supported ruby versions Enumerable#to_a delegate keywords to #each (3.0.1 breaks; 2.7.x warns)
14
+ # https://bugs.ruby-lang.org/issues/18289
15
+ ary = []
16
+ each(**kw) do |e|
17
+ ary << e
18
+ end
19
+ ary.freeze
20
+ end
21
+
22
+ alias_method :entries, :to_a
23
+ end
24
+
25
+ # module extending a {JSI::Base} object when its instance (its {Base#jsi_node_content})
26
+ # is a Hash (or responds to `#to_hash`)
27
+ module Base::HashNode
28
+ include Base::Enumerable
29
+
30
+ # instantiates and yields each property name (hash key) as a JSI described by any `propertyNames` schemas.
31
+ #
32
+ # @yield [JSI::Base]
33
+ # @return [nil, Enumerator] an Enumerator if invoked without a block; otherwise nil
34
+ def jsi_each_propertyName
35
+ return to_enum(__method__) { jsi_node_content_hash_pubsend(:size) } unless block_given?
36
+
37
+ property_schemas = SchemaSet.build do |schemas|
38
+ jsi_schemas.each do |s|
39
+ if s.keyword?('propertyNames') && s['propertyNames'].is_a?(Schema)
40
+ schemas << s['propertyNames']
41
+ end
42
+ end
43
+ end
44
+ jsi_node_content_hash_pubsend(:each_key) do |key|
45
+ yield property_schemas.new_jsi(key)
46
+ end
47
+
48
+ nil
49
+ end
50
+
51
+ # See {Base#jsi_hash?}. Always true for HashNode.
52
+ def jsi_hash?
53
+ true
54
+ end
55
+
56
+ # Yields each key - see {Base#jsi_each_child_token}
57
+ def jsi_each_child_token(&block)
58
+ return to_enum(__method__) { jsi_node_content_hash_pubsend(:size) } unless block
59
+ jsi_node_content_hash_pubsend(:each_key, &block)
60
+ nil
61
+ end
62
+
63
+ # See {Base#jsi_child_token_in_range?}
64
+ def jsi_child_token_in_range?(token)
65
+ jsi_node_content_hash_pubsend(:key?, token)
66
+ end
67
+
68
+ # See {Base#jsi_node_content_child}
69
+ def jsi_node_content_child(token)
70
+ # I could check token_in_range? and return nil here (as ArrayNode does).
71
+ # without that check, if the instance defines Hash#default or #default_proc, that result is returned.
72
+ # the preferred mechanism for a JSI's default value should be its schema.
73
+ # but there's no compelling reason not to support both, so I'll return what #[] returns.
74
+ jsi_node_content_hash_pubsend(:[], token)
75
+ end
76
+
77
+ # See {Base#[]}
78
+ def [](token, as_jsi: jsi_child_as_jsi_default, use_default: jsi_child_use_default_default)
79
+ if jsi_node_content_hash_pubsend(:key?, token)
80
+ jsi_child(token, as_jsi: as_jsi)
81
+ else
82
+ if use_default
83
+ jsi_default_child(token, as_jsi: as_jsi)
84
+ else
85
+ nil
86
+ end
87
+ end
88
+ end
89
+
90
+ # yields each hash key and value of this node.
91
+ #
92
+ # each yielded key is a key of the instance hash, and each yielded value is the result of {Base#[]}.
93
+ #
94
+ # @param kw keyword arguments are passed to {Base#[]}
95
+ # @yield [Object, Object] each key and value of this hash node
96
+ # @return [self, Enumerator] an Enumerator if invoked without a block; otherwise self
97
+ def each(**kw, &block)
98
+ return to_enum(__method__, **kw) { jsi_node_content_hash_pubsend(:size) } unless block
99
+ if block.arity > 1
100
+ jsi_node_content_hash_pubsend(:each_key) { |k| yield k, self[k, **kw] }
101
+ else
102
+ jsi_node_content_hash_pubsend(:each_key) { |k| yield [k, self[k, **kw]] }
103
+ end
104
+ self
105
+ end
106
+
107
+ # a hash in which each key is a key of the instance hash and each value is the result of {Base#[]}
108
+ # @param kw keyword arguments are passed to {Base#[]}
109
+ # @return [Hash]
110
+ def to_hash(**kw)
111
+ hash = {}
112
+ jsi_node_content_hash_pubsend(:each_key) { |k| hash[k] = self[k, **kw] }
113
+ hash.freeze
114
+ end
115
+
116
+ # See {Base#as_json}
117
+ def as_json(options = {})
118
+ hash = {}
119
+ each_key do |k|
120
+ ks = k.is_a?(String) ? k :
121
+ k.is_a?(Symbol) ? k.to_s :
122
+ k.respond_to?(:to_str) && (kstr = k.to_str).is_a?(String) ? kstr :
123
+ raise(TypeError, "JSON object (Hash) cannot be keyed with: #{k.pretty_inspect.chomp}")
124
+ hash[ks] = jsi_child(k, as_jsi: true).as_json(**options)
125
+ end
126
+ hash
127
+ end
128
+
129
+ include Util::Hashlike
130
+
131
+ if Util::LAST_ARGUMENT_AS_KEYWORD_PARAMETERS
132
+ # invokes the method with the given name on the jsi_node_content (if defined) or its #to_hash
133
+ # @param method_name [String, Symbol]
134
+ # @param a positional arguments are passed to the invocation of method_name
135
+ # @param b block is passed to the invocation of method_name
136
+ # @return [Object] the result of calling method method_name on the jsi_node_content or its #to_hash
137
+ def jsi_node_content_hash_pubsend(method_name, *a, &b)
138
+ if jsi_node_content.respond_to?(method_name)
139
+ jsi_node_content.public_send(method_name, *a, &b)
140
+ else
141
+ jsi_node_content.to_hash.public_send(method_name, *a, &b)
142
+ end
143
+ end
144
+ else
145
+ # invokes the method with the given name on the jsi_node_content (if defined) or its #to_hash
146
+ # @param method_name [String, Symbol]
147
+ # @param a positional arguments are passed to the invocation of method_name
148
+ # @param kw keyword arguments are passed to the invocation of method_name
149
+ # @param b block is passed to the invocation of method_name
150
+ # @return [Object] the result of calling method method_name on the jsi_node_content or its #to_hash
151
+ def jsi_node_content_hash_pubsend(method_name, *a, **kw, &b)
152
+ if jsi_node_content.respond_to?(method_name)
153
+ jsi_node_content.public_send(method_name, *a, **kw, &b)
154
+ else
155
+ jsi_node_content.to_hash.public_send(method_name, *a, **kw, &b)
156
+ end
157
+ end
158
+ end
159
+
160
+ # methods that don't look at the value; can skip the overhead of #[] (invoked by #to_hash)
161
+ SAFE_KEY_ONLY_METHODS.each do |method_name|
162
+ if Util::LAST_ARGUMENT_AS_KEYWORD_PARAMETERS
163
+ define_method(method_name) do |*a, &b|
164
+ jsi_node_content_hash_pubsend(method_name, *a, &b)
165
+ end
166
+ else
167
+ define_method(method_name) do |*a, **kw, &b|
168
+ jsi_node_content_hash_pubsend(method_name, *a, **kw, &b)
169
+ end
170
+ end
171
+ end
172
+ end
173
+
174
+ # module extending a {JSI::Base} object when its instance (its {Base#jsi_node_content})
175
+ # is an Array (or responds to `#to_ary`)
176
+ module Base::ArrayNode
177
+ include Base::Enumerable
178
+
179
+ # See {Base#jsi_array?}. Always true for ArrayNode.
180
+ def jsi_array?
181
+ true
182
+ end
183
+
184
+ # Yields each index - see {Base#jsi_each_child_token}
185
+ def jsi_each_child_token(&block)
186
+ return to_enum(__method__) { jsi_node_content_ary_pubsend(:size) } unless block
187
+ jsi_node_content_ary_pubsend(:each_index, &block)
188
+ nil
189
+ end
190
+
191
+ # See {Base#jsi_child_token_in_range?}
192
+ def jsi_child_token_in_range?(token)
193
+ token.is_a?(Integer) && token >= 0 && token < jsi_node_content_ary_pubsend(:size)
194
+ end
195
+
196
+ # See {Base#jsi_node_content_child}
197
+ def jsi_node_content_child(token)
198
+ # we check token_in_range? here (unlike HashNode) because we do not want to pass
199
+ # negative indices, Ranges, or non-Integers to Array#[]
200
+ if jsi_child_token_in_range?(token)
201
+ jsi_node_content_ary_pubsend(:[], token)
202
+ else
203
+ nil
204
+ end
205
+ end
206
+
207
+ # See {Base#[]}
208
+ def [](token, as_jsi: jsi_child_as_jsi_default, use_default: jsi_child_use_default_default)
209
+ size = jsi_node_content_ary_pubsend(:size)
210
+ if token.is_a?(Integer)
211
+ if token < 0
212
+ if token < -size
213
+ nil
214
+ else
215
+ jsi_child(token + size, as_jsi: as_jsi)
216
+ end
217
+ else
218
+ if token < size
219
+ jsi_child(token, as_jsi: as_jsi)
220
+ else
221
+ if use_default
222
+ jsi_default_child(token, as_jsi: as_jsi)
223
+ else
224
+ nil
225
+ end
226
+ end
227
+ end
228
+ elsif token.is_a?(Range)
229
+ type_err = proc do
230
+ raise(TypeError, [
231
+ "given range does not contain Integers",
232
+ "range: #{token.inspect}",
233
+ ].join("\n"))
234
+ end
235
+
236
+ start_idx = token.begin
237
+ if start_idx.is_a?(Integer)
238
+ start_idx += size if start_idx < 0
239
+ return Util::EMPTY_ARY if start_idx == size
240
+ return nil if start_idx < 0 || start_idx > size
241
+ elsif start_idx.nil?
242
+ start_idx = 0
243
+ else
244
+ type_err.call
245
+ end
246
+
247
+ end_idx = token.end
248
+ if end_idx.is_a?(Integer)
249
+ end_idx += size if end_idx < 0
250
+ end_idx += 1 unless token.exclude_end?
251
+ end_idx = size if end_idx > size
252
+ return Util::EMPTY_ARY if start_idx >= end_idx
253
+ elsif end_idx.nil?
254
+ end_idx = size
255
+ else
256
+ type_err.call
257
+ end
258
+
259
+ (start_idx...end_idx).map { |i| jsi_child(i, as_jsi: as_jsi) }.freeze
260
+ else
261
+ raise(TypeError, [
262
+ "expected `token` param to be an Integer or Range",
263
+ "token: #{token.inspect}",
264
+ ].join("\n"))
265
+ end
266
+ end
267
+
268
+ # yields each array element of this node.
269
+ #
270
+ # each yielded element is the result of {Base#[]} for each index of the instance array.
271
+ #
272
+ # @param kw keyword arguments are passed to {Base#[]}
273
+ # @yield [Object] each element of this array node
274
+ # @return [self, Enumerator] an Enumerator if invoked without a block; otherwise self
275
+ def each(**kw, &block)
276
+ return to_enum(__method__, **kw) { jsi_node_content_ary_pubsend(:size) } unless block
277
+ jsi_node_content_ary_pubsend(:each_index) { |i| yield(self[i, **kw]) }
278
+ self
279
+ end
280
+
281
+ # an array, the same size as the instance array, in which the element at each index is the
282
+ # result of {Base#[]}.
283
+ # @param kw keyword arguments are passed to {Base#[]}
284
+ # @return [Array]
285
+ def to_ary(**kw)
286
+ to_a(**kw)
287
+ end
288
+
289
+ # See {Base#as_json}
290
+ def as_json(options = {})
291
+ each_index.map { |i| jsi_child(i, as_jsi: true).as_json(**options) }
292
+ end
293
+
294
+ include Util::Arraylike
295
+
296
+ if Util::LAST_ARGUMENT_AS_KEYWORD_PARAMETERS
297
+ # invokes the method with the given name on the jsi_node_content (if defined) or its #to_ary
298
+ # @param method_name [String, Symbol]
299
+ # @param a positional arguments are passed to the invocation of method_name
300
+ # @param b block is passed to the invocation of method_name
301
+ # @return [Object] the result of calling method method_name on the jsi_node_content or its #to_ary
302
+ def jsi_node_content_ary_pubsend(method_name, *a, &b)
303
+ if jsi_node_content.respond_to?(method_name)
304
+ jsi_node_content.public_send(method_name, *a, &b)
305
+ else
306
+ jsi_node_content.to_ary.public_send(method_name, *a, &b)
307
+ end
308
+ end
309
+ else
310
+ # invokes the method with the given name on the jsi_node_content (if defined) or its #to_ary
311
+ # @param method_name [String, Symbol]
312
+ # @param a positional arguments are passed to the invocation of method_name
313
+ # @param kw keyword arguments are passed to the invocation of method_name
314
+ # @param b block is passed to the invocation of method_name
315
+ # @return [Object] the result of calling method method_name on the jsi_node_content or its #to_ary
316
+ def jsi_node_content_ary_pubsend(method_name, *a, **kw, &b)
317
+ if jsi_node_content.respond_to?(method_name)
318
+ jsi_node_content.public_send(method_name, *a, **kw, &b)
319
+ else
320
+ jsi_node_content.to_ary.public_send(method_name, *a, **kw, &b)
321
+ end
322
+ end
323
+ end
324
+
325
+ # methods that don't look at the value; can skip the overhead of #[] (invoked by #to_a).
326
+ # we override these methods from Arraylike
327
+ SAFE_INDEX_ONLY_METHODS.each do |method_name|
328
+ if Util::LAST_ARGUMENT_AS_KEYWORD_PARAMETERS
329
+ define_method(method_name) do |*a, &b|
330
+ jsi_node_content_ary_pubsend(method_name, *a, &b)
331
+ end
332
+ else
333
+ define_method(method_name) do |*a, **kw, &b|
334
+ jsi_node_content_ary_pubsend(method_name, *a, **kw, &b)
335
+ end
336
+ end
337
+ end
338
+ end
339
+
340
+ module Base::StringNode
341
+ delegate_methods = %w(% * + << =~ [] []=
342
+ ascii_only? b byteindex byterindex bytes bytesize byteslice bytesplice capitalize capitalize!
343
+ casecmp casecmp? center chars chomp chomp! chop chop! chr clear codepoints concat count delete delete!
344
+ delete_prefix delete_prefix! delete_suffix delete_suffix! downcase downcase!
345
+ each_byte each_char each_codepoint each_grapheme_cluster each_line
346
+ empty? encode encode! encoding end_with? force_encoding getbyte grapheme_clusters gsub gsub! hex
347
+ include? index insert intern length lines ljust lstrip lstrip! match match? next next! oct ord
348
+ partition prepend replace reverse reverse! rindex rjust rpartition rstrip rstrip! scan scrub scrub!
349
+ setbyte size slice slice! split squeeze squeeze! start_with? strip strip! sub sub! succ succ! sum
350
+ swapcase swapcase! to_c to_f to_i to_r to_s to_str to_sym tr tr! tr_s tr_s!
351
+ unicode_normalize unicode_normalize! unicode_normalized? unpack unpack1 upcase upcase! upto valid_encoding?
352
+ )
353
+ delegate_methods.each do |method_name|
354
+ if Util::LAST_ARGUMENT_AS_KEYWORD_PARAMETERS
355
+ define_method(method_name) do |*a, &b|
356
+ if jsi_node_content.respond_to?(method_name)
357
+ jsi_node_content.public_send(method_name, *a, &b)
358
+ else
359
+ jsi_node_content.to_str.public_send(method_name, *a, &b)
360
+ end
361
+ end
362
+ else
363
+ define_method(method_name) do |*a, **kw, &b|
364
+ if jsi_node_content.respond_to?(method_name)
365
+ jsi_node_content.public_send(method_name, *a, **kw, &b)
366
+ else
367
+ jsi_node_content.to_str.public_send(method_name, *a, **kw, &b)
368
+ end
369
+ end
370
+ end
371
+ end
372
+ end
373
+ end