jsi 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.yardopts +1 -0
- data/CHANGELOG.md +7 -0
- data/README.md +49 -17
- data/lib/jsi.rb +4 -1
- data/lib/jsi/base.rb +147 -26
- data/lib/jsi/base/to_rb.rb +2 -3
- data/lib/jsi/json-schema-fragments.rb +6 -5
- data/lib/jsi/json/node.rb +117 -46
- data/lib/jsi/schema.rb +94 -62
- data/lib/jsi/typelike_modules.rb +65 -15
- data/lib/jsi/util.rb +30 -16
- data/lib/jsi/version.rb +1 -1
- data/test/base_array_test.rb +2 -2
- data/test/base_hash_test.rb +7 -7
- data/test/base_test.rb +19 -19
- data/test/jsi_json_arraynode_test.rb +133 -117
- data/test/jsi_json_hashnode_test.rb +116 -102
- data/test/jsi_json_node_test.rb +13 -13
- data/test/schema_instance_json_coder_test.rb +6 -7
- data/test/schema_test.rb +196 -0
- data/test/struct_json_coder_test.rb +2 -2
- data/test/test_helper.rb +36 -2
- data/test/util_test.rb +4 -4
- metadata +5 -2
data/lib/jsi/typelike_modules.rb
CHANGED
@@ -1,24 +1,50 @@
|
|
1
1
|
module JSI
|
2
|
+
# a module relating to objects that act like Hash or Array instances
|
2
3
|
module Typelike
|
3
|
-
|
4
|
-
|
5
|
-
|
4
|
+
# yields the content of the given param `object`. for objects which have a
|
5
|
+
# #modified_copy method of their own (JSI::Base, JSI::JSON::Node) that
|
6
|
+
# method is invoked with the given block. otherwise the given object itself
|
7
|
+
# is yielded.
|
8
|
+
#
|
9
|
+
# the given block must result in a modified copy of its block parameter
|
10
|
+
# (not destructively modifying the yielded content).
|
11
|
+
#
|
12
|
+
# @yield [Object] the content of the given object. the block should result
|
13
|
+
# in a (nondestructively) modified copy of this.
|
14
|
+
# @return [object.class] modified copy of the given object
|
15
|
+
def self.modified_copy(object, &block)
|
16
|
+
if object.respond_to?(:modified_copy)
|
17
|
+
object.modified_copy(&block)
|
6
18
|
else
|
7
|
-
return yield(
|
19
|
+
return yield(object)
|
8
20
|
end
|
9
21
|
end
|
10
22
|
|
11
|
-
#
|
23
|
+
# recursive method to express the given argument object in json-compatible
|
24
|
+
# types of Hash, Array, and basic types of String/boolean/numeric/nil. this
|
25
|
+
# will raise TypeError if an object is given that is not a type that seems
|
26
|
+
# to be expressable as json.
|
27
|
+
#
|
28
|
+
# similar effect could be achieved by requiring 'json/add/core' and using
|
29
|
+
# #as_json, but I don't much care for how it represents classes that are
|
30
|
+
# not naturally expressable in JSON, and prefer not to load its
|
31
|
+
# monkey-patching.
|
32
|
+
#
|
33
|
+
# @param object [Object] the object to be converted to jsonifiability
|
34
|
+
# @return [Array, Hash, String, Boolean, NilClass, Numeric] jsonifiable
|
35
|
+
# expression of param object
|
36
|
+
# @raise [TypeError] when the object (or an object nested with a hash or
|
37
|
+
# array of object) cannot be expressed as json
|
12
38
|
def self.as_json(object, *opt)
|
13
39
|
if object.respond_to?(:to_hash)
|
14
|
-
object.map do |k, v|
|
40
|
+
(object.respond_to?(:map) ? object : object.to_hash).map do |k, v|
|
15
41
|
unless k.is_a?(Symbol) || k.respond_to?(:to_str)
|
16
42
|
raise(TypeError, "json object (hash) cannot be keyed with: #{k.pretty_inspect.chomp}")
|
17
43
|
end
|
18
44
|
{k.to_s => as_json(v, *opt)}
|
19
45
|
end.inject({}, &:update)
|
20
46
|
elsif object.respond_to?(:to_ary)
|
21
|
-
object.map { |e| as_json(e, *opt) }
|
47
|
+
(object.respond_to?(:map) ? object : object.to_ary).map { |e| as_json(e, *opt) }
|
22
48
|
elsif [String, TrueClass, FalseClass, NilClass, Numeric].any? { |c| object.is_a?(c) }
|
23
49
|
object
|
24
50
|
elsif object.is_a?(Symbol)
|
@@ -32,6 +58,11 @@ module JSI
|
|
32
58
|
end
|
33
59
|
end
|
34
60
|
end
|
61
|
+
|
62
|
+
# a module of methods for objects which behave like Hash but are not Hash.
|
63
|
+
#
|
64
|
+
# this module is intended to be internal to JSI. no guarantees or API promises
|
65
|
+
# are made for non-JSI classes including this module.
|
35
66
|
module Hashlike
|
36
67
|
# safe methods which can be delegated to #to_hash (which the includer is assumed to have defined).
|
37
68
|
# 'safe' means, in this context, nondestructive - methods which do not modify the receiver.
|
@@ -52,33 +83,40 @@ module JSI
|
|
52
83
|
safe_modified_copy_methods.each do |method_name|
|
53
84
|
define_method(method_name) do |*a, &b|
|
54
85
|
JSI::Typelike.modified_copy(self) do |object_to_modify|
|
55
|
-
object_to_modify.
|
86
|
+
responsive_object = object_to_modify.respond_to?(method_name) ? object_to_modify : object_to_modify.to_hash
|
87
|
+
responsive_object.public_send(method_name, *a, &b)
|
56
88
|
end
|
57
89
|
end
|
58
90
|
end
|
59
91
|
safe_kv_block_modified_copy_methods.each do |method_name|
|
60
92
|
define_method(method_name) do |*a, &b|
|
61
93
|
JSI::Typelike.modified_copy(self) do |object_to_modify|
|
62
|
-
object_to_modify.
|
94
|
+
responsive_object = object_to_modify.respond_to?(method_name) ? object_to_modify : object_to_modify.to_hash
|
95
|
+
responsive_object.public_send(method_name, *a) do |k, _v|
|
63
96
|
b.call(k, self[k])
|
64
97
|
end
|
65
98
|
end
|
66
99
|
end
|
67
100
|
end
|
68
101
|
|
102
|
+
# @return [String] basically the same #inspect as Hash, but has the
|
103
|
+
# class name and, if responsive, self's #object_group_text
|
69
104
|
def inspect
|
70
105
|
object_group_text = respond_to?(:object_group_text) ? ' ' + self.object_group_text : ''
|
71
|
-
"\#{<#{self.class
|
106
|
+
"\#{<#{self.class}#{object_group_text}>#{empty? ? '' : ' '}#{self.map { |k, v| "#{k.inspect} => #{v.inspect}" }.join(', ')}}"
|
72
107
|
end
|
73
108
|
|
109
|
+
# @return [String] see #inspect
|
74
110
|
def to_s
|
75
111
|
inspect
|
76
112
|
end
|
77
113
|
|
114
|
+
# pretty-prints a representation this node to the given printer
|
115
|
+
# @return [void]
|
78
116
|
def pretty_print(q)
|
79
117
|
q.instance_exec(self) do |obj|
|
80
118
|
object_group_text = obj.respond_to?(:object_group_text) ? ' ' + obj.object_group_text : ''
|
81
|
-
text "\#{<#{obj.class
|
119
|
+
text "\#{<#{obj.class}#{object_group_text}>"
|
82
120
|
group_sub {
|
83
121
|
nest(2) {
|
84
122
|
breakable(obj.any? { true } ? ' ' : '')
|
@@ -96,6 +134,11 @@ module JSI
|
|
96
134
|
end
|
97
135
|
end
|
98
136
|
end
|
137
|
+
|
138
|
+
# a module of methods for objects which behave like Array but are not Array.
|
139
|
+
#
|
140
|
+
# this module is intended to be internal to JSI. no guarantees or API promises
|
141
|
+
# are made for non-JSI classes including this module.
|
99
142
|
module Arraylike
|
100
143
|
# safe methods which can be delegated to #to_ary (which the includer is assumed to have defined).
|
101
144
|
# 'safe' means, in this context, nondestructive - methods which do not modify the receiver.
|
@@ -120,7 +163,8 @@ module JSI
|
|
120
163
|
safe_modified_copy_methods.each do |method_name|
|
121
164
|
define_method(method_name) do |*a, &b|
|
122
165
|
JSI::Typelike.modified_copy(self) do |object_to_modify|
|
123
|
-
object_to_modify.
|
166
|
+
responsive_object = object_to_modify.respond_to?(method_name) ? object_to_modify : object_to_modify.to_ary
|
167
|
+
responsive_object.public_send(method_name, *a, &b)
|
124
168
|
end
|
125
169
|
end
|
126
170
|
end
|
@@ -128,26 +172,32 @@ module JSI
|
|
128
172
|
define_method(method_name) do |*a, &b|
|
129
173
|
JSI::Typelike.modified_copy(self) do |object_to_modify|
|
130
174
|
i = 0
|
131
|
-
object_to_modify.
|
175
|
+
responsive_object = object_to_modify.respond_to?(method_name) ? object_to_modify : object_to_modify.to_ary
|
176
|
+
responsive_object.public_send(method_name, *a) do |_e|
|
132
177
|
b.call(self[i]).tap { i += 1 }
|
133
178
|
end
|
134
179
|
end
|
135
180
|
end
|
136
181
|
end
|
137
182
|
|
183
|
+
# @return [String] basically the same #inspect as Array, but has the
|
184
|
+
# class name and, if responsive, self's #object_group_text
|
138
185
|
def inspect
|
139
186
|
object_group_text = respond_to?(:object_group_text) ? ' ' + self.object_group_text : ''
|
140
|
-
"\#[<#{self.class
|
187
|
+
"\#[<#{self.class}#{object_group_text}>#{empty? ? '' : ' '}#{self.map { |e| e.inspect }.join(', ')}]"
|
141
188
|
end
|
142
189
|
|
190
|
+
# @return [String] see #inspect
|
143
191
|
def to_s
|
144
192
|
inspect
|
145
193
|
end
|
146
194
|
|
195
|
+
# pretty-prints a representation this node to the given printer
|
196
|
+
# @return [void]
|
147
197
|
def pretty_print(q)
|
148
198
|
q.instance_exec(self) do |obj|
|
149
199
|
object_group_text = obj.respond_to?(:object_group_text) ? ' ' + obj.object_group_text : ''
|
150
|
-
text "\#[<#{obj.class
|
200
|
+
text "\#[<#{obj.class}#{object_group_text}>"
|
151
201
|
group_sub {
|
152
202
|
nest(2) {
|
153
203
|
breakable(obj.any? { true } ? ' ' : '')
|
data/lib/jsi/util.rb
CHANGED
@@ -1,5 +1,19 @@
|
|
1
1
|
module JSI
|
2
2
|
module Util
|
3
|
+
# returns a version of the given hash, in which any symbol keys are
|
4
|
+
# converted to strings. behavior on collisions is undefined (but in the
|
5
|
+
# future could take a block like
|
6
|
+
# ActiveSupport::HashWithIndifferentAccess#update)
|
7
|
+
#
|
8
|
+
# at the moment it is undefined whether the returned hash is the same
|
9
|
+
# instance as the `hash` param. if `hash` is already a hash which contains
|
10
|
+
# no symbol keys, this method MAY return that same instance. use #dup on
|
11
|
+
# the return if you need to ensure it is not the same instance as the
|
12
|
+
# argument instance.
|
13
|
+
#
|
14
|
+
# @param hash [#to_hash] the hash from which to convert symbol keys to strings
|
15
|
+
# @return [same class as the param `hash`, or Hash if the former cannot be done] a
|
16
|
+
# hash(-like) instance containing no symbol keys
|
3
17
|
def stringify_symbol_keys(hash)
|
4
18
|
unless hash.respond_to?(:to_hash)
|
5
19
|
raise(ArgumentError, "expected argument to be a hash; got #{hash.class.inspect}: #{hash.pretty_inspect.chomp}")
|
@@ -23,7 +37,7 @@ module JSI
|
|
23
37
|
JSI::Typelike.modified_copy(object) do |hash|
|
24
38
|
changed = false
|
25
39
|
out = {}
|
26
|
-
hash.each do |k, v|
|
40
|
+
(hash.respond_to?(:each) ? hash : hash.to_hash).each do |k, v|
|
27
41
|
if k.is_a?(Symbol)
|
28
42
|
changed = true
|
29
43
|
k = k.to_s
|
@@ -39,7 +53,7 @@ module JSI
|
|
39
53
|
elsif object.respond_to?(:to_ary)
|
40
54
|
JSI::Typelike.modified_copy(object) do |ary|
|
41
55
|
changed = false
|
42
|
-
out = ary.map do |e|
|
56
|
+
out = (ary.respond_to?(:each) ? ary : ary.to_ary).map do |e|
|
43
57
|
out_e = deep_stringify_symbol_keys(e)
|
44
58
|
changed = true if out_e.object_id != e.object_id
|
45
59
|
out_e
|
@@ -50,6 +64,20 @@ module JSI
|
|
50
64
|
object
|
51
65
|
end
|
52
66
|
end
|
67
|
+
|
68
|
+
# this is the Y-combinator, which allows anonymous recursive functions. for a simple example,
|
69
|
+
# to define a recursive function to return the length of an array:
|
70
|
+
#
|
71
|
+
# length = ycomb do |len|
|
72
|
+
# proc{|list| list == [] ? 0 : 1 + len.call(list[1..-1]) }
|
73
|
+
# end
|
74
|
+
#
|
75
|
+
# see https://secure.wikimedia.org/wikipedia/en/wiki/Fixed_point_combinator#Y_combinator
|
76
|
+
# and chapter 9 of the little schemer, available as the sample chapter at http://www.ccs.neu.edu/home/matthias/BTLS/
|
77
|
+
def ycomb
|
78
|
+
proc { |f| f.call(f) }.call(proc { |f| yield proc { |*x| f.call(f).call(*x) } })
|
79
|
+
end
|
80
|
+
module_function :ycomb
|
53
81
|
end
|
54
82
|
extend Util
|
55
83
|
|
@@ -86,18 +114,4 @@ module JSI
|
|
86
114
|
end
|
87
115
|
end
|
88
116
|
extend Memoize
|
89
|
-
|
90
|
-
# this is the Y-combinator, which allows anonymous recursive functions. for a simple example,
|
91
|
-
# to define a recursive function to return the length of an array:
|
92
|
-
#
|
93
|
-
# length = ycomb do |len|
|
94
|
-
# proc{|list| list == [] ? 0 : 1 + len.call(list[1..-1]) }
|
95
|
-
# end
|
96
|
-
#
|
97
|
-
# see https://secure.wikimedia.org/wikipedia/en/wiki/Fixed_point_combinator#Y_combinator
|
98
|
-
# and chapter 9 of the little schemer, available as the sample chapter at http://www.ccs.neu.edu/home/matthias/BTLS/
|
99
|
-
def ycomb
|
100
|
-
proc { |f| f.call(f) }.call(proc { |f| yield proc{|*x| f.call(f).call(*x) } })
|
101
|
-
end
|
102
|
-
module_function :ycomb
|
103
117
|
end
|
data/lib/jsi/version.rb
CHANGED
data/test/base_array_test.rb
CHANGED
@@ -112,12 +112,12 @@ describe JSI::BaseArray do
|
|
112
112
|
it('#zip') { assert_equal([['foo', 'foo'], [subject[1], subject[1]], [subject[2], subject[2]]], subject.zip(subject)) }
|
113
113
|
end
|
114
114
|
describe 'modified copy methods' do
|
115
|
-
it('#reject')
|
115
|
+
it('#reject') { assert_equal(class_for_schema.new(JSI::JSON::ArrayNode.new(['foo'], [])), subject.reject { |e| e != 'foo' }) }
|
116
116
|
it('#reject block var') do
|
117
117
|
subj_a = subject.to_a
|
118
118
|
subject.reject { |e| assert_equal(e, subj_a.shift) }
|
119
119
|
end
|
120
|
-
it('#select')
|
120
|
+
it('#select') { assert_equal(class_for_schema.new(JSI::JSON::ArrayNode.new(['foo'], [])), subject.select { |e| e == 'foo' }) }
|
121
121
|
it('#select block var') do
|
122
122
|
subj_a = subject.to_a
|
123
123
|
subject.select { |e| assert_equal(e, subj_a.shift) }
|
data/test/base_hash_test.rb
CHANGED
@@ -30,7 +30,7 @@ describe JSI::BaseHash do
|
|
30
30
|
assert_instance_of(JSI.class_for_schema(schema.schema_node['properties']['foo']), subject['foo'])
|
31
31
|
end
|
32
32
|
it 'sets a property to a schema instance with a different schema' do
|
33
|
-
|
33
|
+
assert(subject['foo'])
|
34
34
|
|
35
35
|
subject['foo'] = subject['bar']
|
36
36
|
|
@@ -41,7 +41,7 @@ describe JSI::BaseHash do
|
|
41
41
|
assert_instance_of(JSI.class_for_schema(schema.schema_node['properties']['bar']), subject['bar'])
|
42
42
|
end
|
43
43
|
it 'sets a property to a schema instance with the same schema' do
|
44
|
-
other_subject = class_for_schema.new(JSI::JSON::Node.
|
44
|
+
other_subject = class_for_schema.new(JSI::JSON::Node.new_doc({'foo' => {'x' => 'y'}, 'bar' => [9], 'baz' => true}))
|
45
45
|
# Given
|
46
46
|
assert_equal(other_subject, subject)
|
47
47
|
|
@@ -103,7 +103,7 @@ describe JSI::BaseHash do
|
|
103
103
|
it('#to_h') { assert_equal({'foo' => subject['foo'], 'bar' => subject['bar'], 'baz' => true}, subject.to_h) }
|
104
104
|
it('#to_proc') { assert_equal(true, subject.to_proc.call('baz')) } if {}.respond_to?(:to_proc)
|
105
105
|
if {}.respond_to?(:transform_values)
|
106
|
-
it('#transform_values') { assert_equal({'foo' => nil, 'bar' => nil, 'baz' => nil}, subject.transform_values { |_| nil}) }
|
106
|
+
it('#transform_values') { assert_equal({'foo' => nil, 'bar' => nil, 'baz' => nil}, subject.transform_values { |_| nil }) }
|
107
107
|
end
|
108
108
|
it('#value?') { assert_equal(false, subject.value?('0')) }
|
109
109
|
it('#values') { assert_equal([subject['foo'], subject['bar'], true], subject.values) }
|
@@ -112,9 +112,9 @@ describe JSI::BaseHash do
|
|
112
112
|
describe 'modified copy methods' do
|
113
113
|
# I'm going to rely on the #merge test above to test the modified copy functionality and just do basic
|
114
114
|
# tests of all the modified copy methods here
|
115
|
-
it('#merge')
|
116
|
-
it('#reject')
|
117
|
-
it('#select')
|
115
|
+
it('#merge') { assert_equal(subject, subject.merge({})) }
|
116
|
+
it('#reject') { assert_equal(class_for_schema.new(JSI::JSON::HashNode.new({}, [])), subject.reject { true }) }
|
117
|
+
it('#select') { assert_equal(class_for_schema.new(JSI::JSON::HashNode.new({}, [])), subject.select { false }) }
|
118
118
|
describe '#select' do
|
119
119
|
it 'yields properly too' do
|
120
120
|
subject.select do |k, v|
|
@@ -124,7 +124,7 @@ describe JSI::BaseHash do
|
|
124
124
|
end
|
125
125
|
# Hash#compact only available as of ruby 2.5.0
|
126
126
|
if {}.respond_to?(:compact)
|
127
|
-
it('#compact')
|
127
|
+
it('#compact') { assert_equal(subject, subject.compact) }
|
128
128
|
end
|
129
129
|
end
|
130
130
|
JSI::Hashlike::DESTRUCTIVE_METHODS.each do |destructive_method_name|
|
data/test/base_test.rb
CHANGED
@@ -51,12 +51,12 @@ describe JSI::Base do
|
|
51
51
|
end
|
52
52
|
describe 'module for schema .inspect' do
|
53
53
|
it '.inspect' do
|
54
|
-
assert_match(%r(\A#<Module for Schema: .+#>\z), JSI.module_for_schema(schema).inspect)
|
54
|
+
assert_match(%r(\A#<Module for Schema: .+#>\z), JSI::SchemaClasses.module_for_schema(schema).inspect)
|
55
55
|
end
|
56
56
|
end
|
57
57
|
describe 'module for schema .schema' do
|
58
58
|
it '.schema' do
|
59
|
-
assert_equal(schema, JSI.module_for_schema(schema).schema)
|
59
|
+
assert_equal(schema, JSI::SchemaClasses.module_for_schema(schema).schema)
|
60
60
|
end
|
61
61
|
end
|
62
62
|
describe 'SchemaClasses[]' do
|
@@ -81,20 +81,20 @@ describe JSI::Base do
|
|
81
81
|
assert_equal(JSI.class_for_schema(schema), JSI.class_for_schema(JSI.class_for_schema({}).new(schema.schema_node)))
|
82
82
|
end
|
83
83
|
end
|
84
|
-
describe '.module_for_schema' do
|
84
|
+
describe 'JSI::SchemaClasses.module_for_schema' do
|
85
85
|
it 'returns a module from a schema' do
|
86
|
-
module_for_schema = JSI.module_for_schema(schema)
|
86
|
+
module_for_schema = JSI::SchemaClasses.module_for_schema(schema)
|
87
87
|
# same module every time
|
88
|
-
assert_equal(JSI.module_for_schema(schema), module_for_schema)
|
88
|
+
assert_equal(JSI::SchemaClasses.module_for_schema(schema), module_for_schema)
|
89
89
|
end
|
90
90
|
it 'returns a module from a hash' do
|
91
|
-
assert_equal(JSI.module_for_schema(schema), JSI.module_for_schema(schema.schema_node.content))
|
91
|
+
assert_equal(JSI::SchemaClasses.module_for_schema(schema), JSI::SchemaClasses.module_for_schema(schema.schema_node.content))
|
92
92
|
end
|
93
93
|
it 'returns a module from a schema node' do
|
94
|
-
assert_equal(JSI.module_for_schema(schema), JSI.module_for_schema(schema.schema_node))
|
94
|
+
assert_equal(JSI::SchemaClasses.module_for_schema(schema), JSI::SchemaClasses.module_for_schema(schema.schema_node))
|
95
95
|
end
|
96
96
|
it 'returns a module from a Base' do
|
97
|
-
assert_equal(JSI.module_for_schema(schema), JSI.module_for_schema(JSI.class_for_schema({}).new(schema.schema_node)))
|
97
|
+
assert_equal(JSI::SchemaClasses.module_for_schema(schema), JSI::SchemaClasses.module_for_schema(JSI.class_for_schema({}).new(schema.schema_node)))
|
98
98
|
end
|
99
99
|
end
|
100
100
|
describe 'initialization' do
|
@@ -107,7 +107,7 @@ describe JSI::Base do
|
|
107
107
|
describe 'nil' do
|
108
108
|
let(:instance) { nil }
|
109
109
|
it 'initializes with nil instance' do
|
110
|
-
assert_equal(JSI::JSON::Node.
|
110
|
+
assert_equal(JSI::JSON::Node.new_doc(nil), subject.instance)
|
111
111
|
assert(!subject.respond_to?(:to_ary))
|
112
112
|
assert(!subject.respond_to?(:to_hash))
|
113
113
|
end
|
@@ -115,7 +115,7 @@ describe JSI::Base do
|
|
115
115
|
describe 'arbitrary instance' do
|
116
116
|
let(:instance) { Object.new }
|
117
117
|
it 'initializes' do
|
118
|
-
assert_equal(JSI::JSON::Node.
|
118
|
+
assert_equal(JSI::JSON::Node.new_doc(instance), subject.instance)
|
119
119
|
assert(!subject.respond_to?(:to_ary))
|
120
120
|
assert(!subject.respond_to?(:to_hash))
|
121
121
|
end
|
@@ -124,7 +124,7 @@ describe JSI::Base do
|
|
124
124
|
let(:instance) { {'foo' => 'bar'} }
|
125
125
|
let(:schema_content) { {'type' => 'object'} }
|
126
126
|
it 'initializes' do
|
127
|
-
assert_equal(JSI::JSON::Node.
|
127
|
+
assert_equal(JSI::JSON::Node.new_doc({'foo' => 'bar'}), subject.instance)
|
128
128
|
assert(!subject.respond_to?(:to_ary))
|
129
129
|
assert(subject.respond_to?(:to_hash))
|
130
130
|
end
|
@@ -142,7 +142,7 @@ describe JSI::Base do
|
|
142
142
|
let(:instance) { ['foo'] }
|
143
143
|
let(:schema_content) { {'type' => 'array'} }
|
144
144
|
it 'initializes' do
|
145
|
-
assert_equal(JSI::JSON::Node.
|
145
|
+
assert_equal(JSI::JSON::Node.new_doc(['foo']), subject.instance)
|
146
146
|
assert(subject.respond_to?(:to_ary))
|
147
147
|
assert(!subject.respond_to?(:to_hash))
|
148
148
|
end
|
@@ -168,8 +168,8 @@ describe JSI::Base do
|
|
168
168
|
end
|
169
169
|
end
|
170
170
|
describe '#parents, #parent' do
|
171
|
-
let(:schema_content) { {properties
|
172
|
-
let(:document) { {foo
|
171
|
+
let(:schema_content) { {'properties' => {'foo' => {'properties' => {'bar' => {'properties' => {'baz' => {}}}}}}} }
|
172
|
+
let(:document) { {'foo' => {'bar' => {'baz' => {}}}} }
|
173
173
|
describe 'no parents' do
|
174
174
|
it 'has none' do
|
175
175
|
assert_equal([], subject.parents)
|
@@ -317,7 +317,7 @@ describe JSI::Base do
|
|
317
317
|
end
|
318
318
|
it 'does not define readers' do
|
319
319
|
assert_equal('bar', subject.foo)
|
320
|
-
assert_equal(JSI.module_for_schema(subject.schema), subject.method(:foo).owner)
|
320
|
+
assert_equal(JSI::SchemaClasses.module_for_schema(subject.schema), subject.method(:foo).owner)
|
321
321
|
|
322
322
|
assert_equal(JSI::Base, subject.method(:initialize).owner)
|
323
323
|
assert_equal('hi', subject['initialize'])
|
@@ -377,10 +377,10 @@ describe JSI::Base do
|
|
377
377
|
end
|
378
378
|
describe '#as_json' do
|
379
379
|
it '#as_json' do
|
380
|
-
assert_equal({'a' => 'b'}, JSI.class_for_schema({}).new(JSI::JSON::Node.
|
381
|
-
assert_equal({'a' => 'b'}, JSI.class_for_schema({'type' => 'object'}).new(JSI::JSON::Node.
|
382
|
-
assert_equal(['a', 'b'], JSI.class_for_schema({'type' => 'array'}).new(JSI::JSON::Node.
|
383
|
-
assert_equal(['a'], JSI
|
380
|
+
assert_equal({'a' => 'b'}, JSI.class_for_schema({}).new(JSI::JSON::Node.new_doc({'a' => 'b'})).as_json)
|
381
|
+
assert_equal({'a' => 'b'}, JSI.class_for_schema({'type' => 'object'}).new(JSI::JSON::Node.new_doc({'a' => 'b'})).as_json)
|
382
|
+
assert_equal(['a', 'b'], JSI.class_for_schema({'type' => 'array'}).new(JSI::JSON::Node.new_doc(['a', 'b'])).as_json)
|
383
|
+
assert_equal(['a'], JSI.class_for_schema({}).new(['a']).as_json(some_option: true))
|
384
384
|
end
|
385
385
|
end
|
386
386
|
describe 'overwrite schema instance with instance=' do
|
@@ -1,133 +1,149 @@
|
|
1
1
|
require_relative 'test_helper'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
3
|
+
document_types = [
|
4
|
+
{
|
5
|
+
make_document: -> (d) { d },
|
6
|
+
document: ['a', ['b', 'q'], {'c' => {'d' => 'e'}}],
|
7
|
+
type_desc: 'Array',
|
8
|
+
},
|
9
|
+
{
|
10
|
+
make_document: -> (d) { SortOfArray.new(d) },
|
11
|
+
document: SortOfArray.new(['a', SortOfArray.new(['b', 'q']), SortOfHash.new({'c' => SortOfHash.new({'d' => 'e'})})]),
|
12
|
+
type_desc: 'sort of Array-like',
|
13
|
+
},
|
14
|
+
]
|
15
|
+
document_types.each do |document_type|
|
16
|
+
describe "JSI::JSON::ArrayNode with #{document_type[:type_desc]}" do
|
17
|
+
# document of the node being tested
|
18
|
+
let(:document) { document_type[:document] }
|
19
|
+
# by default the node is the whole document
|
20
|
+
let(:path) { [] }
|
21
|
+
# the node being tested
|
22
|
+
let(:node) { JSI::JSON::Node.new_by_type(document, path) }
|
10
23
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
24
|
+
describe '#[] bad index' do
|
25
|
+
it 'improves TypeError for Array subsript' do
|
26
|
+
err = assert_raises(TypeError) do
|
27
|
+
node[:x]
|
28
|
+
end
|
29
|
+
assert_match(/^subscripting with :x \(Symbol\) from Array. content is: \[.*\]\z/m, err.message)
|
15
30
|
end
|
16
|
-
assert_match(/^subscripting with :x \(Symbol\) from Array. self is: #\[<JSI::JSON::ArrayNode fragment="#">/, err.message)
|
17
31
|
end
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
32
|
+
describe '#each' do
|
33
|
+
it 'iterates, one argument' do
|
34
|
+
out = []
|
35
|
+
node.each do |arg|
|
36
|
+
out << arg
|
37
|
+
end
|
38
|
+
assert_instance_of(JSI::JSON::ArrayNode, node[1])
|
39
|
+
assert_instance_of(JSI::JSON::HashNode, node[2])
|
40
|
+
assert_equal(['a', node[1], node[2]], out)
|
41
|
+
end
|
42
|
+
it 'returns self' do
|
43
|
+
assert_equal(node.each { }.object_id, node.object_id)
|
44
|
+
end
|
45
|
+
it 'returns an enumerator when called with no block' do
|
46
|
+
enum = node.each
|
47
|
+
assert_instance_of(Enumerator, enum)
|
48
|
+
assert_equal(['a', node[1], node[2]], enum.to_a)
|
24
49
|
end
|
25
|
-
assert_instance_of(JSI::JSON::ArrayNode, node[1])
|
26
|
-
assert_instance_of(JSI::JSON::HashNode, node[2])
|
27
|
-
assert_equal(['a', node[1], node[2]], out)
|
28
50
|
end
|
29
|
-
|
30
|
-
|
51
|
+
describe '#to_ary' do
|
52
|
+
it 'returns a Array with Nodes in' do
|
53
|
+
assert_instance_of(Array, node.to_ary)
|
54
|
+
assert_equal(['a', node[1], node[2]], node.to_ary)
|
55
|
+
end
|
31
56
|
end
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
57
|
+
describe '#as_json' do
|
58
|
+
let(:document) { document_type[:make_document].call(['a', 'b']) }
|
59
|
+
it '#as_json' do
|
60
|
+
assert_equal(['a', 'b'], node.as_json)
|
61
|
+
assert_equal(['a', 'b'], node.as_json(some_option: false))
|
62
|
+
end
|
36
63
|
end
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
64
|
+
# these methods just delegate to Array so not going to test excessively
|
65
|
+
describe 'index only methods' do
|
66
|
+
it('#each_index') { assert_equal([0, 1, 2], node.each_index.to_a) }
|
67
|
+
it('#empty?') { assert_equal(false, node.empty?) }
|
68
|
+
it('#length') { assert_equal(3, node.length) }
|
69
|
+
it('#size') { assert_equal(3, node.size) }
|
42
70
|
end
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
assert_equal([
|
71
|
+
describe 'index + element methods' do
|
72
|
+
it('#|') { assert_equal(['a', node[1], node[2], 0], node | [0]) }
|
73
|
+
it('#&') { assert_equal(['a'], node & ['a']) }
|
74
|
+
it('#*') { assert_equal(node.to_a, node * 1) }
|
75
|
+
it('#+') { assert_equal(node.to_a, node + []) }
|
76
|
+
it('#-') { assert_equal([node[1], node[2]], node - ['a']) }
|
77
|
+
it('#<=>') { assert_equal(1, node <=> []) }
|
78
|
+
it('#<=>') { assert_equal(-1, [] <=> node) }
|
79
|
+
require 'abbrev'
|
80
|
+
it('#abbrev') { assert_equal({'a' => 'a'}, JSI::JSON::Node.new_doc(['a']).abbrev) }
|
81
|
+
it('#assoc') { assert_equal(['b', 'q'], node.assoc('b')) }
|
82
|
+
it('#at') { assert_equal('a', node.at(0)) }
|
83
|
+
it('#bsearch') { assert_equal(nil, node.bsearch { false }) }
|
84
|
+
it('#bsearch_index') { assert_equal(nil, node.bsearch_index { false }) } if [].respond_to?(:bsearch_index)
|
85
|
+
it('#combination') { assert_equal([['a'], [node[1]], [node[2]]], node.combination(1).to_a) }
|
86
|
+
it('#count') { assert_equal(1, node.count('a')) }
|
87
|
+
it('#cycle') { assert_equal(node.to_a, node.cycle(1).to_a) }
|
88
|
+
it('#dig') { assert_equal('e', node.dig(2, 'c', 'd')) } if [].respond_to?(:dig)
|
89
|
+
it('#drop') { assert_equal([node[2]], node.drop(2)) }
|
90
|
+
it('#drop_while') { assert_equal([node[1], node[2]], node.drop_while { |e| e == 'a' }) }
|
91
|
+
it('#fetch') { assert_equal('a', node.fetch(0)) }
|
92
|
+
it('#find_index') { assert_equal(0, node.find_index { true }) }
|
93
|
+
it('#first') { assert_equal('a', node.first) }
|
94
|
+
it('#include?') { assert_equal(true, node.include?('a')) }
|
95
|
+
it('#index') { assert_equal(0, node.index('a')) }
|
96
|
+
it('#join') { assert_equal('a b', JSI::JSON::Node.new_doc(['a', 'b']).join(' ')) }
|
97
|
+
it('#last') { assert_equal(node[2], node.last) }
|
98
|
+
it('#pack') { assert_equal(' ', JSI::JSON::Node.new_doc([32]).pack('c')) }
|
99
|
+
it('#permutation') { assert_equal([['a'], [node[1]], [node[2]]], node.permutation(1).to_a) }
|
100
|
+
it('#product') { assert_equal([], node.product([])) }
|
101
|
+
# due to differences in implementation between #assoc and #rassoc, the reason for which
|
102
|
+
# I cannot begin to fathom, assoc works but rassoc does not because rassoc has different
|
103
|
+
# type checking than assoc for the array(like) array elements.
|
104
|
+
# compare:
|
105
|
+
# assoc: https://github.com/ruby/ruby/blob/v2_5_0/array.c#L3780-L3813
|
106
|
+
# rassoc: https://github.com/ruby/ruby/blob/v2_5_0/array.c#L3815-L3847
|
107
|
+
# for this reason, rassoc is NOT defined on Arraylike. it's here with as_json.
|
108
|
+
#
|
109
|
+
# I've never even seen anybody use rassoc. of all the methods to put into the standard library ...
|
110
|
+
it('#rassoc') { assert_equal(['b', 'q'], node.as_json.rassoc('q')) }
|
111
|
+
it('#repeated_combination') { assert_equal([[]], node.repeated_combination(0).to_a) }
|
112
|
+
it('#repeated_permutation') { assert_equal([[]], node.repeated_permutation(0).to_a) }
|
113
|
+
it('#reverse') { assert_equal([node[2], node[1], 'a'], node.reverse) }
|
114
|
+
it('#reverse_each') { assert_equal([node[2], node[1], 'a'], node.reverse_each.to_a) }
|
115
|
+
it('#rindex') { assert_equal(0, node.rindex('a')) }
|
116
|
+
it('#rotate') { assert_equal([node[1], node[2], 'a'], node.rotate) }
|
117
|
+
it('#sample') { assert_equal('a', JSI::JSON::Node.new_doc(['a']).sample) }
|
118
|
+
it('#shelljoin') { assert_equal('a', JSI::JSON::Node.new_doc(['a']).shelljoin) } if [].respond_to?(:shelljoin)
|
119
|
+
it('#shuffle') { assert_equal(3, node.shuffle.size) }
|
120
|
+
it('#slice') { assert_equal(['a'], node.slice(0, 1)) }
|
121
|
+
it('#sort') { assert_equal(['a'], JSI::JSON::Node.new_doc(['a']).sort) }
|
122
|
+
it('#take') { assert_equal(['a'], node.take(1)) }
|
123
|
+
it('#take_while') { assert_equal([], node.take_while { false }) }
|
124
|
+
it('#transpose') { assert_equal([], JSI::JSON::Node.new_doc([]).transpose) }
|
125
|
+
it('#uniq') { assert_equal(node.to_a, node.uniq) }
|
126
|
+
it('#values_at') { assert_equal(['a'], node.values_at(0)) }
|
127
|
+
it('#zip') { assert_equal([['a', 'a'], [node[1], node[1]], [node[2], node[2]]], node.zip(node)) }
|
49
128
|
end
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
it('#+') { assert_equal(node.to_a, node + []) }
|
63
|
-
it('#-') { assert_equal([node[1], node[2]], node - ['a']) }
|
64
|
-
it('#<=>') { assert_equal(1, node <=> []) }
|
65
|
-
it('#<=>') { assert_equal(-1, [] <=> node) }
|
66
|
-
require 'abbrev'
|
67
|
-
it('#abbrev') { assert_equal({'a' => 'a'}, JSI::JSON::Node.new_by_type(['a'], []).abbrev) }
|
68
|
-
it('#assoc') { assert_equal(['b', 'q'], node.assoc('b')) }
|
69
|
-
it('#at') { assert_equal('a', node.at(0)) }
|
70
|
-
it('#bsearch') { assert_equal(nil, node.bsearch { false }) }
|
71
|
-
it('#bsearch_index') { assert_equal(nil, node.bsearch_index { false }) } if [].respond_to?(:bsearch_index)
|
72
|
-
it('#combination') { assert_equal([['a'], [node[1]], [node[2]]], node.combination(1).to_a) }
|
73
|
-
it('#count') { assert_equal(1, node.count('a')) }
|
74
|
-
it('#cycle') { assert_equal(node.to_a, node.cycle(1).to_a) }
|
75
|
-
it('#dig') { assert_equal('e', node.dig(2, 'c', 'd')) } if [].respond_to?(:dig)
|
76
|
-
it('#drop') { assert_equal([node[2]], node.drop(2)) }
|
77
|
-
it('#drop_while') { assert_equal([node[1], node[2]], node.drop_while { |e| e == 'a' }) }
|
78
|
-
it('#fetch') { assert_equal('a', node.fetch(0)) }
|
79
|
-
it('#find_index') { assert_equal(0, node.find_index { true }) }
|
80
|
-
it('#first') { assert_equal('a', node.first) }
|
81
|
-
it('#include?') { assert_equal(true, node.include?('a')) }
|
82
|
-
it('#index') { assert_equal(0, node.index('a')) }
|
83
|
-
it('#join') { assert_equal('a b', JSI::JSON::Node.new_by_type(['a', 'b'], []).join(' ')) }
|
84
|
-
it('#last') { assert_equal(node[2], node.last) }
|
85
|
-
it('#pack') { assert_equal(' ', JSI::JSON::Node.new_by_type([32], []).pack('c')) }
|
86
|
-
it('#permutation') { assert_equal([['a'], [node[1]], [node[2]]], node.permutation(1).to_a) }
|
87
|
-
it('#product') { assert_equal([], node.product([])) }
|
88
|
-
# due to differences in implementation between #assoc and #rassoc, the reason for which
|
89
|
-
# I cannot begin to fathom, assoc works but rassoc does not because rassoc has different
|
90
|
-
# type checking than assoc for the array(like) array elements.
|
91
|
-
# compare:
|
92
|
-
# assoc: https://github.com/ruby/ruby/blob/v2_5_0/array.c#L3780-L3813
|
93
|
-
# rassoc: https://github.com/ruby/ruby/blob/v2_5_0/array.c#L3815-L3847
|
94
|
-
# for this reason, rassoc is NOT defined on Arraylike and #content must be called.
|
95
|
-
it('#rassoc') { assert_equal(['b', 'q'], node.content.rassoc('q')) }
|
96
|
-
it('#repeated_combination') { assert_equal([[]], node.repeated_combination(0).to_a) }
|
97
|
-
it('#repeated_permutation') { assert_equal([[]], node.repeated_permutation(0).to_a) }
|
98
|
-
it('#reverse') { assert_equal([node[2], node[1], 'a'], node.reverse) }
|
99
|
-
it('#reverse_each') { assert_equal([node[2], node[1], 'a'], node.reverse_each.to_a) }
|
100
|
-
it('#rindex') { assert_equal(0, node.rindex('a')) }
|
101
|
-
it('#rotate') { assert_equal([node[1], node[2], 'a'], node.rotate) }
|
102
|
-
it('#sample') { assert_equal('a', JSI::JSON::Node.new_by_type(['a'], []).sample) }
|
103
|
-
it('#shelljoin') { assert_equal('a', JSI::JSON::Node.new_by_type(['a'], []).shelljoin) } if [].respond_to?(:shelljoin)
|
104
|
-
it('#shuffle') { assert_equal(3, node.shuffle.size) }
|
105
|
-
it('#slice') { assert_equal(['a'], node.slice(0, 1)) }
|
106
|
-
it('#sort') { assert_equal(['a'], JSI::JSON::Node.new_by_type(['a'], []).sort) }
|
107
|
-
it('#take') { assert_equal(['a'], node.take(1)) }
|
108
|
-
it('#take_while') { assert_equal([], node.take_while { false }) }
|
109
|
-
it('#transpose') { assert_equal([], JSI::JSON::Node.new_by_type([], []).transpose) }
|
110
|
-
it('#uniq') { assert_equal(node.to_a, node.uniq) }
|
111
|
-
it('#values_at') { assert_equal(['a'], node.values_at(0)) }
|
112
|
-
it('#zip') { assert_equal([['a', 'a'], [node[1], node[1]], [node[2], node[2]]], node.zip(node)) }
|
113
|
-
end
|
114
|
-
describe 'modified copy methods' do
|
115
|
-
it('#reject') { assert_equal(JSI::JSON::Node.new_by_type(['a'], []), node.reject { |e| e != 'a' }) }
|
116
|
-
it('#select') { assert_equal(JSI::JSON::Node.new_by_type(['a'], []), node.select { |e| e == 'a' }) }
|
117
|
-
it('#compact') { assert_equal(node, node.compact) }
|
118
|
-
describe 'at a depth' do
|
119
|
-
let(:document) { [['b', 'q'], {'c' => ['d', 'e']}] }
|
120
|
-
let(:path) { ['1', 'c'] }
|
121
|
-
it('#select') do
|
122
|
-
selected = node.select { |e| e == 'd' }
|
123
|
-
equivalent = JSI::JSON::Node.new_by_type([['b', 'q'], {'c' => ['d']}], ['1', 'c'])
|
124
|
-
assert_equal(equivalent, selected)
|
129
|
+
describe 'modified copy methods' do
|
130
|
+
it('#reject') { assert_equal(JSI::JSON::Node.new_doc(['a']), node.reject { |e| e != 'a' }) }
|
131
|
+
it('#select') { assert_equal(JSI::JSON::Node.new_doc(['a']), node.select { |e| e == 'a' }) }
|
132
|
+
it('#compact') { assert_equal(JSI::JSON::Node.new_doc(node.content.to_ary), node.compact) }
|
133
|
+
describe 'at a depth' do
|
134
|
+
let(:document) { document_type[:make_document].call([['b', 'q'], {'c' => ['d', 'e']}]) }
|
135
|
+
let(:path) { ['1', 'c'] }
|
136
|
+
it('#select') do
|
137
|
+
selected = node.select { |e| e == 'd' }
|
138
|
+
equivalent = JSI::JSON::Node.new_by_type([['b', 'q'], {'c' => ['d']}], ['1', 'c'])
|
139
|
+
assert_equal(equivalent, selected)
|
140
|
+
end
|
125
141
|
end
|
126
142
|
end
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
143
|
+
JSI::Arraylike::DESTRUCTIVE_METHODS.each do |destructive_method_name|
|
144
|
+
it("does not respond to destructive method #{destructive_method_name}") do
|
145
|
+
assert(!node.respond_to?(destructive_method_name))
|
146
|
+
end
|
131
147
|
end
|
132
148
|
end
|
133
149
|
end
|