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/any.rb
CHANGED
@@ -1,36 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Dry
|
2
4
|
module Types
|
3
|
-
Any
|
5
|
+
# Any is a nominal type that defines Object as the primitive class
|
6
|
+
#
|
7
|
+
# This type is useful in places where you can't be specific about the type
|
8
|
+
# and anything is acceptable.
|
9
|
+
#
|
10
|
+
# @api public
|
11
|
+
class AnyClass < Nominal
|
4
12
|
def self.name
|
5
13
|
'Any'
|
6
14
|
end
|
7
15
|
|
16
|
+
# @api private
|
8
17
|
def initialize(**options)
|
9
18
|
super(::Object, options)
|
10
19
|
end
|
11
20
|
|
12
21
|
# @return [String]
|
22
|
+
#
|
23
|
+
# @api public
|
13
24
|
def name
|
14
25
|
'Any'
|
15
26
|
end
|
16
27
|
|
17
|
-
# @param [Object] any input is valid
|
18
|
-
# @return [true]
|
19
|
-
def valid?(_)
|
20
|
-
true
|
21
|
-
end
|
22
|
-
alias_method :===, :valid?
|
23
|
-
|
24
28
|
# @param [Hash] new_options
|
29
|
+
#
|
25
30
|
# @return [Type]
|
26
|
-
|
31
|
+
#
|
32
|
+
# @api public
|
33
|
+
def with(new_options)
|
27
34
|
self.class.new(**options, meta: @meta, **new_options)
|
28
35
|
end
|
29
36
|
|
30
37
|
# @return [Array]
|
38
|
+
#
|
39
|
+
# @api public
|
31
40
|
def to_ast(meta: true)
|
32
41
|
[:any, meta ? self.meta : EMPTY_HASH]
|
33
42
|
end
|
34
|
-
end
|
43
|
+
end
|
44
|
+
|
45
|
+
Any = AnyClass.new
|
35
46
|
end
|
36
47
|
end
|
data/lib/dry/types/array.rb
CHANGED
@@ -1,10 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'dry/types/array/member'
|
2
4
|
|
3
5
|
module Dry
|
4
6
|
module Types
|
7
|
+
# Array type can be used to define an array with optional member type
|
8
|
+
#
|
9
|
+
# @api public
|
5
10
|
class Array < Nominal
|
6
|
-
#
|
11
|
+
# Build an array type with a member type
|
12
|
+
#
|
13
|
+
# @param [Type,#call] type
|
14
|
+
#
|
7
15
|
# @return [Array::Member]
|
16
|
+
#
|
17
|
+
# @api public
|
8
18
|
def of(type)
|
9
19
|
member =
|
10
20
|
case type
|
@@ -1,46 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Dry
|
2
4
|
module Types
|
3
5
|
class Array < Nominal
|
6
|
+
# Member arrays define their member type that is applied to each element
|
7
|
+
#
|
8
|
+
# @api public
|
4
9
|
class Member < Array
|
5
10
|
# @return [Type]
|
6
11
|
attr_reader :member
|
7
12
|
|
8
13
|
# @param [Class] primitive
|
9
14
|
# @param [Hash] options
|
15
|
+
#
|
10
16
|
# @option options [Type] :member
|
17
|
+
#
|
18
|
+
# @api private
|
11
19
|
def initialize(primitive, options = {})
|
12
20
|
@member = options.fetch(:member)
|
13
21
|
super
|
14
22
|
end
|
15
23
|
|
16
24
|
# @param [Object] input
|
17
|
-
#
|
25
|
+
#
|
18
26
|
# @return [Array]
|
19
|
-
|
20
|
-
|
27
|
+
#
|
28
|
+
# @api private
|
29
|
+
def call_unsafe(input)
|
30
|
+
if primitive?(input)
|
31
|
+
input.each_with_object([]) do |el, output|
|
32
|
+
coerced = member.call_unsafe(el)
|
33
|
+
|
34
|
+
output << coerced unless Undefined.equal?(coerced)
|
35
|
+
end
|
36
|
+
else
|
37
|
+
super
|
38
|
+
end
|
21
39
|
end
|
22
|
-
alias_method :[], :call
|
23
40
|
|
24
|
-
# @param [
|
25
|
-
# @return [
|
26
|
-
|
27
|
-
|
41
|
+
# @param [Object] input
|
42
|
+
# @return [Array]
|
43
|
+
#
|
44
|
+
# @api private
|
45
|
+
def call_safe(input)
|
46
|
+
if primitive?(input)
|
47
|
+
failed = false
|
48
|
+
|
49
|
+
result = input.each_with_object([]) do |el, output|
|
50
|
+
coerced = member.call_safe(el) { |out = el|
|
51
|
+
failed = true
|
52
|
+
out
|
53
|
+
}
|
54
|
+
|
55
|
+
output << coerced unless Undefined.equal?(coerced)
|
56
|
+
end
|
57
|
+
|
58
|
+
failed ? yield(result) : result
|
59
|
+
else
|
60
|
+
yield
|
61
|
+
end
|
28
62
|
end
|
29
63
|
|
30
64
|
# @param [Array, Object] input
|
31
65
|
# @param [#call,nil] block
|
66
|
+
#
|
32
67
|
# @yieldparam [Failure] failure
|
33
68
|
# @yieldreturn [Result]
|
69
|
+
#
|
34
70
|
# @return [Result,Logic::Result]
|
71
|
+
#
|
72
|
+
# @api public
|
35
73
|
def try(input, &block)
|
36
|
-
if
|
37
|
-
|
38
|
-
|
74
|
+
if primitive?(input)
|
75
|
+
output = []
|
76
|
+
|
77
|
+
result = input.map { |el| member.try(el) }
|
78
|
+
result.each do |r|
|
79
|
+
output << r.input unless Undefined.equal?(r.input)
|
80
|
+
end
|
39
81
|
|
40
82
|
if result.all?(&:success?)
|
41
83
|
success(output)
|
42
84
|
else
|
43
|
-
|
85
|
+
error = result.find(&:failure?).error
|
86
|
+
failure = failure(output, error)
|
44
87
|
block ? yield(failure) : failure
|
45
88
|
end
|
46
89
|
else
|
@@ -49,9 +92,18 @@ module Dry
|
|
49
92
|
end
|
50
93
|
end
|
51
94
|
|
52
|
-
#
|
95
|
+
# Build a lax type
|
96
|
+
#
|
97
|
+
# @return [Lax]
|
53
98
|
#
|
99
|
+
# @api public
|
100
|
+
def lax
|
101
|
+
Lax.new(Member.new(primitive, { **options, member: member.lax }))
|
102
|
+
end
|
103
|
+
|
54
104
|
# @see Nominal#to_ast
|
105
|
+
#
|
106
|
+
# @api public
|
55
107
|
def to_ast(meta: true)
|
56
108
|
if member.respond_to?(:to_ast)
|
57
109
|
[:array, [member.to_ast(meta: meta), meta ? self.meta : EMPTY_HASH]]
|
data/lib/dry/types/builder.rb
CHANGED
@@ -1,43 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'dry/core/deprecations'
|
2
4
|
|
3
5
|
module Dry
|
4
6
|
module Types
|
7
|
+
# Common API for building types and composition
|
8
|
+
#
|
9
|
+
# @api public
|
5
10
|
module Builder
|
6
11
|
include Dry::Core::Constants
|
7
12
|
|
8
13
|
# @return [Class]
|
14
|
+
#
|
15
|
+
# @api private
|
9
16
|
def constrained_type
|
10
17
|
Constrained
|
11
18
|
end
|
12
19
|
|
13
20
|
# @return [Class]
|
21
|
+
#
|
22
|
+
# @api private
|
14
23
|
def constructor_type
|
15
24
|
Constructor
|
16
25
|
end
|
17
26
|
|
27
|
+
# Compose two types into a Sum type
|
28
|
+
#
|
18
29
|
# @param [Type] other
|
30
|
+
#
|
19
31
|
# @return [Sum, Sum::Constrained]
|
32
|
+
#
|
33
|
+
# @api private
|
20
34
|
def |(other)
|
21
35
|
klass = constrained? && other.constrained? ? Sum::Constrained : Sum
|
22
36
|
klass.new(self, other)
|
23
37
|
end
|
24
38
|
|
39
|
+
# Turn a type into an optional type
|
40
|
+
#
|
25
41
|
# @return [Sum]
|
42
|
+
#
|
43
|
+
# @api public
|
26
44
|
def optional
|
27
45
|
Types['strict.nil'] | self
|
28
46
|
end
|
29
47
|
|
48
|
+
# Turn a type into a constrained type
|
49
|
+
#
|
30
50
|
# @param [Hash] options constraining rule (see {Types.Rule})
|
51
|
+
#
|
31
52
|
# @return [Constrained]
|
53
|
+
#
|
54
|
+
# @api public
|
32
55
|
def constrained(options)
|
33
56
|
constrained_type.new(self, rule: Types.Rule(options))
|
34
57
|
end
|
35
58
|
|
59
|
+
# Turn a type into a type with a default value
|
60
|
+
#
|
36
61
|
# @param [Object] input
|
37
62
|
# @param [Hash] options
|
38
63
|
# @param [#call,nil] block
|
64
|
+
#
|
39
65
|
# @raise [ConstraintError]
|
66
|
+
#
|
40
67
|
# @return [Default]
|
68
|
+
#
|
69
|
+
# @api public
|
41
70
|
def default(input = Undefined, options = EMPTY_HASH, &block)
|
42
71
|
unless input.frozen? || options[:shared]
|
43
72
|
where = Dry::Core::Deprecations::STACK.()
|
@@ -60,8 +89,13 @@ module Dry
|
|
60
89
|
end
|
61
90
|
end
|
62
91
|
|
92
|
+
# Define an enum on top of the existing type
|
93
|
+
#
|
63
94
|
# @param [Array] values
|
95
|
+
#
|
64
96
|
# @return [Enum]
|
97
|
+
#
|
98
|
+
# @api public
|
65
99
|
def enum(*values)
|
66
100
|
mapping =
|
67
101
|
if values.length == 1 && values[0].is_a?(::Hash)
|
@@ -73,15 +107,25 @@ module Dry
|
|
73
107
|
Enum.new(constrained(included_in: mapping.keys), mapping: mapping)
|
74
108
|
end
|
75
109
|
|
76
|
-
#
|
77
|
-
|
78
|
-
|
110
|
+
# Turn a type into a lax type that will rescue from type-errors and
|
111
|
+
# return the original input
|
112
|
+
#
|
113
|
+
# @return [Lax]
|
114
|
+
#
|
115
|
+
# @api public
|
116
|
+
def lax
|
117
|
+
Lax.new(self)
|
79
118
|
end
|
80
119
|
|
120
|
+
# Define a constructor for the type
|
121
|
+
#
|
81
122
|
# @param [#call,nil] constructor
|
82
123
|
# @param [Hash] options
|
83
124
|
# @param [#call,nil] block
|
125
|
+
#
|
84
126
|
# @return [Constructor]
|
127
|
+
#
|
128
|
+
# @api public
|
85
129
|
def constructor(constructor = nil, **options, &block)
|
86
130
|
constructor_type.new(with(options), fn: constructor || block)
|
87
131
|
end
|
@@ -92,5 +136,5 @@ end
|
|
92
136
|
require 'dry/types/default'
|
93
137
|
require 'dry/types/constrained'
|
94
138
|
require 'dry/types/enum'
|
95
|
-
require 'dry/types/
|
139
|
+
require 'dry/types/lax'
|
96
140
|
require 'dry/types/sum'
|
@@ -1,5 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Dry
|
2
4
|
module Types
|
5
|
+
# Common API for building type objects in a convenient way
|
6
|
+
#
|
7
|
+
# rubocop:disable Naming/MethodName
|
8
|
+
#
|
9
|
+
# @api public
|
3
10
|
module BuilderMethods
|
4
11
|
# @api private
|
5
12
|
def included(base)
|
@@ -8,7 +15,8 @@ module Dry
|
|
8
15
|
end
|
9
16
|
|
10
17
|
# Build an array type.
|
11
|
-
#
|
18
|
+
#
|
19
|
+
# Shortcut for Array#of.
|
12
20
|
#
|
13
21
|
# @example
|
14
22
|
# Types::Strings = Types.Array(Types::String)
|
@@ -25,7 +33,6 @@ module Dry
|
|
25
33
|
# @param [Hash{Symbol => Dry::Types::Type}] type_map
|
26
34
|
#
|
27
35
|
# @return [Dry::Types::Array]
|
28
|
-
# @api public
|
29
36
|
def Hash(type_map)
|
30
37
|
self::Hash.schema(type_map)
|
31
38
|
end
|
@@ -41,7 +48,6 @@ module Dry
|
|
41
48
|
# @param [Class,Module] klass Class or module
|
42
49
|
#
|
43
50
|
# @return [Dry::Types::Type]
|
44
|
-
# @api public
|
45
51
|
def Instance(klass)
|
46
52
|
Nominal.new(klass).constrained(type: klass)
|
47
53
|
end
|
@@ -53,7 +59,6 @@ module Dry
|
|
53
59
|
# @param [Object] value
|
54
60
|
#
|
55
61
|
# @return [Dry::Types::Type]
|
56
|
-
# @api public
|
57
62
|
def Value(value)
|
58
63
|
Nominal.new(value.class).constrained(eql: value)
|
59
64
|
end
|
@@ -64,7 +69,6 @@ module Dry
|
|
64
69
|
# @param [Object] object
|
65
70
|
#
|
66
71
|
# @return [Dry::Types::Type]
|
67
|
-
# @api public
|
68
72
|
def Constant(object)
|
69
73
|
Nominal.new(object.class).constrained(is: object)
|
70
74
|
end
|
@@ -77,7 +81,6 @@ module Dry
|
|
77
81
|
# @param [#call,nil] block Value constructor
|
78
82
|
#
|
79
83
|
# @return [Dry::Types::Type]
|
80
|
-
# @api public
|
81
84
|
def Constructor(klass, cons = nil, &block)
|
82
85
|
Nominal.new(klass).constructor(cons || block || klass.method(:new))
|
83
86
|
end
|
@@ -87,7 +90,6 @@ module Dry
|
|
87
90
|
# @param [Class] klass
|
88
91
|
#
|
89
92
|
# @return [Dry::Types::Type]
|
90
|
-
# @api public
|
91
93
|
def Nominal(klass)
|
92
94
|
Nominal.new(klass)
|
93
95
|
end
|
@@ -102,7 +104,6 @@ module Dry
|
|
102
104
|
# @param [Type] value_type Value type
|
103
105
|
#
|
104
106
|
# @return [Dry::Types::Map]
|
105
|
-
# @api public
|
106
107
|
def Map(key_type, value_type)
|
107
108
|
Types['nominal.hash'].map(key_type, value_type)
|
108
109
|
end
|
data/lib/dry/types/coercions.rb
CHANGED
@@ -1,50 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Dry
|
2
4
|
module Types
|
5
|
+
# Common coercion functions used by the built-in `Params` and `JSON` types
|
6
|
+
#
|
7
|
+
# @api public
|
3
8
|
module Coercions
|
4
9
|
include Dry::Core::Constants
|
5
10
|
|
6
11
|
# @param [String, Object] input
|
7
|
-
#
|
8
|
-
# @return [
|
9
|
-
|
10
|
-
|
12
|
+
#
|
13
|
+
# @return [nil] if the input is an empty string or nil
|
14
|
+
#
|
15
|
+
# @raise CoercionError
|
16
|
+
#
|
17
|
+
# @api public
|
18
|
+
def to_nil(input, &_block)
|
19
|
+
if input.nil? || empty_str?(input)
|
20
|
+
nil
|
21
|
+
elsif block_given?
|
22
|
+
yield
|
23
|
+
else
|
24
|
+
raise CoercionError, "#{input.inspect} is not nil"
|
25
|
+
end
|
11
26
|
end
|
12
27
|
|
13
28
|
# @param [#to_str, Object] input
|
29
|
+
#
|
14
30
|
# @return [Date, Object]
|
31
|
+
#
|
15
32
|
# @see Date.parse
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
33
|
+
#
|
34
|
+
# @api public
|
35
|
+
def to_date(input, &block)
|
36
|
+
if input.respond_to?(:to_str)
|
37
|
+
begin
|
38
|
+
::Date.parse(input)
|
39
|
+
rescue ArgumentError, RangeError => error
|
40
|
+
CoercionError.handle(error, &block)
|
41
|
+
end
|
42
|
+
elsif block_given?
|
43
|
+
yield
|
44
|
+
else
|
45
|
+
raise CoercionError, "#{input.inspect} is not a string"
|
46
|
+
end
|
21
47
|
end
|
22
48
|
|
23
49
|
# @param [#to_str, Object] input
|
50
|
+
#
|
24
51
|
# @return [DateTime, Object]
|
52
|
+
#
|
25
53
|
# @see DateTime.parse
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
54
|
+
#
|
55
|
+
# @api public
|
56
|
+
def to_date_time(input, &block)
|
57
|
+
if input.respond_to?(:to_str)
|
58
|
+
begin
|
59
|
+
::DateTime.parse(input)
|
60
|
+
rescue ArgumentError => error
|
61
|
+
CoercionError.handle(error, &block)
|
62
|
+
end
|
63
|
+
elsif block_given?
|
64
|
+
yield
|
65
|
+
else
|
66
|
+
raise CoercionError, "#{input.inspect} is not a string"
|
67
|
+
end
|
31
68
|
end
|
32
69
|
|
33
70
|
# @param [#to_str, Object] input
|
71
|
+
#
|
34
72
|
# @return [Time, Object]
|
73
|
+
#
|
35
74
|
# @see Time.parse
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
75
|
+
#
|
76
|
+
# @api public
|
77
|
+
def to_time(input, &block)
|
78
|
+
if input.respond_to?(:to_str)
|
79
|
+
begin
|
80
|
+
::Time.parse(input)
|
81
|
+
rescue ArgumentError => error
|
82
|
+
CoercionError.handle(error, &block)
|
83
|
+
end
|
84
|
+
elsif block_given?
|
85
|
+
yield
|
86
|
+
else
|
87
|
+
raise CoercionError, "#{input.inspect} is not a string"
|
88
|
+
end
|
41
89
|
end
|
42
90
|
|
43
91
|
private
|
44
92
|
|
45
93
|
# Checks whether String is empty
|
94
|
+
#
|
46
95
|
# @param [String, Object] value
|
96
|
+
#
|
47
97
|
# @return [Boolean]
|
98
|
+
#
|
99
|
+
# @api private
|
48
100
|
def empty_str?(value)
|
49
101
|
EMPTY_STRING.eql?(value)
|
50
102
|
end
|