jsi 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +6 -1
  3. data/CHANGELOG.md +15 -0
  4. data/README.md +19 -18
  5. data/jsi.gemspec +2 -3
  6. data/lib/jsi/base/mutability.rb +44 -0
  7. data/lib/jsi/base/node.rb +199 -34
  8. data/lib/jsi/base.rb +412 -228
  9. data/lib/jsi/jsi_coder.rb +18 -16
  10. data/lib/jsi/metaschema_node/bootstrap_schema.rb +57 -23
  11. data/lib/jsi/metaschema_node.rb +138 -107
  12. data/lib/jsi/ptr.rb +59 -37
  13. data/lib/jsi/schema/application/child_application/draft04.rb +0 -1
  14. data/lib/jsi/schema/application/child_application/draft06.rb +0 -1
  15. data/lib/jsi/schema/application/child_application/draft07.rb +0 -1
  16. data/lib/jsi/schema/application/child_application.rb +0 -25
  17. data/lib/jsi/schema/application/inplace_application/draft04.rb +0 -1
  18. data/lib/jsi/schema/application/inplace_application/draft06.rb +0 -1
  19. data/lib/jsi/schema/application/inplace_application/draft07.rb +0 -1
  20. data/lib/jsi/schema/application/inplace_application/ref.rb +1 -1
  21. data/lib/jsi/schema/application/inplace_application/someof.rb +1 -1
  22. data/lib/jsi/schema/application/inplace_application.rb +0 -27
  23. data/lib/jsi/schema/draft04.rb +0 -1
  24. data/lib/jsi/schema/draft06.rb +0 -1
  25. data/lib/jsi/schema/draft07.rb +0 -1
  26. data/lib/jsi/schema/ref.rb +44 -18
  27. data/lib/jsi/schema/schema_ancestor_node.rb +65 -56
  28. data/lib/jsi/schema/validation/contains.rb +1 -1
  29. data/lib/jsi/schema/validation/draft04/minmax.rb +2 -0
  30. data/lib/jsi/schema/validation/draft04.rb +0 -2
  31. data/lib/jsi/schema/validation/draft06.rb +0 -2
  32. data/lib/jsi/schema/validation/draft07.rb +0 -2
  33. data/lib/jsi/schema/validation/items.rb +3 -3
  34. data/lib/jsi/schema/validation/pattern.rb +1 -1
  35. data/lib/jsi/schema/validation/properties.rb +4 -4
  36. data/lib/jsi/schema/validation/ref.rb +1 -1
  37. data/lib/jsi/schema/validation.rb +0 -2
  38. data/lib/jsi/schema.rb +405 -194
  39. data/lib/jsi/schema_classes.rb +196 -127
  40. data/lib/jsi/schema_registry.rb +66 -17
  41. data/lib/jsi/schema_set.rb +76 -30
  42. data/lib/jsi/simple_wrap.rb +2 -7
  43. data/lib/jsi/util/private/attr_struct.rb +28 -14
  44. data/lib/jsi/util/private/memo_map.rb +75 -0
  45. data/lib/jsi/util/private.rb +73 -92
  46. data/lib/jsi/util/typelike.rb +28 -28
  47. data/lib/jsi/util.rb +120 -36
  48. data/lib/jsi/validation/error.rb +4 -0
  49. data/lib/jsi/validation/result.rb +18 -32
  50. data/lib/jsi/version.rb +1 -1
  51. data/lib/jsi.rb +67 -25
  52. data/lib/schemas/json-schema.org/draft-04/schema.rb +159 -4
  53. data/lib/schemas/json-schema.org/draft-06/schema.rb +161 -4
  54. data/lib/schemas/json-schema.org/draft-07/schema.rb +188 -4
  55. metadata +19 -5
  56. data/lib/jsi/metaschema.rb +0 -6
  57. data/lib/jsi/schema/validation/core.rb +0 -39
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '0659cc34b129717ffb69089f1d3a25ba3c3ec5364d49a6c0cc93dd5979ac7638'
4
- data.tar.gz: 5b0298a9843a61e6f5693d46dd6d86021d53767638e1f3f84e23fff4b167276e
3
+ metadata.gz: e18f4241f5f0013f657912295cb85db30095223a25030da3bbb690e68a5954d9
4
+ data.tar.gz: '083cbd8436424eeab11c36aeabb42f27ad9072d44450a0b8bfca391503131f9e'
5
5
  SHA512:
