dry-types 0.9.0 → 0.15.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 +5 -5
- data/.codeclimate.yml +15 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +43 -0
- data/.travis.yml +15 -14
- data/.yardopts +5 -0
- data/CHANGELOG.md +494 -88
- data/CONTRIBUTING.md +29 -0
- data/Gemfile +7 -6
- data/README.md +1 -3
- data/Rakefile +8 -3
- data/benchmarks/hash_schemas.rb +7 -7
- data/dry-types.gemspec +11 -9
- data/lib/dry/types/any.rb +36 -0
- data/lib/dry/types/array/member.rb +29 -4
- data/lib/dry/types/array.rb +6 -4
- data/lib/dry/types/builder.rb +48 -6
- data/lib/dry/types/builder_methods.rb +111 -0
- data/lib/dry/types/coercions/json.rb +3 -0
- data/lib/dry/types/coercions/{form.rb → params.rb} +23 -3
- data/lib/dry/types/coercions.rb +16 -3
- data/lib/dry/types/compat.rb +0 -0
- data/lib/dry/types/compiler.rb +66 -39
- data/lib/dry/types/constrained/coercible.rb +7 -1
- data/lib/dry/types/constrained.rb +42 -3
- data/lib/dry/types/constraints.rb +3 -0
- data/lib/dry/types/constructor.rb +98 -16
- data/lib/dry/types/container.rb +2 -0
- data/lib/dry/types/core.rb +30 -14
- data/lib/dry/types/decorator.rb +31 -5
- data/lib/dry/types/default.rb +34 -8
- data/lib/dry/types/enum.rb +71 -14
- data/lib/dry/types/errors.rb +23 -6
- data/lib/dry/types/extensions/maybe.rb +35 -16
- data/lib/dry/types/fn_container.rb +34 -0
- data/lib/dry/types/hash/constructor.rb +20 -0
- data/lib/dry/types/hash.rb +103 -23
- data/lib/dry/types/inflector.rb +7 -0
- data/lib/dry/types/json.rb +7 -7
- data/lib/dry/types/map.rb +98 -0
- data/lib/dry/types/module.rb +115 -0
- data/lib/dry/types/nominal.rb +119 -0
- data/lib/dry/types/options.rb +29 -7
- data/lib/dry/types/params.rb +53 -0
- data/lib/dry/types/printable.rb +12 -0
- data/lib/dry/types/printer.rb +309 -0
- data/lib/dry/types/result.rb +12 -2
- data/lib/dry/types/safe.rb +27 -1
- data/lib/dry/types/schema/key.rb +130 -0
- data/lib/dry/types/schema.rb +298 -0
- data/lib/dry/types/spec/types.rb +102 -0
- data/lib/dry/types/sum.rb +75 -13
- data/lib/dry/types/type.rb +6 -0
- data/lib/dry/types/version.rb +1 -1
- data/lib/dry/types.rb +104 -38
- data/log/.gitkeep +0 -0
- metadata +81 -50
- data/lib/dry/types/definition.rb +0 -79
- data/lib/dry/types/form.rb +0 -53
- data/lib/dry/types/hash/schema.rb +0 -156
- data/lib/spec/dry/types.rb +0 -56
data/lib/dry/types/default.rb
CHANGED
|
@@ -3,22 +3,29 @@ require 'dry/types/decorator'
|
|
|
3
3
|
module Dry
|
|
4
4
|
module Types
|
|
5
5
|
class Default
|
|
6
|
-
include
|
|
6
|
+
include Type
|
|
7
7
|
include Decorator
|
|
8
8
|
include Builder
|
|
9
|
+
include Printable
|
|
10
|
+
include Dry::Equalizer(:type, :options, :value, inspect: false)
|
|
9
11
|
|
|
10
12
|
class Callable < Default
|
|
11
|
-
include Dry::Equalizer(:type, :options)
|
|
13
|
+
include Dry::Equalizer(:type, :options, inspect: false)
|
|
12
14
|
|
|
15
|
+
# Evaluates given callable
|
|
16
|
+
# @return [Object]
|
|
13
17
|
def evaluate
|
|
14
|
-
value.call
|
|
18
|
+
value.call(type)
|
|
15
19
|
end
|
|
16
20
|
end
|
|
17
21
|
|
|
22
|
+
# @return [Object]
|
|
18
23
|
attr_reader :value
|
|
19
24
|
|
|
20
25
|
alias_method :evaluate, :value
|
|
21
26
|
|
|
27
|
+
# @param [Object, #call] value
|
|
28
|
+
# @return [Class] {Default} or {Default::Callable}
|
|
22
29
|
def self.[](value)
|
|
23
30
|
if value.respond_to?(:call)
|
|
24
31
|
Callable
|
|
@@ -27,32 +34,51 @@ module Dry
|
|
|
27
34
|
end
|
|
28
35
|
end
|
|
29
36
|
|
|
30
|
-
|
|
37
|
+
# @param [Type] type
|
|
38
|
+
# @param [Object] value
|
|
39
|
+
def initialize(type, value, **options)
|
|
31
40
|
super
|
|
32
41
|
@value = value
|
|
33
42
|
end
|
|
34
43
|
|
|
44
|
+
# @param [Array] args see {Dry::Types::Builder#constrained}
|
|
45
|
+
# @return [Default]
|
|
35
46
|
def constrained(*args)
|
|
36
47
|
type.constrained(*args).default(value)
|
|
37
48
|
end
|
|
38
49
|
|
|
50
|
+
# @return [true]
|
|
39
51
|
def default?
|
|
40
52
|
true
|
|
41
53
|
end
|
|
42
54
|
|
|
55
|
+
# @param [Object] input
|
|
56
|
+
# @return [Result::Success]
|
|
43
57
|
def try(input)
|
|
44
58
|
success(call(input))
|
|
45
59
|
end
|
|
46
60
|
|
|
47
|
-
def
|
|
48
|
-
|
|
61
|
+
def valid?(value = Undefined)
|
|
62
|
+
value.equal?(Undefined) || super
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @param [Object] input
|
|
66
|
+
# @return [Object] value passed through {#type} or {#default} value
|
|
67
|
+
def call(input = Undefined)
|
|
68
|
+
if input.equal?(Undefined)
|
|
49
69
|
evaluate
|
|
50
70
|
else
|
|
51
|
-
|
|
52
|
-
output.nil? ? evaluate : output
|
|
71
|
+
Undefined.default(type[input]) { evaluate }
|
|
53
72
|
end
|
|
54
73
|
end
|
|
55
74
|
alias_method :[], :call
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
# Replace underlying type
|
|
79
|
+
def __new__(type)
|
|
80
|
+
self.class.new(type, value, options)
|
|
81
|
+
end
|
|
56
82
|
end
|
|
57
83
|
end
|
|
58
84
|
end
|
data/lib/dry/types/enum.rb
CHANGED
|
@@ -3,29 +3,86 @@ require 'dry/types/decorator'
|
|
|
3
3
|
module Dry
|
|
4
4
|
module Types
|
|
5
5
|
class Enum
|
|
6
|
-
include
|
|
6
|
+
include Type
|
|
7
|
+
include Dry::Equalizer(:type, :options, :mapping, inspect: false)
|
|
7
8
|
include Decorator
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
# @return [Array]
|
|
11
|
+
attr_reader :values
|
|
10
12
|
|
|
13
|
+
# @return [Hash]
|
|
14
|
+
attr_reader :mapping
|
|
15
|
+
|
|
16
|
+
# @return [Hash]
|
|
17
|
+
attr_reader :inverted_mapping
|
|
18
|
+
|
|
19
|
+
# @param [Type] type
|
|
20
|
+
# @param [Hash] options
|
|
21
|
+
# @option options [Array] :values
|
|
11
22
|
def initialize(type, options)
|
|
12
23
|
super
|
|
13
|
-
@
|
|
14
|
-
@values.
|
|
15
|
-
@
|
|
24
|
+
@mapping = options.fetch(:mapping).freeze
|
|
25
|
+
@values = @mapping.keys.freeze
|
|
26
|
+
@inverted_mapping = @mapping.invert.freeze
|
|
27
|
+
freeze
|
|
16
28
|
end
|
|
17
29
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
elsif mapping.key?(input)
|
|
23
|
-
mapping[input]
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
type[value || input]
|
|
30
|
+
# @param [Object] input
|
|
31
|
+
# @return [Object]
|
|
32
|
+
def call(input = Undefined)
|
|
33
|
+
type[map_value(input)]
|
|
27
34
|
end
|
|
28
35
|
alias_method :[], :call
|
|
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
|
+
|
|
46
|
+
def default(*)
|
|
47
|
+
raise '.enum(*values).default(value) is not supported. Call '\
|
|
48
|
+
'.default(value).enum(*values) instead'
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Check whether a value is in the enum
|
|
52
|
+
alias_method :include?, :valid?
|
|
53
|
+
|
|
54
|
+
# @api public
|
|
55
|
+
#
|
|
56
|
+
# @see Nominal#to_ast
|
|
57
|
+
def to_ast(meta: true)
|
|
58
|
+
[:enum, [type.to_ast(meta: meta),
|
|
59
|
+
mapping,
|
|
60
|
+
meta ? self.meta : EMPTY_HASH]]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# @return [String]
|
|
64
|
+
# @api public
|
|
65
|
+
def to_s
|
|
66
|
+
PRINTER.(self)
|
|
67
|
+
end
|
|
68
|
+
alias_method :inspect, :to_s
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
# Maps a value
|
|
73
|
+
#
|
|
74
|
+
# @param [Object]
|
|
75
|
+
# @return [Object]
|
|
76
|
+
# @api private
|
|
77
|
+
def map_value(input)
|
|
78
|
+
if input.equal?(Undefined)
|
|
79
|
+
type.call
|
|
80
|
+
elsif mapping.key?(input)
|
|
81
|
+
input
|
|
82
|
+
else
|
|
83
|
+
inverted_mapping.fetch(input, input)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
29
86
|
end
|
|
30
87
|
end
|
|
31
88
|
end
|
data/lib/dry/types/errors.rb
CHANGED
|
@@ -1,33 +1,49 @@
|
|
|
1
1
|
module Dry
|
|
2
2
|
module Types
|
|
3
|
-
extend Dry::
|
|
3
|
+
extend Dry::Core::ClassAttributes
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
# @!attribute [r] namespace
|
|
6
|
+
# @return [Container{String => Nominal}]
|
|
7
|
+
defines :namespace
|
|
8
|
+
|
|
9
|
+
namespace self
|
|
6
10
|
|
|
7
11
|
class SchemaError < TypeError
|
|
8
|
-
|
|
9
|
-
|
|
12
|
+
# @param [String,Symbol] key
|
|
13
|
+
# @param [Object] value
|
|
14
|
+
# @param [String, #to_s] result
|
|
15
|
+
def initialize(key, value, result)
|
|
16
|
+
super("#{value.inspect} (#{value.class}) has invalid type for :#{key} violates constraints (#{result} failed)")
|
|
10
17
|
end
|
|
11
18
|
end
|
|
12
19
|
|
|
20
|
+
MapError = Class.new(TypeError)
|
|
21
|
+
|
|
13
22
|
SchemaKeyError = Class.new(KeyError)
|
|
14
23
|
private_constant(:SchemaKeyError)
|
|
15
24
|
|
|
16
25
|
class MissingKeyError < SchemaKeyError
|
|
26
|
+
# @param [String,Symbol] key
|
|
17
27
|
def initialize(key)
|
|
18
28
|
super(":#{key} is missing in Hash input")
|
|
19
29
|
end
|
|
20
30
|
end
|
|
21
31
|
|
|
22
32
|
class UnknownKeysError < SchemaKeyError
|
|
33
|
+
# @param [<String, Symbol>] keys
|
|
23
34
|
def initialize(*keys)
|
|
24
35
|
super("unexpected keys #{keys.inspect} in Hash input")
|
|
25
36
|
end
|
|
26
37
|
end
|
|
27
38
|
|
|
28
|
-
ConstraintError
|
|
29
|
-
|
|
39
|
+
class ConstraintError < TypeError
|
|
40
|
+
# @return [String, #to_s]
|
|
41
|
+
attr_reader :result
|
|
42
|
+
# @return [Object]
|
|
43
|
+
attr_reader :input
|
|
30
44
|
|
|
45
|
+
# @param [String, #to_s] result
|
|
46
|
+
# @param [Object] input
|
|
31
47
|
def initialize(result, input)
|
|
32
48
|
@result = result
|
|
33
49
|
@input = input
|
|
@@ -39,6 +55,7 @@ module Dry
|
|
|
39
55
|
end
|
|
40
56
|
end
|
|
41
57
|
|
|
58
|
+
# @return [String]
|
|
42
59
|
def to_s
|
|
43
60
|
"#{input.inspect} violates constraints (#{result} failed)"
|
|
44
61
|
end
|
|
@@ -4,24 +4,46 @@ require 'dry/types/decorator'
|
|
|
4
4
|
module Dry
|
|
5
5
|
module Types
|
|
6
6
|
class Maybe
|
|
7
|
-
include
|
|
7
|
+
include Type
|
|
8
|
+
include Dry::Equalizer(:type, :options, inspect: false)
|
|
8
9
|
include Decorator
|
|
9
10
|
include Builder
|
|
10
11
|
include Dry::Monads::Maybe::Mixin
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
# @param [Dry::Monads::Maybe, Object] input
|
|
14
|
+
# @return [Dry::Monads::Maybe]
|
|
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
|
|
14
24
|
end
|
|
15
25
|
alias_method :[], :call
|
|
16
26
|
|
|
17
|
-
|
|
18
|
-
|
|
27
|
+
# @param [Object] input
|
|
28
|
+
# @return [Result::Success]
|
|
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)
|
|
19
37
|
end
|
|
20
38
|
|
|
21
|
-
|
|
39
|
+
# @return [true]
|
|
40
|
+
def default?
|
|
22
41
|
true
|
|
23
42
|
end
|
|
24
43
|
|
|
44
|
+
# @param [Object] value
|
|
45
|
+
# @see Dry::Types::Builder#default
|
|
46
|
+
# @raise [ArgumentError] if nil provided as default value
|
|
25
47
|
def default(value)
|
|
26
48
|
if value.nil?
|
|
27
49
|
raise ArgumentError, "nil cannot be used as a default of a maybe type"
|
|
@@ -32,23 +54,20 @@ module Dry
|
|
|
32
54
|
end
|
|
33
55
|
|
|
34
56
|
module Builder
|
|
57
|
+
# @return [Maybe]
|
|
35
58
|
def maybe
|
|
36
59
|
Maybe.new(Types['strict.nil'] | self)
|
|
37
60
|
end
|
|
38
61
|
end
|
|
39
62
|
|
|
40
|
-
class
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
super
|
|
47
|
-
end
|
|
63
|
+
class Printer
|
|
64
|
+
MAPPING[Maybe] = :visit_maybe
|
|
65
|
+
|
|
66
|
+
def visit_maybe(maybe)
|
|
67
|
+
visit(maybe.type) do |type|
|
|
68
|
+
yield "Maybe<#{ type }>"
|
|
48
69
|
end
|
|
49
70
|
end
|
|
50
|
-
|
|
51
|
-
Schema.include MaybeTypes
|
|
52
71
|
end
|
|
53
72
|
|
|
54
73
|
# Register non-coercible maybe types
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
require 'dry/types/container'
|
|
2
|
+
|
|
3
|
+
module Dry
|
|
4
|
+
module Types
|
|
5
|
+
class FnContainer
|
|
6
|
+
# @api private
|
|
7
|
+
def self.container
|
|
8
|
+
@container ||= Container.new
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# @api private
|
|
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
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @api private
|
|
20
|
+
def self.[](fn_name)
|
|
21
|
+
if container.key?(fn_name)
|
|
22
|
+
container[fn_name]
|
|
23
|
+
else
|
|
24
|
+
fn_name
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @api private
|
|
29
|
+
def self.register_name(function)
|
|
30
|
+
"fn_#{function.object_id}"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require 'dry/types/constructor'
|
|
2
|
+
|
|
3
|
+
module Dry
|
|
4
|
+
module Types
|
|
5
|
+
class Hash < Nominal
|
|
6
|
+
class Constructor < ::Dry::Types::Constructor
|
|
7
|
+
# @api private
|
|
8
|
+
def constructor_type
|
|
9
|
+
::Dry::Types::Hash::Constructor
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def composable?(value)
|
|
15
|
+
super && !value.is_a?(Schema::Key)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
data/lib/dry/types/hash.rb
CHANGED
|
@@ -1,45 +1,125 @@
|
|
|
1
|
-
require 'dry/types/hash/
|
|
1
|
+
require 'dry/types/hash/constructor'
|
|
2
2
|
|
|
3
3
|
module Dry
|
|
4
4
|
module Types
|
|
5
|
-
class Hash <
|
|
6
|
-
|
|
7
|
-
member_types = type_map.each_with_object({}) { |(name, type), result|
|
|
8
|
-
result[name] =
|
|
9
|
-
case type
|
|
10
|
-
when String, Class then Types[type]
|
|
11
|
-
else type
|
|
12
|
-
end
|
|
13
|
-
}
|
|
5
|
+
class Hash < Nominal
|
|
6
|
+
NOT_REQUIRED = { required: false }.freeze
|
|
14
7
|
|
|
15
|
-
|
|
8
|
+
# @overload schmea(type_map, meta = EMPTY_HASH)
|
|
9
|
+
# @param [{Symbol => Dry::Types::Nominal}] type_map
|
|
10
|
+
# @param [Hash] meta
|
|
11
|
+
# @return [Dry::Types::Schema]
|
|
12
|
+
# @overload schema(keys)
|
|
13
|
+
# @param [Array<Dry::Types::Schema::Key>] key List of schema keys
|
|
14
|
+
# @param [Hash] meta
|
|
15
|
+
# @return [Dry::Types::Schema]
|
|
16
|
+
def schema(keys_or_map, meta = EMPTY_HASH)
|
|
17
|
+
if keys_or_map.is_a?(::Array)
|
|
18
|
+
keys = keys_or_map
|
|
19
|
+
else
|
|
20
|
+
keys = build_keys(keys_or_map)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
Schema.new(primitive, keys: keys, **options, meta: self.meta.merge(meta))
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Build a map type
|
|
27
|
+
#
|
|
28
|
+
# @param [Type] key_type
|
|
29
|
+
# @param [Type] value_type
|
|
30
|
+
# @return [Map]
|
|
31
|
+
def map(key_type, value_type)
|
|
32
|
+
Map.new(
|
|
33
|
+
primitive,
|
|
34
|
+
key_type: resolve_type(key_type),
|
|
35
|
+
value_type: resolve_type(value_type),
|
|
36
|
+
meta: meta
|
|
37
|
+
)
|
|
16
38
|
end
|
|
17
39
|
|
|
18
|
-
|
|
19
|
-
|
|
40
|
+
# @param [{Symbol => Nominal}] type_map
|
|
41
|
+
# @return [Schema]
|
|
42
|
+
def weak(*)
|
|
43
|
+
raise "Support for old hash schemas was removed, please refer to the CHANGELOG "\
|
|
44
|
+
"on how to proceed with the new API https://github.com/dry-rb/dry-types/blob/master/CHANGELOG.md"
|
|
20
45
|
end
|
|
46
|
+
alias_method :permissive, :weak
|
|
47
|
+
alias_method :strict, :weak
|
|
48
|
+
alias_method :strict_with_defaults, :weak
|
|
49
|
+
alias_method :symbolized, :weak
|
|
21
50
|
|
|
22
|
-
|
|
23
|
-
|
|
51
|
+
# Injects a type transformation function for building schemas
|
|
52
|
+
# @param [#call,nil] proc
|
|
53
|
+
# @param [#call,nil] block
|
|
54
|
+
# @return [Hash]
|
|
55
|
+
def with_type_transform(proc = nil, &block)
|
|
56
|
+
fn = proc || block
|
|
57
|
+
|
|
58
|
+
if fn.nil?
|
|
59
|
+
raise ArgumentError, "a block or callable argument is required"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
handle = Dry::Types::FnContainer.register(fn)
|
|
63
|
+
with(type_transform_fn: handle)
|
|
24
64
|
end
|
|
25
65
|
|
|
26
|
-
|
|
27
|
-
|
|
66
|
+
# @api private
|
|
67
|
+
def constructor_type
|
|
68
|
+
::Dry::Types::Hash::Constructor
|
|
28
69
|
end
|
|
29
70
|
|
|
30
|
-
|
|
31
|
-
|
|
71
|
+
# Whether the type transforms types of schemas created by {Dry::Types::Hash#schema}
|
|
72
|
+
# @return [Boolean]
|
|
73
|
+
# @api public
|
|
74
|
+
def transform_types?
|
|
75
|
+
!options[:type_transform_fn].nil?
|
|
32
76
|
end
|
|
33
77
|
|
|
34
|
-
|
|
35
|
-
|
|
78
|
+
# @param meta [Boolean] Whether to dump the meta to the AST
|
|
79
|
+
# @return [Array] An AST representation
|
|
80
|
+
def to_ast(meta: true)
|
|
81
|
+
if RUBY_VERSION >= "2.5"
|
|
82
|
+
opts = options.slice(:type_transform_fn)
|
|
83
|
+
else
|
|
84
|
+
opts = options.select { |k, _| k == :type_transform_fn }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
[:hash, [opts, meta ? self.meta : EMPTY_HASH]]
|
|
36
88
|
end
|
|
37
89
|
|
|
38
90
|
private
|
|
39
91
|
|
|
40
|
-
|
|
41
|
-
|
|
92
|
+
# @api private
|
|
93
|
+
def build_keys(type_map)
|
|
94
|
+
type_fn = options.fetch(:type_transform_fn, Schema::NO_TRANSFORM)
|
|
95
|
+
type_transform = Dry::Types::FnContainer[type_fn]
|
|
96
|
+
|
|
97
|
+
type_map.map do |map_key, type|
|
|
98
|
+
name, options = key_name(map_key)
|
|
99
|
+
key = Schema::Key.new(resolve_type(type), name, options)
|
|
100
|
+
type_transform.(key)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# @api private
|
|
105
|
+
def resolve_type(type)
|
|
106
|
+
case type
|
|
107
|
+
when String, Class then Types[type]
|
|
108
|
+
else type
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# @api private
|
|
113
|
+
def key_name(key)
|
|
114
|
+
if key.to_s.end_with?('?')
|
|
115
|
+
[key.to_s.chop.to_sym, NOT_REQUIRED]
|
|
116
|
+
else
|
|
117
|
+
[key, EMPTY_HASH]
|
|
118
|
+
end
|
|
42
119
|
end
|
|
43
120
|
end
|
|
44
121
|
end
|
|
45
122
|
end
|
|
123
|
+
|
|
124
|
+
require 'dry/types/schema/key'
|
|
125
|
+
require 'dry/types/schema'
|
data/lib/dry/types/json.rb
CHANGED
|
@@ -3,31 +3,31 @@ require 'dry/types/coercions/json'
|
|
|
3
3
|
module Dry
|
|
4
4
|
module Types
|
|
5
5
|
register('json.nil') do
|
|
6
|
-
self['nil'].constructor(Coercions::JSON.method(:to_nil))
|
|
6
|
+
self['nominal.nil'].constructor(Coercions::JSON.method(:to_nil))
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
register('json.date') do
|
|
10
|
-
self['date'].constructor(Coercions::JSON.method(:to_date))
|
|
10
|
+
self['nominal.date'].constructor(Coercions::JSON.method(:to_date))
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
register('json.date_time') do
|
|
14
|
-
self['date_time'].constructor(Coercions::JSON.method(:to_date_time))
|
|
14
|
+
self['nominal.date_time'].constructor(Coercions::JSON.method(:to_date_time))
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
register('json.time') do
|
|
18
|
-
self['time'].constructor(Coercions::JSON.method(:to_time))
|
|
18
|
+
self['nominal.time'].constructor(Coercions::JSON.method(:to_time))
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
register('json.decimal') do
|
|
22
|
-
self['decimal'].constructor(Coercions::JSON.method(:to_decimal))
|
|
22
|
+
self['nominal.decimal'].constructor(Coercions::JSON.method(:to_decimal))
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
register('json.array') do
|
|
26
|
-
self['array'].safe
|
|
26
|
+
self['nominal.array'].safe
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
register('json.hash') do
|
|
30
|
-
self['hash'].safe
|
|
30
|
+
self['nominal.hash'].safe
|
|
31
31
|
end
|
|
32
32
|
end
|
|
33
33
|
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
module Dry
|
|
2
|
+
module Types
|
|
3
|
+
class Map < Nominal
|
|
4
|
+
def initialize(_primitive, key_type: Types['any'], value_type: Types['any'], meta: EMPTY_HASH)
|
|
5
|
+
super(_primitive, key_type: key_type, value_type: value_type, meta: meta)
|
|
6
|
+
validate_options!
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# @return [Type]
|
|
10
|
+
def key_type
|
|
11
|
+
options[:key_type]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# @return [Type]
|
|
15
|
+
def value_type
|
|
16
|
+
options[:value_type]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @return [String]
|
|
20
|
+
def name
|
|
21
|
+
"Map"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @param [Hash] hash
|
|
25
|
+
# @return [Hash]
|
|
26
|
+
def call(hash)
|
|
27
|
+
try(hash) do |failure|
|
|
28
|
+
raise MapError, failure.error.join("\n")
|
|
29
|
+
end.input
|
|
30
|
+
end
|
|
31
|
+
alias_method :[], :call
|
|
32
|
+
|
|
33
|
+
# @param [Hash] hash
|
|
34
|
+
# @return [Boolean]
|
|
35
|
+
def valid?(hash)
|
|
36
|
+
coerce(hash).success?
|
|
37
|
+
end
|
|
38
|
+
alias_method :===, :valid?
|
|
39
|
+
|
|
40
|
+
# @param [Hash] hash
|
|
41
|
+
# @return [Result]
|
|
42
|
+
def try(hash)
|
|
43
|
+
result = coerce(hash)
|
|
44
|
+
return result if result.success? || !block_given?
|
|
45
|
+
yield(result)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @param meta [Boolean] Whether to dump the meta to the AST
|
|
49
|
+
# @return [Array] An AST representation
|
|
50
|
+
def to_ast(meta: true)
|
|
51
|
+
[:map,
|
|
52
|
+
[key_type.to_ast(meta: true), value_type.to_ast(meta: true),
|
|
53
|
+
meta ? self.meta : EMPTY_HASH]]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# @return [Boolean]
|
|
57
|
+
def constrained?
|
|
58
|
+
value_type.constrained?
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def coerce(input)
|
|
64
|
+
return failure(
|
|
65
|
+
input, "#{input.inspect} must be an instance of #{primitive}"
|
|
66
|
+
) unless primitive?(input)
|
|
67
|
+
|
|
68
|
+
output, failures = {}, []
|
|
69
|
+
|
|
70
|
+
input.each do |k,v|
|
|
71
|
+
res_k = options[:key_type].try(k)
|
|
72
|
+
res_v = options[:value_type].try(v)
|
|
73
|
+
if res_k.failure?
|
|
74
|
+
failures << "input key #{k.inspect} is invalid: #{res_k.error}"
|
|
75
|
+
elsif output.key?(res_k.input)
|
|
76
|
+
failures << "duplicate coerced hash key #{res_k.input.inspect}"
|
|
77
|
+
elsif res_v.failure?
|
|
78
|
+
failures << "input value #{v.inspect} for key #{k.inspect} is invalid: #{res_v.error}"
|
|
79
|
+
else
|
|
80
|
+
output[res_k.input] = res_v.input
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
return success(output) if failures.empty?
|
|
85
|
+
|
|
86
|
+
failure(input, failures)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def validate_options!
|
|
90
|
+
%i(key_type value_type).each do |opt|
|
|
91
|
+
type = send(opt)
|
|
92
|
+
next if type.is_a?(Type)
|
|
93
|
+
raise MapError, ":#{opt} must be a #{Type}, got: #{type.inspect}"
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|