dry-types 1.0.1 → 1.2.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/.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