6
- metadata.gz: 8b68dcbb479756b8fa58a43f7a8e83accda02a8b0aa87617dabb6623a4b1a8aa05f94cb03de601cf7bf88c943ce62e42963b533ef125729847159e3b775f32c2
7
- data.tar.gz: 49997a70900b9d250e4888bcf3e5470e68b116c3a558651db9e5551f73027ea4abd18b0585d23f5201b15e8d80f1d72b46488d1870ac530d40368c7577d3b048
6
+ metadata.gz: 1030a1013ae2209a7c6b1df083efe2466a670e1f644d64fd349475ba07331c182629c41a77d4d7220aaffcfb669f0c13bc5c49e6cb23a4f231747fbdc4b5ec7a
7
+ data.tar.gz: d9f24dec46a507e9d2f40046e891acec2bbef92150e72dd29bb1245618bb6be0ea9beb140cc4f0a465f187bd65e6e52c68bf82d906f8df0edaff67f062dd4c15
data/.yardopts CHANGED
@@ -1 +1,6 @@
1
- --main README.md --markup=markdown --no-private {lib}/**/*.rb
1
+ --main README.md
2
+ --markup=markdown
3
+ --markup-provider=commonmarker
4
+ --no-private
5
+ --hide-void-return
6
+ {lib}/**/*.rb
data/CHANGELOG.md CHANGED
@@ -1,3 +1,18 @@
1
+ # v0.8.0
2
+
3
+ - Immutable JSIs with new_jsi param `mutable`
4
+ - JSIs are still mutable by default, but in the next release they will default to immutable
5
+ - Base#jsi_indicated_schemas
6
+ - Base::StringNode
7
+ - rename metaschema modules /JSONSchemaOrgDraft0X/JSONSchemaDraft0X/
8
+ - terminology: /Metaschema/Meta-Schema/ and /metaschema/meta-schema/ (where hyphen is allowed)
9
+ - Base::HashNode#jsi_each_propertyName
10
+ - new_schema and/or new_jsi params register, schema_registry, stringify_symbol_keys, to_immutable
11
+ - new_schema block param will module_exec on schema module
12
+ - Base#[] param use_default default false, overridable
13
+ - SchemaModule::Connects, SchemaModule::Connection
14
+ - rm Schema#jsi_schema_instance_modules
15
+
1
16
  # v0.7.0
2
17
 
3
18
  - JSI::Base instances include Array/Hash-like modules on subclasses rather than extending each instance; are only Enumerable when appropriate instead of always
data/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  JSI offers an Object-Oriented representation for JSON data using JSON Schemas. Given your JSON Schemas, JSI constructs Ruby modules and classes which are used to instantiate your JSON data. These modules let you use JSON with all the niceties of OOP such as property accessors and application-defined instance methods.
7
7
 
