jsi 0.0.4 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.simplecov +3 -1
  3. data/CHANGELOG.md +48 -0
  4. data/LICENSE.md +613 -0
  5. data/README.md +84 -45
  6. data/jsi.gemspec +11 -14
  7. data/lib/jsi.rb +31 -12
  8. data/lib/jsi/base.rb +310 -344
  9. data/lib/jsi/base/to_rb.rb +2 -0
  10. data/lib/jsi/jsi_coder.rb +91 -0
  11. data/lib/jsi/json-schema-fragments.rb +3 -135
  12. data/lib/jsi/json.rb +3 -0
  13. data/lib/jsi/json/node.rb +72 -197
  14. data/lib/jsi/json/pointer.rb +419 -0
  15. data/lib/jsi/metaschema.rb +7 -0
  16. data/lib/jsi/metaschema_node.rb +218 -0
  17. data/lib/jsi/pathed_node.rb +118 -0
  18. data/lib/jsi/schema.rb +168 -223
  19. data/lib/jsi/schema_classes.rb +158 -0
  20. data/lib/jsi/simple_wrap.rb +12 -0
  21. data/lib/jsi/typelike_modules.rb +71 -45
  22. data/lib/jsi/util.rb +47 -57
  23. data/lib/jsi/version.rb +1 -1
  24. data/lib/schemas/json-schema.org/draft-04/schema.rb +7 -0
  25. data/lib/schemas/json-schema.org/draft-06/schema.rb +7 -0
  26. data/resources/icons/AGPL-3.0.png +0 -0
  27. data/test/base_array_test.rb +210 -84
  28. data/test/base_hash_test.rb +201 -58
  29. data/test/base_test.rb +212 -121
  30. data/test/jsi_coder_test.rb +85 -0
  31. data/test/jsi_json_arraynode_test.rb +26 -25
  32. data/test/jsi_json_hashnode_test.rb +40 -39
  33. data/test/jsi_json_node_test.rb +95 -126
  34. data/test/jsi_json_pointer_test.rb +102 -0
  35. data/test/jsi_typelike_as_json_test.rb +53 -0
  36. data/test/metaschema_node_test.rb +19 -0
  37. data/test/schema_module_test.rb +21 -0
  38. data/test/schema_test.rb +109 -97
  39. data/test/spreedly_openapi_test.rb +8 -0
  40. data/test/test_helper.rb +42 -8
  41. data/test/util_test.rb +14 -14
  42. metadata +54 -25
  43. data/LICENSE.txt +0 -21
  44. data/lib/jsi/schema_instance_json_coder.rb +0 -83
  45. data/lib/jsi/struct_json_coder.rb +0 -30
  46. data/test/schema_instance_json_coder_test.rb +0 -121
  47. data/test/struct_json_coder_test.rb +0 -130
@@ -1,57 +1,54 @@
1
1
  require_relative 'test_helper'
2
2
 
3
- NamedSchemaInstance = JSI.class_for_schema({id: 'https://schemas.jsi.unth.net/test/base/named_schema'})
3
+ NamedSchemaInstance = JSI::Schema.new({id: 'https://schemas.jsi.unth.net/test/base/named_schema'}).jsi_schema_class
4
+
5
+ # hitting .tap(&:name) causes JSI to assign a constant name from the ID,
6
+ # meaning the name NamedSchemaInstanceTwo is not known.
7
+ NamedSchemaInstanceTwo = JSI::Schema.new({id: 'https://schemas.jsi.unth.net/test/base/named_schema_two'}).jsi_schema_class.tap(&:name)
4
8
 
5
9
  describe JSI::Base do
6
- let(:document) { {} }
7
- let(:path) { [] }
8
- let(:instance) { JSI::JSON::Node.new_by_type(document, path) }
9
10
  let(:schema_content) { {} }
10
11
  let(:schema) { JSI::Schema.new(schema_content) }
11
- let(:subject) { JSI.class_for_schema(schema).new(instance) }
12
- describe 'class .inspect + .to_s' do
12
+ let(:instance) { {} }
13
+ let(:subject) { schema.new_jsi(instance) }
14
+ describe 'class .inspect' do
13
15
  it 'is the same as Class#inspect on the base' do
14
16
  assert_equal('JSI::Base', JSI::Base.inspect)
15
- assert_equal('JSI::Base', JSI::Base.to_s)
16
17
  end
