dry-types 0.15.0 → 1.5.1
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/CHANGELOG.md +547 -161
- data/LICENSE +17 -17
- data/README.md +15 -13
- data/dry-types.gemspec +27 -30
- data/lib/dry/types/any.rb +23 -12
- data/lib/dry/types/array/constructor.rb +32 -0
- data/lib/dry/types/array/member.rb +74 -15
- data/lib/dry/types/array.rb +18 -2
- data/lib/dry/types/builder.rb +118 -22
- data/lib/dry/types/builder_methods.rb +46 -16
- data/lib/dry/types/coercions/json.rb +43 -7
- data/lib/dry/types/coercions/params.rb +117 -32
- data/lib/dry/types/coercions.rb +76 -22
- data/lib/dry/types/compiler.rb +44 -21
- data/lib/dry/types/constrained/coercible.rb +36 -6
- data/lib/dry/types/constrained.rb +79 -31
- data/lib/dry/types/constraints.rb +18 -4
- data/lib/dry/types/constructor/function.rb +216 -0
- data/lib/dry/types/constructor/wrapper.rb +94 -0
- data/lib/dry/types/constructor.rb +110 -61
- data/lib/dry/types/container.rb +6 -1
- data/lib/dry/types/core.rb +34 -11
- data/lib/dry/types/decorator.rb +38 -17
- data/lib/dry/types/default.rb +61 -16
- data/lib/dry/types/enum.rb +36 -20
- data/lib/dry/types/errors.rb +74 -8
- data/lib/dry/types/extensions/maybe.rb +65 -17
- data/lib/dry/types/extensions/monads.rb +29 -0
- data/lib/dry/types/extensions.rb +7 -1
- data/lib/dry/types/fn_container.rb +6 -1
- data/lib/dry/types/hash/constructor.rb +17 -4
- data/lib/dry/types/hash.rb +32 -20
- data/lib/dry/types/inflector.rb +3 -1
- data/lib/dry/types/json.rb +18 -16
- data/lib/dry/types/lax.rb +75 -0
- data/lib/dry/types/map.rb +70 -32
- data/lib/dry/types/meta.rb +51 -0
- data/lib/dry/types/module.rb +16 -11
- data/lib/dry/types/nominal.rb +113 -22
- data/lib/dry/types/options.rb +12 -25
- data/lib/dry/types/params.rb +39 -25
- data/lib/dry/types/predicate_inferrer.rb +238 -0
- data/lib/dry/types/predicate_registry.rb +34 -0
- data/lib/dry/types/primitive_inferrer.rb +97 -0
- data/lib/dry/types/printable.rb +5 -1
- data/lib/dry/types/printer.rb +63 -57
- data/lib/dry/types/result.rb +29 -3
- data/lib/dry/types/schema/key.rb +62 -36
- data/lib/dry/types/schema.rb +201 -91
- data/lib/dry/types/spec/types.rb +99 -37
- data/lib/dry/types/sum.rb +75 -25
- data/lib/dry/types/type.rb +49 -0
- data/lib/dry/types/version.rb +3 -1
- data/lib/dry/types.rb +106 -48
- data/lib/dry-types.rb +3 -1
- metadata +55 -78
- data/.codeclimate.yml +0 -15
- data/.gitignore +0 -10
- data/.rspec +0 -2
- data/.rubocop.yml +0 -43
- data/.travis.yml +0 -28
- data/.yardopts +0 -5
- data/CONTRIBUTING.md +0 -29
- data/Gemfile +0 -23
- data/Rakefile +0 -20
- data/benchmarks/hash_schemas.rb +0 -51
- data/lib/dry/types/safe.rb +0 -61
- data/log/.gitkeep +0 -0
data/lib/dry/types/schema.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/types/fn_container"
|
2
4
|
|
3
5
|
module Dry
|
4
6
|
module Types
|
@@ -14,6 +16,8 @@ module Dry
|
|
14
16
|
# @see Dry::Types::Default::Callable#evaluate
|
15
17
|
#
|
16
18
|
# {Schema} implements Enumerable using its keys as collection.
|
19
|
+
#
|
20
|
+
# @api public
|
17
21
|
class Schema < Hash
|
18
22
|
NO_TRANSFORM = Dry::Types::FnContainer.register { |x| x }
|
19
23
|
SYMBOLIZE_KEY = Dry::Types::FnContainer.register(:to_sym.to_proc)
|
@@ -31,8 +35,11 @@ module Dry
|
|
31
35
|
|
32
36
|
# @param [Class] _primitive
|
33
37
|
# @param [Hash] options
|
38
|
+
#
|
34
39
|
# @option options [Array[Dry::Types::Schema::Key]] :keys
|
35
40
|
# @option options [String] :key_transform_fn
|
41
|
+
#
|
42
|
+
# @api private
|
36
43
|
def initialize(_primitive, **options)
|
37
44
|
@keys = options.fetch(:keys)
|
38
45
|
@name_key_map = keys.each_with_object({}) do |key, idx|
|
@@ -47,116 +54,146 @@ module Dry
|
|
47
54
|
end
|
48
55
|
|
49
56
|
# @param [Hash] hash
|
57
|
+
#
|
50
58
|
# @return [Hash{Symbol => Object}]
|
51
|
-
|
52
|
-
|
59
|
+
#
|
60
|
+
# @api private
|
61
|
+
def call_unsafe(hash, options = EMPTY_HASH)
|
62
|
+
resolve_unsafe(coerce(hash), options)
|
63
|
+
end
|
64
|
+
|
65
|
+
# @param [Hash] hash
|
66
|
+
#
|
67
|
+
# @return [Hash{Symbol => Object}]
|
68
|
+
#
|
69
|
+
# @api private
|
70
|
+
def call_safe(hash, options = EMPTY_HASH)
|
71
|
+
resolve_safe(coerce(hash) { return yield }, options) { return yield }
|
53
72
|
end
|
54
|
-
alias_method :[], :call
|
55
73
|
|
56
74
|
# @param [Hash] hash
|
75
|
+
#
|
57
76
|
# @option options [Boolean] :skip_missing If true don't raise error if on missing keys
|
58
77
|
# @option options [Boolean] :resolve_defaults If false default value
|
59
78
|
# won't be evaluated for missing key
|
60
79
|
# @return [Hash{Symbol => Object}]
|
80
|
+
#
|
81
|
+
# @api public
|
61
82
|
def apply(hash, options = EMPTY_HASH)
|
62
|
-
|
83
|
+
call_unsafe(hash, options)
|
63
84
|
end
|
64
85
|
|
65
|
-
# @param [Hash] hash
|
86
|
+
# @param input [Hash] hash
|
87
|
+
#
|
66
88
|
# @yieldparam [Failure] failure
|
67
89
|
# @yieldreturn [Result]
|
90
|
+
#
|
68
91
|
# @return [Logic::Result]
|
69
92
|
# @return [Object] if coercion fails and a block is given
|
70
|
-
|
71
|
-
|
93
|
+
#
|
94
|
+
# @api public
|
95
|
+
def try(input)
|
96
|
+
if primitive?(input)
|
72
97
|
success = true
|
73
|
-
output
|
98
|
+
output = {}
|
99
|
+
result = {}
|
74
100
|
|
75
|
-
|
76
|
-
|
101
|
+
input.each do |key, value|
|
102
|
+
k = @transform_key.(key)
|
103
|
+
type = @name_key_map[k]
|
104
|
+
|
105
|
+
if type
|
106
|
+
key_result = type.try(value)
|
107
|
+
result[k] = key_result
|
108
|
+
output[k] = key_result.input
|
77
109
|
success &&= key_result.success?
|
78
|
-
|
110
|
+
elsif strict?
|
111
|
+
success = false
|
112
|
+
end
|
113
|
+
end
|
79
114
|
|
80
|
-
|
115
|
+
if output.size < keys.size
|
116
|
+
resolve_missing_keys(output, options) do
|
117
|
+
success = false
|
81
118
|
end
|
82
|
-
|
83
|
-
|
84
|
-
|
119
|
+
end
|
120
|
+
|
121
|
+
success &&= primitive?(output)
|
122
|
+
|
123
|
+
if success
|
124
|
+
failure = nil
|
125
|
+
else
|
126
|
+
error = CoercionError.new("#{input} doesn't conform schema", meta: result)
|
127
|
+
failure = failure(output, error)
|
85
128
|
end
|
86
129
|
else
|
87
|
-
|
88
|
-
output = hash
|
89
|
-
result = "#{hash} must be a hash"
|
130
|
+
failure = failure(input, CoercionError.new("#{input} must be a hash"))
|
90
131
|
end
|
91
132
|
|
92
|
-
if
|
133
|
+
if failure.nil?
|
93
134
|
success(output)
|
135
|
+
elsif block_given?
|
136
|
+
yield(failure)
|
94
137
|
else
|
95
|
-
failure
|
96
|
-
block_given? ? yield(failure) : failure
|
138
|
+
failure
|
97
139
|
end
|
98
140
|
end
|
99
141
|
|
100
142
|
# @param meta [Boolean] Whether to dump the meta to the AST
|
143
|
+
#
|
101
144
|
# @return [Array] An AST representation
|
145
|
+
#
|
146
|
+
# @api public
|
102
147
|
def to_ast(meta: true)
|
103
|
-
if RUBY_VERSION >= "2.5"
|
104
|
-
opts = options.slice(:key_transform_fn, :type_transform_fn, :strict)
|
105
|
-
else
|
106
|
-
opts = options.select { |k, _|
|
107
|
-
k == :key_transform_fn || k == :type_transform_fn || k == :strict
|
108
|
-
}
|
109
|
-
end
|
110
|
-
|
111
148
|
[
|
112
149
|
:schema,
|
113
|
-
[
|
114
|
-
|
115
|
-
|
116
|
-
meta ? self.meta : EMPTY_HASH
|
117
|
-
]
|
150
|
+
[keys.map { |key| key.to_ast(meta: meta) },
|
151
|
+
options.slice(:key_transform_fn, :type_transform_fn, :strict),
|
152
|
+
meta ? self.meta : EMPTY_HASH]
|
118
153
|
]
|
119
154
|
end
|
120
155
|
|
121
|
-
# @param [Hash] hash
|
122
|
-
# @return [Boolean]
|
123
|
-
def valid?(hash)
|
124
|
-
result = try(hash)
|
125
|
-
result.success?
|
126
|
-
end
|
127
|
-
alias_method :===, :valid?
|
128
|
-
|
129
156
|
# Whether the schema rejects unknown keys
|
157
|
+
#
|
130
158
|
# @return [Boolean]
|
159
|
+
#
|
160
|
+
# @api public
|
131
161
|
def strict?
|
132
162
|
options.fetch(:strict, false)
|
133
163
|
end
|
134
164
|
|
135
165
|
# Make the schema intolerant to unknown keys
|
166
|
+
#
|
136
167
|
# @return [Schema]
|
137
|
-
|
138
|
-
|
168
|
+
#
|
169
|
+
# @api public
|
170
|
+
def strict(strict = true)
|
171
|
+
with(strict: strict)
|
139
172
|
end
|
140
173
|
|
141
|
-
#
|
174
|
+
# Inject a key transformation function
|
175
|
+
#
|
142
176
|
# @param [#call,nil] proc
|
143
177
|
# @param [#call,nil] block
|
178
|
+
#
|
144
179
|
# @return [Schema]
|
180
|
+
#
|
181
|
+
# @api public
|
145
182
|
def with_key_transform(proc = nil, &block)
|
146
183
|
fn = proc || block
|
147
184
|
|
148
|
-
if fn.nil?
|
149
|
-
raise ArgumentError, "a block or callable argument is required"
|
150
|
-
end
|
185
|
+
raise ArgumentError, "a block or callable argument is required" if fn.nil?
|
151
186
|
|
152
187
|
handle = Dry::Types::FnContainer.register(fn)
|
153
188
|
with(key_transform_fn: handle)
|
154
189
|
end
|
155
190
|
|
156
191
|
# Whether the schema transforms input keys
|
192
|
+
#
|
157
193
|
# @return [Boolean]
|
194
|
+
#
|
158
195
|
# @api public
|
159
|
-
def
|
196
|
+
def transform_keys?
|
160
197
|
!options[:key_transform_fn].nil?
|
161
198
|
end
|
162
199
|
|
@@ -164,10 +201,13 @@ module Dry
|
|
164
201
|
# @param [{Symbol => Dry::Types::Nominal}] type_map
|
165
202
|
# @param [Hash] meta
|
166
203
|
# @return [Dry::Types::Schema]
|
204
|
+
#
|
167
205
|
# @overload schema(keys)
|
168
206
|
# @param [Array<Dry::Types::Schema::Key>] key List of schema keys
|
169
207
|
# @param [Hash] meta
|
170
208
|
# @return [Dry::Types::Schema]
|
209
|
+
#
|
210
|
+
# @api public
|
171
211
|
def schema(keys_or_map)
|
172
212
|
if keys_or_map.is_a?(::Array)
|
173
213
|
new_keys = keys_or_map
|
@@ -182,6 +222,8 @@ module Dry
|
|
182
222
|
# Iterate over each key type
|
183
223
|
#
|
184
224
|
# @return [Array<Dry::Types::Schema::Key>,Enumerator]
|
225
|
+
#
|
226
|
+
# @api public
|
185
227
|
def each(&block)
|
186
228
|
keys.each(&block)
|
187
229
|
end
|
@@ -189,12 +231,16 @@ module Dry
|
|
189
231
|
# Whether the schema has the given key
|
190
232
|
#
|
191
233
|
# @param [Symbol] name Key name
|
234
|
+
#
|
192
235
|
# @return [Boolean]
|
236
|
+
#
|
237
|
+
# @api public
|
193
238
|
def key?(name)
|
194
239
|
name_key_map.key?(name)
|
195
240
|
end
|
196
241
|
|
197
|
-
# Fetch key type by a key name
|
242
|
+
# Fetch key type by a key name
|
243
|
+
#
|
198
244
|
# Behaves as ::Hash#fetch
|
199
245
|
#
|
200
246
|
# @overload key(name, fallback = Undefined)
|
@@ -206,6 +252,8 @@ module Dry
|
|
206
252
|
# @param [Symbol] name Key name
|
207
253
|
# @param [Proc] block Fallback block, runs if key is missing
|
208
254
|
# @return [Dry::Types::Schema::Key,Object] key type or block value if key is not in schema
|
255
|
+
#
|
256
|
+
# @api public
|
209
257
|
def key(name, fallback = Undefined, &block)
|
210
258
|
if Undefined.equal?(fallback)
|
211
259
|
name_key_map.fetch(name, &block)
|
@@ -215,83 +263,145 @@ module Dry
|
|
215
263
|
end
|
216
264
|
|
217
265
|
# @return [Boolean]
|
266
|
+
#
|
267
|
+
# @api public
|
218
268
|
def constrained?
|
219
269
|
true
|
220
270
|
end
|
221
271
|
|
272
|
+
# @return [Lax]
|
273
|
+
#
|
274
|
+
# @api public
|
275
|
+
def lax
|
276
|
+
Lax.new(schema(keys.map(&:lax)))
|
277
|
+
end
|
278
|
+
|
279
|
+
# Merge given schema keys into current schema
|
280
|
+
#
|
281
|
+
# A new instance is returned.
|
282
|
+
#
|
283
|
+
# @param other [Schema] schema
|
284
|
+
# @return [Schema]
|
285
|
+
#
|
286
|
+
# @api public
|
287
|
+
def merge(other)
|
288
|
+
schema(other.keys)
|
289
|
+
end
|
290
|
+
|
291
|
+
# Empty schema with the same options
|
292
|
+
#
|
293
|
+
# @return [Schema]
|
294
|
+
#
|
295
|
+
# @api public
|
296
|
+
def clear
|
297
|
+
with(keys: EMPTY_ARRAY)
|
298
|
+
end
|
299
|
+
|
222
300
|
private
|
223
301
|
|
224
302
|
# @param [Array<Dry::Types::Schema::Keys>] keys
|
303
|
+
#
|
225
304
|
# @return [Dry::Types::Schema]
|
305
|
+
#
|
226
306
|
# @api private
|
227
307
|
def merge_keys(*keys)
|
228
|
-
keys
|
229
|
-
flatten(1)
|
230
|
-
each_with_object({}) { |key, merged| merged[key.name] = key }
|
231
|
-
values
|
308
|
+
keys
|
309
|
+
.flatten(1)
|
310
|
+
.each_with_object({}) { |key, merged| merged[key.name] = key }
|
311
|
+
.values
|
232
312
|
end
|
233
313
|
|
234
|
-
|
314
|
+
# Validate and coerce a hash. Raise an exception on any error
|
315
|
+
#
|
316
|
+
# @api private
|
317
|
+
#
|
318
|
+
# @return [Hash]
|
319
|
+
def resolve_unsafe(hash, options = EMPTY_HASH)
|
235
320
|
result = {}
|
236
321
|
|
237
322
|
hash.each do |key, value|
|
238
|
-
k = transform_key.(key)
|
239
|
-
|
240
|
-
|
241
|
-
|
323
|
+
k = @transform_key.(key)
|
324
|
+
type = @name_key_map[k]
|
325
|
+
|
326
|
+
if type
|
327
|
+
begin
|
328
|
+
result[k] = type.call_unsafe(value)
|
329
|
+
rescue ConstraintError => e
|
330
|
+
raise SchemaError.new(type.name, value, e.result)
|
331
|
+
rescue CoercionError => e
|
332
|
+
raise SchemaError.new(type.name, value, e.message)
|
333
|
+
end
|
242
334
|
elsif strict?
|
243
|
-
raise
|
335
|
+
raise unexpected_keys(hash.keys)
|
244
336
|
end
|
245
337
|
end
|
246
338
|
|
247
|
-
if result.size < keys.size
|
248
|
-
|
339
|
+
resolve_missing_keys(result, options) if result.size < keys.size
|
340
|
+
|
341
|
+
result
|
342
|
+
end
|
343
|
+
|
344
|
+
# Validate and coerce a hash. Call a block and halt on any error
|
345
|
+
#
|
346
|
+
# @api private
|
347
|
+
#
|
348
|
+
# @return [Hash]
|
349
|
+
def resolve_safe(hash, options = EMPTY_HASH, &block)
|
350
|
+
result = {}
|
351
|
+
|
352
|
+
hash.each do |key, value|
|
353
|
+
k = @transform_key.(key)
|
354
|
+
type = @name_key_map[k]
|
355
|
+
|
356
|
+
if type
|
357
|
+
result[k] = type.call_safe(value, &block)
|
358
|
+
elsif strict?
|
359
|
+
yield
|
360
|
+
end
|
249
361
|
end
|
250
362
|
|
363
|
+
resolve_missing_keys(result, options, &block) if result.size < keys.size
|
364
|
+
|
251
365
|
result
|
252
366
|
end
|
253
367
|
|
254
|
-
|
368
|
+
# Try to add missing keys to the hash
|
369
|
+
#
|
370
|
+
# @api private
|
371
|
+
def resolve_missing_keys(hash, options)
|
255
372
|
skip_missing = options.fetch(:skip_missing, false)
|
256
373
|
resolve_defaults = options.fetch(:resolve_defaults, true)
|
257
374
|
|
258
375
|
keys.each do |key|
|
259
|
-
next if
|
376
|
+
next if hash.key?(key.name)
|
260
377
|
|
261
378
|
if key.default? && resolve_defaults
|
262
|
-
|
379
|
+
hash[key.name] = key.call_unsafe(Undefined)
|
263
380
|
elsif key.required? && !skip_missing
|
264
|
-
|
381
|
+
if block_given?
|
382
|
+
return yield
|
383
|
+
else
|
384
|
+
raise missing_key(key.name)
|
385
|
+
end
|
265
386
|
end
|
266
387
|
end
|
267
388
|
end
|
268
389
|
|
269
|
-
# @param
|
270
|
-
#
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
def try_coerce(hash)
|
278
|
-
resolve(hash) do |key, value|
|
279
|
-
yield(key, key.try(value))
|
280
|
-
end
|
390
|
+
# @param hash_keys [Array<Symbol>]
|
391
|
+
#
|
392
|
+
# @return [UnknownKeysError]
|
393
|
+
#
|
394
|
+
# @api private
|
395
|
+
def unexpected_keys(hash_keys)
|
396
|
+
extra_keys = hash_keys.map(&transform_key) - name_key_map.keys
|
397
|
+
UnknownKeysError.new(extra_keys)
|
281
398
|
end
|
282
399
|
|
283
|
-
# @
|
284
|
-
#
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
key.(value)
|
289
|
-
rescue ConstraintError => e
|
290
|
-
raise SchemaError.new(key.name, value, e.result)
|
291
|
-
rescue TypeError, ArgumentError => e
|
292
|
-
raise SchemaError.new(key.name, value, e.message)
|
293
|
-
end
|
294
|
-
end
|
400
|
+
# @return [MissingKeyError]
|
401
|
+
#
|
402
|
+
# @api private
|
403
|
+
def missing_key(key)
|
404
|
+
MissingKeyError.new(key)
|
295
405
|
end
|
296
406
|
end
|
297
407
|
end
|
data/lib/dry/types/spec/types.rb
CHANGED
@@ -1,102 +1,164 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.shared_examples_for "Dry::Types::Nominal without primitive" do
|
2
4
|
def be_boolean
|
3
|
-
satisfy { |x| x == true || x == false
|
5
|
+
satisfy { |x| x == true || x == false }
|
4
6
|
end
|
5
7
|
|
6
|
-
describe
|
7
|
-
it
|
8
|
+
describe "#constrained?" do
|
9
|
+
it "returns a boolean value" do
|
8
10
|
expect(type.constrained?).to be_boolean
|
9
11
|
end
|
10
12
|
end
|
11
13
|
|
12
|
-
describe
|
13
|
-
it
|
14
|
+
describe "#default?" do
|
15
|
+
it "returns a boolean value" do
|
14
16
|
expect(type.default?).to be_boolean
|
15
17
|
end
|
16
18
|
end
|
17
19
|
|
18
|
-
describe
|
19
|
-
it
|
20
|
+
describe "#valid?" do
|
21
|
+
it "returns a boolean value" do
|
20
22
|
expect(type.valid?(1)).to be_boolean
|
21
23
|
end
|
22
24
|
end
|
23
25
|
|
24
|
-
describe
|
25
|
-
it
|
26
|
+
describe "#eql?" do
|
27
|
+
it "has #eql? defined" do
|
26
28
|
expect(type).to eql(type)
|
27
29
|
end
|
28
30
|
end
|
29
31
|
|
30
|
-
describe
|
31
|
-
it
|
32
|
+
describe "#==" do
|
33
|
+
it "has #== defined" do
|
32
34
|
expect(type).to eq(type)
|
33
35
|
end
|
34
36
|
end
|
35
37
|
|
36
|
-
describe
|
37
|
-
it
|
38
|
+
describe "#optional?" do
|
39
|
+
it "returns a boolean value" do
|
38
40
|
expect(type.optional?).to be_boolean
|
39
41
|
end
|
40
42
|
end
|
41
43
|
|
42
|
-
describe
|
43
|
-
it
|
44
|
-
if type.class.name.start_with?(
|
45
|
-
|
46
|
-
|
44
|
+
describe "#to_s" do
|
45
|
+
it "returns a custom string representation" do
|
46
|
+
expect(type.to_s).to start_with("#<Dry::Types") if type.class.name.start_with?("Dry::Types")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "#to_proc" do
|
51
|
+
subject(:callable) { type.to_proc }
|
52
|
+
|
53
|
+
it "converts a type to a proc" do
|
54
|
+
expect(callable).to be_a(Proc)
|
47
55
|
end
|
48
56
|
end
|
49
57
|
end
|
50
58
|
|
51
|
-
RSpec.shared_examples_for
|
52
|
-
describe
|
53
|
-
it
|
54
|
-
with_meta = type.meta(foo: :bar).meta(baz:
|
59
|
+
RSpec.shared_examples_for "Dry::Types::Nominal#meta" do
|
60
|
+
describe "#meta" do
|
61
|
+
it "allows setting meta information" do
|
62
|
+
with_meta = type.meta(foo: :bar).meta(baz: "1")
|
55
63
|
|
56
64
|
expect(with_meta).to be_instance_of(type.class)
|
57
|
-
expect(with_meta.meta).to eql(foo: :bar, baz:
|
65
|
+
expect(with_meta.meta).to eql(foo: :bar, baz: "1")
|
58
66
|
end
|
59
67
|
|
60
|
-
it
|
68
|
+
it "equalizes on empty meta" do
|
61
69
|
expect(type).to eql(type.meta({}))
|
62
70
|
end
|
63
71
|
|
64
|
-
it
|
65
|
-
expect(type).to_not eql(type.meta(i_am:
|
72
|
+
it "equalizes on filled meta" do
|
73
|
+
expect(type).to_not eql(type.meta(i_am: "different"))
|
66
74
|
end
|
67
75
|
|
68
|
-
it
|
76
|
+
it "is locally immutable" do
|
69
77
|
expect(type.meta).to be_a ::Hash
|
70
78
|
expect(type.meta).to be_frozen
|
71
79
|
expect(type.meta).not_to have_key :immutable_test
|
72
|
-
derived = type.
|
80
|
+
derived = type.meta(immutable_test: 1)
|
73
81
|
expect(derived.meta).to be_frozen
|
74
|
-
expect(derived.meta).to eql(
|
82
|
+
expect(derived.meta).to eql(immutable_test: 1)
|
75
83
|
expect(type.meta).not_to have_key :immutable_test
|
76
84
|
end
|
77
85
|
end
|
78
86
|
|
79
|
-
describe
|
80
|
-
it
|
87
|
+
describe "#pristine" do
|
88
|
+
it "erases meta" do
|
81
89
|
expect(type.meta(foo: :bar).pristine).to eql(type)
|
82
90
|
end
|
83
91
|
end
|
84
92
|
end
|
85
93
|
|
86
94
|
RSpec.shared_examples_for Dry::Types::Nominal do
|
87
|
-
it_behaves_like
|
95
|
+
it_behaves_like "Dry::Types::Nominal without primitive"
|
88
96
|
|
89
|
-
describe
|
90
|
-
it
|
97
|
+
describe "#primitive" do
|
98
|
+
it "returns a class" do
|
91
99
|
expect(type.primitive).to be_instance_of(Class)
|
92
100
|
end
|
93
101
|
end
|
94
102
|
|
95
|
-
describe
|
96
|
-
it
|
103
|
+
describe "#constructor" do
|
104
|
+
it "returns a constructor" do
|
97
105
|
constructor = type.constructor(&:to_s)
|
98
106
|
|
99
107
|
expect(constructor).to be_a(Dry::Types::Type)
|
100
108
|
end
|
101
109
|
end
|
102
110
|
end
|
111
|
+
|
112
|
+
RSpec.shared_examples_for "a constrained type" do |options = {inputs: Object.new}|
|
113
|
+
inputs = options[:inputs]
|
114
|
+
|
115
|
+
let(:fallback) { Object.new }
|
116
|
+
|
117
|
+
describe "#call" do
|
118
|
+
it "yields a block on failure" do
|
119
|
+
Array(inputs).each do |input|
|
120
|
+
expect(type.(input) { fallback }).to be(fallback)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
it "throws an error on invalid input" do
|
125
|
+
Array(inputs).each do |input|
|
126
|
+
expect { type.(input) }.to raise_error(Dry::Types::CoercionError)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
describe "#constructor" do
|
132
|
+
let(:wrapping_constructor) do
|
133
|
+
type.constructor { |input, type| type.(input) { fallback } }
|
134
|
+
end
|
135
|
+
|
136
|
+
it "can be wrapped" do
|
137
|
+
Array(inputs).each do |input|
|
138
|
+
expect(wrapping_constructor.(input)).to be(fallback)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
RSpec.shared_examples_for "a nominal type" do |inputs: Object.new|
|
145
|
+
describe "#call" do
|
146
|
+
it "always returns the input back" do
|
147
|
+
Array(inputs).each do |input|
|
148
|
+
expect(type.(input) { raise }).to be(input)
|
149
|
+
expect(type.(input)).to be(input)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
RSpec.shared_examples_for "a composable constructor" do
|
156
|
+
describe "#constructor" do
|
157
|
+
it "has aliases for composition" do
|
158
|
+
expect(type.method(:append)).to eql(type.method(:constructor))
|
159
|
+
expect(type.method(:prepend)).to eql(type.method(:constructor))
|
160
|
+
expect(type.method(:<<)).to eql(type.method(:constructor))
|
161
|
+
expect(type.method(:>>)).to eql(type.method(:constructor))
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|