dry-types 1.0.1 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md +10 -0
- data/.github/ISSUE_TEMPLATE/---bug-report.md +34 -0
- data/.github/ISSUE_TEMPLATE/---feature-request.md +18 -0
- data/.travis.yml +10 -4
- data/CHANGELOG.md +101 -3
- data/Gemfile +9 -6
- data/README.md +2 -2
- data/Rakefile +2 -2
- data/benchmarks/hash_schemas.rb +8 -6
- data/benchmarks/lax_schema.rb +0 -1
- data/benchmarks/profile_invalid_input.rb +1 -1
- data/benchmarks/profile_lax_schema_valid.rb +1 -1
- data/benchmarks/profile_valid_input.rb +1 -1
- data/docsite/source/array-with-member.html.md +13 -0
- data/docsite/source/built-in-types.html.md +116 -0
- data/docsite/source/constraints.html.md +31 -0
- data/docsite/source/custom-types.html.md +93 -0
- data/docsite/source/default-values.html.md +91 -0
- data/docsite/source/enum.html.md +69 -0
- data/docsite/source/getting-started.html.md +57 -0
- data/docsite/source/hash-schemas.html.md +169 -0
- data/docsite/source/index.html.md +155 -0
- data/docsite/source/map.html.md +17 -0
- data/docsite/source/optional-values.html.md +96 -0
- data/docsite/source/sum.html.md +21 -0
- data/dry-types.gemspec +19 -19
- data/lib/dry/types.rb +9 -4
- data/lib/dry/types/array.rb +6 -0
- data/lib/dry/types/array/constructor.rb +32 -0
- data/lib/dry/types/array/member.rb +8 -1
- data/lib/dry/types/builder.rb +1 -1
- data/lib/dry/types/builder_methods.rb +33 -23
- data/lib/dry/types/coercions.rb +19 -6
- data/lib/dry/types/coercions/params.rb +4 -4
- data/lib/dry/types/constrained.rb +5 -0
- data/lib/dry/types/constructor.rb +5 -37
- data/lib/dry/types/constructor/function.rb +4 -5
- data/lib/dry/types/core.rb +27 -8
- data/lib/dry/types/decorator.rb +1 -1
- data/lib/dry/types/enum.rb +1 -0
- data/lib/dry/types/extensions.rb +4 -0
- data/lib/dry/types/extensions/maybe.rb +9 -1
- data/lib/dry/types/extensions/monads.rb +29 -0
- data/lib/dry/types/hash.rb +10 -11
- data/lib/dry/types/hash/constructor.rb +5 -5
- data/lib/dry/types/json.rb +4 -0
- data/lib/dry/types/lax.rb +4 -4
- data/lib/dry/types/map.rb +8 -4
- data/lib/dry/types/module.rb +3 -3
- data/lib/dry/types/nominal.rb +3 -4
- data/lib/dry/types/params.rb +9 -0
- data/lib/dry/types/predicate_inferrer.rb +197 -0
- data/lib/dry/types/predicate_registry.rb +34 -0
- data/lib/dry/types/primitive_inferrer.rb +97 -0
- data/lib/dry/types/printer.rb +17 -12
- data/lib/dry/types/schema.rb +14 -20
- data/lib/dry/types/schema/key.rb +19 -1
- data/lib/dry/types/spec/types.rb +3 -6
- data/lib/dry/types/version.rb +1 -1
- metadata +79 -52
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'dry/types/array/constructor'
|
4
|
+
|
3
5
|
module Dry
|
4
6
|
module Types
|
5
7
|
class Array < Nominal
|
@@ -98,7 +100,7 @@ module Dry
|
|
98
100
|
#
|
99
101
|
# @api public
|
100
102
|
def lax
|
101
|
-
Lax.new(Member.new(primitive, { **options, member: member.lax }))
|
103
|
+
Lax.new(Member.new(primitive, { **options, member: member.lax, meta: meta }))
|
102
104
|
end
|
103
105
|
|
104
106
|
# @see Nominal#to_ast
|
@@ -111,6 +113,11 @@ module Dry
|
|
111
113
|
[:array, [member, meta ? self.meta : EMPTY_HASH]]
|
112
114
|
end
|
113
115
|
end
|
116
|
+
|
117
|
+
# @api private
|
118
|
+
def constructor_type
|
119
|
+
::Dry::Types::Array::Constructor
|
120
|
+
end
|
114
121
|
end
|
115
122
|
end
|
116
123
|
end
|
data/lib/dry/types/builder.rb
CHANGED
@@ -75,7 +75,7 @@ module Dry
|
|
75
75
|
' Be careful: types will return the same instance of the default'\
|
76
76
|
' value every time. Call `.freeze` when setting the default'\
|
77
77
|
' or pass `shared: true` to discard this warning.'\
|
78
|
-
"\n#{
|
78
|
+
"\n#{where}",
|
79
79
|
tag: :'dry-types'
|
80
80
|
)
|
81
81
|
end
|
@@ -25,7 +25,7 @@ module Dry
|
|
25
25
|
#
|
26
26
|
# @return [Dry::Types::Array]
|
27
27
|
def Array(type)
|
28
|
-
|
28
|
+
Strict(::Array).of(type)
|
29
29
|
end
|
30
30
|
|
31
31
|
# Build a hash schema
|
@@ -34,7 +34,7 @@ module Dry
|
|
34
34
|
#
|
35
35
|
# @return [Dry::Types::Array]
|
36
36
|
def Hash(type_map)
|
37
|
-
|
37
|
+
Strict(::Hash).schema(type_map)
|
38
38
|
end
|
39
39
|
|
40
40
|
# Build a type which values are instances of a given class
|
@@ -49,7 +49,7 @@ module Dry
|
|
49
49
|
#
|
50
50
|
# @return [Dry::Types::Type]
|
51
51
|
def Instance(klass)
|
52
|
-
Nominal
|
52
|
+
Nominal(klass).constrained(type: klass)
|
53
53
|
end
|
54
54
|
alias_method :Strict, :Instance
|
55
55
|
|
@@ -60,7 +60,7 @@ module Dry
|
|
60
60
|
#
|
61
61
|
# @return [Dry::Types::Type]
|
62
62
|
def Value(value)
|
63
|
-
Nominal
|
63
|
+
Nominal(value.class).constrained(eql: value)
|
64
64
|
end
|
65
65
|
|
66
66
|
# Build a type with a single value
|
@@ -70,7 +70,7 @@ module Dry
|
|
70
70
|
#
|
71
71
|
# @return [Dry::Types::Type]
|
72
72
|
def Constant(object)
|
73
|
-
Nominal
|
73
|
+
Nominal(object.class).constrained(is: object)
|
74
74
|
end
|
75
75
|
|
76
76
|
# Build a constructor type
|
@@ -82,7 +82,11 @@ module Dry
|
|
82
82
|
#
|
83
83
|
# @return [Dry::Types::Type]
|
84
84
|
def Constructor(klass, cons = nil, &block)
|
85
|
-
|
85
|
+
if klass.is_a?(Type)
|
86
|
+
klass.constructor(cons || block || klass.method(:new))
|
87
|
+
else
|
88
|
+
Nominal(klass).constructor(cons || block || klass.method(:new))
|
89
|
+
end
|
86
90
|
end
|
87
91
|
|
88
92
|
# Build a nominal type
|
@@ -91,7 +95,13 @@ module Dry
|
|
91
95
|
#
|
92
96
|
# @return [Dry::Types::Type]
|
93
97
|
def Nominal(klass)
|
94
|
-
|
98
|
+
if klass <= ::Array
|
99
|
+
Array.new(klass)
|
100
|
+
elsif klass <= ::Hash
|
101
|
+
Hash.new(klass)
|
102
|
+
else
|
103
|
+
Nominal.new(klass)
|
104
|
+
end
|
95
105
|
end
|
96
106
|
|
97
107
|
# Build a map type
|
@@ -105,24 +115,24 @@ module Dry
|
|
105
115
|
#
|
106
116
|
# @return [Dry::Types::Map]
|
107
117
|
def Map(key_type, value_type)
|
108
|
-
|
118
|
+
Nominal(::Hash).map(key_type, value_type)
|
109
119
|
end
|
110
120
|
|
111
|
-
#
|
112
|
-
#
|
113
|
-
#
|
114
|
-
#
|
115
|
-
#
|
116
|
-
#
|
117
|
-
#
|
118
|
-
#
|
119
|
-
#
|
120
|
-
#
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
121
|
+
# Builds a constrained nominal type accepting any value that
|
122
|
+
# responds to given methods
|
123
|
+
#
|
124
|
+
# @example
|
125
|
+
# Types::Callable = Types.Interface(:call)
|
126
|
+
# Types::Contact = Types.Interface(:name, :address)
|
127
|
+
#
|
128
|
+
# @param methods [Array<String, Symbol>] Method names
|
129
|
+
#
|
130
|
+
# @return [Dry::Types::Contrained]
|
131
|
+
def Interface(*methods)
|
132
|
+
methods.reduce(Types['nominal.any']) do |type, method|
|
133
|
+
type.constrained(respond_to: method)
|
134
|
+
end
|
135
|
+
end
|
126
136
|
end
|
127
137
|
end
|
128
138
|
end
|
data/lib/dry/types/coercions.rb
CHANGED
@@ -36,8 +36,8 @@ module Dry
|
|
36
36
|
if input.respond_to?(:to_str)
|
37
37
|
begin
|
38
38
|
::Date.parse(input)
|
39
|
-
rescue ArgumentError, RangeError =>
|
40
|
-
CoercionError.handle(
|
39
|
+
rescue ArgumentError, RangeError => e
|
40
|
+
CoercionError.handle(e, &block)
|
41
41
|
end
|
42
42
|
elsif block_given?
|
43
43
|
yield
|
@@ -57,8 +57,8 @@ module Dry
|
|
57
57
|
if input.respond_to?(:to_str)
|
58
58
|
begin
|
59
59
|
::DateTime.parse(input)
|
60
|
-
rescue ArgumentError =>
|
61
|
-
CoercionError.handle(
|
60
|
+
rescue ArgumentError => e
|
61
|
+
CoercionError.handle(e, &block)
|
62
62
|
end
|
63
63
|
elsif block_given?
|
64
64
|
yield
|
@@ -78,8 +78,8 @@ module Dry
|
|
78
78
|
if input.respond_to?(:to_str)
|
79
79
|
begin
|
80
80
|
::Time.parse(input)
|
81
|
-
rescue ArgumentError =>
|
82
|
-
CoercionError.handle(
|
81
|
+
rescue ArgumentError => e
|
82
|
+
CoercionError.handle(e, &block)
|
83
83
|
end
|
84
84
|
elsif block_given?
|
85
85
|
yield
|
@@ -88,6 +88,19 @@ module Dry
|
|
88
88
|
end
|
89
89
|
end
|
90
90
|
|
91
|
+
# @param [#to_sym, Object] input
|
92
|
+
#
|
93
|
+
# @return [Symbol, Object]
|
94
|
+
#
|
95
|
+
# @raise CoercionError
|
96
|
+
#
|
97
|
+
# @api public
|
98
|
+
def to_symbol(input, &block)
|
99
|
+
input.to_sym
|
100
|
+
rescue NoMethodError => e
|
101
|
+
CoercionError.handle(e, &block)
|
102
|
+
end
|
103
|
+
|
91
104
|
private
|
92
105
|
|
93
106
|
# Checks whether String is empty
|
@@ -71,8 +71,8 @@ module Dry
|
|
71
71
|
else
|
72
72
|
Integer(input)
|
73
73
|
end
|
74
|
-
rescue ArgumentError, TypeError =>
|
75
|
-
CoercionError.handle(
|
74
|
+
rescue ArgumentError, TypeError => e
|
75
|
+
CoercionError.handle(e, &block)
|
76
76
|
end
|
77
77
|
|
78
78
|
# @param [#to_f, Object] input
|
@@ -84,8 +84,8 @@ module Dry
|
|
84
84
|
# @api public
|
85
85
|
def self.to_float(input, &block)
|
86
86
|
Float(input)
|
87
|
-
rescue ArgumentError, TypeError =>
|
88
|
-
CoercionError.handle(
|
87
|
+
rescue ArgumentError, TypeError => e
|
88
|
+
CoercionError.handle(e, &block)
|
89
89
|
end
|
90
90
|
|
91
91
|
# @param [#to_d, Object] input
|
@@ -12,15 +12,13 @@ module Dry
|
|
12
12
|
class Constructor < Nominal
|
13
13
|
include Dry::Equalizer(:type, :options, inspect: false)
|
14
14
|
|
15
|
-
private :meta
|
16
|
-
|
17
15
|
# @return [#call]
|
18
16
|
attr_reader :fn
|
19
17
|
|
20
18
|
# @return [Type]
|
21
19
|
attr_reader :type
|
22
20
|
|
23
|
-
undef :constrained
|
21
|
+
undef :constrained?, :meta, :optional?, :primitive, :default?, :name
|
24
22
|
|
25
23
|
# @param [Builder, Object] input
|
26
24
|
# @param [Hash] options
|
@@ -46,31 +44,6 @@ module Dry
|
|
46
44
|
super(type, **options, fn: fn)
|
47
45
|
end
|
48
46
|
|
49
|
-
# Return the inner type's primitive
|
50
|
-
#
|
51
|
-
# @return [Class]
|
52
|
-
#
|
53
|
-
# @api public
|
54
|
-
def primitive
|
55
|
-
type.primitive
|
56
|
-
end
|
57
|
-
|
58
|
-
# Return the inner type's name
|
59
|
-
#
|
60
|
-
# @return [String]
|
61
|
-
#
|
62
|
-
# @api public
|
63
|
-
def name
|
64
|
-
type.name
|
65
|
-
end
|
66
|
-
|
67
|
-
# @return [Boolean]
|
68
|
-
#
|
69
|
-
# @api public
|
70
|
-
def default?
|
71
|
-
type.default?
|
72
|
-
end
|
73
|
-
|
74
47
|
# @return [Object]
|
75
48
|
#
|
76
49
|
# @api private
|
@@ -95,8 +68,8 @@ module Dry
|
|
95
68
|
# @api public
|
96
69
|
def try(input, &block)
|
97
70
|
value = fn.(input)
|
98
|
-
rescue CoercionError =>
|
99
|
-
failure = failure(input,
|
71
|
+
rescue CoercionError => e
|
72
|
+
failure = failure(input, e)
|
100
73
|
block_given? ? yield(failure) : failure
|
101
74
|
else
|
102
75
|
type.try(value, &block)
|
@@ -182,9 +155,9 @@ module Dry
|
|
182
155
|
# @api private
|
183
156
|
def method_missing(method, *args, &block)
|
184
157
|
if type.respond_to?(method)
|
185
|
-
response = type.
|
158
|
+
response = type.public_send(method, *args, &block)
|
186
159
|
|
187
|
-
if
|
160
|
+
if response.is_a?(Type) && type.class == response.class
|
188
161
|
response.constructor_type.new(response, options)
|
189
162
|
else
|
190
163
|
response
|
@@ -193,11 +166,6 @@ module Dry
|
|
193
166
|
super
|
194
167
|
end
|
195
168
|
end
|
196
|
-
|
197
|
-
# @api private
|
198
|
-
def composable?(value)
|
199
|
-
value.is_a?(Builder)
|
200
|
-
end
|
201
169
|
end
|
202
170
|
end
|
203
171
|
end
|
@@ -1,4 +1,3 @@
|
|
1
|
-
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
3
|
require 'concurrent/map'
|
@@ -16,8 +15,8 @@ module Dry
|
|
16
15
|
class Safe < Function
|
17
16
|
def call(input, &block)
|
18
17
|
@fn.(input, &block)
|
19
|
-
rescue NoMethodError, TypeError, ArgumentError =>
|
20
|
-
CoercionError.handle(
|
18
|
+
rescue NoMethodError, TypeError, ArgumentError => e
|
19
|
+
CoercionError.handle(e, &block)
|
21
20
|
end
|
22
21
|
end
|
23
22
|
|
@@ -91,8 +90,8 @@ module Dry
|
|
91
90
|
class PrivateSafeCall < PrivateCall
|
92
91
|
def call(input, &block)
|
93
92
|
@target.send(@name, input)
|
94
|
-
rescue NoMethodError, TypeError, ArgumentError =>
|
95
|
-
CoercionError.handle(
|
93
|
+
rescue NoMethodError, TypeError, ArgumentError => e
|
94
|
+
CoercionError.handle(e, &block)
|
96
95
|
end
|
97
96
|
end
|
98
97
|
|
data/lib/dry/types/core.rb
CHANGED
@@ -5,7 +5,7 @@ require 'dry/types/any'
|
|
5
5
|
module Dry
|
6
6
|
module Types
|
7
7
|
# Primitives with {Kernel} coercion methods
|
8
|
-
|
8
|
+
KERNEL_COERCIBLE = {
|
9
9
|
string: String,
|
10
10
|
integer: Integer,
|
11
11
|
float: Float,
|
@@ -14,10 +14,19 @@ module Dry
|
|
14
14
|
hash: ::Hash
|
15
15
|
}.freeze
|
16
16
|
|
17
|
-
# Primitives
|
17
|
+
# Primitives with coercions through by convention `to_*` methods
|
18
|
+
METHOD_COERCIBLE = {
|
19
|
+
symbol: Symbol
|
20
|
+
}.freeze
|
21
|
+
|
22
|
+
# By convention methods to coerce {METHOD_COERCIBLE} primitives
|
23
|
+
METHOD_COERCIBLE_METHODS = {
|
24
|
+
symbol: :to_sym
|
25
|
+
}.freeze
|
26
|
+
|
27
|
+
# Primitives that are non-coercible
|
18
28
|
NON_COERCIBLE = {
|
19
29
|
nil: NilClass,
|
20
|
-
symbol: Symbol,
|
21
30
|
class: Class,
|
22
31
|
true: TrueClass,
|
23
32
|
false: FalseClass,
|
@@ -28,7 +37,10 @@ module Dry
|
|
28
37
|
}.freeze
|
29
38
|
|
30
39
|
# All built-in primitives
|
31
|
-
ALL_PRIMITIVES =
|
40
|
+
ALL_PRIMITIVES = [KERNEL_COERCIBLE, METHOD_COERCIBLE, NON_COERCIBLE].reduce(&:merge).freeze
|
41
|
+
|
42
|
+
# All coercible types
|
43
|
+
COERCIBLE = KERNEL_COERCIBLE.merge(METHOD_COERCIBLE).freeze
|
32
44
|
|
33
45
|
# All built-in primitives except {NilClass}
|
34
46
|
NON_NIL = ALL_PRIMITIVES.reject { |name, _| name == :nil }.freeze
|
@@ -46,11 +58,18 @@ module Dry
|
|
46
58
|
register("strict.#{name}", type)
|
47
59
|
end
|
48
60
|
|
49
|
-
# Register {
|
50
|
-
|
61
|
+
# Register {KERNEL_COERCIBLE} types
|
62
|
+
KERNEL_COERCIBLE.each do |name, primitive|
|
51
63
|
register("coercible.#{name}", self["nominal.#{name}"].constructor(Kernel.method(primitive.name)))
|
52
64
|
end
|
53
65
|
|
66
|
+
# Register {METHOD_COERCIBLE} types
|
67
|
+
METHOD_COERCIBLE.each_key do |name|
|
68
|
+
register(
|
69
|
+
"coercible.#{name}", self["nominal.#{name}"].constructor(&METHOD_COERCIBLE_METHODS[name])
|
70
|
+
)
|
71
|
+
end
|
72
|
+
|
54
73
|
# Register optional strict {NON_NIL} types
|
55
74
|
NON_NIL.each_key do |name|
|
56
75
|
register("optional.strict.#{name}", self["strict.#{name}"].optional)
|
@@ -64,8 +83,8 @@ module Dry
|
|
64
83
|
# Register `:bool` since it's common and not a built-in Ruby type :(
|
65
84
|
register('nominal.bool', self['nominal.true'] | self['nominal.false'])
|
66
85
|
bool = self['strict.true'] | self['strict.false']
|
67
|
-
register(
|
68
|
-
register(
|
86
|
+
register('strict.bool', bool)
|
87
|
+
register('bool', bool)
|
69
88
|
|
70
89
|
register('any', Any)
|
71
90
|
register('nominal.any', Any)
|
data/lib/dry/types/decorator.rb
CHANGED
data/lib/dry/types/enum.rb
CHANGED