jsi-dev 0.0.0.pre.kramdown

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 (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