dry-types 0.14.0 → 0.15.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.codeclimate.yml +15 -0
- data/.rubocop.yml +43 -0
- data/.travis.yml +2 -3
- data/CHANGELOG.md +119 -1
- data/Gemfile +2 -2
- data/benchmarks/hash_schemas.rb +5 -5
- data/dry-types.gemspec +2 -2
- data/lib/dry/types.rb +67 -45
- data/lib/dry/types/any.rb +11 -2
- data/lib/dry/types/array.rb +1 -4
- data/lib/dry/types/array/member.rb +2 -2
- data/lib/dry/types/builder.rb +23 -3
- data/lib/dry/types/builder_methods.rb +10 -11
- data/lib/dry/types/coercions/params.rb +2 -0
- data/lib/dry/types/compat.rb +0 -2
- data/lib/dry/types/compiler.rb +25 -33
- data/lib/dry/types/constrained.rb +5 -4
- data/lib/dry/types/constructor.rb +33 -12
- data/lib/dry/types/container.rb +2 -0
- data/lib/dry/types/core.rb +22 -12
- data/lib/dry/types/default.rb +4 -4
- data/lib/dry/types/enum.rb +10 -3
- data/lib/dry/types/errors.rb +1 -1
- data/lib/dry/types/extensions/maybe.rb +11 -1
- data/lib/dry/types/hash.rb +70 -63
- data/lib/dry/types/hash/constructor.rb +20 -0
- data/lib/dry/types/json.rb +7 -7
- data/lib/dry/types/map.rb +6 -1
- data/lib/dry/types/module.rb +115 -0
- data/lib/dry/types/{definition.rb → nominal.rb} +10 -4
- data/lib/dry/types/options.rb +2 -2
- data/lib/dry/types/params.rb +11 -11
- data/lib/dry/types/printable.rb +12 -0
- data/lib/dry/types/printer.rb +309 -0
- data/lib/dry/types/result.rb +2 -2
- data/lib/dry/types/safe.rb +4 -2
- data/lib/dry/types/schema.rb +298 -0
- data/lib/dry/types/schema/key.rb +130 -0
- data/lib/dry/types/spec/types.rb +12 -4
- data/lib/dry/types/sum.rb +14 -15
- data/lib/dry/types/version.rb +1 -1
- metadata +22 -12
- data/lib/dry/types/compat/form_types.rb +0 -27
- data/lib/dry/types/compat/int.rb +0 -14
- data/lib/dry/types/hash/schema.rb +0 -199
- data/lib/dry/types/hash/schema_builder.rb +0 -75
data/lib/dry/types/default.rb
CHANGED
@@ -4,12 +4,13 @@ module Dry
|
|
4
4
|
module Types
|
5
5
|
class Default
|
6
6
|
include Type
|
7
|
-
include Dry::Equalizer(:type, :options, :value)
|
8
7
|
include Decorator
|
9
8
|
include Builder
|
9
|
+
include Printable
|
10
|
+
include Dry::Equalizer(:type, :options, :value, inspect: false)
|
10
11
|
|
11
12
|
class Callable < Default
|
12
|
-
include Dry::Equalizer(:type, :options)
|
13
|
+
include Dry::Equalizer(:type, :options, inspect: false)
|
13
14
|
|
14
15
|
# Evaluates given callable
|
15
16
|
# @return [Object]
|
@@ -67,8 +68,7 @@ module Dry
|
|
67
68
|
if input.equal?(Undefined)
|
68
69
|
evaluate
|
69
70
|
else
|
70
|
-
|
71
|
-
output.nil? ? evaluate : output
|
71
|
+
Undefined.default(type[input]) { evaluate }
|
72
72
|
end
|
73
73
|
end
|
74
74
|
alias_method :[], :call
|
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, :mapping)
|
7
|
+
include Dry::Equalizer(:type, :options, :mapping, inspect: false)
|
8
8
|
include Decorator
|
9
9
|
|
10
10
|
# @return [Array]
|
@@ -53,18 +53,25 @@ module Dry
|
|
53
53
|
|
54
54
|
# @api public
|
55
55
|
#
|
56
|
-
# @see
|
56
|
+
# @see Nominal#to_ast
|
57
57
|
def to_ast(meta: true)
|
58
58
|
[:enum, [type.to_ast(meta: meta),
|
59
59
|
mapping,
|
60
60
|
meta ? self.meta : EMPTY_HASH]]
|
61
61
|
end
|
62
62
|
|
63
|
+
# @return [String]
|
64
|
+
# @api public
|
65
|
+
def to_s
|
66
|
+
PRINTER.(self)
|
67
|
+
end
|
68
|
+
alias_method :inspect, :to_s
|
69
|
+
|
63
70
|
private
|
64
71
|
|
65
72
|
# Maps a value
|
66
73
|
#
|
67
|
-
# @
|
74
|
+
# @param [Object]
|
68
75
|
# @return [Object]
|
69
76
|
# @api private
|
70
77
|
def map_value(input)
|
data/lib/dry/types/errors.rb
CHANGED
@@ -5,7 +5,7 @@ module Dry
|
|
5
5
|
module Types
|
6
6
|
class Maybe
|
7
7
|
include Type
|
8
|
-
include Dry::Equalizer(:type, :options)
|
8
|
+
include Dry::Equalizer(:type, :options, inspect: false)
|
9
9
|
include Decorator
|
10
10
|
include Builder
|
11
11
|
include Dry::Monads::Maybe::Mixin
|
@@ -60,6 +60,16 @@ module Dry
|
|
60
60
|
end
|
61
61
|
end
|
62
62
|
|
63
|
+
class Printer
|
64
|
+
MAPPING[Maybe] = :visit_maybe
|
65
|
+
|
66
|
+
def visit_maybe(maybe)
|
67
|
+
visit(maybe.type) do |type|
|
68
|
+
yield "Maybe<#{ type }>"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
63
73
|
# Register non-coercible maybe types
|
64
74
|
NON_NIL.each_key do |name|
|
65
75
|
register("maybe.strict.#{name}", self["strict.#{name}"].maybe)
|
data/lib/dry/types/hash.rb
CHANGED
@@ -1,27 +1,26 @@
|
|
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
|
-
|
8
|
-
# @
|
9
|
-
#
|
10
|
-
#
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
5
|
+
class Hash < Nominal
|
6
|
+
NOT_REQUIRED = { required: false }.freeze
|
7
|
+
|
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
|
16
19
|
else
|
17
|
-
|
18
|
-
primitive,
|
19
|
-
**options,
|
20
|
-
member_types: member_types,
|
21
|
-
meta: meta,
|
22
|
-
hash_type: constructor
|
23
|
-
)
|
20
|
+
keys = build_keys(keys_or_map)
|
24
21
|
end
|
22
|
+
|
23
|
+
Schema.new(primitive, keys: keys, **options, meta: self.meta.merge(meta))
|
25
24
|
end
|
26
25
|
|
27
26
|
# Build a map type
|
@@ -38,43 +37,16 @@ module Dry
|
|
38
37
|
)
|
39
38
|
end
|
40
39
|
|
41
|
-
# @param [{Symbol =>
|
42
|
-
# @return [Schema]
|
43
|
-
def weak(type_map)
|
44
|
-
schema(type_map, :weak)
|
45
|
-
end
|
46
|
-
|
47
|
-
# @param [{Symbol => Definition}] type_map
|
48
|
-
# @return [Schema]
|
49
|
-
def permissive(type_map)
|
50
|
-
schema(type_map, :permissive)
|
51
|
-
end
|
52
|
-
|
53
|
-
# @param [{Symbol => Definition}] type_map
|
40
|
+
# @param [{Symbol => Nominal}] type_map
|
54
41
|
# @return [Schema]
|
55
|
-
def
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
# @param [{Symbol => Definition}] type_map
|
60
|
-
# @return [Schema]
|
61
|
-
def strict_with_defaults(type_map)
|
62
|
-
schema(type_map, :strict_with_defaults)
|
63
|
-
end
|
64
|
-
|
65
|
-
# @param [{Symbol => Definition}] type_map
|
66
|
-
# @return [Schema]
|
67
|
-
def symbolized(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)
|
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"
|
77
45
|
end
|
46
|
+
alias_method :permissive, :weak
|
47
|
+
alias_method :strict, :weak
|
48
|
+
alias_method :strict_with_defaults, :weak
|
49
|
+
alias_method :symbolized, :weak
|
78
50
|
|
79
51
|
# Injects a type transformation function for building schemas
|
80
52
|
# @param [#call,nil] proc
|
@@ -88,22 +60,45 @@ module Dry
|
|
88
60
|
end
|
89
61
|
|
90
62
|
handle = Dry::Types::FnContainer.register(fn)
|
91
|
-
|
63
|
+
with(type_transform_fn: handle)
|
64
|
+
end
|
65
|
+
|
66
|
+
# @api private
|
67
|
+
def constructor_type
|
68
|
+
::Dry::Types::Hash::Constructor
|
69
|
+
end
|
70
|
+
|
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?
|
76
|
+
end
|
77
|
+
|
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]]
|
92
88
|
end
|
93
89
|
|
94
90
|
private
|
95
91
|
|
96
92
|
# @api private
|
97
|
-
def
|
98
|
-
type_fn =
|
93
|
+
def build_keys(type_map)
|
94
|
+
type_fn = options.fetch(:type_transform_fn, Schema::NO_TRANSFORM)
|
99
95
|
type_transform = Dry::Types::FnContainer[type_fn]
|
100
96
|
|
101
|
-
type_map.
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
}
|
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
|
107
102
|
end
|
108
103
|
|
109
104
|
# @api private
|
@@ -113,6 +108,18 @@ module Dry
|
|
113
108
|
else type
|
114
109
|
end
|
115
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
|
119
|
+
end
|
116
120
|
end
|
117
121
|
end
|
118
122
|
end
|
123
|
+
|
124
|
+
require 'dry/types/schema/key'
|
125
|
+
require 'dry/types/schema'
|
@@ -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/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
|
data/lib/dry/types/map.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Dry
|
2
2
|
module Types
|
3
|
-
class Map <
|
3
|
+
class Map < Nominal
|
4
4
|
def initialize(_primitive, key_type: Types['any'], value_type: Types['any'], meta: EMPTY_HASH)
|
5
5
|
super(_primitive, key_type: key_type, value_type: value_type, meta: meta)
|
6
6
|
validate_options!
|
@@ -53,6 +53,11 @@ module Dry
|
|
53
53
|
meta ? self.meta : EMPTY_HASH]]
|
54
54
|
end
|
55
55
|
|
56
|
+
# @return [Boolean]
|
57
|
+
def constrained?
|
58
|
+
value_type.constrained?
|
59
|
+
end
|
60
|
+
|
56
61
|
private
|
57
62
|
|
58
63
|
def coerce(input)
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'dry/core/deprecations'
|
2
|
+
require 'dry/types/builder_methods'
|
3
|
+
|
4
|
+
module Dry
|
5
|
+
module Types
|
6
|
+
# Export types registered in a container as module constants.
|
7
|
+
# @example
|
8
|
+
# module Types
|
9
|
+
# include Dry::Types.module(:strict, :coercible, :nominal, default: :strict)
|
10
|
+
# end
|
11
|
+
# # Types.constants
|
12
|
+
# # => [:Class, :Strict, :Symbol, :Integer, :Float, :String, :Array, :Hash,
|
13
|
+
# # :Decimal, :Nil, :True, :False, :Bool, :Date, :Nominal, :DateTime, :Range,
|
14
|
+
# # :Coercible, :Time]
|
15
|
+
class Module < ::Module
|
16
|
+
def initialize(registry, *args)
|
17
|
+
@registry = registry
|
18
|
+
check_parameters(*args)
|
19
|
+
constants = type_constants(*args)
|
20
|
+
define_constants(constants)
|
21
|
+
extend(BuilderMethods)
|
22
|
+
|
23
|
+
if constants.key?(:Nominal)
|
24
|
+
singleton_class.send(:define_method, :included) do |base|
|
25
|
+
super(base)
|
26
|
+
base.instance_exec(const_get(:Nominal, false)) do |nominal|
|
27
|
+
extend Dry::Core::Deprecations[:'dry-types']
|
28
|
+
const_set(:Definition, nominal)
|
29
|
+
deprecate_constant(:Definition, message: "Nominal")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# @api private
|
36
|
+
def type_constants(*namespaces, default: Undefined, **aliases)
|
37
|
+
if namespaces.empty? && aliases.empty? && Undefined.equal?(default)
|
38
|
+
default_ns = :Strict
|
39
|
+
elsif Undefined.equal?(default)
|
40
|
+
default_ns = Undefined
|
41
|
+
else
|
42
|
+
default_ns = Inflector.camelize(default).to_sym
|
43
|
+
end
|
44
|
+
|
45
|
+
tree = registry_tree
|
46
|
+
|
47
|
+
if namespaces.empty? && aliases.empty?
|
48
|
+
modules = tree.select { |_, v| v.is_a?(::Hash) }.map(&:first)
|
49
|
+
else
|
50
|
+
modules = (namespaces + aliases.keys).map { |n| Inflector.camelize(n).to_sym }
|
51
|
+
end
|
52
|
+
|
53
|
+
tree.each_with_object({}) do |(key, value), constants|
|
54
|
+
if modules.include?(key)
|
55
|
+
name = aliases.fetch(Inflector.underscore(key).to_sym, key)
|
56
|
+
constants[name] = value
|
57
|
+
end
|
58
|
+
|
59
|
+
constants.update(value) if key == default_ns
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# @api private
|
64
|
+
def registry_tree
|
65
|
+
@registry_tree ||= @registry.keys.each_with_object({}) { |key, tree|
|
66
|
+
type = @registry[key]
|
67
|
+
*modules, const_name = key.split('.').map { |part|
|
68
|
+
Inflector.camelize(part).to_sym
|
69
|
+
}
|
70
|
+
next if modules.empty?
|
71
|
+
|
72
|
+
modules.reduce(tree) { |br, name| br[name] ||= {} }[const_name] = type
|
73
|
+
}.freeze
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
# @api private
|
79
|
+
def check_parameters(*namespaces, default: Undefined, **aliases)
|
80
|
+
referenced = namespaces.dup
|
81
|
+
referenced << default unless false.equal?(default) || Undefined.equal?(default)
|
82
|
+
referenced.concat(aliases.keys)
|
83
|
+
|
84
|
+
known = @registry.keys.map { |k|
|
85
|
+
ns, *path = k.split('.')
|
86
|
+
ns.to_sym unless path.empty?
|
87
|
+
}.compact.uniq
|
88
|
+
|
89
|
+
(referenced.uniq - known).each do |name|
|
90
|
+
raise ArgumentError,
|
91
|
+
"#{ name.inspect } is not a known type namespace. "\
|
92
|
+
"Supported options are #{ known.map(&:inspect).join(', ') }"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# @api private
|
97
|
+
def define_constants(constants, mod = self)
|
98
|
+
constants.each do |name, value|
|
99
|
+
case value
|
100
|
+
when ::Hash
|
101
|
+
if mod.const_defined?(name, false)
|
102
|
+
define_constants(value, mod.const_get(name, false))
|
103
|
+
else
|
104
|
+
m = ::Module.new
|
105
|
+
mod.const_set(name, m)
|
106
|
+
define_constants(value, m)
|
107
|
+
end
|
108
|
+
else
|
109
|
+
mod.const_set(name, value)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|