shape_of 1.2.2 → 3.0.0
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/lib/shape_of.rb +204 -46
- metadata +7 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 302fcc2359111f2a5ea57aa97fa6b686499fe2e93048ca204655989711157ee1
|
4
|
+
data.tar.gz: 8481cc4a6c1c709b680c6babbe2b5aa3335f3a689ec45c8bf24551b80c776f3e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5319dce493532fccf310d7b2c52c53fad2699dab8c23d4a914a7a9754f8db22c336874005eca5a317ab60f185af6ac7057334388e7498760d70e93382a791ad8
|
7
|
+
data.tar.gz: dfaaedf3abcf1dd77c0830215a787a9e2a29d0f06b2db35fc592a28add9aa495344ed7e4c22f46370d840da490fc57b240225a72092d28fccf758df87467ef09
|
data/lib/shape_of.rb
CHANGED
@@ -84,37 +84,125 @@
|
|
84
84
|
# hash_shape.shape_of?({ value: [{}] }) # => false
|
85
85
|
# ```
|
86
86
|
#
|
87
|
+
require 'pp'
|
88
|
+
|
87
89
|
module ShapeOf
|
90
|
+
# Used in Assertions, and can also be used separately. It is used to keep track of places where the shape of
|
91
|
+
# data does not match, so that you can have greater insight than just a simple true/false.
|
92
|
+
class Validator
|
93
|
+
attr_reader :shape, :object
|
94
|
+
|
95
|
+
def initialize(shape:, object:)
|
96
|
+
@current_error_key_nesting = [:base] # stack of the current error key.
|
97
|
+
@errors = {}
|
98
|
+
@object = object
|
99
|
+
@shape = shape
|
100
|
+
|
101
|
+
validate
|
102
|
+
end
|
103
|
+
|
104
|
+
def valid?
|
105
|
+
@errors.empty?
|
106
|
+
end
|
107
|
+
|
108
|
+
def errors
|
109
|
+
@errors[:base]
|
110
|
+
end
|
111
|
+
|
112
|
+
def error_message
|
113
|
+
errors.pretty_inspect
|
114
|
+
end
|
115
|
+
|
116
|
+
def add_error(message)
|
117
|
+
create_nested_error_structure
|
118
|
+
|
119
|
+
@errors.dig(*@current_error_key_nesting)[:errors] << message.dup
|
120
|
+
end
|
121
|
+
|
122
|
+
def push_key(key)
|
123
|
+
@current_error_key_nesting.push(key.dup)
|
124
|
+
end
|
125
|
+
|
126
|
+
def pop_key
|
127
|
+
@current_error_key_nesting.pop
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def create_nested_error_structure
|
133
|
+
errors = @errors
|
134
|
+
@current_error_key_nesting.each do |key|
|
135
|
+
errors[key] ||= {}
|
136
|
+
errors = errors[key]
|
137
|
+
end
|
138
|
+
@errors.dig(*@current_error_key_nesting)[:errors] ||= []
|
139
|
+
end
|
140
|
+
|
141
|
+
def validate
|
142
|
+
shape.shape_of?(object, validator: self)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
88
146
|
# To be included in a MiniTest test class
|
89
147
|
module Assertions
|
90
|
-
|
148
|
+
# For backward compatibility, this is "off" by default, and the order in the assertions are reverse.
|
149
|
+
@proper_expected_actual_order = false
|
150
|
+
def self.use_proper_expected_actual_order!
|
151
|
+
@proper_expected_actual_order = true
|
152
|
+
end
|
153
|
+
|
154
|
+
def assert_shape_of(arg1, arg2)
|
155
|
+
shape = object = nil
|
156
|
+
if @proper_expected_actual_order
|
157
|
+
shape = arg1
|
158
|
+
object = arg2
|
159
|
+
else
|
160
|
+
shape = arg2
|
161
|
+
object = arg1
|
162
|
+
end
|
163
|
+
|
164
|
+
validator = nil
|
91
165
|
if shape.respond_to? :shape_of?
|
92
|
-
|
166
|
+
validator = Validator.new(shape: shape, object: object)
|
93
167
|
elsif shape.instance_of? ::Array
|
94
|
-
|
168
|
+
validator = Validator.new(shape: Array[shape.first], object: object)
|
95
169
|
elsif shape.instance_of? ::Hash
|
96
|
-
|
170
|
+
validator = Validator.new(shape: Hash[shape], object: object)
|
97
171
|
else
|
98
172
|
raise TypeError, "Expected #{Shape.inspect}, an #{::Array.inspect}, or a #{::Hash.inspect} as the shape"
|
99
173
|
end
|
174
|
+
|
175
|
+
assert validator.valid?, validator.error_message
|
100
176
|
end
|
101
177
|
|
102
|
-
def refute_shape_of(
|
178
|
+
def refute_shape_of(arg1, arg2)
|
179
|
+
shape = object = nil
|
180
|
+
if @proper_expected_actual_order
|
181
|
+
shape = arg1
|
182
|
+
object = arg2
|
183
|
+
else
|
184
|
+
shape = arg2
|
185
|
+
object = arg1
|
186
|
+
end
|
187
|
+
|
188
|
+
validator = nil
|
103
189
|
if shape.respond_to? :shape_of?
|
104
|
-
|
190
|
+
validator = Validator.new(shape: shape, object: object)
|
105
191
|
elsif shape.instance_of? ::Array
|
106
|
-
|
192
|
+
validator = Validator.new(shape: Array[shape.first], object: object)
|
107
193
|
elsif shape.instance_of? ::Hash
|
108
|
-
|
194
|
+
validator = Validator.new(shape: Hash[shape], object: object)
|
109
195
|
else
|
110
196
|
raise TypeError, "Expected #{Shape.inspect}, an #{::Array.inspect}, or a #{::Hash.inspect} as the shape"
|
111
197
|
end
|
198
|
+
|
199
|
+
refute validator.valid?, "#{shape} is shape_of? #{object}"
|
112
200
|
end
|
113
201
|
end
|
114
202
|
|
115
203
|
# Generic shape which all shapes subclass from
|
116
204
|
class Shape
|
117
|
-
def self.shape_of?(
|
205
|
+
def self.shape_of?(object, validator: Validator.new(shape: self, object: object))
|
118
206
|
raise NotImplementedError
|
119
207
|
end
|
120
208
|
|
@@ -134,8 +222,11 @@ module ShapeOf
|
|
134
222
|
class Array < Shape
|
135
223
|
@internal_class = ::Array
|
136
224
|
|
137
|
-
def self.shape_of?(object)
|
138
|
-
object.instance_of?
|
225
|
+
def self.shape_of?(object, validator: Validator.new(shape: self, object: object))
|
226
|
+
is_instance_of = object.instance_of?(@internal_class)
|
227
|
+
validator.add_error(object.inspect + " is not instance of " + @internal_class.inspect) unless is_instance_of
|
228
|
+
|
229
|
+
is_instance_of
|
139
230
|
end
|
140
231
|
|
141
232
|
def self.[](shape)
|
@@ -156,20 +247,37 @@ module ShapeOf
|
|
156
247
|
@class_name
|
157
248
|
end
|
158
249
|
|
159
|
-
def self.shape_of?(array)
|
160
|
-
|
161
|
-
|
162
|
-
|
250
|
+
def self.shape_of?(array, validator: Validator.new(shape: self, object: array))
|
251
|
+
return false unless super
|
252
|
+
|
253
|
+
idx = 0
|
254
|
+
each_is_shape_of = true
|
255
|
+
array.each do |elem|
|
256
|
+
validator.push_key("idx_#{idx}".to_sym)
|
257
|
+
|
258
|
+
is_shape_of = if @shape.respond_to? :shape_of?
|
259
|
+
@shape.shape_of?(elem, validator: validator)
|
163
260
|
elsif @shape.is_a? ::Array
|
164
|
-
Array[@shape.first].shape_of?
|
261
|
+
Array[@shape.first].shape_of?(elem, validator: validator)
|
165
262
|
elsif @shape.is_a? ::Hash
|
166
|
-
Hash[@shape].shape_of?
|
263
|
+
Hash[@shape].shape_of?(elem, validator: validator)
|
167
264
|
elsif @shape.is_a? Class
|
168
|
-
elem.instance_of?
|
265
|
+
is_instance_of = elem.instance_of?(@shape)
|
266
|
+
validator.add_error(elem.inspect + " is not instance of " + @shape.inspect) unless is_instance_of
|
267
|
+
|
268
|
+
is_instance_of
|
169
269
|
else
|
170
|
-
elem == @shape
|
270
|
+
is_equal_to = elem == @shape
|
271
|
+
validator.add_error(elem.inspect " is not equal to (==) " + @shape.inspect) unless is_equal_to
|
272
|
+
|
273
|
+
is_equal_to
|
171
274
|
end
|
275
|
+
|
276
|
+
validator.pop_key
|
277
|
+
idx += 1
|
278
|
+
each_is_shape_of &&= is_shape_of
|
172
279
|
end
|
280
|
+
each_is_shape_of
|
173
281
|
end
|
174
282
|
end
|
175
283
|
end
|
@@ -182,8 +290,11 @@ module ShapeOf
|
|
182
290
|
class Hash < Shape
|
183
291
|
@internal_class = ::Hash
|
184
292
|
|
185
|
-
def self.shape_of?(object)
|
186
|
-
object.instance_of?
|
293
|
+
def self.shape_of?(object, validator: Validator.new(shape: self, object: object))
|
294
|
+
is_instance_of = object.instance_of?(@internal_class)
|
295
|
+
validator.add_error(object.inspect + " is not instance of " + @internal_class.inspect) unless is_instance_of
|
296
|
+
|
297
|
+
is_instance_of
|
187
298
|
end
|
188
299
|
|
189
300
|
def self.[](shape = {})
|
@@ -206,32 +317,57 @@ module ShapeOf
|
|
206
317
|
@class_name
|
207
318
|
end
|
208
319
|
|
209
|
-
def self.shape_of?(hash)
|
320
|
+
def self.shape_of?(hash, validator: Validator.new(shape: self, object: hash))
|
210
321
|
return false unless super
|
211
322
|
|
323
|
+
each_is_shape_of = true
|
212
324
|
rb_hash = stringify_rb_hash_keys(hash)
|
213
325
|
|
214
326
|
rb_hash.keys.each do |key|
|
215
|
-
|
327
|
+
has_key = @shape.key?(key)
|
328
|
+
unless has_key
|
329
|
+
validator.push_key(key)
|
330
|
+
validator.add_error("unexpected key")
|
331
|
+
validator.pop_key
|
332
|
+
each_is_shape_of = false
|
333
|
+
end
|
216
334
|
end
|
217
335
|
|
218
336
|
@shape.each do |key, shape|
|
219
|
-
|
337
|
+
unless rb_hash.key?(key) || shape.respond_to?(:required?) && !shape.required?
|
338
|
+
validator.push_key(key)
|
339
|
+
validator.add_error("required key not present")
|
340
|
+
validator.pop_key
|
341
|
+
each_is_shape_of = false
|
342
|
+
end
|
220
343
|
end
|
221
344
|
|
222
|
-
rb_hash.
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
345
|
+
rb_hash.each do |key, elem|
|
346
|
+
shape_elem = @shape[key]
|
347
|
+
validator.push_key(key)
|
348
|
+
|
349
|
+
is_shape_of = if shape_elem.respond_to? :shape_of?
|
350
|
+
shape_elem.shape_of?(elem, validator: validator)
|
351
|
+
elsif shape_elem.is_a? ::Array
|
352
|
+
Array[shape_elem.first].shape_of?(elem, validator: validator)
|
353
|
+
elsif shape_elem.is_a? ::Hash
|
354
|
+
Hash[shape_elem].shape_of?(elem, validator: validator)
|
355
|
+
elsif shape_elem.is_a? Class
|
356
|
+
is_instance_of = elem.instance_of?(shape_elem)
|
357
|
+
validator.add_error(elem.inspect + " is not instance of " + shape_elem.inspect) unless is_instance_of
|
358
|
+
|
359
|
+
is_instance_of
|
231
360
|
else
|
232
|
-
elem ==
|
361
|
+
is_equal_to = elem == shape_elem
|
362
|
+
validator.add_error(elem.inspect + " is not equal to (==) " + shape_elem.inspect) unless is_equal_to
|
363
|
+
|
364
|
+
is_equal_to
|
233
365
|
end
|
366
|
+
|
367
|
+
validator.pop_key
|
368
|
+
each_is_shape_of &&= is_shape_of
|
234
369
|
end
|
370
|
+
each_is_shape_of
|
235
371
|
end
|
236
372
|
end
|
237
373
|
end
|
@@ -239,7 +375,7 @@ module ShapeOf
|
|
239
375
|
private
|
240
376
|
|
241
377
|
def self.stringify_rb_hash_keys(rb_hash)
|
242
|
-
rb_hash.
|
378
|
+
rb_hash.transform_keys(&:to_s)
|
243
379
|
end
|
244
380
|
end
|
245
381
|
|
@@ -262,20 +398,36 @@ module ShapeOf
|
|
262
398
|
@class_name
|
263
399
|
end
|
264
400
|
|
265
|
-
def self.shape_of?(object)
|
266
|
-
|
401
|
+
def self.shape_of?(object, validator: Validator.new(shape: self, object: object))
|
402
|
+
is_any_shape_of_shape_or_hash_or_array = false
|
403
|
+
is_any_shape_of = @shapes.any? do |shape|
|
267
404
|
if shape.respond_to? :shape_of?
|
268
|
-
shape.shape_of?
|
405
|
+
is_any_shape_of_shape_or_hash_or_array ||= shape.shape_of?(object)
|
269
406
|
elsif shape.is_a? ::Hash
|
270
|
-
Hash[shape].shape_of?
|
407
|
+
is_any_shape_of_shape_or_hash_or_array ||= Hash[shape].shape_of?(object)
|
271
408
|
elsif shape.is_a? ::Array
|
272
|
-
Array[shape].shape_of?
|
409
|
+
is_any_shape_of_shape_or_hash_or_array ||= Array[shape].shape_of?(object)
|
273
410
|
elsif shape.is_a? Class
|
274
|
-
object.instance_of?
|
411
|
+
object.instance_of?(shape)
|
275
412
|
else
|
276
413
|
object == shape
|
277
414
|
end
|
278
415
|
end
|
416
|
+
|
417
|
+
if !is_any_shape_of && !is_any_shape_of_shape_or_hash_or_array
|
418
|
+
shape_shapes = @shapes.select { |shape| shape.respond_to?(:shape_of?) }
|
419
|
+
class_shapes = @shapes.select do |shape|
|
420
|
+
!shape.respond_to?(:shape_of?) && !shape.is_a?(::Hash) && !shape.is_a?(::Array) && shape.is_a?(Class)
|
421
|
+
end
|
422
|
+
object_shapes = @shapes.select do |shape|
|
423
|
+
!shape.respond_to?(:shape_of?) && !shape.is_a?(::Hash) && !shape.is_a?(::Array) && !shape.is_a?(Class)
|
424
|
+
end
|
425
|
+
validator.add_error(object.inspect + " is not shape of any of (" + shape_shapes.map(&:inspect).join(", ") +
|
426
|
+
") or is not instance of any of (" + class_shapes.map(&:inspect).join(", ") +
|
427
|
+
") or is not equal to (==) any of (" + object_shapes.map(&:inspect).join(", ") + ")")
|
428
|
+
end
|
429
|
+
|
430
|
+
is_any_shape_of
|
279
431
|
end
|
280
432
|
end
|
281
433
|
end
|
@@ -303,14 +455,15 @@ module ShapeOf
|
|
303
455
|
|
304
456
|
# Anything matches unless key does not exist in the Hash.
|
305
457
|
class Any < Shape
|
306
|
-
def self.shape_of?(object)
|
458
|
+
def self.shape_of?(object, validator: Validator.new(shape: self, object: object))
|
307
459
|
true
|
308
460
|
end
|
309
461
|
end
|
310
462
|
|
311
463
|
# Only passes when the key does not exist in the Hash.
|
312
464
|
class Nothing < Shape
|
313
|
-
def self.shape_of?(object)
|
465
|
+
def self.shape_of?(object, validator: Validator.new(shape: self, object: object))
|
466
|
+
validator.add_error("key present when not allowed")
|
314
467
|
false
|
315
468
|
end
|
316
469
|
|
@@ -342,10 +495,15 @@ module ShapeOf
|
|
342
495
|
@class_name
|
343
496
|
end
|
344
497
|
|
345
|
-
def self.shape_of?(object)
|
346
|
-
|
498
|
+
def self.shape_of?(object, validator: Validator.new(shape: self, object: object))
|
499
|
+
unless object.instance_of?(String)
|
500
|
+
validator.add_error(object.inspect + " is not instance of " + String.inspect)
|
501
|
+
return false
|
502
|
+
end
|
347
503
|
|
348
|
-
@shape.match?(object)
|
504
|
+
does_regexp_match = @shape.match?(object)
|
505
|
+
validator.add_error(object.inspect + " does not match " + @shape.inspect) unless does_regexp_match
|
506
|
+
does_regexp_match
|
349
507
|
end
|
350
508
|
end
|
351
509
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: shape_of
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Isom
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-01-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitest
|
@@ -38,7 +38,7 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '1'
|
41
|
-
description:
|
41
|
+
description:
|
42
42
|
email: john@johnisom.dev
|
43
43
|
executables: []
|
44
44
|
extensions: []
|
@@ -49,7 +49,7 @@ homepage: https://github.com/johnisom/shape_of
|
|
49
49
|
licenses:
|
50
50
|
- MIT
|
51
51
|
metadata: {}
|
52
|
-
post_install_message:
|
52
|
+
post_install_message:
|
53
53
|
rdoc_options: []
|
54
54
|
require_paths:
|
55
55
|
- lib
|
@@ -64,8 +64,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
64
64
|
- !ruby/object:Gem::Version
|
65
65
|
version: '0'
|
66
66
|
requirements: []
|
67
|
-
rubygems_version: 3.2.
|
68
|
-
signing_key:
|
67
|
+
rubygems_version: 3.2.22
|
68
|
+
signing_key:
|
69
69
|
specification_version: 4
|
70
70
|
summary: A shape/type checker for Ruby objects.
|
71
71
|
test_files: []
|