dry-types 0.15.0 → 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/.gitignore +1 -0
- data/.rubocop.yml +18 -2
- data/.travis.yml +10 -5
- data/.yardopts +6 -2
- data/CHANGELOG.md +186 -3
- data/Gemfile +11 -5
- data/README.md +4 -3
- data/Rakefile +4 -2
- data/benchmarks/hash_schemas.rb +10 -6
- data/benchmarks/lax_schema.rb +15 -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/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 +21 -19
- data/lib/dry-types.rb +2 -0
- data/lib/dry/types.rb +60 -17
- data/lib/dry/types/any.rb +21 -10
- data/lib/dry/types/array.rb +17 -1
- data/lib/dry/types/array/constructor.rb +32 -0
- data/lib/dry/types/array/member.rb +72 -13
- data/lib/dry/types/builder.rb +49 -5
- data/lib/dry/types/builder_methods.rb +43 -16
- data/lib/dry/types/coercions.rb +84 -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 +78 -27
- data/lib/dry/types/constrained/coercible.rb +36 -6
- data/lib/dry/types/constraints.rb +15 -1
- data/lib/dry/types/constructor.rb +77 -62
- data/lib/dry/types/constructor/function.rb +200 -0
- data/lib/dry/types/container.rb +5 -0
- data/lib/dry/types/core.rb +35 -14
- data/lib/dry/types/decorator.rb +37 -10
- data/lib/dry/types/default.rb +48 -16
- data/lib/dry/types/enum.rb +31 -16
- data/lib/dry/types/errors.rb +73 -7
- data/lib/dry/types/extensions.rb +6 -0
- data/lib/dry/types/extensions/maybe.rb +52 -5
- data/lib/dry/types/extensions/monads.rb +29 -0
- data/lib/dry/types/fn_container.rb +5 -0
- data/lib/dry/types/hash.rb +32 -14
- data/lib/dry/types/hash/constructor.rb +16 -3
- data/lib/dry/types/inflector.rb +2 -0
- data/lib/dry/types/json.rb +7 -5
- data/lib/dry/types/{safe.rb → lax.rb} +33 -16
- data/lib/dry/types/map.rb +70 -32
- data/lib/dry/types/meta.rb +51 -0
- data/lib/dry/types/module.rb +10 -5
- data/lib/dry/types/nominal.rb +105 -14
- data/lib/dry/types/options.rb +12 -25
- data/lib/dry/types/params.rb +14 -3
- 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/printable.rb +5 -1
- data/lib/dry/types/printer.rb +70 -64
- data/lib/dry/types/result.rb +26 -0
- data/lib/dry/types/schema.rb +177 -80
- data/lib/dry/types/schema/key.rb +48 -35
- data/lib/dry/types/spec/types.rb +43 -6
- 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 +91 -62
data/lib/dry/types/schema/key.rb
CHANGED
@@ -1,7 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'dry/equalizer'
|
4
|
+
require 'dry/core/deprecations'
|
2
5
|
|
3
6
|
module Dry
|
4
7
|
module Types
|
8
|
+
# Schema is a hash with explicit member types defined
|
9
|
+
#
|
10
|
+
# @api public
|
5
11
|
class Schema < Hash
|
6
12
|
# Proxy type for schema keys. Contains only key name and
|
7
13
|
# whether it's required or not. All other calls deletaged
|
@@ -9,6 +15,7 @@ module Dry
|
|
9
15
|
#
|
10
16
|
# @see Dry::Types::Schema
|
11
17
|
class Key
|
18
|
+
extend ::Dry::Core::Deprecations[:'dry-types']
|
12
19
|
include Type
|
13
20
|
include Dry::Equalizer(:name, :type, :options, inspect: false)
|
14
21
|
include Decorator
|
@@ -28,12 +35,19 @@ module Dry
|
|
28
35
|
@name = name
|
29
36
|
end
|
30
37
|
|
31
|
-
# @
|
32
|
-
def
|
33
|
-
type.(input, &block)
|
38
|
+
# @api private
|
39
|
+
def call_safe(input, &block)
|
40
|
+
type.call_safe(input, &block)
|
41
|
+
end
|
42
|
+
|
43
|
+
# @api private
|
44
|
+
def call_unsafe(input)
|
45
|
+
type.call_unsafe(input)
|
34
46
|
end
|
35
47
|
|
36
48
|
# @see Dry::Types::Nominal#try
|
49
|
+
#
|
50
|
+
# @api public
|
37
51
|
def try(input, &block)
|
38
52
|
type.try(input, &block)
|
39
53
|
end
|
@@ -41,6 +55,8 @@ module Dry
|
|
41
55
|
# Whether the key is required in schema input
|
42
56
|
#
|
43
57
|
# @return [Boolean]
|
58
|
+
#
|
59
|
+
# @api public
|
44
60
|
def required?
|
45
61
|
options.fetch(:required)
|
46
62
|
end
|
@@ -55,6 +71,8 @@ module Dry
|
|
55
71
|
#
|
56
72
|
# @param [Boolean] required New value
|
57
73
|
# @return [Dry::Types::Schema::Key]
|
74
|
+
#
|
75
|
+
# @api public
|
58
76
|
def required(required = Undefined)
|
59
77
|
if Undefined.equal?(required)
|
60
78
|
options.fetch(:required)
|
@@ -66,36 +84,26 @@ module Dry
|
|
66
84
|
# Make key not required
|
67
85
|
#
|
68
86
|
# @return [Dry::Types::Schema::Key]
|
87
|
+
#
|
88
|
+
# @api public
|
69
89
|
def omittable
|
70
90
|
required(false)
|
71
91
|
end
|
72
92
|
|
73
|
-
#
|
74
|
-
# evaluated/applied when key is absent in schema
|
75
|
-
# input.
|
93
|
+
# Turn key into a lax type. Lax types are not strict hence such keys are not required
|
76
94
|
#
|
77
|
-
# @
|
78
|
-
#
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
# Replace the underlying type
|
84
|
-
# @param [Dry::Types::Type] type
|
85
|
-
# @return [Dry::Types::Schema::Key]
|
86
|
-
def new(type)
|
87
|
-
self.class.new(type, name, options)
|
88
|
-
end
|
89
|
-
|
90
|
-
# @see Dry::Types::Safe
|
91
|
-
# @return [Dry::Types::Schema::Key]
|
92
|
-
def safe
|
93
|
-
new(type.safe)
|
95
|
+
# @return [Lax]
|
96
|
+
#
|
97
|
+
# @api public
|
98
|
+
def lax
|
99
|
+
__new__(type.lax).required(false)
|
94
100
|
end
|
95
101
|
|
96
102
|
# Dump to internal AST representation
|
97
103
|
#
|
98
104
|
# @return [Array]
|
105
|
+
#
|
106
|
+
# @api public
|
99
107
|
def to_ast(meta: true)
|
100
108
|
[
|
101
109
|
:key,
|
@@ -107,23 +115,28 @@ module Dry
|
|
107
115
|
]
|
108
116
|
end
|
109
117
|
|
110
|
-
#
|
111
|
-
# its out meta, it delegates these calls to the underlying
|
112
|
-
# type.
|
118
|
+
# @see Dry::Types::Meta#meta
|
113
119
|
#
|
114
|
-
# @
|
115
|
-
# @return [Hash] metadata associated with type
|
116
|
-
#
|
117
|
-
# @overload meta(data)
|
118
|
-
# @param [Hash] new metadata to merge into existing metadata
|
119
|
-
# @return [Type] new type with added metadata
|
120
|
+
# @api public
|
120
121
|
def meta(data = nil)
|
121
|
-
if data.nil?
|
122
|
-
|
122
|
+
if data.nil? || !data.key?(:omittable)
|
123
|
+
super
|
123
124
|
else
|
124
|
-
|
125
|
+
self.class.warn(
|
126
|
+
'Using meta for making schema keys is deprecated, ' \
|
127
|
+
'please use .omittable or .required(false) instead' \
|
128
|
+
"\n" + Core::Deprecations::STACK.()
|
129
|
+
)
|
130
|
+
super.required(!data[:omittable])
|
125
131
|
end
|
126
132
|
end
|
133
|
+
|
134
|
+
private
|
135
|
+
|
136
|
+
# @api private
|
137
|
+
def decorate?(response)
|
138
|
+
response.is_a?(Type)
|
139
|
+
end
|
127
140
|
end
|
128
141
|
end
|
129
142
|
end
|
data/lib/dry/types/spec/types.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
RSpec.shared_examples_for 'Dry::Types::Nominal without primitive' do
|
2
4
|
def be_boolean
|
3
|
-
satisfy { |x| x == true || x == false
|
5
|
+
satisfy { |x| x == true || x == false }
|
4
6
|
end
|
5
7
|
|
6
8
|
describe '#constrained?' do
|
@@ -41,9 +43,15 @@ RSpec.shared_examples_for 'Dry::Types::Nominal without primitive' do
|
|
41
43
|
|
42
44
|
describe '#to_s' do
|
43
45
|
it 'returns a custom string representation' do
|
44
|
-
if type.class.name.start_with?('Dry::Types')
|
45
|
-
|
46
|
-
|
46
|
+
expect(type.to_s).to start_with('#<Dry::Types') if type.class.name.start_with?('Dry::Types')
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe '#to_proc' do
|
51
|
+
subject(:callable) { type.to_proc }
|
52
|
+
|
53
|
+
it 'converts a type to a proc' do
|
54
|
+
expect(callable).to be_a(Proc)
|
47
55
|
end
|
48
56
|
end
|
49
57
|
end
|
@@ -69,9 +77,9 @@ RSpec.shared_examples_for 'Dry::Types::Nominal#meta' do
|
|
69
77
|
expect(type.meta).to be_a ::Hash
|
70
78
|
expect(type.meta).to be_frozen
|
71
79
|
expect(type.meta).not_to have_key :immutable_test
|
72
|
-
derived = type.
|
80
|
+
derived = type.meta(immutable_test: 1)
|
73
81
|
expect(derived.meta).to be_frozen
|
74
|
-
expect(derived.meta).to eql(
|
82
|
+
expect(derived.meta).to eql(immutable_test: 1)
|
75
83
|
expect(type.meta).not_to have_key :immutable_test
|
76
84
|
end
|
77
85
|
end
|
@@ -100,3 +108,32 @@ RSpec.shared_examples_for Dry::Types::Nominal do
|
|
100
108
|
end
|
101
109
|
end
|
102
110
|
end
|
111
|
+
|
112
|
+
RSpec.shared_examples_for 'a constrained type' do |inputs: Object.new|
|
113
|
+
let(:fallback) { Object.new }
|
114
|
+
|
115
|
+
describe '#call' do
|
116
|
+
it 'yields a block on failure' do
|
117
|
+
Array(inputs).each do |input|
|
118
|
+
expect(type.(input) { fallback }).to be(fallback)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'throws an error on invalid input' do
|
123
|
+
Array(inputs).each do |input|
|
124
|
+
expect { type.(input) }.to raise_error(Dry::Types::CoercionError)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
RSpec.shared_examples_for 'a nominal type' do |inputs: Object.new|
|
131
|
+
describe '#call' do
|
132
|
+
it 'always returns the input back' do
|
133
|
+
Array(inputs).each do |input|
|
134
|
+
expect(type.(input) { fail }).to be(input)
|
135
|
+
expect(type.(input)).to be(input)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
data/lib/dry/types/sum.rb
CHANGED
@@ -1,11 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'dry/types/options'
|
4
|
+
require 'dry/types/meta'
|
2
5
|
|
3
6
|
module Dry
|
4
7
|
module Types
|
8
|
+
# Sum type
|
9
|
+
#
|
10
|
+
# @api public
|
5
11
|
class Sum
|
6
12
|
include Type
|
7
13
|
include Builder
|
8
14
|
include Options
|
15
|
+
include Meta
|
9
16
|
include Printable
|
10
17
|
include Dry::Equalizer(:left, :right, :options, :meta, inspect: false)
|
11
18
|
|
@@ -15,6 +22,7 @@ module Dry
|
|
15
22
|
# @return [Type]
|
16
23
|
attr_reader :right
|
17
24
|
|
25
|
+
# @api private
|
18
26
|
class Constrained < Sum
|
19
27
|
# @return [Dry::Logic::Operations::Or]
|
20
28
|
def rule
|
@@ -25,21 +33,13 @@ module Dry
|
|
25
33
|
def constrained?
|
26
34
|
true
|
27
35
|
end
|
28
|
-
|
29
|
-
# @param [Object] input
|
30
|
-
# @return [Object]
|
31
|
-
# @raise [ConstraintError] if given +input+ not passing {#try}
|
32
|
-
def call(input)
|
33
|
-
try(input) { |result|
|
34
|
-
raise ConstraintError.new(result, input)
|
35
|
-
}.input
|
36
|
-
end
|
37
|
-
alias_method :[], :call
|
38
36
|
end
|
39
37
|
|
40
38
|
# @param [Type] left
|
41
39
|
# @param [Type] right
|
42
40
|
# @param [Hash] options
|
41
|
+
#
|
42
|
+
# @api private
|
43
43
|
def initialize(left, right, options = {})
|
44
44
|
super
|
45
45
|
@left, @right = left, right
|
@@ -47,33 +47,55 @@ module Dry
|
|
47
47
|
end
|
48
48
|
|
49
49
|
# @return [String]
|
50
|
+
#
|
51
|
+
# @api public
|
50
52
|
def name
|
51
53
|
[left, right].map(&:name).join(' | ')
|
52
54
|
end
|
53
55
|
|
54
56
|
# @return [false]
|
57
|
+
#
|
58
|
+
# @api public
|
55
59
|
def default?
|
56
60
|
false
|
57
61
|
end
|
58
62
|
|
59
63
|
# @return [false]
|
64
|
+
#
|
65
|
+
# @api public
|
60
66
|
def constrained?
|
61
67
|
false
|
62
68
|
end
|
63
69
|
|
64
70
|
# @return [Boolean]
|
71
|
+
#
|
72
|
+
# @api public
|
65
73
|
def optional?
|
66
74
|
primitive?(nil)
|
67
75
|
end
|
68
76
|
|
69
77
|
# @param [Object] input
|
78
|
+
#
|
70
79
|
# @return [Object]
|
71
|
-
|
72
|
-
|
80
|
+
#
|
81
|
+
# @api private
|
82
|
+
def call_unsafe(input)
|
83
|
+
left.call_safe(input) { right.call_unsafe(input) }
|
73
84
|
end
|
74
|
-
alias_method :[], :call
|
75
85
|
|
76
|
-
|
86
|
+
# @param [Object] input
|
87
|
+
#
|
88
|
+
# @return [Object]
|
89
|
+
#
|
90
|
+
# @api private
|
91
|
+
def call_safe(input, &block)
|
92
|
+
left.call_safe(input) { right.call_safe(input, &block) }
|
93
|
+
end
|
94
|
+
|
95
|
+
# @param [Object] input
|
96
|
+
#
|
97
|
+
# @api public
|
98
|
+
def try(input)
|
77
99
|
left.try(input) do
|
78
100
|
right.try(input) do |failure|
|
79
101
|
if block_given?
|
@@ -85,6 +107,7 @@ module Dry
|
|
85
107
|
end
|
86
108
|
end
|
87
109
|
|
110
|
+
# @api private
|
88
111
|
def success(input)
|
89
112
|
if left.valid?(input)
|
90
113
|
left.success(input)
|
@@ -95,6 +118,7 @@ module Dry
|
|
95
118
|
end
|
96
119
|
end
|
97
120
|
|
121
|
+
# @api private
|
98
122
|
def failure(input, _error = nil)
|
99
123
|
if !left.valid?(input)
|
100
124
|
left.failure(input, left.try(input).error)
|
@@ -104,28 +128,44 @@ module Dry
|
|
104
128
|
end
|
105
129
|
|
106
130
|
# @param [Object] value
|
131
|
+
#
|
107
132
|
# @return [Boolean]
|
133
|
+
#
|
134
|
+
# @api private
|
108
135
|
def primitive?(value)
|
109
136
|
left.primitive?(value) || right.primitive?(value)
|
110
137
|
end
|
111
138
|
|
112
|
-
#
|
113
|
-
#
|
114
|
-
|
115
|
-
|
139
|
+
# Manage metadata to the type. If the type is an optional, #meta delegates
|
140
|
+
# to the right branch
|
141
|
+
#
|
142
|
+
# @see [Meta#meta]
|
143
|
+
#
|
144
|
+
# @api public
|
145
|
+
def meta(data = nil)
|
146
|
+
if data.nil?
|
147
|
+
optional? ? right.meta : super
|
148
|
+
elsif optional?
|
149
|
+
self.class.new(left, right.meta(data), options)
|
150
|
+
else
|
151
|
+
super
|
152
|
+
end
|
116
153
|
end
|
117
|
-
alias_method :===, :valid?
|
118
154
|
|
119
|
-
# @api public
|
120
|
-
#
|
121
155
|
# @see Nominal#to_ast
|
156
|
+
#
|
157
|
+
# @api public
|
122
158
|
def to_ast(meta: true)
|
123
159
|
[:sum, [left.to_ast(meta: meta), right.to_ast(meta: meta), meta ? self.meta : EMPTY_HASH]]
|
124
160
|
end
|
125
161
|
|
126
162
|
# @param [Hash] options
|
163
|
+
#
|
127
164
|
# @return [Constrained,Sum]
|
165
|
+
#
|
128
166
|
# @see Builder#constrained
|
167
|
+
#
|
168
|
+
# @api public
|
129
169
|
def constrained(options)
|
130
170
|
if optional?
|
131
171
|
right.constrained(options).optional
|
@@ -133,6 +173,15 @@ module Dry
|
|
133
173
|
super
|
134
174
|
end
|
135
175
|
end
|
176
|
+
|
177
|
+
# Wrap the type with a proc
|
178
|
+
#
|
179
|
+
# @return [Proc]
|
180
|
+
#
|
181
|
+
# @api public
|
182
|
+
def to_proc
|
183
|
+
proc { |value| self.(value) }
|
184
|
+
end
|
136
185
|
end
|
137
186
|
end
|
138
187
|
end
|
data/lib/dry/types/type.rb
CHANGED
@@ -1,6 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dry/core/deprecations'
|
4
|
+
|
1
5
|
module Dry
|
2
6
|
module Types
|
7
|
+
# Common Type module denoting an object is a Type
|
8
|
+
#
|
9
|
+
# @api public
|
3
10
|
module Type
|
11
|
+
extend ::Dry::Core::Deprecations[:'dry-types']
|
12
|
+
|
13
|
+
deprecate(:safe, :lax)
|
14
|
+
|
15
|
+
# Whether a value is a valid member of the type
|
16
|
+
#
|
17
|
+
# @return [Boolean]
|
18
|
+
#
|
19
|
+
# @api private
|
20
|
+
def valid?(input = Undefined)
|
21
|
+
call_safe(input) { return false }
|
22
|
+
true
|
23
|
+
end
|
24
|
+
# Anything can be coerced matches
|
25
|
+
alias_method :===, :valid?
|
26
|
+
|
27
|
+
# Apply type to a value
|
28
|
+
#
|
29
|
+
# @overload call(input = Undefined)
|
30
|
+
# Possibly unsafe coercion attempt. If a value doesn't
|
31
|
+
# match the type, an exception will be raised.
|
32
|
+
#
|
33
|
+
# @param [Object] input
|
34
|
+
# @return [Object]
|
35
|
+
#
|
36
|
+
# @overload call(input = Undefined)
|
37
|
+
# When a block is passed, {#call} will never throw an exception on
|
38
|
+
# failed coercion, instead it will call the block.
|
39
|
+
#
|
40
|
+
# @param [Object] input
|
41
|
+
# @yieldparam [Object] output Partially coerced value
|
42
|
+
# @return [Object]
|
43
|
+
#
|
44
|
+
# @api public
|
45
|
+
def call(input = Undefined, &block)
|
46
|
+
if block_given?
|
47
|
+
call_safe(input, &block)
|
48
|
+
else
|
49
|
+
call_unsafe(input)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
alias_method :[], :call
|
4
53
|
end
|
5
54
|
end
|
6
55
|
end
|