dry-types 1.0.0 → 1.2.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/.codeclimate.yml +2 -5
- 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/.github/workflows/custom_ci.yml +76 -0
- data/.github/workflows/docsite.yml +34 -0
- data/.github/workflows/sync_configs.yml +34 -0
- data/.gitignore +1 -1
- data/.rspec +3 -1
- data/.rubocop.yml +89 -0
- data/CHANGELOG.md +127 -3
- data/CODE_OF_CONDUCT.md +13 -0
- data/CONTRIBUTING.md +2 -2
- data/Gemfile +12 -6
- data/LICENSE +17 -17
- 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/extensions.html.md +15 -0
- data/docsite/source/extensions/maybe.html.md +57 -0
- data/docsite/source/extensions/monads.html.md +61 -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 +156 -0
- data/docsite/source/map.html.md +17 -0
- data/docsite/source/optional-values.html.md +35 -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/any.rb +2 -2
- data/lib/dry/types/array.rb +6 -0
- data/lib/dry/types/array/constructor.rb +32 -0
- data/lib/dry/types/array/member.rb +10 -3
- data/lib/dry/types/builder.rb +2 -2
- data/lib/dry/types/builder_methods.rb +34 -8
- data/lib/dry/types/coercions.rb +19 -6
- data/lib/dry/types/coercions/params.rb +4 -4
- data/lib/dry/types/compiler.rb +2 -2
- data/lib/dry/types/constrained.rb +6 -1
- data/lib/dry/types/constructor.rb +10 -42
- data/lib/dry/types/constructor/function.rb +4 -5
- data/lib/dry/types/core.rb +27 -8
- data/lib/dry/types/decorator.rb +3 -2
- data/lib/dry/types/enum.rb +2 -1
- 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 +11 -12
- 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/meta.rb +1 -1
- data/lib/dry/types/module.rb +6 -6
- 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 +16 -22
- data/lib/dry/types/schema/key.rb +19 -1
- data/lib/dry/types/spec/types.rb +6 -7
- data/lib/dry/types/sum.rb +2 -2
- data/lib/dry/types/version.rb +1 -1
- metadata +67 -35
- data/.travis.yml +0 -27
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
|
@@ -127,7 +127,7 @@ module Dry
|
|
127
127
|
#
|
128
128
|
# @api public
|
129
129
|
def constructor(constructor = nil, **options, &block)
|
130
|
-
constructor_type.new(with(options), fn: constructor || block)
|
130
|
+
constructor_type.new(with(**options), fn: constructor || block)
|
131
131
|
end
|
132
132
|
end
|
133
133
|
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,7 +115,23 @@ 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)
|
119
|
+
end
|
120
|
+
|
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
|
109
135
|
end
|
110
136
|
end
|
111
137
|
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
|
data/lib/dry/types/compiler.rb
CHANGED
@@ -68,12 +68,12 @@ module Dry
|
|
68
68
|
|
69
69
|
def visit_hash(node)
|
70
70
|
opts, meta = node
|
71
|
-
registry['nominal.hash'].with(opts
|
71
|
+
registry['nominal.hash'].with(**opts, meta: meta)
|
72
72
|
end
|
73
73
|
|
74
74
|
def visit_schema(node)
|
75
75
|
keys, options, meta = node
|
76
|
-
registry['nominal.hash'].schema(keys.map { |key| visit(key) }).with(options
|
76
|
+
registry['nominal.hash'].schema(keys.map { |key| visit(key) }).with(**options, meta: meta)
|
77
77
|
end
|
78
78
|
|
79
79
|
def visit_json_hash(node)
|
@@ -24,7 +24,7 @@ module Dry
|
|
24
24
|
# @param [Hash] options
|
25
25
|
#
|
26
26
|
# @api public
|
27
|
-
def initialize(type, options)
|
27
|
+
def initialize(type, **options)
|
28
28
|
super
|
29
29
|
@rule = options.fetch(:rule)
|
30
30
|
end
|
@@ -126,6 +126,11 @@ module Dry
|
|
126
126
|
[:constrained, [type.to_ast(meta: meta), rule.to_ast]]
|
127
127
|
end
|
128
128
|
|
129
|
+
# @api private
|
130
|
+
def constructor_type
|
131
|
+
type.constructor_type
|
132
|
+
end
|
133
|
+
|
129
134
|
private
|
130
135
|
|
131
136
|
# @param [Object] response
|
@@ -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,36 +44,11 @@ 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
|
77
50
|
def call_safe(input)
|
78
|
-
coerced = fn.(input) { return yield }
|
51
|
+
coerced = fn.(input) { |output = input| return yield(output) }
|
79
52
|
type.call_safe(coerced) { |output = coerced| yield(output) }
|
80
53
|
end
|
81
54
|
|
@@ -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)
|
@@ -112,7 +85,7 @@ module Dry
|
|
112
85
|
#
|
113
86
|
# @api public
|
114
87
|
def constructor(new_fn = nil, **options, &block)
|
115
|
-
with(
|
88
|
+
with(**options, fn: fn >> (new_fn || block))
|
116
89
|
end
|
117
90
|
alias_method :append, :constructor
|
118
91
|
alias_method :>>, :constructor
|
@@ -141,7 +114,7 @@ module Dry
|
|
141
114
|
#
|
142
115
|
# @api public
|
143
116
|
def prepend(new_fn = nil, **options, &block)
|
144
|
-
with(
|
117
|
+
with(**options, fn: fn << (new_fn || block))
|
145
118
|
end
|
146
119
|
alias_method :<<, :prepend
|
147
120
|
|
@@ -150,7 +123,7 @@ module Dry
|
|
150
123
|
# @return [Lax]
|
151
124
|
# @api public
|
152
125
|
def lax
|
153
|
-
Lax.new(Constructor.new(type.lax, options))
|
126
|
+
Lax.new(Constructor.new(type.lax, **options))
|
154
127
|
end
|
155
128
|
|
156
129
|
# Wrap the type with a proc
|
@@ -182,10 +155,10 @@ 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
|
188
|
-
response.constructor_type.new(response, options)
|
160
|
+
if response.is_a?(Type) && type.class == response.class
|
161
|
+
response.constructor_type.new(response, **options)
|
189
162
|
else
|
190
163
|
response
|
191
164
|
end
|
@@ -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
@@ -14,7 +14,7 @@ module Dry
|
|
14
14
|
attr_reader :type
|
15
15
|
|
16
16
|
# @param [Type] type
|
17
|
-
def initialize(type,
|
17
|
+
def initialize(type, *, **)
|
18
18
|
super
|
19
19
|
@type = type
|
20
20
|
end
|
@@ -90,7 +90,7 @@ module Dry
|
|
90
90
|
# @api private
|
91
91
|
def method_missing(meth, *args, &block)
|
92
92
|
if type.respond_to?(meth)
|
93
|
-
response = type.
|
93
|
+
response = type.public_send(meth, *args, &block)
|
94
94
|
|
95
95
|
if decorate?(response)
|
96
96
|
__new__(response)
|
@@ -101,6 +101,7 @@ module Dry
|
|
101
101
|
super
|
102
102
|
end
|
103
103
|
end
|
104
|
+
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
|
104
105
|
|
105
106
|
# Replace underlying type
|
106
107
|
#
|