8
- To learn more about JSON Schema see [https://json-schema.org/](https://json-schema.org/).
8
+ To learn more about JSON Schema see <https://json-schema.org/>.
9
9
 
10
10
  JSI marries object-oriented programming with JSON Schemas by associating a module with each schema, and extending every instance described by a schema with that module. When an application adds methods to a schema module, those methods can be used on its instances.
11
11
 
@@ -15,7 +15,9 @@ Note: The canonical location of this README is on [RubyDoc](http://rubydoc.info/
15
15
 
16
16
  ## Example
17
17
 
18
- Words are boring, let's code. Here's a schema in yaml:
18
+ Words are boring, let's code. You can follow along from the code blocks - install the gem (`gem install jsi`), load an irb (`irb -r jsi`), and copy/paste/hack.
19
+
20
+ Here's a schema in yaml:
19
21
 
20
22
  ```yaml
21
23
  $schema: "http://json-schema.org/draft-07/schema"
@@ -91,7 +93,7 @@ bill.phone.map(&:location)
91
93
  # => ["home"]
92
94
  ```
93
95
 
94
- We also get validations, as you'd expect given that's largely what json-schema exists to do:
96
+ We also get validations, as you'd expect given that's largely what JSON Schema exists to do:
95
97
 
96
98
  ```ruby
97
99
  bill.jsi_valid?
@@ -115,7 +117,7 @@ bad.phone.jsi_validate
115
117
  # #<Set: {#<JSI::Validation::Error
116
118
  # message: "instance type does not match `type` value",
117
119
  # keyword: "type",
118
- # schema: #{<JSI (JSI::JSONSchemaOrgDraft07) Schema> "type" => "string"},
120
+ # schema: #{<JSI (JSI::JSONSchemaDraft07) Schema> "type" => "string"},
119
121
  # instance_ptr: JSI::Ptr["phone", 0, "number"],
120
122
  # instance_document: {"phone"=>[{"number"=>[5, 5, 5]}]}
121
123
  # >,
@@ -140,7 +142,7 @@ There's plenty more JSI has to offer, but this should give you a pretty good ide
140
142
  ## Terminology and Concepts
141
143
 
142
144
  - `JSI::Base` is the base class for each JSI schema class representing instances of JSON Schemas.
143
- - a "JSI Schema" is a JSON Schema, instantiated as (usually) a JSI::Base described by a metaschema (see the sections on Metaschemas below). a JSI Schema is an instance of the module `JSI::Schema`.
145
+ - a "JSI Schema" is a JSON Schema, instantiated as (usually) a JSI::Base described by a meta-schema (see the section on meta-schemas below). A JSI Schema is an instance of the module `JSI::Schema`.
144
146
  - a "JSI Schema Module" is a module which represents one schema, dynamically created by that Schema. Instances of that schema are extended with its JSI schema module. applications may reopen these modules to add functionality to JSI instances described by a given schema.
145
147
  - a "JSI schema class" is a subclass of `JSI::Base` representing one or more JSON schemas. Instances of such a class are described by all of the represented schemas. A JSI schema class includes the JSI schema module of each represented schema.
146
148
  - "instance" is a term that is significantly overloaded in this space, so documentation will attempt to be clear what kind of instance is meant:
@@ -154,9 +156,9 @@ JSI supports these JSON Schema specification versions:
154
156
 
155
157
  | Version | `$schema` URI | JSI Schema Module |
156
158
  | --- | --- | --- |
157
- | Draft 4 | `http://json-schema.org/draft-04/schema#` | {JSI::JSONSchemaOrgDraft04} |
158
- | Draft 6 | `http://json-schema.org/draft-06/schema#` | {JSI::JSONSchemaOrgDraft06} |
159
- | Draft 7 | `http://json-schema.org/draft-07/schema#` | {JSI::JSONSchemaOrgDraft07} |
159
+ | Draft 4 | `http://json-schema.org/draft-04/schema#` | {JSI::JSONSchemaDraft04} |
160
+ | Draft 6 | `http://json-schema.org/draft-06/schema#` | {JSI::JSONSchemaDraft06} |
161
+ | Draft 7 | `http://json-schema.org/draft-07/schema#` | {JSI::JSONSchemaDraft07} |
160
162
 
161
163
  ## JSI and Object Oriented Programming
162
164
 
@@ -190,7 +192,7 @@ bill['name']
190
192
 
191
193
  For `#name` and `#name=`, we're overriding existing accessor methods. note the use of `super` - this invokes the accessor methods defined by JSI which these override. You could alternatively use `self['name']` and `self['name']=` in these methods, with the same effect as `super`.
192
194
 
193
- Working with subschemas is just about as easy as with root schemas.
195
+ Working with subschemas to add methods is just about as easy as with root schemas.
194
196
 
195
197
  You can subscript or use property accessors on a JSI schema module to refer to the schema modules of its subschemas, e.g.:
196
198
 
@@ -211,8 +213,7 @@ bill.phone.first.number_with_dashes
211
213
  # => "5-5-5"
212
214
  ```
213
215
 
214
- A recommended convention for naming subschemas is to define them in the namespace of the module of their
215
- parent schema. The module can then be opened to add methods to the subschema's module.
216
+ A recommended convention for naming subschemas is to define them in the namespace of the module of their parent schema. The module can then be opened to add methods to the subschema's module.
216
217
 
217
218
  ```ruby
218
219
  module Contact
@@ -242,9 +243,9 @@ The classes used to instantiate JSIs are dynamically generated subclasses of JSI
242
243
 
243
244
  ## Registration
244
245
 
245
- In order for references across documents (generally from a `$ref` schema keyword) to resolve, JSI provides a registry which associates URIs with schemas (or resources containing schemas). This registry is accessible on {JSI.schema_registry} and is a {JSI::SchemaRegistry}.
246
+ In order for references across documents (generally from a `$ref` schema keyword) to resolve, JSI provides a registry (a {JSI::SchemaRegistry}) which associates URIs with schemas (or resources containing schemas). The default registry is accessible on {JSI.schema_registry}.
246
247
 
247
- Schemas instantiated with `.new_schema`, and their subschemas, are automatically registered with `JSI.schema_registry` if they identify an absolute URI.
248
+ Schemas instantiated with `.new_schema`, and their subschemas, are by default registered with `JSI.schema_registry` if they are identified by an absolute URI. This can be controlled by params `register` and `schema_registry`.
248
249
 
249
250
  Schemas can automatically be lazily loaded by registering a block which instantiates them with {JSI::SchemaRegistry#autoload_uri} (see its documentation).
250
251
 
@@ -258,13 +259,13 @@ The following optional features are not completely supported:
258
259
  - Regular expressions are interpreted by Ruby's Regexp class, whereas JSON Schema recommends interpreting these as ECMA 262 regular expressions. Certain expressions behave differently, particularly `^` and `$`.
259
260
  - Keywords `contentMediaType` and `contentEncoding` do not perform validation.
260
261
 
261
- ## Metaschemas
262
+ ## Meta-Schemas
262
263
 
263
- A metaschema is a schema which describes schemas. Likewise, a schema is an instance of a metaschema.
264
+ A meta-schema is a schema that describes schemas. Likewise, a schema is an instance of a meta-schema.
264
265
 
265
- In JSI, a schema is generally a JSI::Base instance whose schemas include a metaschema.
266
+ In JSI, a schema is generally a JSI::Base instance whose schemas include a meta-schema.
266
267
 
267
- A self-descriptive metaschema - most commonly one of the JSON schema draft metaschemas - is an object whose schemas include itself. This is instantiated in JSI as a JSI::MetaschemaNode, a special subclass of JSI::Base.
268
+ A self-descriptive meta-schema - most commonly one of the JSON schema draft meta-schemas - is an object whose schemas include itself. This is instantiated in JSI as a JSI::MetaSchemaNode, a special subclass of JSI::Base.
268
269
 
269
270
  ## ActiveRecord serialization
270
271
 
@@ -282,7 +283,7 @@ class User < ActiveRecord::Base
282
283
  end
283
284
  ```
284
285
 
285
- Now `user.contact_info` will be instantiated as a `Contact` JSI instance, from the JSON type in the database, with Contact's accessors, validations, and user-defined instance methods.
286
+ Now `user.contact_info` will be instantiated as a `Contact` JSI instance, from the JSON type in the database, with Contact's accessors, validations, and application-defined instance methods.
286
287
 
287
288
  See the gem [`arms`](https://github.com/notEthan/arms) if you wish to serialize the dumped JSON-compatible objects further as text.
288
289
 
data/jsi.gemspec CHANGED
@@ -1,6 +1,4 @@
1
- lib = File.expand_path("lib", __dir__)
2
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
- require "jsi/version"
1
+ require_relative "lib/jsi/version"
4
2
 
5
3
  Gem::Specification.new do |spec|
6
4
  spec.name = "jsi"
@@ -27,4 +25,5 @@ Gem::Specification.new do |spec|
27
25
  spec.require_paths = ["lib"]
28
26
 
29
27
  spec.add_dependency "addressable", '~> 2.3'
28
+ spec.add_dependency "bigdecimal"
30
29
  end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSI
4
+ module Base::Mutable
5
+ include(Util::FingerprintHash)
6
+
7
+ def jsi_node_content
8
+ jsi_ptr.evaluate(jsi_document)
9
+ end
10
+
11
+ def jsi_mutable?
12
+ true
13
+ end
14
+
15
+ private
16
+
17
+ def jsi_mutability_initialize
18
+ end
19
+
20
+ def jsi_memomap_class
21
+ Util::MemoMap::Mutable
22
+ end
23
+ end
24
+
25
+ module Base::Immutable
26
+ include(Util::FingerprintHash::Immutable)
27
+
28
+ attr_reader(:jsi_node_content)
29
+
30
+ def jsi_mutable?
31
+ false
32
+ end
33
+
34
+ private
35
+
36
+ def jsi_mutability_initialize
37
+ @jsi_node_content = @jsi_ptr.evaluate(@jsi_document)
38
+ end
39
+
40
+ def jsi_memomap_class
41
+ Util::MemoMap::Immutable
42
+ end
43
+ end
44
+ end
data/lib/jsi/base/node.rb CHANGED
@@ -1,47 +1,73 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JSI
4
- module Base::Enumerable
5
- include ::Enumerable
6
-
7
- # an Array containing each item in this JSI.
4
+ # module extending a {JSI::Base} object when its instance (its {Base#jsi_node_content})
5
+ # is a Hash (or responds to `#to_hash`)
6
+ module Base::HashNode
7
+ # instantiates and yields each property name (hash key) as a JSI described by any `propertyNames` schemas.
8
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
9
+ # @yield [JSI::Base]
10
+ # @return [nil, Enumerator] an Enumerator if invoked without a block; otherwise nil
11
+ def jsi_each_propertyName
12
+ return to_enum(__method__) { jsi_node_content_hash_pubsend(:size) } unless block_given?
13
+
14
+ property_schemas = SchemaSet.build do |schemas|
15
+ jsi_schemas.each do |s|
16
+ if s.keyword?('propertyNames') && s['propertyNames'].is_a?(Schema)
17
+ schemas << s['propertyNames']
18
+ end
19
+ end
20
+ end
21
+ jsi_node_content_hash_pubsend(:each_key) do |key|
22
+ yield property_schemas.new_jsi(key)
18
23
  end
19
- ary
24
+
25
+ nil
20
26
  end
21
27
 
22
- alias_method :entries, :to_a
28
+ # See {Base#jsi_hash?}. Always true for HashNode.
29
+ def jsi_hash?
30
+ true
31
+ end
23
32
 
24
- # a jsonifiable representation of the node content
25
- # @return [Object]
26
- def as_json(*opt)
27
- # include Enumerable (above) means, if ActiveSupport is loaded, its undesirable #as_json is included
28
- # https://github.com/rails/rails/blob/v7.0.0/activesupport/lib/active_support/core_ext/object/json.rb#L139-L143
29
- # although Base#as_json does clobber activesupport's, I want as_json defined correctly on the module too.
30
- Typelike.as_json(jsi_node_content, *opt)
33
+ # Yields each key - see {Base#jsi_each_child_token}
34
+ def jsi_each_child_token(&block)
35
+ return to_enum(__method__) { jsi_node_content_hash_pubsend(:size) } unless block
36
+ jsi_node_content_hash_pubsend(:each_key, &block)
37
+ nil
31
38
  end
32
- end
33
39
 
34
- # module extending a {JSI::Base} object when its instance (its {Base#jsi_node_content})
35
- # is a Hash (or responds to `#to_hash`)
36
- module Base::HashNode
37
- include Base::Enumerable
40
+ # See {Base#jsi_child_token_in_range?}
41
+ def jsi_child_token_in_range?(token)
42
+ jsi_node_content_hash_pubsend(:key?, token)
43
+ end
44
+
45
+ # See {Base#jsi_node_content_child}
46
+ def jsi_node_content_child(token)
47
+ # I could check token_in_range? and return nil here (as ArrayNode does).
48
+ # without that check, if the instance defines Hash#default or #default_proc, that result is returned.
49
+ # the preferred mechanism for a JSI's default value should be its schema.
50
+ # but there's no compelling reason not to support both, so I'll return what #[] returns.
51
+ jsi_node_content_hash_pubsend(:[], token)
52
+ end
53
+
54
+ # See {Base#[]}
55
+ def [](token, as_jsi: jsi_child_as_jsi_default, use_default: jsi_child_use_default_default)
56
+ if jsi_node_content_hash_pubsend(:key?, token)
57
+ jsi_child(token, as_jsi: as_jsi)
58
+ else
59
+ if use_default
60
+ jsi_default_child(token, as_jsi: as_jsi)
61
+ else
62
+ nil
63
+ end
64
+ end
65
+ end
38
66
 
39
- # yields each hash key and value of this node.
67
+ # yields each Hash key (JSON object property name) and value of this node.
40
68
  #
41
69
  # each yielded key is a key of the instance hash, and each yielded value is the result of {Base#[]}.
42
70
  #
43
- # returns an Enumerator if no block is given.
44
- #
45
71
  # @param kw keyword arguments are passed to {Base#[]}
46
72
  # @yield [Object, Object] each key and value of this hash node
47
73
  # @return [self, Enumerator] an Enumerator if invoked without a block; otherwise self
@@ -59,7 +85,22 @@ module JSI
59
85
  # @param kw keyword arguments are passed to {Base#[]}
60
86
  # @return [Hash]
61
87
  def to_hash(**kw)
62
- {}.tap { |h| jsi_node_content_hash_pubsend(:each_key) { |k| h[k] = self[k, **kw] } }
88
+ hash = {}
89
+ jsi_node_content_hash_pubsend(:each_key) { |k| hash[k] = self[k, **kw] }
90
+ hash.freeze
91
+ end
92
+
93
+ # See {Base#as_json}
94
+ def as_json(options = {})
95
+ hash = {}
96
+ each_key do |k|
97
+ ks = k.is_a?(String) ? k :
98
+ k.is_a?(Symbol) ? k.to_s :
99
+ k.respond_to?(:to_str) && (kstr = k.to_str).is_a?(String) ? kstr :
100
+ raise(TypeError, "JSON object (Hash) cannot be keyed with: #{k.pretty_inspect.chomp}")
101
+ hash[ks] = jsi_child_node(k).as_json(**options)
102
+ end
103
+ hash
63
104
  end
64
105
 
65
106
  include Util::Hashlike
@@ -110,14 +151,99 @@ module JSI
110
151
  # module extending a {JSI::Base} object when its instance (its {Base#jsi_node_content})
111
152
  # is an Array (or responds to `#to_ary`)
112
153
  module Base::ArrayNode
113
- include Base::Enumerable
154
+ # See {Base#jsi_array?}. Always true for ArrayNode.
155
+ def jsi_array?
156
+ true
157
+ end
158
+
159
+ # Yields each index - see {Base#jsi_each_child_token}
160
+ def jsi_each_child_token(&block)
161
+ return to_enum(__method__) { jsi_node_content_ary_pubsend(:size) } unless block
162
+ jsi_node_content_ary_pubsend(:each_index, &block)
163
+ nil
164
+ end
165
+
166
+ # See {Base#jsi_child_token_in_range?}
167
+ def jsi_child_token_in_range?(token)
168
+ token.is_a?(Integer) && token >= 0 && token < jsi_node_content_ary_pubsend(:size)
169
+ end
170
+
171
+ # See {Base#jsi_node_content_child}
172
+ def jsi_node_content_child(token)
173
+ # we check token_in_range? here (unlike HashNode) because we do not want to pass
174
+ # negative indices, Ranges, or non-Integers to Array#[]
175
+ if jsi_child_token_in_range?(token)
176
+ jsi_node_content_ary_pubsend(:[], token)
177
+ else
178
+ nil
179
+ end
180
+ end
181
+
182
+ # See {Base#[]}
183
+ def [](token, as_jsi: jsi_child_as_jsi_default, use_default: jsi_child_use_default_default)
184
+ size = jsi_node_content_ary_pubsend(:size)
185
+ if token.is_a?(Integer)
186
+ if token < 0
187
+ if token < -size
188
+ nil
189
+ else
190
+ jsi_child(token + size, as_jsi: as_jsi)
191
+ end
192
+ else
193
+ if token < size
194
+ jsi_child(token, as_jsi: as_jsi)
195
+ else
196
+ if use_default
197
+ jsi_default_child(token, as_jsi: as_jsi)
198
+ else
199
+ nil
200
+ end
201
+ end
202
+ end
203
+ elsif token.is_a?(Range)
204
+ type_err = proc do
205
+ raise(TypeError, [
206
+ "given range does not contain Integers",
207
+ "range: #{token.inspect}",
208
+ ].join("\n"))
209
+ end
210
+
211
+ start_idx = token.begin
212
+ if start_idx.is_a?(Integer)
213
+ start_idx += size if start_idx < 0
214
+ return Util::EMPTY_ARY if start_idx == size
215
+ return nil if start_idx < 0 || start_idx > size
216
+ elsif start_idx.nil?
217
+ start_idx = 0
218
+ else
219
+ type_err.call
220
+ end
221
+
222
+ end_idx = token.end
223
+ if end_idx.is_a?(Integer)
224
+ end_idx += size if end_idx < 0
225
+ end_idx += 1 unless token.exclude_end?
226
+ end_idx = size if end_idx > size
227
+ return Util::EMPTY_ARY if start_idx >= end_idx
228
+ elsif end_idx.nil?
229
+ end_idx = size
230
+ else
231
+ type_err.call
232
+ end
233
+
234
+ (start_idx...end_idx).map { |i| jsi_child(i, as_jsi: as_jsi) }.freeze
235
+ else
236
+ raise(TypeError, [
237
+ "expected `token` param to be an Integer or Range",
238
+ "token: #{token.inspect}",
239
+ ].join("\n"))
240
+ end
241
+ end
114
242
 
115
243
  # yields each array element of this node.
116
244
  #
117
245
  # each yielded element is the result of {Base#[]} for each index of the instance array.
118
246
  #
119
- # returns an Enumerator if no block is given.
120
- #
121
247
  # @param kw keyword arguments are passed to {Base#[]}
122
248
  # @yield [Object] each element of this array node
123
249
  # @return [self, Enumerator] an Enumerator if invoked without a block; otherwise self
@@ -135,6 +261,11 @@ module JSI
135
261
  to_a(**kw)
136
262
  end
137
263
 
264
+ # See {Base#as_json}
265
+ def as_json(options = {})
266
+ each_index.map { |i| jsi_child_node(i).as_json(**options) }
267
+ end
268
+
138
269
  include Util::Arraylike
139
270
 
140
271
  if Util::LAST_ARGUMENT_AS_KEYWORD_PARAMETERS
@@ -180,4 +311,38 @@ module JSI
180
311
  end
181
312
  end
182
313
  end
314
+
315
+ module Base::StringNode
316
+ delegate_methods = %w(% * + << =~ [] []=
317
+ ascii_only? b byteindex byterindex bytes bytesize byteslice bytesplice capitalize capitalize!
318
+ casecmp casecmp? center chars chomp chomp! chop chop! chr clear codepoints concat count delete delete!
319
+ delete_prefix delete_prefix! delete_suffix delete_suffix! downcase downcase!
320
+ each_byte each_char each_codepoint each_grapheme_cluster each_line
321
+ empty? encode encode! encoding end_with? force_encoding getbyte grapheme_clusters gsub gsub! hex
322
+ include? index insert intern length lines ljust lstrip lstrip! match match? next next! oct ord
323
+ partition prepend replace reverse reverse! rindex rjust rpartition rstrip rstrip! scan scrub scrub!
324
+ setbyte size slice slice! split squeeze squeeze! start_with? strip strip! sub sub! succ succ! sum
325
+ swapcase swapcase! to_c to_f to_i to_r to_s to_str to_sym tr tr! tr_s tr_s!
326
+ unicode_normalize unicode_normalize! unicode_normalized? unpack unpack1 upcase upcase! upto valid_encoding?
327
+ )
328
+ delegate_methods.each do |method_name|
329
+ if Util::LAST_ARGUMENT_AS_KEYWORD_PARAMETERS
330
+ define_method(method_name) do |*a, &b|
331
+ if jsi_node_content.respond_to?(method_name)
332
+ jsi_node_content.public_send(method_name, *a, &b)
333
+ else
334
+ jsi_node_content.to_str.public_send(method_name, *a, &b)
335
+ end
336
+ end
337
+ else
338
+ define_method(method_name) do |*a, **kw, &b|
339
+ if jsi_node_content.respond_to?(method_name)
340
+ jsi_node_content.public_send(method_name, *a, **kw, &b)
341
+ else
342
+ jsi_node_content.to_str.public_send(method_name, *a, **kw, &b)
343
+ end
344
+ end
345
+ end
346
+ end
347
+ end
183
348
  end