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/enum.rb
CHANGED
@@ -11,6 +11,7 @@ module Dry
|
|
11
11
|
include Type
|
12
12
|
include Dry::Equalizer(:type, :mapping, inspect: false)
|
13
13
|
include Decorator
|
14
|
+
include Builder
|
14
15
|
|
15
16
|
# @return [Array]
|
16
17
|
attr_reader :values
|
@@ -26,7 +27,7 @@ module Dry
|
|
26
27
|
# @option options [Array] :values
|
27
28
|
#
|
28
29
|
# @api private
|
29
|
-
def initialize(type, options)
|
30
|
+
def initialize(type, **options)
|
30
31
|
super
|
31
32
|
@mapping = options.fetch(:mapping).freeze
|
32
33
|
@values = @mapping.keys.freeze
|
data/lib/dry/types/extensions.rb
CHANGED
@@ -79,7 +79,7 @@ module Dry
|
|
79
79
|
# @api public
|
80
80
|
def default(value)
|
81
81
|
if value.nil?
|
82
|
-
raise ArgumentError,
|
82
|
+
raise ArgumentError, 'nil cannot be used as a default of a maybe type'
|
83
83
|
else
|
84
84
|
super
|
85
85
|
end
|
@@ -97,6 +97,14 @@ module Dry
|
|
97
97
|
end
|
98
98
|
end
|
99
99
|
|
100
|
+
# @api private
|
101
|
+
class Schema::Key
|
102
|
+
# @api private
|
103
|
+
def maybe
|
104
|
+
__new__(type.maybe)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
100
108
|
# @api private
|
101
109
|
class Printer
|
102
110
|
MAPPING[Maybe] = :visit_maybe
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dry/monads/result'
|
4
|
+
|
5
|
+
module Dry
|
6
|
+
module Types
|
7
|
+
# Monad extension for Result
|
8
|
+
#
|
9
|
+
# @api public
|
10
|
+
class Result
|
11
|
+
include Dry::Monads::Result::Mixin
|
12
|
+
|
13
|
+
# Turn result into a monad
|
14
|
+
#
|
15
|
+
# This makes result objects work with dry-monads (or anything with a compatible interface)
|
16
|
+
#
|
17
|
+
# @return [Dry::Monads::Success,Dry::Monads::Failure]
|
18
|
+
#
|
19
|
+
# @api public
|
20
|
+
def to_monad
|
21
|
+
if success?
|
22
|
+
Success(input)
|
23
|
+
else
|
24
|
+
Failure([error, input])
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/dry/types/hash.rb
CHANGED
@@ -50,8 +50,8 @@ module Dry
|
|
50
50
|
|
51
51
|
# @api private
|
52
52
|
def weak(*)
|
53
|
-
raise
|
54
|
-
|
53
|
+
raise 'Support for old hash schemas was removed, please refer to the CHANGELOG '\
|
54
|
+
'on how to proceed with the new API https://github.com/dry-rb/dry-types/blob/master/CHANGELOG.md'
|
55
55
|
end
|
56
56
|
alias_method :permissive, :weak
|
57
57
|
alias_method :strict, :weak
|
@@ -69,9 +69,7 @@ module Dry
|
|
69
69
|
def with_type_transform(proc = nil, &block)
|
70
70
|
fn = proc || block
|
71
71
|
|
72
|
-
if fn.nil?
|
73
|
-
raise ArgumentError, "a block or callable argument is required"
|
74
|
-
end
|
72
|
+
raise ArgumentError, 'a block or callable argument is required' if fn.nil?
|
75
73
|
|
76
74
|
handle = Dry::Types::FnContainer.register(fn)
|
77
75
|
with(type_transform_fn: handle)
|
@@ -97,11 +95,11 @@ module Dry
|
|
97
95
|
#
|
98
96
|
# @api public
|
99
97
|
def to_ast(meta: true)
|
100
|
-
if RUBY_VERSION >=
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
98
|
+
opts = if RUBY_VERSION >= '2.5'
|
99
|
+
options.slice(:type_transform_fn)
|
100
|
+
else
|
101
|
+
options.select { |k, _| k == :type_transform_fn }
|
102
|
+
end
|
105
103
|
|
106
104
|
[:hash, [opts, meta ? self.meta : EMPTY_HASH]]
|
107
105
|
end
|
@@ -115,7 +113,7 @@ module Dry
|
|
115
113
|
|
116
114
|
type_map.map do |map_key, type|
|
117
115
|
name, options = key_name(map_key)
|
118
|
-
key = Schema::Key.new(resolve_type(type), name, options)
|
116
|
+
key = Schema::Key.new(resolve_type(type), name, **options)
|
119
117
|
type_transform.(key)
|
120
118
|
end
|
121
119
|
end
|
@@ -123,7 +121,8 @@ module Dry
|
|
123
121
|
# @api private
|
124
122
|
def resolve_type(type)
|
125
123
|
case type
|
126
|
-
when
|
124
|
+
when Type then type
|
125
|
+
when ::Class, ::String then Types[type]
|
127
126
|
else type
|
128
127
|
end
|
129
128
|
end
|
@@ -21,11 +21,11 @@ module Dry
|
|
21
21
|
type.lax.constructor(fn, meta: meta)
|
22
22
|
end
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
# @api
|
27
|
-
def
|
28
|
-
|
24
|
+
# @see Dry::Types::Array#of
|
25
|
+
#
|
26
|
+
# @api public
|
27
|
+
def schema(*args)
|
28
|
+
type.schema(*args).constructor(fn, meta: meta)
|
29
29
|
end
|
30
30
|
end
|
31
31
|
end
|
data/lib/dry/types/json.rb
CHANGED
@@ -24,6 +24,10 @@ module Dry
|
|
24
24
|
self['nominal.decimal'].constructor(Coercions::JSON.method(:to_decimal))
|
25
25
|
end
|
26
26
|
|
27
|
+
register('json.symbol') do
|
28
|
+
self['nominal.symbol'].constructor(Coercions::JSON.method(:to_symbol))
|
29
|
+
end
|
30
|
+
|
27
31
|
register('json.array') { self['array'] }
|
28
32
|
|
29
33
|
register('json.hash') { self['hash'] }
|
data/lib/dry/types/lax.rb
CHANGED
@@ -15,7 +15,7 @@ module Dry
|
|
15
15
|
include Printable
|
16
16
|
include Dry::Equalizer(:type, inspect: false)
|
17
17
|
|
18
|
-
|
18
|
+
undef :options, :constructor
|
19
19
|
|
20
20
|
# @param [Object] input
|
21
21
|
#
|
@@ -40,8 +40,8 @@ module Dry
|
|
40
40
|
# @api public
|
41
41
|
def try(input, &block)
|
42
42
|
type.try(input, &block)
|
43
|
-
rescue CoercionError =>
|
44
|
-
result = failure(input,
|
43
|
+
rescue CoercionError => e
|
44
|
+
result = failure(input, e.message)
|
45
45
|
block ? yield(result) : result
|
46
46
|
end
|
47
47
|
|
@@ -67,7 +67,7 @@ module Dry
|
|
67
67
|
#
|
68
68
|
# @api private
|
69
69
|
def decorate?(response)
|
70
|
-
super || response.is_a?(constructor_type)
|
70
|
+
super || response.is_a?(type.constructor_type)
|
71
71
|
end
|
72
72
|
end
|
73
73
|
|
data/lib/dry/types/map.rb
CHANGED
@@ -74,6 +74,7 @@ module Dry
|
|
74
74
|
def try(hash)
|
75
75
|
result = coerce(hash)
|
76
76
|
return result if result.success? || !block_given?
|
77
|
+
|
77
78
|
yield(result)
|
78
79
|
end
|
79
80
|
|
@@ -100,11 +101,14 @@ module Dry
|
|
100
101
|
|
101
102
|
# @api private
|
102
103
|
def coerce(input)
|
103
|
-
|
104
|
-
|
105
|
-
|
104
|
+
unless primitive?(input)
|
105
|
+
return failure(
|
106
|
+
input, CoercionError.new("#{input.inspect} must be an instance of #{primitive}")
|
107
|
+
)
|
108
|
+
end
|
106
109
|
|
107
|
-
output
|
110
|
+
output = {}
|
111
|
+
failures = []
|
108
112
|
|
109
113
|
input.each do |k, v|
|
110
114
|
res_k = key_type.try(k)
|
data/lib/dry/types/meta.rb
CHANGED
data/lib/dry/types/module.rb
CHANGED
@@ -18,10 +18,10 @@ module Dry
|
|
18
18
|
#
|
19
19
|
# @api public
|
20
20
|
class Module < ::Module
|
21
|
-
def initialize(registry, *args)
|
21
|
+
def initialize(registry, *args, **kwargs)
|
22
22
|
@registry = registry
|
23
|
-
check_parameters(*args)
|
24
|
-
constants = type_constants(*args)
|
23
|
+
check_parameters(*args, **kwargs)
|
24
|
+
constants = type_constants(*args, **kwargs)
|
25
25
|
define_constants(constants)
|
26
26
|
extend(BuilderMethods)
|
27
27
|
|
@@ -31,7 +31,7 @@ module Dry
|
|
31
31
|
base.instance_exec(const_get(:Nominal, false)) do |nominal|
|
32
32
|
extend Dry::Core::Deprecations[:'dry-types']
|
33
33
|
const_set(:Definition, nominal)
|
34
|
-
deprecate_constant(:Definition, message:
|
34
|
+
deprecate_constant(:Definition, message: 'Nominal')
|
35
35
|
end
|
36
36
|
end
|
37
37
|
end
|
@@ -93,8 +93,8 @@ module Dry
|
|
93
93
|
|
94
94
|
(referenced.uniq - known).each do |name|
|
95
95
|
raise ArgumentError,
|
96
|
-
"#{
|
97
|
-
"Supported options are #{
|
96
|
+
"#{name.inspect} is not a known type namespace. "\
|
97
|
+
"Supported options are #{known.map(&:inspect).join(', ')}"
|
98
98
|
end
|
99
99
|
end
|
100
100
|
|
data/lib/dry/types/nominal.rb
CHANGED
@@ -126,9 +126,8 @@ module Dry
|
|
126
126
|
#
|
127
127
|
# @api public
|
128
128
|
def failure(input, error)
|
129
|
-
unless error.is_a?(CoercionError)
|
130
|
-
|
131
|
-
end
|
129
|
+
raise ArgumentError, 'error must be a CoercionError' unless error.is_a?(CoercionError)
|
130
|
+
|
132
131
|
Result::Failure.new(input, error)
|
133
132
|
end
|
134
133
|
|
@@ -202,7 +201,7 @@ module Dry
|
|
202
201
|
|
203
202
|
extend Dry::Core::Deprecations[:'dry-types']
|
204
203
|
Definition = Nominal
|
205
|
-
deprecate_constant(:Definition, message:
|
204
|
+
deprecate_constant(:Definition, message: 'Nominal')
|
206
205
|
end
|
207
206
|
end
|
208
207
|
|
data/lib/dry/types/params.rb
CHANGED
@@ -51,5 +51,14 @@ module Dry
|
|
51
51
|
register('params.hash') do
|
52
52
|
self['nominal.hash'].constructor(Coercions::Params.method(:to_hash))
|
53
53
|
end
|
54
|
+
|
55
|
+
register('params.symbol') do
|
56
|
+
self['nominal.symbol'].constructor(Coercions::Params.method(:to_symbol))
|
57
|
+
end
|
58
|
+
|
59
|
+
COERCIBLE.each_key do |name|
|
60
|
+
next if name.equal?(:string)
|
61
|
+
register("optional.params.#{name}", self['params.nil'] | self["params.#{name}"])
|
62
|
+
end
|
54
63
|
end
|
55
64
|
end
|
@@ -0,0 +1,197 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dry/core/cache'
|
4
|
+
require 'dry/types/predicate_registry'
|
5
|
+
|
6
|
+
module Dry
|
7
|
+
module Types
|
8
|
+
# PredicateInferrer returns the list of predicates used by a type.
|
9
|
+
#
|
10
|
+
# @api public
|
11
|
+
class PredicateInferrer
|
12
|
+
extend Core::Cache
|
13
|
+
|
14
|
+
TYPE_TO_PREDICATE = {
|
15
|
+
DateTime => :date_time?,
|
16
|
+
FalseClass => :false?,
|
17
|
+
Integer => :int?,
|
18
|
+
NilClass => :nil?,
|
19
|
+
String => :str?,
|
20
|
+
TrueClass => :true?,
|
21
|
+
BigDecimal => :decimal?
|
22
|
+
}.freeze
|
23
|
+
|
24
|
+
REDUCED_TYPES = {
|
25
|
+
[[[:true?], [:false?]]] => %i[bool?]
|
26
|
+
}.freeze
|
27
|
+
|
28
|
+
HASH = %i[hash?].freeze
|
29
|
+
|
30
|
+
ARRAY = %i[array?].freeze
|
31
|
+
|
32
|
+
NIL = %i[nil?].freeze
|
33
|
+
|
34
|
+
# Compiler reduces type AST into a list of predicates
|
35
|
+
#
|
36
|
+
# @api private
|
37
|
+
class Compiler
|
38
|
+
# @return [PredicateRegistry]
|
39
|
+
# @api private
|
40
|
+
attr_reader :registry
|
41
|
+
|
42
|
+
# @api private
|
43
|
+
def initialize(registry)
|
44
|
+
@registry = registry
|
45
|
+
end
|
46
|
+
|
47
|
+
# @api private
|
48
|
+
def infer_predicate(type)
|
49
|
+
[TYPE_TO_PREDICATE.fetch(type) { :"#{type.name.split('::').last.downcase}?" }]
|
50
|
+
end
|
51
|
+
|
52
|
+
# @api private
|
53
|
+
def visit(node)
|
54
|
+
meth, rest = node
|
55
|
+
public_send(:"visit_#{meth}", rest)
|
56
|
+
end
|
57
|
+
|
58
|
+
# @api private
|
59
|
+
def visit_nominal(node)
|
60
|
+
type = node[0]
|
61
|
+
predicate = infer_predicate(type)
|
62
|
+
|
63
|
+
if registry.key?(predicate[0])
|
64
|
+
predicate
|
65
|
+
else
|
66
|
+
[type?: type]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# @api private
|
71
|
+
def visit_hash(_)
|
72
|
+
HASH
|
73
|
+
end
|
74
|
+
|
75
|
+
# @api private
|
76
|
+
def visit_array(_)
|
77
|
+
ARRAY
|
78
|
+
end
|
79
|
+
|
80
|
+
# @api private
|
81
|
+
def visit_lax(node)
|
82
|
+
visit(node)
|
83
|
+
end
|
84
|
+
|
85
|
+
# @api private
|
86
|
+
def visit_constructor(node)
|
87
|
+
other, * = node
|
88
|
+
visit(other)
|
89
|
+
end
|
90
|
+
|
91
|
+
# @api private
|
92
|
+
def visit_enum(node)
|
93
|
+
other, * = node
|
94
|
+
visit(other)
|
95
|
+
end
|
96
|
+
|
97
|
+
# @api private
|
98
|
+
def visit_sum(node)
|
99
|
+
left_node, right_node, = node
|
100
|
+
left = visit(left_node)
|
101
|
+
right = visit(right_node)
|
102
|
+
|
103
|
+
if left.eql?(NIL)
|
104
|
+
right
|
105
|
+
else
|
106
|
+
[[left, right]]
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# @api private
|
111
|
+
def visit_constrained(node)
|
112
|
+
other, rules = node
|
113
|
+
predicates = visit(rules)
|
114
|
+
|
115
|
+
if predicates.empty?
|
116
|
+
visit(other)
|
117
|
+
else
|
118
|
+
[*visit(other), *merge_predicates(predicates)]
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# @api private
|
123
|
+
def visit_any(_)
|
124
|
+
EMPTY_ARRAY
|
125
|
+
end
|
126
|
+
|
127
|
+
# @api private
|
128
|
+
def visit_and(node)
|
129
|
+
left, right = node
|
130
|
+
visit(left) + visit(right)
|
131
|
+
end
|
132
|
+
|
133
|
+
# @api private
|
134
|
+
def visit_predicate(node)
|
135
|
+
pred, args = node
|
136
|
+
|
137
|
+
if pred.equal?(:type?)
|
138
|
+
EMPTY_ARRAY
|
139
|
+
elsif registry.key?(pred)
|
140
|
+
*curried, _ = args
|
141
|
+
values = curried.map { |_, v| v }
|
142
|
+
|
143
|
+
if values.empty?
|
144
|
+
[pred]
|
145
|
+
else
|
146
|
+
[pred => values[0]]
|
147
|
+
end
|
148
|
+
else
|
149
|
+
EMPTY_ARRAY
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
private
|
154
|
+
|
155
|
+
# @api private
|
156
|
+
def merge_predicates(nodes)
|
157
|
+
preds, merged = nodes.each_with_object([[], {}]) do |predicate, (ps, h)|
|
158
|
+
if predicate.is_a?(::Hash)
|
159
|
+
h.update(predicate)
|
160
|
+
else
|
161
|
+
ps << predicate
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
merged.empty? ? preds : [*preds, merged]
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# @return [Compiler]
|
170
|
+
# @api private
|
171
|
+
attr_reader :compiler
|
172
|
+
|
173
|
+
# @api private
|
174
|
+
def initialize(registry = PredicateRegistry.new)
|
175
|
+
@compiler = Compiler.new(registry)
|
176
|
+
end
|
177
|
+
|
178
|
+
# Infer predicate identifier from the provided type
|
179
|
+
#
|
180
|
+
# @param [Type] type
|
181
|
+
# @return [Symbol]
|
182
|
+
#
|
183
|
+
# @api private
|
184
|
+
def [](type)
|
185
|
+
self.class.fetch_or_store(type) do
|
186
|
+
predicates = compiler.visit(type.to_ast)
|
187
|
+
|
188
|
+
if predicates.is_a?(::Hash)
|
189
|
+
predicates
|
190
|
+
else
|
191
|
+
REDUCED_TYPES[predicates] || predicates
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|