dry-types 0.15.0 → 1.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/.gitignore +1 -0
- data/.rubocop.yml +18 -2
- data/.travis.yml +4 -5
- data/.yardopts +6 -2
- data/CHANGELOG.md +69 -1
- data/Gemfile +3 -0
- data/README.md +2 -1
- data/Rakefile +2 -0
- data/benchmarks/hash_schemas.rb +2 -0
- data/benchmarks/lax_schema.rb +16 -0
- data/benchmarks/profile_invalid_input.rb +15 -0
- data/benchmarks/profile_lax_schema_valid.rb +16 -0
- data/benchmarks/profile_valid_input.rb +15 -0
- data/benchmarks/schema_valid_vs_invalid.rb +21 -0
- data/benchmarks/setup.rb +17 -0
- data/dry-types.gemspec +4 -2
- data/lib/dry-types.rb +2 -0
- data/lib/dry/types.rb +51 -13
- data/lib/dry/types/any.rb +21 -10
- data/lib/dry/types/array.rb +11 -1
- data/lib/dry/types/array/member.rb +65 -13
- data/lib/dry/types/builder.rb +48 -4
- data/lib/dry/types/builder_methods.rb +9 -8
- data/lib/dry/types/coercions.rb +71 -19
- data/lib/dry/types/coercions/json.rb +22 -3
- data/lib/dry/types/coercions/params.rb +98 -30
- data/lib/dry/types/compiler.rb +35 -12
- data/lib/dry/types/constrained.rb +73 -27
- data/lib/dry/types/constrained/coercible.rb +36 -6
- data/lib/dry/types/constraints.rb +15 -1
- data/lib/dry/types/constructor.rb +90 -43
- data/lib/dry/types/constructor/function.rb +201 -0
- data/lib/dry/types/container.rb +5 -0
- data/lib/dry/types/core.rb +7 -5
- data/lib/dry/types/decorator.rb +36 -9
- data/lib/dry/types/default.rb +48 -16
- data/lib/dry/types/enum.rb +30 -16
- data/lib/dry/types/errors.rb +73 -7
- data/lib/dry/types/extensions.rb +2 -0
- data/lib/dry/types/extensions/maybe.rb +43 -4
- data/lib/dry/types/fn_container.rb +5 -0
- data/lib/dry/types/hash.rb +22 -3
- data/lib/dry/types/hash/constructor.rb +13 -0
- data/lib/dry/types/inflector.rb +2 -0
- data/lib/dry/types/json.rb +4 -6
- data/lib/dry/types/{safe.rb → lax.rb} +34 -17
- data/lib/dry/types/map.rb +63 -29
- data/lib/dry/types/meta.rb +51 -0
- data/lib/dry/types/module.rb +7 -2
- data/lib/dry/types/nominal.rb +105 -13
- data/lib/dry/types/options.rb +12 -25
- data/lib/dry/types/params.rb +5 -3
- data/lib/dry/types/printable.rb +5 -1
- data/lib/dry/types/printer.rb +58 -57
- data/lib/dry/types/result.rb +26 -0
- data/lib/dry/types/schema.rb +169 -66
- data/lib/dry/types/schema/key.rb +34 -39
- data/lib/dry/types/spec/types.rb +41 -1
- data/lib/dry/types/sum.rb +70 -21
- data/lib/dry/types/type.rb +49 -0
- data/lib/dry/types/version.rb +3 -1
- metadata +14 -12
data/lib/dry/types/container.rb
CHANGED
data/lib/dry/types/core.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'dry/types/any'
|
2
4
|
|
3
5
|
module Dry
|
@@ -60,14 +62,14 @@ module Dry
|
|
60
62
|
end
|
61
63
|
|
62
64
|
# Register `:bool` since it's common and not a built-in Ruby type :(
|
63
|
-
register(
|
64
|
-
bool = self[
|
65
|
+
register('nominal.bool', self['nominal.true'] | self['nominal.false'])
|
66
|
+
bool = self['strict.true'] | self['strict.false']
|
65
67
|
register("strict.bool", bool)
|
66
68
|
register("bool", bool)
|
67
69
|
|
68
|
-
register(
|
69
|
-
register(
|
70
|
-
register(
|
70
|
+
register('any', Any)
|
71
|
+
register('nominal.any', Any)
|
72
|
+
register('strict.any', Any)
|
71
73
|
end
|
72
74
|
end
|
73
75
|
|
data/lib/dry/types/decorator.rb
CHANGED
@@ -1,7 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'dry/types/options'
|
2
4
|
|
3
5
|
module Dry
|
4
6
|
module Types
|
7
|
+
# Common API for types
|
8
|
+
#
|
9
|
+
# @api public
|
5
10
|
module Decorator
|
6
11
|
include Options
|
7
12
|
|
@@ -16,53 +21,73 @@ module Dry
|
|
16
21
|
|
17
22
|
# @param [Object] input
|
18
23
|
# @param [#call, nil] block
|
24
|
+
#
|
19
25
|
# @return [Result,Logic::Result]
|
20
26
|
# @return [Object] if block given and try fails
|
27
|
+
#
|
28
|
+
# @api public
|
21
29
|
def try(input, &block)
|
22
30
|
type.try(input, &block)
|
23
31
|
end
|
24
32
|
|
25
|
-
# @param [Object] value
|
26
|
-
# @return [Boolean]
|
27
|
-
def valid?(value)
|
28
|
-
type.valid?(value)
|
29
|
-
end
|
30
|
-
alias_method :===, :valid?
|
31
|
-
|
32
33
|
# @return [Boolean]
|
34
|
+
#
|
35
|
+
# @api public
|
33
36
|
def default?
|
34
37
|
type.default?
|
35
38
|
end
|
36
39
|
|
37
40
|
# @return [Boolean]
|
41
|
+
#
|
42
|
+
# @api public
|
38
43
|
def constrained?
|
39
44
|
type.constrained?
|
40
45
|
end
|
41
46
|
|
42
47
|
# @return [Sum]
|
48
|
+
#
|
49
|
+
# @api public
|
43
50
|
def optional
|
44
51
|
Types['strict.nil'] | self
|
45
52
|
end
|
46
53
|
|
47
54
|
# @param [Symbol] meth
|
48
55
|
# @param [Boolean] include_private
|
56
|
+
#
|
49
57
|
# @return [Boolean]
|
58
|
+
#
|
59
|
+
# @api public
|
50
60
|
def respond_to_missing?(meth, include_private = false)
|
51
61
|
super || type.respond_to?(meth)
|
52
62
|
end
|
53
63
|
|
64
|
+
# Wrap the type with a proc
|
65
|
+
#
|
66
|
+
# @return [Proc]
|
67
|
+
#
|
68
|
+
# @api public
|
69
|
+
def to_proc
|
70
|
+
proc { |value| self.(value) }
|
71
|
+
end
|
72
|
+
|
54
73
|
private
|
55
74
|
|
56
75
|
# @param [Object] response
|
76
|
+
#
|
57
77
|
# @return [Boolean]
|
78
|
+
#
|
79
|
+
# @api private
|
58
80
|
def decorate?(response)
|
59
|
-
response.
|
81
|
+
response.is_a?(type.class)
|
60
82
|
end
|
61
83
|
|
62
84
|
# Delegates missing methods to {#type}
|
85
|
+
#
|
63
86
|
# @param [Symbol] meth
|
64
87
|
# @param [Array] args
|
65
88
|
# @param [#call, nil] block
|
89
|
+
#
|
90
|
+
# @api private
|
66
91
|
def method_missing(meth, *args, &block)
|
67
92
|
if type.respond_to?(meth)
|
68
93
|
response = type.__send__(meth, *args, &block)
|
@@ -78,8 +103,10 @@ module Dry
|
|
78
103
|
end
|
79
104
|
|
80
105
|
# Replace underlying type
|
106
|
+
#
|
107
|
+
# @api private
|
81
108
|
def __new__(type)
|
82
|
-
self.class.new(type, options)
|
109
|
+
self.class.new(type, *@__args__[1..-1], **@options)
|
83
110
|
end
|
84
111
|
end
|
85
112
|
end
|
data/lib/dry/types/default.rb
CHANGED
@@ -1,16 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'dry/types/decorator'
|
2
4
|
|
3
5
|
module Dry
|
4
6
|
module Types
|
7
|
+
# Default types are useful when a missing value should be replaced by a default one
|
8
|
+
#
|
9
|
+
# @api public
|
5
10
|
class Default
|
6
|
-
|
7
|
-
include Decorator
|
8
|
-
include Builder
|
9
|
-
include Printable
|
10
|
-
include Dry::Equalizer(:type, :options, :value, inspect: false)
|
11
|
-
|
11
|
+
# @api private
|
12
12
|
class Callable < Default
|
13
|
-
include Dry::Equalizer(:type,
|
13
|
+
include Dry::Equalizer(:type, inspect: false)
|
14
14
|
|
15
15
|
# Evaluates given callable
|
16
16
|
# @return [Object]
|
@@ -19,13 +19,22 @@ module Dry
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
+
include Type
|
23
|
+
include Decorator
|
24
|
+
include Builder
|
25
|
+
include Printable
|
26
|
+
include Dry::Equalizer(:type, :value, inspect: false)
|
27
|
+
|
22
28
|
# @return [Object]
|
23
29
|
attr_reader :value
|
24
30
|
|
25
31
|
alias_method :evaluate, :value
|
26
32
|
|
27
33
|
# @param [Object, #call] value
|
34
|
+
#
|
28
35
|
# @return [Class] {Default} or {Default::Callable}
|
36
|
+
#
|
37
|
+
# @api private
|
29
38
|
def self.[](value)
|
30
39
|
if value.respond_to?(:call)
|
31
40
|
Callable
|
@@ -36,48 +45,71 @@ module Dry
|
|
36
45
|
|
37
46
|
# @param [Type] type
|
38
47
|
# @param [Object] value
|
48
|
+
#
|
49
|
+
# @api private
|
39
50
|
def initialize(type, value, **options)
|
40
51
|
super
|
41
52
|
@value = value
|
42
53
|
end
|
43
54
|
|
55
|
+
# Build a constrained type
|
56
|
+
#
|
44
57
|
# @param [Array] args see {Dry::Types::Builder#constrained}
|
58
|
+
#
|
45
59
|
# @return [Default]
|
60
|
+
#
|
61
|
+
# @api public
|
46
62
|
def constrained(*args)
|
47
63
|
type.constrained(*args).default(value)
|
48
64
|
end
|
49
65
|
|
50
66
|
# @return [true]
|
67
|
+
#
|
68
|
+
# @api public
|
51
69
|
def default?
|
52
70
|
true
|
53
71
|
end
|
54
72
|
|
55
73
|
# @param [Object] input
|
74
|
+
#
|
56
75
|
# @return [Result::Success]
|
76
|
+
#
|
77
|
+
# @api public
|
57
78
|
def try(input)
|
58
79
|
success(call(input))
|
59
80
|
end
|
60
81
|
|
82
|
+
# @return [Boolean]
|
83
|
+
#
|
84
|
+
# @api public
|
61
85
|
def valid?(value = Undefined)
|
62
|
-
|
86
|
+
Undefined.equal?(value) || super
|
63
87
|
end
|
64
88
|
|
65
89
|
# @param [Object] input
|
90
|
+
#
|
66
91
|
# @return [Object] value passed through {#type} or {#default} value
|
67
|
-
|
92
|
+
#
|
93
|
+
# @api private
|
94
|
+
def call_unsafe(input = Undefined)
|
68
95
|
if input.equal?(Undefined)
|
69
96
|
evaluate
|
70
97
|
else
|
71
|
-
Undefined.default(type
|
98
|
+
Undefined.default(type.call_unsafe(input)) { evaluate }
|
72
99
|
end
|
73
100
|
end
|
74
|
-
alias_method :[], :call
|
75
|
-
|
76
|
-
private
|
77
101
|
|
78
|
-
#
|
79
|
-
|
80
|
-
|
102
|
+
# @param [Object] input
|
103
|
+
#
|
104
|
+
# @return [Object] value passed through {#type} or {#default} value
|
105
|
+
#
|
106
|
+
# @api private
|
107
|
+
def call_safe(input = Undefined, &block)
|
108
|
+
if input.equal?(Undefined)
|
109
|
+
evaluate
|
110
|
+
else
|
111
|
+
Undefined.default(type.call_safe(input, &block)) { evaluate }
|
112
|
+
end
|
81
113
|
end
|
82
114
|
end
|
83
115
|
end
|
data/lib/dry/types/enum.rb
CHANGED
@@ -1,10 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'dry/types/decorator'
|
2
4
|
|
3
5
|
module Dry
|
4
6
|
module Types
|
7
|
+
# Enum types can be used to define an enum on top of an existing type
|
8
|
+
#
|
9
|
+
# @api public
|
5
10
|
class Enum
|
6
11
|
include Type
|
7
|
-
include Dry::Equalizer(:type, :
|
12
|
+
include Dry::Equalizer(:type, :mapping, inspect: false)
|
8
13
|
include Decorator
|
9
14
|
|
10
15
|
# @return [Array]
|
@@ -19,6 +24,8 @@ module Dry
|
|
19
24
|
# @param [Type] type
|
20
25
|
# @param [Hash] options
|
21
26
|
# @option options [Array] :values
|
27
|
+
#
|
28
|
+
# @api private
|
22
29
|
def initialize(type, options)
|
23
30
|
super
|
24
31
|
@mapping = options.fetch(:mapping).freeze
|
@@ -27,22 +34,28 @@ module Dry
|
|
27
34
|
freeze
|
28
35
|
end
|
29
36
|
|
30
|
-
# @param [Object] input
|
31
37
|
# @return [Object]
|
32
|
-
|
33
|
-
|
38
|
+
#
|
39
|
+
# @api private
|
40
|
+
def call_unsafe(input)
|
41
|
+
type.call_unsafe(map_value(input))
|
42
|
+
end
|
43
|
+
|
44
|
+
# @return [Object]
|
45
|
+
#
|
46
|
+
# @api private
|
47
|
+
def call_safe(input, &block)
|
48
|
+
type.call_safe(map_value(input), &block)
|
34
49
|
end
|
35
|
-
alias_method :[], :call
|
36
50
|
|
37
|
-
# @
|
38
|
-
#
|
39
|
-
# @
|
40
|
-
# @return [Logic::Result]
|
41
|
-
# @return [Object] if coercion fails and a block is given
|
51
|
+
# @see Dry::Types::Constrained#try
|
52
|
+
#
|
53
|
+
# @api public
|
42
54
|
def try(input)
|
43
55
|
super(map_value(input))
|
44
56
|
end
|
45
57
|
|
58
|
+
# @api private
|
46
59
|
def default(*)
|
47
60
|
raise '.enum(*values).default(value) is not supported. Call '\
|
48
61
|
'.default(value).enum(*values) instead'
|
@@ -51,16 +64,15 @@ module Dry
|
|
51
64
|
# Check whether a value is in the enum
|
52
65
|
alias_method :include?, :valid?
|
53
66
|
|
54
|
-
# @api public
|
55
|
-
#
|
56
67
|
# @see Nominal#to_ast
|
68
|
+
#
|
69
|
+
# @api public
|
57
70
|
def to_ast(meta: true)
|
58
|
-
[:enum, [type.to_ast(meta: meta),
|
59
|
-
mapping,
|
60
|
-
meta ? self.meta : EMPTY_HASH]]
|
71
|
+
[:enum, [type.to_ast(meta: meta), mapping]]
|
61
72
|
end
|
62
73
|
|
63
74
|
# @return [String]
|
75
|
+
#
|
64
76
|
# @api public
|
65
77
|
def to_s
|
66
78
|
PRINTER.(self)
|
@@ -71,8 +83,10 @@ module Dry
|
|
71
83
|
|
72
84
|
# Maps a value
|
73
85
|
#
|
74
|
-
# @param [Object]
|
86
|
+
# @param [Object] input
|
87
|
+
#
|
75
88
|
# @return [Object]
|
89
|
+
#
|
76
90
|
# @api private
|
77
91
|
def map_value(input)
|
78
92
|
if input.equal?(Undefined)
|
data/lib/dry/types/errors.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Dry
|
2
4
|
module Types
|
3
5
|
extend Dry::Core::ClassAttributes
|
@@ -8,7 +10,62 @@ module Dry
|
|
8
10
|
|
9
11
|
namespace self
|
10
12
|
|
11
|
-
class
|
13
|
+
# Base class for coercion errors raise by dry-types
|
14
|
+
#
|
15
|
+
class CoercionError < StandardError
|
16
|
+
# @api private
|
17
|
+
def self.handle(exception, meta: Undefined)
|
18
|
+
if block_given?
|
19
|
+
yield
|
20
|
+
else
|
21
|
+
raise new(
|
22
|
+
exception.message,
|
23
|
+
meta: meta,
|
24
|
+
backtrace: exception.backtrace
|
25
|
+
)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Metadata associated with the error
|
30
|
+
#
|
31
|
+
# @return [Object]
|
32
|
+
attr_reader :meta
|
33
|
+
|
34
|
+
# @api private
|
35
|
+
def initialize(message, meta: Undefined, backtrace: Undefined)
|
36
|
+
unless message.is_a?(::String)
|
37
|
+
raise ArgumentError, "message must be a string, #{message.class} given"
|
38
|
+
end
|
39
|
+
|
40
|
+
super(message)
|
41
|
+
@meta = Undefined.default(meta, nil)
|
42
|
+
set_backtrace(backtrace) unless Undefined.equal?(backtrace)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Collection of multiple errors
|
47
|
+
#
|
48
|
+
class MultipleError < CoercionError
|
49
|
+
# @return [Array<CoercionError>]
|
50
|
+
attr_reader :errors
|
51
|
+
|
52
|
+
# @param [Array<CoercionError>] errors
|
53
|
+
def initialize(errors)
|
54
|
+
@errors = errors
|
55
|
+
end
|
56
|
+
|
57
|
+
# @return string
|
58
|
+
def message
|
59
|
+
errors.map(&:message).join(', ')
|
60
|
+
end
|
61
|
+
|
62
|
+
# @return [Array]
|
63
|
+
def meta
|
64
|
+
errors.map(&:meta)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class SchemaError < CoercionError
|
12
69
|
# @param [String,Symbol] key
|
13
70
|
# @param [Object] value
|
14
71
|
# @param [String, #to_s] result
|
@@ -17,26 +74,34 @@ module Dry
|
|
17
74
|
end
|
18
75
|
end
|
19
76
|
|
20
|
-
MapError = Class.new(
|
77
|
+
MapError = Class.new(CoercionError)
|
21
78
|
|
22
|
-
SchemaKeyError = Class.new(
|
79
|
+
SchemaKeyError = Class.new(CoercionError)
|
23
80
|
private_constant(:SchemaKeyError)
|
24
81
|
|
25
82
|
class MissingKeyError < SchemaKeyError
|
83
|
+
# @return [Symbol]
|
84
|
+
attr_reader :key
|
85
|
+
|
26
86
|
# @param [String,Symbol] key
|
27
87
|
def initialize(key)
|
28
|
-
|
88
|
+
@key = key
|
89
|
+
super("#{key.inspect} is missing in Hash input")
|
29
90
|
end
|
30
91
|
end
|
31
92
|
|
32
93
|
class UnknownKeysError < SchemaKeyError
|
94
|
+
# @return [Array<Symbol>]
|
95
|
+
attr_reader :keys
|
96
|
+
|
33
97
|
# @param [<String, Symbol>] keys
|
34
|
-
def initialize(
|
98
|
+
def initialize(keys)
|
99
|
+
@keys = keys
|
35
100
|
super("unexpected keys #{keys.inspect} in Hash input")
|
36
101
|
end
|
37
102
|
end
|
38
103
|
|
39
|
-
class ConstraintError <
|
104
|
+
class ConstraintError < CoercionError
|
40
105
|
# @return [String, #to_s]
|
41
106
|
attr_reader :result
|
42
107
|
# @return [Object]
|
@@ -56,9 +121,10 @@ module Dry
|
|
56
121
|
end
|
57
122
|
|
58
123
|
# @return [String]
|
59
|
-
def
|
124
|
+
def message
|
60
125
|
"#{input.inspect} violates constraints (#{result} failed)"
|
61
126
|
end
|
127
|
+
alias_method :to_s, :message
|
62
128
|
end
|
63
129
|
end
|
64
130
|
end
|