dry-types 0.12.3 → 0.13.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/.travis.yml +9 -6
- data/CHANGELOG.md +92 -3
- data/CONTRIBUTING.md +1 -1
- data/Gemfile +0 -2
- data/Rakefile +4 -2
- data/benchmarks/hash_schemas.rb +2 -2
- data/dry-types.gemspec +2 -3
- data/lib/dry/types.rb +12 -18
- data/lib/dry/types/array.rb +1 -3
- data/lib/dry/types/array/member.rb +6 -2
- data/lib/dry/types/builder.rb +9 -2
- data/lib/dry/types/builder_methods.rb +22 -1
- data/lib/dry/types/coercions/{form.rb → params.rb} +1 -1
- data/lib/dry/types/compat.rb +2 -0
- data/lib/dry/types/compat/form_types.rb +27 -0
- data/lib/dry/types/compat/int.rb +13 -0
- data/lib/dry/types/compiler.rb +30 -7
- data/lib/dry/types/constructor.rb +5 -0
- data/lib/dry/types/core.rb +4 -3
- data/lib/dry/types/decorator.rb +5 -0
- data/lib/dry/types/default.rb +2 -2
- data/lib/dry/types/definition.rb +2 -12
- data/lib/dry/types/enum.rb +42 -14
- data/lib/dry/types/errors.rb +6 -2
- data/lib/dry/types/extensions/maybe.rb +18 -28
- data/lib/dry/types/fn_container.rb +9 -8
- data/lib/dry/types/hash.rb +83 -28
- data/lib/dry/types/hash/schema.rb +98 -179
- data/lib/dry/types/hash/schema_builder.rb +75 -0
- data/lib/dry/types/inflector.rb +7 -0
- data/lib/dry/types/map.rb +93 -0
- data/lib/dry/types/options.rb +3 -3
- data/lib/dry/types/params.rb +53 -0
- data/lib/dry/types/safe.rb +3 -2
- data/lib/dry/types/spec/types.rb +10 -0
- data/lib/dry/types/sum.rb +12 -6
- data/lib/dry/types/version.rb +1 -1
- metadata +29 -37
- data/lib/dry/types/form.rb +0 -53
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'dry/core/deprecations'
|
2
|
+
|
3
|
+
Dry::Core::Deprecations.warn('Int type was renamed to Integer', tag: :'dry-types')
|
4
|
+
|
5
|
+
module Dry
|
6
|
+
module Types
|
7
|
+
register('int', self['integer'])
|
8
|
+
register('strict.int', self['strict.integer'])
|
9
|
+
register('coercible.int', self['coercible.integer'])
|
10
|
+
register('optional.strict.int', self['optional.strict.integer'])
|
11
|
+
register('optional.coercible.int', self['optional.coercible.integer'])
|
12
|
+
end
|
13
|
+
end
|
data/lib/dry/types/compiler.rb
CHANGED
@@ -54,7 +54,8 @@ module Dry
|
|
54
54
|
|
55
55
|
def visit_array(node)
|
56
56
|
member, meta = node
|
57
|
-
|
57
|
+
member = member.is_a?(Class) ? member : visit(member)
|
58
|
+
registry['array'].of(member).meta(meta)
|
58
59
|
end
|
59
60
|
|
60
61
|
def visit_hash(node)
|
@@ -62,6 +63,11 @@ module Dry
|
|
62
63
|
merge_with('hash', constructor, schema).meta(meta)
|
63
64
|
end
|
64
65
|
|
66
|
+
def visit_hash_schema(node)
|
67
|
+
schema, meta = node
|
68
|
+
merge_with_schema('hash', schema).meta(meta)
|
69
|
+
end
|
70
|
+
|
65
71
|
def visit_json_hash(node)
|
66
72
|
schema, meta = node
|
67
73
|
merge_with('json.hash', :symbolized, schema).meta(meta)
|
@@ -72,14 +78,14 @@ module Dry
|
|
72
78
|
registry['json.array'].of(visit(member)).meta(meta)
|
73
79
|
end
|
74
80
|
|
75
|
-
def
|
81
|
+
def visit_params_hash(node)
|
76
82
|
schema, meta = node
|
77
|
-
merge_with('
|
83
|
+
merge_with('params.hash', :symbolized, schema).meta(meta)
|
78
84
|
end
|
79
85
|
|
80
|
-
def
|
86
|
+
def visit_params_array(node)
|
81
87
|
member, meta = node
|
82
|
-
registry['
|
88
|
+
registry['params.array'].of(visit(member)).meta(meta)
|
83
89
|
end
|
84
90
|
|
85
91
|
def visit_member(node)
|
@@ -87,9 +93,26 @@ module Dry
|
|
87
93
|
{ name => visit(type) }
|
88
94
|
end
|
89
95
|
|
96
|
+
def visit_enum(node)
|
97
|
+
type, mapping, meta = node
|
98
|
+
Enum.new(visit(type), mapping: mapping, meta: meta)
|
99
|
+
end
|
100
|
+
|
101
|
+
def visit_map(node)
|
102
|
+
key_type, value_type, meta = node
|
103
|
+
registry['hash'].map(visit(key_type), visit(value_type)).meta(meta)
|
104
|
+
end
|
105
|
+
|
90
106
|
def merge_with(hash_id, constructor, schema)
|
91
|
-
registry[hash_id].
|
92
|
-
|
107
|
+
registry[hash_id].schema(
|
108
|
+
schema.map { |key| visit(key) }.reduce({}, :update),
|
109
|
+
constructor
|
110
|
+
)
|
111
|
+
end
|
112
|
+
|
113
|
+
def merge_with_schema(hash_id, schema)
|
114
|
+
registry[hash_id].instantiate(
|
115
|
+
schema.map { |key| visit(key) }.reduce({}, :update)
|
93
116
|
)
|
94
117
|
end
|
95
118
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'dry/types/decorator'
|
2
|
+
require 'dry/types/fn_container'
|
2
3
|
|
3
4
|
module Dry
|
4
5
|
module Types
|
@@ -43,6 +44,10 @@ module Dry
|
|
43
44
|
type.name
|
44
45
|
end
|
45
46
|
|
47
|
+
def default?
|
48
|
+
type.default?
|
49
|
+
end
|
50
|
+
|
46
51
|
# @param [Object] input
|
47
52
|
# @return [Object]
|
48
53
|
def call(input)
|
data/lib/dry/types/core.rb
CHANGED
@@ -4,7 +4,7 @@ module Dry
|
|
4
4
|
module Types
|
5
5
|
COERCIBLE = {
|
6
6
|
string: String,
|
7
|
-
|
7
|
+
integer: Integer,
|
8
8
|
float: Float,
|
9
9
|
decimal: BigDecimal,
|
10
10
|
array: ::Array,
|
@@ -19,7 +19,8 @@ module Dry
|
|
19
19
|
false: FalseClass,
|
20
20
|
date: Date,
|
21
21
|
date_time: DateTime,
|
22
|
-
time: Time
|
22
|
+
time: Time,
|
23
|
+
range: Range
|
23
24
|
}.freeze
|
24
25
|
|
25
26
|
ALL_PRIMITIVES = COERCIBLE.merge(NON_COERCIBLE).freeze
|
@@ -61,5 +62,5 @@ module Dry
|
|
61
62
|
end
|
62
63
|
|
63
64
|
require 'dry/types/coercions'
|
64
|
-
require 'dry/types/
|
65
|
+
require 'dry/types/params'
|
65
66
|
require 'dry/types/json'
|
data/lib/dry/types/decorator.rb
CHANGED
data/lib/dry/types/default.rb
CHANGED
data/lib/dry/types/definition.rb
CHANGED
@@ -6,12 +6,9 @@ module Dry
|
|
6
6
|
module Types
|
7
7
|
class Definition
|
8
8
|
include Type
|
9
|
-
include Dry::Equalizer(:primitive, :options, :meta)
|
10
9
|
include Options
|
11
10
|
include Builder
|
12
|
-
|
13
|
-
# @return [Hash]
|
14
|
-
attr_reader :options
|
11
|
+
include Dry::Equalizer(:primitive, :options, :meta)
|
15
12
|
|
16
13
|
# @return [Class]
|
17
14
|
attr_reader :primitive
|
@@ -84,20 +81,12 @@ module Dry
|
|
84
81
|
Result::Success.new(input)
|
85
82
|
end
|
86
83
|
|
87
|
-
|
88
84
|
# @param (see Failure#initialize)
|
89
85
|
# @return [Result::Failure]
|
90
86
|
def failure(input, error)
|
91
87
|
Result::Failure.new(input, error)
|
92
88
|
end
|
93
89
|
|
94
|
-
# @param [Object] klass class of the result instance
|
95
|
-
# @param [Array] args arguments for the +klass#initialize+ method
|
96
|
-
# @return [Object] new instance of the given +klass+
|
97
|
-
def result(klass, *args)
|
98
|
-
klass.new(*args)
|
99
|
-
end
|
100
|
-
|
101
90
|
# Checks whether value is of a #primitive class
|
102
91
|
# @param [Object] value
|
103
92
|
# @return [Boolean]
|
@@ -121,3 +110,4 @@ end
|
|
121
110
|
|
122
111
|
require 'dry/types/array'
|
123
112
|
require 'dry/types/hash'
|
113
|
+
require 'dry/types/map'
|
data/lib/dry/types/enum.rb
CHANGED
@@ -4,7 +4,7 @@ module Dry
|
|
4
4
|
module Types
|
5
5
|
class Enum
|
6
6
|
include Type
|
7
|
-
include Dry::Equalizer(:type, :options, :
|
7
|
+
include Dry::Equalizer(:type, :options, :mapping)
|
8
8
|
include Decorator
|
9
9
|
|
10
10
|
# @return [Array]
|
@@ -13,40 +13,68 @@ module Dry
|
|
13
13
|
# @return [Hash]
|
14
14
|
attr_reader :mapping
|
15
15
|
|
16
|
+
# @return [Hash]
|
17
|
+
attr_reader :inverted_mapping
|
18
|
+
|
16
19
|
# @param [Type] type
|
17
20
|
# @param [Hash] options
|
18
21
|
# @option options [Array] :values
|
19
22
|
def initialize(type, options)
|
20
23
|
super
|
21
|
-
@
|
22
|
-
@values.
|
23
|
-
@
|
24
|
+
@mapping = options.fetch(:mapping).freeze
|
25
|
+
@values = @mapping.keys.freeze
|
26
|
+
@inverted_mapping = @mapping.invert.freeze
|
27
|
+
freeze
|
24
28
|
end
|
25
29
|
|
26
30
|
# @param [Object] input
|
27
31
|
# @return [Object]
|
28
|
-
def call(input)
|
29
|
-
|
30
|
-
if values.include?(input)
|
31
|
-
input
|
32
|
-
elsif mapping.key?(input)
|
33
|
-
mapping[input]
|
34
|
-
end
|
35
|
-
|
36
|
-
type[value || input]
|
32
|
+
def call(input = Undefined)
|
33
|
+
type[map_value(input)]
|
37
34
|
end
|
38
35
|
alias_method :[], :call
|
39
36
|
|
37
|
+
# @param [Object] input
|
38
|
+
# @yieldparam [Failure] failure
|
39
|
+
# @yieldreturn [Result]
|
40
|
+
# @return [Logic::Result]
|
41
|
+
# @return [Object] if coercion fails and a block is given
|
42
|
+
def try(input)
|
43
|
+
super(map_value(input))
|
44
|
+
end
|
45
|
+
|
40
46
|
def default(*)
|
41
47
|
raise '.enum(*values).default(value) is not supported. Call '\
|
42
48
|
'.default(value).enum(*values) instead'
|
43
49
|
end
|
44
50
|
|
51
|
+
# Check whether a value is in the enum
|
52
|
+
alias_method :include?, :valid?
|
53
|
+
|
45
54
|
# @api public
|
46
55
|
#
|
47
56
|
# @see Definition#to_ast
|
48
57
|
def to_ast(meta: true)
|
49
|
-
[:enum, [type.to_ast(meta: meta),
|
58
|
+
[:enum, [type.to_ast(meta: meta),
|
59
|
+
mapping,
|
60
|
+
meta ? self.meta : EMPTY_HASH]]
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
# Maps a value
|
66
|
+
#
|
67
|
+
# @params [Object]
|
68
|
+
# @return [Object]
|
69
|
+
# @api private
|
70
|
+
def map_value(input)
|
71
|
+
if input.equal?(Undefined)
|
72
|
+
type.call
|
73
|
+
elsif mapping.key?(input)
|
74
|
+
input
|
75
|
+
else
|
76
|
+
inverted_mapping.fetch(input, input)
|
77
|
+
end
|
50
78
|
end
|
51
79
|
end
|
52
80
|
end
|
data/lib/dry/types/errors.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
module Dry
|
2
2
|
module Types
|
3
|
-
extend Dry::
|
3
|
+
extend Dry::Core::ClassAttributes
|
4
4
|
|
5
5
|
# @!attribute [r] namespace
|
6
6
|
# @return [Container{String => Definition}]
|
7
|
-
|
7
|
+
defines :namespace
|
8
|
+
|
9
|
+
namespace self
|
8
10
|
|
9
11
|
class SchemaError < TypeError
|
10
12
|
# @param [String,Symbol] key
|
@@ -15,6 +17,8 @@ module Dry
|
|
15
17
|
end
|
16
18
|
end
|
17
19
|
|
20
|
+
MapError = Class.new(TypeError)
|
21
|
+
|
18
22
|
SchemaKeyError = Class.new(KeyError)
|
19
23
|
private_constant(:SchemaKeyError)
|
20
24
|
|
@@ -12,19 +12,32 @@ module Dry
|
|
12
12
|
|
13
13
|
# @param [Dry::Monads::Maybe, Object] input
|
14
14
|
# @return [Dry::Monads::Maybe]
|
15
|
-
def call(input)
|
16
|
-
|
15
|
+
def call(input = Undefined)
|
16
|
+
case input
|
17
|
+
when Dry::Monads::Maybe
|
18
|
+
input
|
19
|
+
when Undefined
|
20
|
+
None()
|
21
|
+
else
|
22
|
+
Maybe(type[input])
|
23
|
+
end
|
17
24
|
end
|
18
25
|
alias_method :[], :call
|
19
26
|
|
20
27
|
# @param [Object] input
|
21
28
|
# @return [Result::Success]
|
22
|
-
def try(input)
|
23
|
-
|
29
|
+
def try(input = Undefined)
|
30
|
+
res = if input.equal?(Undefined)
|
31
|
+
None()
|
32
|
+
else
|
33
|
+
Maybe(type[input])
|
34
|
+
end
|
35
|
+
|
36
|
+
Result::Success.new(res)
|
24
37
|
end
|
25
38
|
|
26
39
|
# @return [true]
|
27
|
-
def
|
40
|
+
def default?
|
28
41
|
true
|
29
42
|
end
|
30
43
|
|
@@ -47,29 +60,6 @@ module Dry
|
|
47
60
|
end
|
48
61
|
end
|
49
62
|
|
50
|
-
class Hash
|
51
|
-
module MaybeTypes
|
52
|
-
# @param [Hash] result
|
53
|
-
# @param [Symbol] key
|
54
|
-
# @param [Definition] type
|
55
|
-
def resolve_missing_value(result, key, type)
|
56
|
-
if type.respond_to?(:maybe?) && type.maybe?
|
57
|
-
result[key] = type[nil]
|
58
|
-
else
|
59
|
-
super
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
class StrictWithDefaults < Strict
|
65
|
-
include MaybeTypes
|
66
|
-
end
|
67
|
-
|
68
|
-
class Schema < Hash
|
69
|
-
include MaybeTypes
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
63
|
# Register non-coercible maybe types
|
74
64
|
NON_NIL.each_key do |name|
|
75
65
|
register("maybe.strict.#{name}", self["strict.#{name}"].maybe)
|
@@ -9,18 +9,19 @@ module Dry
|
|
9
9
|
end
|
10
10
|
|
11
11
|
# @api private
|
12
|
-
def self.register(function)
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
def self.register(function = Dry::Core::Constants::Undefined, &block)
|
13
|
+
fn = Dry::Core::Constants::Undefined.default(function, block)
|
14
|
+
fn_name = register_name(fn)
|
15
|
+
container.register(fn_name, fn) unless container.key?(fn_name)
|
16
|
+
fn_name
|
16
17
|
end
|
17
18
|
|
18
19
|
# @api private
|
19
|
-
def self.[](
|
20
|
-
if container.key?(
|
21
|
-
container[
|
20
|
+
def self.[](fn_name)
|
21
|
+
if container.key?(fn_name)
|
22
|
+
container[fn_name]
|
22
23
|
else
|
23
|
-
|
24
|
+
fn_name
|
24
25
|
end
|
25
26
|
end
|
26
27
|
|
data/lib/dry/types/hash.rb
CHANGED
@@ -1,62 +1,117 @@
|
|
1
|
-
require 'dry/types/hash/
|
1
|
+
require 'dry/types/hash/schema_builder'
|
2
2
|
|
3
3
|
module Dry
|
4
4
|
module Types
|
5
5
|
class Hash < Definition
|
6
|
+
SCHEMA_BUILDER = SchemaBuilder.new.freeze
|
7
|
+
|
6
8
|
# @param [{Symbol => Definition}] type_map
|
7
|
-
# @param [
|
8
|
-
# {Schema} or one of its subclasses ({Weak}, {Permissive}, {Strict},
|
9
|
-
# {StrictWithDefaults}, {Symbolized})
|
9
|
+
# @param [Symbol] constructor
|
10
10
|
# @return [Schema]
|
11
|
-
def schema(type_map,
|
12
|
-
member_types = type_map
|
13
|
-
result[name] =
|
14
|
-
case type
|
15
|
-
when String, Class then Types[type]
|
16
|
-
else type
|
17
|
-
end
|
18
|
-
}
|
11
|
+
def schema(type_map, constructor = nil)
|
12
|
+
member_types = transform_types(type_map)
|
19
13
|
|
20
|
-
|
14
|
+
if constructor.nil?
|
15
|
+
Schema.new(primitive, member_types: member_types, **options, meta: meta)
|
16
|
+
else
|
17
|
+
SCHEMA_BUILDER.(
|
18
|
+
primitive,
|
19
|
+
**options,
|
20
|
+
member_types: member_types,
|
21
|
+
meta: meta,
|
22
|
+
hash_type: constructor
|
23
|
+
)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Build a map type
|
28
|
+
#
|
29
|
+
# @param [Type] key_type
|
30
|
+
# @param [Type] value_type
|
31
|
+
# @return [Map]
|
32
|
+
def map(key_type, value_type)
|
33
|
+
Map.new(
|
34
|
+
primitive,
|
35
|
+
key_type: resolve_type(key_type),
|
36
|
+
value_type: resolve_type(value_type),
|
37
|
+
meta: meta
|
38
|
+
)
|
21
39
|
end
|
22
40
|
|
23
41
|
# @param [{Symbol => Definition}] type_map
|
24
|
-
# @return [
|
42
|
+
# @return [Schema]
|
25
43
|
def weak(type_map)
|
26
|
-
schema(type_map,
|
44
|
+
schema(type_map, :weak)
|
27
45
|
end
|
28
46
|
|
29
47
|
# @param [{Symbol => Definition}] type_map
|
30
|
-
# @return [
|
48
|
+
# @return [Schema]
|
31
49
|
def permissive(type_map)
|
32
|
-
schema(type_map,
|
50
|
+
schema(type_map, :permissive)
|
33
51
|
end
|
34
52
|
|
35
53
|
# @param [{Symbol => Definition}] type_map
|
36
|
-
# @return [
|
54
|
+
# @return [Schema]
|
37
55
|
def strict(type_map)
|
38
|
-
schema(type_map,
|
56
|
+
schema(type_map, :strict)
|
39
57
|
end
|
40
58
|
|
41
59
|
# @param [{Symbol => Definition}] type_map
|
42
|
-
# @return [
|
60
|
+
# @return [Schema]
|
43
61
|
def strict_with_defaults(type_map)
|
44
|
-
schema(type_map,
|
62
|
+
schema(type_map, :strict_with_defaults)
|
45
63
|
end
|
46
64
|
|
47
65
|
# @param [{Symbol => Definition}] type_map
|
48
|
-
# @return [
|
66
|
+
# @return [Schema]
|
49
67
|
def symbolized(type_map)
|
50
|
-
schema(type_map,
|
68
|
+
schema(type_map, :symbolized)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Build a schema from an AST
|
72
|
+
# @api private
|
73
|
+
# @param [{Symbol => Definition}] member_types
|
74
|
+
# @return [Schema]
|
75
|
+
def instantiate(member_types)
|
76
|
+
SCHEMA_BUILDER.instantiate(primitive, **options, member_types: member_types)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Injects a type transformation function for building schemas
|
80
|
+
# @param [#call,nil] proc
|
81
|
+
# @param [#call,nil] block
|
82
|
+
# @return [Hash]
|
83
|
+
def with_type_transform(proc = nil, &block)
|
84
|
+
fn = proc || block
|
85
|
+
|
86
|
+
if fn.nil?
|
87
|
+
raise ArgumentError, "a block or callable argument is required"
|
88
|
+
end
|
89
|
+
|
90
|
+
handle = Dry::Types::FnContainer.register(fn)
|
91
|
+
meta(type_transform_fn: handle)
|
51
92
|
end
|
52
93
|
|
53
94
|
private
|
54
95
|
|
55
|
-
# @
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
96
|
+
# @api private
|
97
|
+
def transform_types(type_map)
|
98
|
+
type_fn = meta.fetch(:type_transform_fn, Schema::NO_TRANSFORM)
|
99
|
+
type_transform = Dry::Types::FnContainer[type_fn]
|
100
|
+
|
101
|
+
type_map.each_with_object({}) { |(name, type), result|
|
102
|
+
result[name] = type_transform.(
|
103
|
+
resolve_type(type),
|
104
|
+
name
|
105
|
+
)
|
106
|
+
}
|
107
|
+
end
|
108
|
+
|
109
|
+
# @api private
|
110
|
+
def resolve_type(type)
|
111
|
+
case type
|
112
|
+
when String, Class then Types[type]
|
113
|
+
else type
|
114
|
+
end
|
60
115
|
end
|
61
116
|
end
|
62
117
|
end
|