17
- it 'is SchemaClasses[] for generated subclass without id' do
18
- assert_match(%r(\AJSI::SchemaClasses\["[a-f0-9\-]+#"\]\z), subject.class.inspect)
19
- assert_match(%r(\AJSI::SchemaClasses\["[a-f0-9\-]+#"\]\z), subject.class.to_s)
18
+ it 'is (JSI Schema Class) for generated subclass without id' do
19
+ assert_equal("(JSI Schema Class: #)", subject.class.inspect)
20
20
  end
21
21
  describe 'with schema id' do
22
22
  let(:schema_content) { {'id' => 'https://jsi/foo'} }
23
- it 'is SchemaClasses[] for generated subclass with id' do
24
- assert_equal(%q(JSI::SchemaClasses["https://jsi/foo#"]), subject.class.inspect)
25
- assert_equal(%q(JSI::SchemaClasses["https://jsi/foo#"]), subject.class.to_s)
23
+ it 'is (JSI Schema Class: ...) for generated subclass with id' do
24
+ assert_equal("(JSI Schema Class: https://jsi/foo#)", subject.class.inspect)
26
25
  end
27
26
  end
28
- it 'is the constant name (plus id for .inspect) for a class assigned to a constant' do
27
+ it 'is the constant name plus id for a class assigned to a constant' do
29
28
  assert_equal(%q(NamedSchemaInstance (https://schemas.jsi.unth.net/test/base/named_schema#)), NamedSchemaInstance.inspect)
30
- assert_equal(%q(NamedSchemaInstance), NamedSchemaInstance.to_s)
29
+ end
30
+ it 'is not the constant name when the constant name has been generated from the schema_id' do
31
+ assert_equal("JSI::SchemaClasses::Xhttps___schemas_jsi_unth_net_test_base_named_schema_two_", NamedSchemaInstanceTwo.name)
32
+ assert_equal("(JSI Schema Class: https://schemas.jsi.unth.net/test/base/named_schema_two#)", NamedSchemaInstanceTwo.inspect)
31
33
  end
32
34
  end
33
35
  describe 'class name' do
34
36
  let(:schema_content) { {'id' => 'https://jsi/BaseTest'} }
35
37
  it 'generates a class name from schema_id' do
36
- assert_equal('JSI::SchemaClasses::Https___jsi_BaseTest_', subject.class.name)
38
+ assert_equal('JSI::SchemaClasses::Xhttps___jsi_BaseTest_', subject.class.name)
37
39
  end
38
40
  it 'uses an existing name' do
39
41
  assert_equal('NamedSchemaInstance', NamedSchemaInstance.name)
40
42
  end
41
43
  end
42
- describe 'class for schema .schema' do
43
- it '.schema' do
44
- assert_equal(schema, JSI.class_for_schema(schema).schema)
45
- end
46
- end
47
- describe 'class for schema .schema_id' do
48
- it '.schema_id' do
49
- assert_equal(schema.schema_id, JSI.class_for_schema(schema).schema_id)
44
+ describe 'class for schema .jsi_class_schemas' do
45
+ it '.jsi_class_schemas' do
46
+ assert_equal(Set.new << schema, schema.jsi_schema_class.jsi_class_schemas)
50
47
  end
51
48
  end
52
49
  describe 'module for schema .inspect' do
53
50
  it '.inspect' do
54
- assert_match(%r(\A#<Module for Schema: .+#>\z), JSI::SchemaClasses.module_for_schema(schema).inspect)
51
+ assert_equal("(JSI Schema Module: #)", JSI::SchemaClasses.module_for_schema(schema).inspect)
55
52
  end
56
53
  end
57
54
  describe 'module for schema .schema' do
@@ -59,26 +56,15 @@ describe JSI::Base do
59
56
  assert_equal(schema, JSI::SchemaClasses.module_for_schema(schema).schema)
60
57
  end
61
58
  end
62
- describe 'SchemaClasses[]' do
63
- it 'stores the class for the schema' do
64
- assert_equal(JSI.class_for_schema(schema), JSI::SchemaClasses[schema.schema_id])
65
- end
66
- end
67
- describe '.class_for_schema' do
59
+ describe '.class_for_schemas' do
68
60
  it 'returns a class from a schema' do
69
- class_for_schema = JSI.class_for_schema(schema)
61
+ class_for_schema = JSI.class_for_schemas([schema])
70
62
  # same class every time
71
- assert_equal(JSI.class_for_schema(schema), class_for_schema)
63
+ assert_equal(JSI.class_for_schemas([schema]), class_for_schema)
72
64
  assert_operator(class_for_schema, :<, JSI::Base)
73
65
  end
74
- it 'returns a class from a hash' do
75
- assert_equal(JSI.class_for_schema(schema), JSI.class_for_schema(schema.schema_node.content))
76
- end
77
- it 'returns a class from a schema node' do
78
- assert_equal(JSI.class_for_schema(schema), JSI.class_for_schema(schema.schema_node))
79
- end
80
- it 'returns a class from a Base' do
81
- assert_equal(JSI.class_for_schema(schema), JSI.class_for_schema(JSI.class_for_schema({}).new(schema.schema_node)))
66
+ it 'returns the same class from a hash' do
67
+ assert_equal(JSI.class_for_schemas([schema]), JSI.class_for_schemas([schema_content]))
82
68
  end
83
69
  end
84
70
  describe 'JSI::SchemaClasses.module_for_schema' do
@@ -88,26 +74,20 @@ describe JSI::Base do
88
74
  assert_equal(JSI::SchemaClasses.module_for_schema(schema), module_for_schema)
89
75
  end
90
76
  it 'returns a module from a hash' do
91
- assert_equal(JSI::SchemaClasses.module_for_schema(schema), JSI::SchemaClasses.module_for_schema(schema.schema_node.content))
92
- end
93
- it 'returns a module from a schema node' do
94
- assert_equal(JSI::SchemaClasses.module_for_schema(schema), JSI::SchemaClasses.module_for_schema(schema.schema_node))
95
- end
96
- it 'returns a module from a Base' do
97
- assert_equal(JSI::SchemaClasses.module_for_schema(schema), JSI::SchemaClasses.module_for_schema(JSI.class_for_schema({}).new(schema.schema_node)))
77
+ assert_equal(JSI::SchemaClasses.module_for_schema(schema), JSI::SchemaClasses.module_for_schema(schema.jsi_instance))
98
78
  end
99
79
  end
100
80
  describe 'initialization' do
101
81
  describe 'on Base' do
102
82
  it 'errors' do
103
83
  err = assert_raises(TypeError) { JSI::Base.new({}) }
104
- assert_equal('cannot instantiate JSI::Base which has no method #schema. please use JSI.class_for_schema', err.message)
84
+ assert_equal('cannot instantiate JSI::Base which has no method #jsi_schemas. it is recommended to instantiate JSIs from a schema using JSI::Schema#new_jsi.', err.message)
105
85
  end
106
86
  end
107
87
  describe 'nil' do
108
88
  let(:instance) { nil }
109
89
  it 'initializes with nil instance' do
110
- assert_equal(JSI::JSON::Node.new_doc(nil), subject.instance)
90
+ assert_equal(nil, subject.jsi_instance)
111
91
  assert(!subject.respond_to?(:to_ary))
112
92
  assert(!subject.respond_to?(:to_hash))
113
93
  end
@@ -115,7 +95,7 @@ describe JSI::Base do
115
95
  describe 'arbitrary instance' do
116
96
  let(:instance) { Object.new }
117
97
  it 'initializes' do
118
- assert_equal(JSI::JSON::Node.new_doc(instance), subject.instance)
98
+ assert_equal(instance, subject.jsi_instance)
119
99
  assert(!subject.respond_to?(:to_ary))
120
100
  assert(!subject.respond_to?(:to_hash))
121
101
  end
@@ -124,16 +104,16 @@ describe JSI::Base do
124
104
  let(:instance) { {'foo' => 'bar'} }
125
105
  let(:schema_content) { {'type' => 'object'} }
126
106
  it 'initializes' do
127
- assert_equal(JSI::JSON::Node.new_doc({'foo' => 'bar'}), subject.instance)
107
+ assert_equal({'foo' => 'bar'}, subject.jsi_instance)
128
108
  assert(!subject.respond_to?(:to_ary))
129
109
  assert(subject.respond_to?(:to_hash))
130
110
  end
131
111
  end
132
- describe 'JSI::JSON::Hashnode' do
133
- let(:document) { {'foo' => 'bar'} }
112
+ describe 'JSI::JSON::HashNode' do
113
+ let(:instance) { JSI::JSON::HashNode.new({'foo' => 'bar'}, JSI::JSON::Pointer.new([])) }
134
114
  let(:schema_content) { {'type' => 'object'} }
135
115
  it 'initializes' do
136
- assert_equal(JSI::JSON::HashNode.new({'foo' => 'bar'}, []), subject.instance)
116
+ assert_equal(JSI::JSON::HashNode.new({'foo' => 'bar'}, JSI::JSON::Pointer.new([])), subject.jsi_instance)
137
117
  assert(!subject.respond_to?(:to_ary))
138
118
  assert(subject.respond_to?(:to_hash))
139
119
  end
@@ -142,69 +122,93 @@ describe JSI::Base do
142
122
  let(:instance) { ['foo'] }
143
123
  let(:schema_content) { {'type' => 'array'} }
144
124
  it 'initializes' do
145
- assert_equal(JSI::JSON::Node.new_doc(['foo']), subject.instance)
125
+ assert_equal(['foo'], subject.jsi_instance)
146
126
  assert(subject.respond_to?(:to_ary))
147
127
  assert(!subject.respond_to?(:to_hash))
148
128
  end
149
129
  end
150
- describe 'JSI::JSON::Arraynode' do
151
- let(:document) { ['foo'] }
130
+ describe 'JSI::JSON::ArrayNode' do
131
+ let(:instance) { JSI::JSON::ArrayNode.new(['foo'], JSI::JSON::Pointer.new([])) }
152
132
  let(:schema_content) { {'type' => 'array'} }
153
133
  it 'initializes' do
154
- assert_equal(JSI::JSON::ArrayNode.new(['foo'], []), subject.instance)
134
+ assert_equal(JSI::JSON::ArrayNode.new(['foo'], JSI::JSON::Pointer.new([])), subject.jsi_instance)
155
135
  assert(subject.respond_to?(:to_ary))
156
136
  assert(!subject.respond_to?(:to_hash))
157
137
  end
158
138
  end
159
- describe 'another Base' do
139
+ describe 'another JSI::Base invalid' do
160
140
  let(:schema_content) { {'type' => 'object'} }
161
- let(:instance) { JSI.class_for_schema(schema).new({'foo' => 'bar'}) }
162
- it 'initializes with a warning' do
163
- assert_output(nil, /assigning instance to a Base instance is incorrect. received: #\{<JSI::SchemaClasses\["[^"]+#"\][^>]*>[^}]+}/) do
164
- subject
165
- end
166
- assert_equal(JSI::JSON::HashNode.new({'foo' => 'bar'}, []), subject.instance)
141
+ let(:instance) { schema.new_jsi({'foo' => 'bar'}) }
142
+ it 'initializes with an error' do
143
+ err = assert_raises(TypeError) { subject }
144
+ assert_equal("assigning another JSI::Base instance to a (JSI Schema Class: #) instance is incorrect. received: \#{<JSI> \"foo\" => \"bar\"}", err.message)
145
+ end
146
+ end
147
+ describe 'Schema invalid' do
148
+ let(:instance) { JSI::Schema.new({}) }
149
+ it 'initializes with an error' do
150
+ err = assert_raises(TypeError) { subject }
151
+ assert_equal("assigning a schema to a (JSI Schema Class: #) instance is incorrect. received: \#{<JSI (JSI::JSONSchemaOrgDraft06) Schema>}", err.message)
167
152
  end
168
153
  end
169
154
  end
170
- describe '#parents, #parent' do
155
+ describe '#parent_jsis, #parent_jsi' do
171
156
  let(:schema_content) { {'properties' => {'foo' => {'properties' => {'bar' => {'properties' => {'baz' => {}}}}}}} }
172
- let(:document) { {'foo' => {'bar' => {'baz' => {}}}} }
173
- describe 'no parents' do
157
+ let(:instance) { {'foo' => {'bar' => {'baz' => {}}}} }
158
+ describe 'no parent_jsis' do
174
159
  it 'has none' do
175
160
  assert_equal([], subject.parents)
161
+ assert_equal([], subject.parent_jsis)
176
162
  assert_equal(nil, subject.parent)
163
+ assert_equal(nil, subject.parent_jsi)
177
164
  end
178
165
  end
179
- describe 'one parent' do
166
+ describe 'one parent_jsi' do
180
167
  it 'has one' do
181
168
  assert_equal([subject], subject.foo.parents)
169
+ assert_equal([subject], subject.foo.parent_jsis)
182
170
  assert_equal(subject, subject.foo.parent)
171
+ assert_equal(subject, subject.foo.parent_jsi)
183
172
  end
184
173
  end
185
- describe 'more parents' do
174
+ describe 'more parent_jsis' do
186
175
  it 'has more' do
187
176
  assert_equal([subject.foo.bar, subject.foo, subject], subject.foo.bar.baz.parents)
177
+ assert_equal([subject.foo.bar, subject.foo, subject], subject.foo.bar.baz.parent_jsis)
188
178
  assert_equal(subject.foo.bar, subject.foo.bar.baz.parent)
179
+ assert_equal(subject.foo.bar, subject.foo.bar.baz.parent_jsi)
189
180
  end
190
181
  end
191
182
  end
192
183
  describe '#each, Enumerable methods' do
193
- let(:document) { 'a string' }
184
+ let(:instance) { 'a string' }
194
185
  it "raises NoMethodError calling each or Enumerable methods" do
195
186
  assert_raises(NoMethodError) { subject.each { nil } }
196
187
  assert_raises(NoMethodError) { subject.map { nil } }
197
188
  end
198
189
  end
199
190
  describe '#modified_copy' do
191
+ describe 'with an instance that does not have #modified_copy' do
192
+ let(:instance) { Object.new }
193
+ it 'yields the instance to modify' do
194
+ new_instance = Object.new
195
+ modified = subject.modified_copy do |o|
196
+ assert_equal(instance, o)
197
+ new_instance
198
+ end
199
+ assert_equal(new_instance, modified.jsi_instance)
200
+ assert_equal(instance, subject.jsi_instance)
201
+ refute_equal(instance, modified)
202
+ end
203
+ end
200
204
  describe 'with an instance that does have #modified_copy' do
201
205
  it 'yields the instance to modify' do
202
206
  modified = subject.modified_copy do |o|
203
207
  assert_equal({}, o)
204
208
  {'a' => 'b'}
205
209
  end
206
- assert_equal({'a' => 'b'}, modified.instance.content)
207
- assert_equal({}, subject.instance.content)
210
+ assert_equal({'a' => 'b'}, modified.jsi_instance)
211
+ assert_equal({}, subject.jsi_instance)
208
212
  refute_equal(instance, modified)
209
213
  end
210
214
  end
@@ -212,7 +216,7 @@ describe JSI::Base do
212
216
  it 'yields the instance to modify' do
213
217
  modified = subject.modified_copy { |o| o }
214
218
  # this doesn't really need to be tested but ... whatever
215
- assert_equal(subject.instance.content.object_id, modified.instance.content.object_id)
219
+ assert_equal(subject.jsi_instance.object_id, modified.jsi_instance.object_id)
216
220
  assert_equal(subject, modified)
217
221
  refute_equal(subject.object_id, modified.object_id)
218
222
  end
@@ -224,18 +228,15 @@ describe JSI::Base do
224
228
  modified = subject.modified_copy do |o|
225
229
  o.to_s
226
230
  end
227
- assert_equal('{}', modified.instance.content)
228
- assert_equal({}, subject.instance.content)
231
+ assert_equal('{}', modified.jsi_instance)
232
+ assert_equal({}, subject.jsi_instance)
229
233
  refute_equal(instance, modified)
230
234
  # interesting side effect
231
235
  assert(subject.respond_to?(:to_hash))
232
236
  assert(!modified.respond_to?(:to_hash))
233
- assert_equal(JSI::JSON::HashNode, subject.instance.class)
234
- assert_equal(JSI::JSON::Node, modified.instance.class)
235
237
  end
236
238
  end
237
239
  end
238
- it('#fragment') { assert_equal('#', subject.fragment) }
239
240
  describe 'validation' do
240
241
  describe 'without errors' do
241
242
  it '#fully_validate' do
@@ -248,6 +249,94 @@ describe JSI::Base do
248
249
  assert_equal(true, subject.validate!)
249
250
  end
250
251
  end
252
+ describe 'with errors' do
253
+ let(:schema_content) {
254
+ {
255
+ 'id' => 'https://schemas.jsi.unth.net/test/JSI::Base::validation::with errors',
256
+ 'type' => 'object',
257
+ 'properties' => {
258
+ 'some_number' => {
259
+ 'type' => 'number'
260
+ },
261
+ 'a_required_property' => {
262
+ 'type' => 'string'
263
+ }
264
+ }
265
+ }
266
+ }
267
+ let(:instance) { "this is a string" }
268
+
269
+ it '#validate' do
270
+ assert_equal(false, subject.validate)
271
+ end
272
+ it '#validate!' do
273
+ assert_raises JSON::Schema::ValidationError do
274
+ subject.validate!
275
+ end
276
+ end
277
+ describe 'fully_validate' do
278
+ it '#fully_validate ' do
279
+ assert_equal(["The property '#/' of type string did not match the following type: object in schema https://schemas.jsi.unth.net/test/JSI::Base::validation::with errors"], subject.fully_validate)
280
+ end
281
+ it '#fully_validate :errors_as_objects' do
282
+ expected = [
283
+ {
284
+ :schema => Addressable::URI.parse('https://schemas.jsi.unth.net/test/JSI::Base::validation::with errors'),
285
+ :fragment => "#/",
286
+ :message => "The property '#/' of type string did not match the following type: object in schema https://schemas.jsi.unth.net/test/JSI::Base::validation::with errors",
287
+ :failed_attribute=>"TypeV4"
288
+ }
289
+ ]
290
+ assert_equal(expected, subject.fully_validate(:errors_as_objects => true))
291
+ end
292
+ end
293
+ end
294
+ describe 'at a depth' do
295
+ let(:schema_content) do
296
+ {
297
+ 'id' => 'https://schemas.jsi.unth.net/test/JSI::Base::validation::at a depth',
298
+ 'description' => 'hash schema',
299
+ 'type' => 'object',
300
+ 'properties' => {
301
+ 'foo' => {'type' => 'object'},
302
+ 'bar' => {},
303
+ 'baz' => {'type' => 'array'},
304
+ },
305
+ 'additionalProperties' => {'not' => {}},
306
+ }
307
+ end
308
+
309
+ describe 'without errors' do
310
+ let(:instance) { {'foo' => {'x' => 'y'}, 'bar' => [9], 'baz' => [true]} }
311
+
312
+ it '#fully_validate' do
313
+ assert_equal([], subject.foo.fully_validate)
314
+ assert_equal([], subject.bar.fully_validate)
315
+ end
316
+ it '#validate' do
317
+ assert_equal(true, subject.foo.validate)
318
+ assert_equal(true, subject.bar.validate)
319
+ end
320
+ end
321
+ describe 'with errors' do
322
+ let(:instance) { {'foo' => [true], 'bar' => [9], 'baz' => {'x' => 'y'}, 'more' => {}} }
323
+
324
+ it '#fully_validate' do
325
+ assert_equal(["The property '#/' of type array did not match the following type: object in schema https://schemas.jsi.unth.net/test/JSI::Base::validation::at a depth"], subject.foo.fully_validate)
326
+ assert_equal([], subject.bar.fully_validate)
327
+ assert_equal(["The property '#/' of type object did not match the following type: array in schema https://schemas.jsi.unth.net/test/JSI::Base::validation::at a depth"], subject.baz.fully_validate)
328
+ assert_equal(["The property '#/' of type object matched the disallowed schema in schema https://schemas.jsi.unth.net/test/JSI::Base::validation::at a depth"], subject['more'].fully_validate)
329
+ assert_equal(["The property '#/foo' of type array did not match the following type: object in schema https://schemas.jsi.unth.net/test/JSI::Base::validation::at a depth", "The property '#/baz' of type object did not match the following type: array in schema https://schemas.jsi.unth.net/test/JSI::Base::validation::at a depth", "The property '#/more' of type object matched the disallowed schema in schema https://schemas.jsi.unth.net/test/JSI::Base::validation::at a depth"], subject.fully_validate)
330
+ end
331
+ it '#validate' do
332
+ assert_equal(false, subject.foo.validate)
333
+ assert_equal(true, subject.bar.validate)
334
+ assert_equal(false, subject.baz.validate)
335
+ assert_equal(false, subject['more'].validate)
336
+ assert_equal(false, subject.validate)
337
+ end
338
+ end
339
+ end
251
340
  end
252
341
  describe 'property accessors' do
253
342
  let(:schema_content) do
@@ -260,17 +349,17 @@ describe JSI::Base do
260
349
  },
261
350
  }
262
351
  end
263
- let(:document) do
352
+ let(:instance) do
264
353
  {'foo' => {'x' => 'y'}, 'bar' => [3.14159], 'baz' => true, 'qux' => []}
265
354
  end
266
355
  describe 'readers' do
267
356
  it 'reads attributes described as properties' do
268
357
  assert_equal({'x' => 'y'}, subject.foo.as_json)
269
- assert_instance_of(JSI.class_for_schema(schema.schema_node['properties']['foo']), subject.foo)
358
+ assert_is_a(schema.properties['foo'].jsi_schema_module, subject.foo)
270
359
  assert_respond_to(subject.foo, :to_hash)
271
360
  refute_respond_to(subject.foo, :to_ary)
272
361
  assert_equal([3.14159], subject.bar.as_json)
273
- assert_instance_of(JSI.class_for_schema(schema.schema_node['properties']['bar']), subject.bar)
362
+ assert_is_a(schema.properties['bar'].jsi_schema_module, subject.bar)
274
363
  refute_respond_to(subject.bar, :to_hash)
275
364
  assert_respond_to(subject.bar, :to_ary)
276
365
  assert_equal(true, subject.baz)
@@ -281,8 +370,8 @@ describe JSI::Base do
281
370
  describe 'when the instance is not hashlike' do
282
371
  let(:instance) { nil }
283
372
  it 'errors' do
284
- err = assert_raises(NoMethodError) { subject.foo }
285
- assert_match(%r(\Ainstance does not respond to \[\]; cannot call reader `foo' for: #<JSI::SchemaClasses\["[^"]+#"\].*nil.*>\z)m, err.message)
373
+ err = assert_raises(JSI::Base::CannotSubscriptError) { subject.foo }
374
+ assert_equal(%q(cannot subcript (using token: "foo") from instance: nil), err.message)
286
375
  end
287
376
  end
288
377
  describe 'properties with the same names as instance methods' do
@@ -295,14 +384,14 @@ describe JSI::Base do
295
384
  'inspect' => {}, # Base
296
385
  'pretty_inspect' => {}, # Kernel
297
386
  'as_json' => {}, # Base::OverrideFromExtensions, extended on initialization
298
- 'each' => {}, # BaseHash / BaseArray
387
+ 'each' => {}, # PathedHashNode / PathedArrayNode
299
388
  'instance_exec' => {}, # BasicObject
300
- 'instance' => {}, # Base
301
- 'schema' => {}, # module_for_schema singleton definition
389
+ 'jsi_instance' => {}, # Base
390
+ 'jsi_schemas' => {}, # module_for_schema singleton definition
302
391
  },
303
392
  }
304
393
  end
305
- let(:document) do
394
+ let(:instance) do
306
395
  {
307
396
  'foo' => 'bar',
308
397
  'initialize' => 'hi',
@@ -311,24 +400,23 @@ describe JSI::Base do
311
400
  'as_json' => 'hi',
312
401
  'each' => 'hi',
313
402
  'instance_exec' => 'hi',
314
- 'instance' => 'hi',
315
- 'schema' => 'hi',
403
+ 'jsi_instance' => 'hi',
404
+ 'jsi_schemas' => 'hi',
316
405
  }
317
406
  end
318
407
  it 'does not define readers' do
319
- assert_equal('bar', subject.foo)
320
- assert_equal(JSI::SchemaClasses.module_for_schema(subject.schema), subject.method(:foo).owner)
408
+ assert_equal('bar', subject.foo) # this one is defined
321
409
 
322
410
  assert_equal(JSI::Base, subject.method(:initialize).owner)
323
411
  assert_equal('hi', subject['initialize'])
324
- assert_match(%r(\A#\{<JSI::SchemaClasses\[".*#"\].*}\z)m, subject.inspect)
412
+ assert_equal(%q(#{<JSI> "foo" => "bar", "initialize" => "hi", "inspect" => "hi", "pretty_inspect" => "hi", "as_json" => "hi", "each" => "hi", "instance_exec" => "hi", "jsi_instance" => "hi", "jsi_schemas" => "hi"}), subject.inspect)
325
413
  assert_equal('hi', subject['inspect'])
326
- assert_match(%r(\A#\{<JSI::SchemaClasses\[".*#"\].*}\Z)m, subject.pretty_inspect)
327
- assert_equal(document, subject.as_json)
414
+ assert_equal(%Q(\#{<JSI>\n "foo" => "bar",\n "initialize" => "hi",\n "inspect" => "hi",\n "pretty_inspect" => "hi",\n "as_json" => "hi",\n "each" => "hi",\n "instance_exec" => "hi",\n "jsi_instance" => "hi",\n "jsi_schemas" => "hi"\n}\n), subject.pretty_inspect)
415
+ assert_equal(instance, subject.as_json)
328
416
  assert_equal(subject, subject.each { })
329
417
  assert_equal(2, subject.instance_exec { 2 })
330
- assert_equal(instance, subject.instance)
331
- assert_equal(schema, subject.schema)
418
+ assert_equal(instance, subject.jsi_instance)
419
+ assert_equal(Set.new << schema, subject.jsi_schemas)
332
420
  end
333
421
  end
334
422
  end
@@ -339,57 +427,60 @@ describe JSI::Base do
339
427
  subject.foo = {'y' => 'z'}
340
428
 
341
429
  assert_equal({'y' => 'z'}, subject.foo.as_json)
342
- assert_instance_of(JSI.class_for_schema(schema.schema_node['properties']['foo']), orig_foo)
343
- assert_instance_of(JSI.class_for_schema(schema.schema_node['properties']['foo']), subject.foo)
430
+ assert_is_a(schema.properties['foo'].jsi_schema_module, orig_foo)
431
+ assert_is_a(schema.properties['foo'].jsi_schema_module, subject.foo)
344
432
  end
345
433
  it 'modifies the instance, visible to other references to the same instance' do
346
- orig_instance = subject.instance
434
+ orig_instance = subject.jsi_instance
347
435
 
348
436
  subject.foo = {'y' => 'z'}
349
437
 
350
- assert_equal(orig_instance, subject.instance)
351
- assert_equal({'y' => 'z'}, orig_instance['foo'].as_json)
352
- assert_equal({'y' => 'z'}, subject.instance['foo'].as_json)
353
- assert_equal(orig_instance.class, subject.instance.class)
438
+ assert_equal(orig_instance, subject.jsi_instance)
439
+ assert_equal({'y' => 'z'}, orig_instance['foo'])
440
+ assert_equal({'y' => 'z'}, subject.jsi_instance['foo'])
441
+ assert_equal(orig_instance.class, subject.jsi_instance.class)
354
442
  end
355
443
  describe 'when the instance is not hashlike' do
356
444
  let(:instance) { nil }
357
445
  it 'errors' do
358
446
  err = assert_raises(NoMethodError) { subject.foo = 0 }
359
- assert_match(%r(\Ainstance does not respond to \[\]=; cannot call writer `foo=' for: #<JSI::SchemaClasses\["[^"]+#"\].*nil.*>\z)m, err.message)
447
+ assert_equal('cannot assign subcript (using token: "foo") to instance: nil', err.message)
360
448
  end
361
449
  end
362
450
  end
363
451
  end
364
452
  describe '#inspect' do
365
453
  # if the instance is hash-like, #inspect gets overridden
366
- let(:document) { Object.new }
454
+ let(:instance) { Object.new }
367
455
  it 'inspects' do
368
- assert_match(%r(\A#<JSI::SchemaClasses\["[^"]+#"\] #<JSI::JSON::Node fragment="#" #<Object:[^<>]*>>>\z), subject.inspect)
456
+ assert_match(%r(\A\#<JSI\ \#<Object:[^<>]*>>\z), subject.inspect)
369
457
  end
370
458
  end
371
459
  describe '#pretty_print' do
372
460
  # if the instance is hash-like, #pretty_print gets overridden
373
- let(:document) { Object.new }
461
+ let(:instance) { Object.new }
374
462
  it 'pretty_prints' do
375
- assert_match(%r(\A#<JSI::SchemaClasses\["[^"]+#"\]\n #<JSI::JSON::Node fragment="#" #<Object:[^<>]*>>\n>\z), subject.pretty_inspect.chomp)
463
+ assert_match(%r(\A\#<JSI\ \#<Object:[^<>]*>>\z), subject.pretty_inspect.chomp)
376
464
  end
377
465
  end
378
466
  describe '#as_json' do
379
467
  it '#as_json' do
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))
468
+ assert_equal({'a' => 'b'}, JSI::Schema.new({}).new_jsi({'a' => 'b'}).as_json)
469
+ assert_equal({'a' => 'b'}, JSI::Schema.new({}).new_jsi(JSI::JSON::Node.new_doc({'a' => 'b'})).as_json)
470
+ assert_equal({'a' => 'b'}, JSI::Schema.new({'type' => 'object'}).new_jsi(JSI::JSON::Node.new_doc({'a' => 'b'})).as_json)
471
+ assert_equal(['a', 'b'], JSI::Schema.new({'type' => 'array'}).new_jsi(JSI::JSON::Node.new_doc(['a', 'b'])).as_json)
472
+ assert_equal(['a'], JSI::Schema.new({}).new_jsi(['a']).as_json(some_option: true))
384
473
  end
385
474
  end
386
- describe 'overwrite schema instance with instance=' do
387
- # this error message indicates an internal bug (hence Bug class), so there isn't an intended way to
388
- # trigger it using JSI::Base properly. we use it improperly just to test that code path. this
389
- # is definitely not defined behavior.
390
- it 'errors' do
391
- err = assert_raises(JSI::Bug) { subject.send(:instance=, {'foo' => 'bar'}) }
392
- assert_match(%r(\Aoverwriting instance is not supported\z), err.message)
475
+ describe 'equality between different classes of JSI::Base subclasses' do
476
+ let(:subject_subclass) { Class.new(schema.jsi_schema_class).new(instance) }
477
+
478
+ it 'considers a Base subclass (class_for_schema) and subsubclass to be equal with the same instance' do
479
+ assert_equal(subject.hash, subject_subclass.hash)
480
+ assert(subject == subject_subclass)
481
+ assert(subject_subclass == subject)
482
+ assert(subject.eql?(subject_subclass))
483
+ assert(subject_subclass.eql?(subject))
393
484
  end
394
485
  end
395
486
  end