dry-types 0.13.2 → 1.5.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/CHANGELOG.md +763 -233
- data/LICENSE +17 -17
- data/README.md +15 -13
- data/dry-types.gemspec +28 -28
- data/lib/dry-types.rb +3 -1
- data/lib/dry/types.rb +156 -76
- data/lib/dry/types/any.rb +32 -12
- data/lib/dry/types/array.rb +19 -6
- data/lib/dry/types/array/constructor.rb +32 -0
- data/lib/dry/types/array/member.rb +75 -16
- data/lib/dry/types/builder.rb +131 -15
- data/lib/dry/types/builder_methods.rb +49 -20
- data/lib/dry/types/coercions.rb +76 -22
- data/lib/dry/types/coercions/json.rb +43 -7
- data/lib/dry/types/coercions/params.rb +118 -31
- data/lib/dry/types/compat.rb +0 -2
- data/lib/dry/types/compiler.rb +56 -41
- data/lib/dry/types/constrained.rb +81 -32
- data/lib/dry/types/constrained/coercible.rb +36 -6
- data/lib/dry/types/constraints.rb +18 -4
- data/lib/dry/types/constructor.rb +127 -54
- data/lib/dry/types/constructor/function.rb +216 -0
- data/lib/dry/types/constructor/wrapper.rb +94 -0
- data/lib/dry/types/container.rb +7 -0
- data/lib/dry/types/core.rb +54 -21
- data/lib/dry/types/decorator.rb +38 -17
- data/lib/dry/types/default.rb +61 -16
- data/lib/dry/types/enum.rb +43 -20
- data/lib/dry/types/errors.rb +75 -9
- data/lib/dry/types/extensions.rb +7 -1
- data/lib/dry/types/extensions/maybe.rb +74 -16
- data/lib/dry/types/extensions/monads.rb +29 -0
- data/lib/dry/types/fn_container.rb +6 -1
- data/lib/dry/types/hash.rb +86 -67
- data/lib/dry/types/hash/constructor.rb +33 -0
- data/lib/dry/types/inflector.rb +3 -1
- data/lib/dry/types/json.rb +18 -16
- data/lib/dry/types/lax.rb +75 -0
- data/lib/dry/types/map.rb +76 -33
- data/lib/dry/types/meta.rb +51 -0
- data/lib/dry/types/module.rb +120 -0
- data/lib/dry/types/nominal.rb +210 -0
- data/lib/dry/types/options.rb +13 -26
- data/lib/dry/types/params.rb +39 -25
- data/lib/dry/types/predicate_inferrer.rb +238 -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 +16 -0
- data/lib/dry/types/printer.rb +315 -0
- data/lib/dry/types/result.rb +29 -3
- data/lib/dry/types/schema.rb +408 -0
- data/lib/dry/types/schema/key.rb +156 -0
- data/lib/dry/types/spec/types.rb +103 -33
- data/lib/dry/types/sum.rb +84 -35
- data/lib/dry/types/type.rb +49 -0
- data/lib/dry/types/version.rb +3 -1
- metadata +68 -79
- data/.gitignore +0 -10
- data/.rspec +0 -2
- data/.travis.yml +0 -29
- data/.yardopts +0 -5
- data/CONTRIBUTING.md +0 -29
- data/Gemfile +0 -24
- data/Rakefile +0 -20
- data/benchmarks/hash_schemas.rb +0 -51
- data/lib/dry/types/compat/form_types.rb +0 -27
- data/lib/dry/types/compat/int.rb +0 -14
- data/lib/dry/types/definition.rb +0 -113
- data/lib/dry/types/hash/schema.rb +0 -199
- data/lib/dry/types/hash/schema_builder.rb +0 -75
- data/lib/dry/types/safe.rb +0 -59
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Types
|
5
|
+
# Storage for meta-data
|
6
|
+
#
|
7
|
+
# @api public
|
8
|
+
module Meta
|
9
|
+
def initialize(*args, meta: EMPTY_HASH, **options)
|
10
|
+
super(*args, **options)
|
11
|
+
@meta = meta.freeze
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param options [Hash] new_options
|
15
|
+
#
|
16
|
+
# @return [Type]
|
17
|
+
#
|
18
|
+
# @api public
|
19
|
+
def with(**options)
|
20
|
+
super(meta: @meta, **options)
|
21
|
+
end
|
22
|
+
|
23
|
+
# @overload meta
|
24
|
+
# @return [Hash] metadata associated with type
|
25
|
+
#
|
26
|
+
# @overload meta(data)
|
27
|
+
# @param [Hash] new metadata to merge into existing metadata
|
28
|
+
# @return [Type] new type with added metadata
|
29
|
+
#
|
30
|
+
# @api public
|
31
|
+
def meta(data = Undefined)
|
32
|
+
if Undefined.equal?(data)
|
33
|
+
@meta
|
34
|
+
elsif data.empty?
|
35
|
+
self
|
36
|
+
else
|
37
|
+
with(meta: @meta.merge(data))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Resets meta
|
42
|
+
#
|
43
|
+
# @return [Dry::Types::Type]
|
44
|
+
#
|
45
|
+
# @api public
|
46
|
+
def pristine
|
47
|
+
with(meta: EMPTY_HASH)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/core/deprecations"
|
4
|
+
require "dry/types/builder_methods"
|
5
|
+
|
6
|
+
module Dry
|
7
|
+
module Types
|
8
|
+
# Export types registered in a container as module constants.
|
9
|
+
# @example
|
10
|
+
# module Types
|
11
|
+
# include Dry::Types(:strict, :coercible, :nominal, default: :strict)
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# Types.constants
|
15
|
+
# # => [:Class, :Strict, :Symbol, :Integer, :Float, :String, :Array, :Hash,
|
16
|
+
# # :Decimal, :Nil, :True, :False, :Bool, :Date, :Nominal, :DateTime, :Range,
|
17
|
+
# # :Coercible, :Time]
|
18
|
+
#
|
19
|
+
# @api public
|
20
|
+
class Module < ::Module
|
21
|
+
def initialize(registry, *args, **kwargs)
|
22
|
+
@registry = registry
|
23
|
+
check_parameters(*args, **kwargs)
|
24
|
+
constants = type_constants(*args, **kwargs)
|
25
|
+
define_constants(constants)
|
26
|
+
extend(BuilderMethods)
|
27
|
+
|
28
|
+
if constants.key?(:Nominal)
|
29
|
+
singleton_class.send(:define_method, :included) do |base|
|
30
|
+
super(base)
|
31
|
+
base.instance_exec(const_get(:Nominal, false)) do |nominal|
|
32
|
+
extend Dry::Core::Deprecations[:'dry-types']
|
33
|
+
const_set(:Definition, nominal)
|
34
|
+
deprecate_constant(:Definition, message: "Nominal")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# @api private
|
41
|
+
def type_constants(*namespaces, default: Undefined, **aliases)
|
42
|
+
if namespaces.empty? && aliases.empty? && Undefined.equal?(default)
|
43
|
+
default_ns = :Strict
|
44
|
+
elsif Undefined.equal?(default)
|
45
|
+
default_ns = Undefined
|
46
|
+
else
|
47
|
+
default_ns = Inflector.camelize(default).to_sym
|
48
|
+
end
|
49
|
+
|
50
|
+
tree = registry_tree
|
51
|
+
|
52
|
+
if namespaces.empty? && aliases.empty?
|
53
|
+
modules = tree.select { |_, v| v.is_a?(::Hash) }.map(&:first)
|
54
|
+
else
|
55
|
+
modules = (namespaces + aliases.keys).map { |n| Inflector.camelize(n).to_sym }
|
56
|
+
end
|
57
|
+
|
58
|
+
tree.each_with_object({}) do |(key, value), constants|
|
59
|
+
if modules.include?(key)
|
60
|
+
name = aliases.fetch(Inflector.underscore(key).to_sym, key)
|
61
|
+
constants[name] = value
|
62
|
+
end
|
63
|
+
|
64
|
+
constants.update(value) if key == default_ns
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# @api private
|
69
|
+
def registry_tree
|
70
|
+
@registry_tree ||= @registry.keys.each_with_object({}) { |key, tree|
|
71
|
+
type = @registry[key]
|
72
|
+
*modules, const_name = key.split(".").map { |part|
|
73
|
+
Inflector.camelize(part).to_sym
|
74
|
+
}
|
75
|
+
next if modules.empty?
|
76
|
+
|
77
|
+
modules.reduce(tree) { |br, name| br[name] ||= {} }[const_name] = type
|
78
|
+
}.freeze
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
# @api private
|
84
|
+
def check_parameters(*namespaces, default: Undefined, **aliases)
|
85
|
+
referenced = namespaces.dup
|
86
|
+
referenced << default unless false.equal?(default) || Undefined.equal?(default)
|
87
|
+
referenced.concat(aliases.keys)
|
88
|
+
|
89
|
+
known = @registry.keys.map { |k|
|
90
|
+
ns, *path = k.split(".")
|
91
|
+
ns.to_sym unless path.empty?
|
92
|
+
}.compact.uniq
|
93
|
+
|
94
|
+
(referenced.uniq - known).each do |name|
|
95
|
+
raise ArgumentError,
|
96
|
+
"#{name.inspect} is not a known type namespace. "\
|
97
|
+
"Supported options are #{known.map(&:inspect).join(", ")}"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# @api private
|
102
|
+
def define_constants(constants, mod = self)
|
103
|
+
constants.each do |name, value|
|
104
|
+
case value
|
105
|
+
when ::Hash
|
106
|
+
if mod.const_defined?(name, false)
|
107
|
+
define_constants(value, mod.const_get(name, false))
|
108
|
+
else
|
109
|
+
m = ::Module.new
|
110
|
+
mod.const_set(name, m)
|
111
|
+
define_constants(value, m)
|
112
|
+
end
|
113
|
+
else
|
114
|
+
mod.const_set(name, value)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,210 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/core/deprecations"
|
4
|
+
require "dry/core/equalizer"
|
5
|
+
require "dry/types/builder"
|
6
|
+
require "dry/types/result"
|
7
|
+
require "dry/types/options"
|
8
|
+
require "dry/types/meta"
|
9
|
+
|
10
|
+
module Dry
|
11
|
+
module Types
|
12
|
+
# Nominal types define a primitive class and do not apply any constructors or constraints
|
13
|
+
#
|
14
|
+
# Use these types for annotations and the base for building more complex types on top of them.
|
15
|
+
#
|
16
|
+
# @api public
|
17
|
+
class Nominal
|
18
|
+
include Type
|
19
|
+
include Options
|
20
|
+
include Meta
|
21
|
+
include Builder
|
22
|
+
include Printable
|
23
|
+
include Dry::Equalizer(:primitive, :options, :meta, inspect: false, immutable: true)
|
24
|
+
|
25
|
+
# @return [Class]
|
26
|
+
attr_reader :primitive
|
27
|
+
|
28
|
+
# @param [Class] primitive
|
29
|
+
#
|
30
|
+
# @return [Type]
|
31
|
+
#
|
32
|
+
# @api private
|
33
|
+
def self.[](primitive)
|
34
|
+
if primitive == ::Array
|
35
|
+
Types::Array
|
36
|
+
elsif primitive == ::Hash
|
37
|
+
Types::Hash
|
38
|
+
else
|
39
|
+
self
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
ALWAYS = proc { true }
|
44
|
+
|
45
|
+
# @param [Type,Class] primitive
|
46
|
+
# @param [Hash] options
|
47
|
+
#
|
48
|
+
# @api private
|
49
|
+
def initialize(primitive, **options)
|
50
|
+
super
|
51
|
+
@primitive = primitive
|
52
|
+
freeze
|
53
|
+
end
|
54
|
+
|
55
|
+
# @return [String]
|
56
|
+
#
|
57
|
+
# @api public
|
58
|
+
def name
|
59
|
+
primitive.name
|
60
|
+
end
|
61
|
+
|
62
|
+
# @return [false]
|
63
|
+
#
|
64
|
+
# @api public
|
65
|
+
def default?
|
66
|
+
false
|
67
|
+
end
|
68
|
+
|
69
|
+
# @return [false]
|
70
|
+
#
|
71
|
+
# @api public
|
72
|
+
def constrained?
|
73
|
+
false
|
74
|
+
end
|
75
|
+
|
76
|
+
# @return [false]
|
77
|
+
#
|
78
|
+
# @api public
|
79
|
+
def optional?
|
80
|
+
false
|
81
|
+
end
|
82
|
+
|
83
|
+
# @param [BasicObject] input
|
84
|
+
#
|
85
|
+
# @return [BasicObject]
|
86
|
+
#
|
87
|
+
# @api private
|
88
|
+
def call_unsafe(input)
|
89
|
+
input
|
90
|
+
end
|
91
|
+
|
92
|
+
# @param [BasicObject] input
|
93
|
+
#
|
94
|
+
# @return [BasicObject]
|
95
|
+
#
|
96
|
+
# @api private
|
97
|
+
def call_safe(input)
|
98
|
+
input
|
99
|
+
end
|
100
|
+
|
101
|
+
# @param [Object] input
|
102
|
+
#
|
103
|
+
# @yieldparam [Failure] failure
|
104
|
+
# @yieldreturn [Result]
|
105
|
+
#
|
106
|
+
# @return [Result,Logic::Result] when a block is not provided
|
107
|
+
# @return [nil] otherwise
|
108
|
+
#
|
109
|
+
# @api public
|
110
|
+
def try(input)
|
111
|
+
success(input)
|
112
|
+
end
|
113
|
+
|
114
|
+
# @param (see Dry::Types::Success#initialize)
|
115
|
+
#
|
116
|
+
# @return [Result::Success]
|
117
|
+
#
|
118
|
+
# @api public
|
119
|
+
def success(input)
|
120
|
+
Result::Success.new(input)
|
121
|
+
end
|
122
|
+
|
123
|
+
# @param (see Failure#initialize)
|
124
|
+
#
|
125
|
+
# @return [Result::Failure]
|
126
|
+
#
|
127
|
+
# @api public
|
128
|
+
def failure(input, error)
|
129
|
+
raise ArgumentError, "error must be a CoercionError" unless error.is_a?(CoercionError)
|
130
|
+
|
131
|
+
Result::Failure.new(input, error)
|
132
|
+
end
|
133
|
+
|
134
|
+
# Checks whether value is of a #primitive class
|
135
|
+
#
|
136
|
+
# @param [Object] value
|
137
|
+
#
|
138
|
+
# @return [Boolean]
|
139
|
+
#
|
140
|
+
# @api public
|
141
|
+
def primitive?(value)
|
142
|
+
value.is_a?(primitive)
|
143
|
+
end
|
144
|
+
|
145
|
+
# @api private
|
146
|
+
def coerce(input, &_block)
|
147
|
+
if primitive?(input)
|
148
|
+
input
|
149
|
+
elsif block_given?
|
150
|
+
yield
|
151
|
+
else
|
152
|
+
raise CoercionError, "#{input.inspect} must be an instance of #{primitive}"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# @api private
|
157
|
+
def try_coerce(input)
|
158
|
+
result = success(input)
|
159
|
+
|
160
|
+
coerce(input) do
|
161
|
+
result = failure(
|
162
|
+
input,
|
163
|
+
CoercionError.new("#{input.inspect} must be an instance of #{primitive}")
|
164
|
+
)
|
165
|
+
end
|
166
|
+
|
167
|
+
if block_given?
|
168
|
+
yield(result)
|
169
|
+
else
|
170
|
+
result
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# Return AST representation of a type nominal
|
175
|
+
#
|
176
|
+
# @return [Array]
|
177
|
+
#
|
178
|
+
# @api public
|
179
|
+
def to_ast(meta: true)
|
180
|
+
[:nominal, [primitive, meta ? self.meta : EMPTY_HASH]]
|
181
|
+
end
|
182
|
+
|
183
|
+
# Return self. Nominal types are lax by definition
|
184
|
+
#
|
185
|
+
# @return [Nominal]
|
186
|
+
#
|
187
|
+
# @api public
|
188
|
+
def lax
|
189
|
+
self
|
190
|
+
end
|
191
|
+
|
192
|
+
# Wrap the type with a proc
|
193
|
+
#
|
194
|
+
# @return [Proc]
|
195
|
+
#
|
196
|
+
# @api public
|
197
|
+
def to_proc
|
198
|
+
ALWAYS
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
extend Dry::Core::Deprecations[:'dry-types']
|
203
|
+
Definition = Nominal
|
204
|
+
deprecate_constant(:Definition, message: "Nominal")
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
require "dry/types/array"
|
209
|
+
require "dry/types/hash"
|
210
|
+
require "dry/types/map"
|
data/lib/dry/types/options.rb
CHANGED
@@ -1,42 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Dry
|
2
4
|
module Types
|
5
|
+
# Common API for types with options
|
6
|
+
#
|
7
|
+
# @api private
|
3
8
|
module Options
|
4
9
|
# @return [Hash]
|
5
10
|
attr_reader :options
|
6
11
|
|
7
|
-
# @see
|
8
|
-
|
12
|
+
# @see Nominal#initialize
|
13
|
+
#
|
14
|
+
# @api private
|
15
|
+
def initialize(*args, **options)
|
9
16
|
@__args__ = args.freeze
|
10
17
|
@options = options.freeze
|
11
|
-
@meta = meta.freeze
|
12
18
|
end
|
13
19
|
|
14
20
|
# @param [Hash] new_options
|
21
|
+
#
|
15
22
|
# @return [Type]
|
16
|
-
def with(new_options)
|
17
|
-
self.class.new(*@__args__, **options, meta: @meta, **new_options)
|
18
|
-
end
|
19
|
-
|
20
|
-
# @overload meta
|
21
|
-
# @return [Hash] metadata associated with type
|
22
23
|
#
|
23
|
-
# @
|
24
|
-
|
25
|
-
|
26
|
-
def meta(data = nil)
|
27
|
-
if !data
|
28
|
-
@meta
|
29
|
-
elsif data.empty?
|
30
|
-
self
|
31
|
-
else
|
32
|
-
with(meta: @meta.merge(data))
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
# Resets meta
|
37
|
-
# @return [Dry::Types::Type]
|
38
|
-
def pristine
|
39
|
-
with(meta: EMPTY_HASH)
|
24
|
+
# @api private
|
25
|
+
def with(**new_options)
|
26
|
+
self.class.new(*@__args__, **options, **new_options)
|
40
27
|
end
|
41
28
|
end
|
42
29
|
end
|
data/lib/dry/types/params.rb
CHANGED
@@ -1,53 +1,67 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/types/coercions/params"
|
2
4
|
|
3
5
|
module Dry
|
4
6
|
module Types
|
5
|
-
register(
|
6
|
-
self[
|
7
|
+
register("params.nil") do
|
8
|
+
self["nominal.nil"].constructor(Coercions::Params.method(:to_nil))
|
9
|
+
end
|
10
|
+
|
11
|
+
register("params.date") do
|
12
|
+
self["nominal.date"].constructor(Coercions::Params.method(:to_date))
|
7
13
|
end
|
8
14
|
|
9
|
-
register(
|
10
|
-
self[
|
15
|
+
register("params.date_time") do
|
16
|
+
self["nominal.date_time"].constructor(Coercions::Params.method(:to_date_time))
|
11
17
|
end
|
12
18
|
|
13
|
-
register(
|
14
|
-
self[
|
19
|
+
register("params.time") do
|
20
|
+
self["nominal.time"].constructor(Coercions::Params.method(:to_time))
|
15
21
|
end
|
16
22
|
|
17
|
-
register(
|
18
|
-
self[
|
23
|
+
register("params.true") do
|
24
|
+
self["nominal.true"].constructor(Coercions::Params.method(:to_true))
|
19
25
|
end
|
20
26
|
|
21
|
-
register(
|
22
|
-
self[
|
27
|
+
register("params.false") do
|
28
|
+
self["nominal.false"].constructor(Coercions::Params.method(:to_false))
|
23
29
|
end
|
24
30
|
|
25
|
-
register(
|
26
|
-
self[
|
31
|
+
register("params.bool") do
|
32
|
+
self["params.true"] | self["params.false"]
|
27
33
|
end
|
28
34
|
|
29
|
-
register(
|
30
|
-
|
35
|
+
register("params.integer") do
|
36
|
+
self["nominal.integer"].constructor(Coercions::Params.method(:to_int))
|
31
37
|
end
|
32
38
|
|
33
|
-
register(
|
34
|
-
self[
|
39
|
+
register("params.float") do
|
40
|
+
self["nominal.float"].constructor(Coercions::Params.method(:to_float))
|
35
41
|
end
|
36
42
|
|
37
|
-
register(
|
38
|
-
self[
|
43
|
+
register("params.decimal") do
|
44
|
+
self["nominal.decimal"].constructor(Coercions::Params.method(:to_decimal))
|
39
45
|
end
|
40
46
|
|
41
|
-
register(
|
42
|
-
self[
|
47
|
+
register("params.array") do
|
48
|
+
self["nominal.array"].constructor(Coercions::Params.method(:to_ary))
|
43
49
|
end
|
44
50
|
|
45
|
-
register(
|
46
|
-
self[
|
51
|
+
register("params.hash") do
|
52
|
+
self["nominal.hash"].constructor(Coercions::Params.method(:to_hash))
|
47
53
|
end
|
48
54
|
|
49
|
-
register(
|
50
|
-
self[
|
55
|
+
register("params.symbol") do
|
56
|
+
self["nominal.symbol"].constructor(Coercions::Params.method(:to_symbol))
|
57
|
+
end
|
58
|
+
|
59
|
+
register("params.string", self["string"])
|
60
|
+
|
61
|
+
COERCIBLE.each_key do |name|
|
62
|
+
next if name.equal?(:string)
|
63
|
+
|
64
|
+
register("optional.params.#{name}", self["params.nil"] | self["params.#{name}"])
|
51
65
|
end
|
52
66
|
end
|
53
67
|
end
|