dry-types 0.13.2 → 1.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +763 -233
  3. data/LICENSE +17 -17
  4. data/README.md +15 -13
  5. data/dry-types.gemspec +28 -28
  6. data/lib/dry-types.rb +3 -1
  7. data/lib/dry/types.rb +156 -76
  8. data/lib/dry/types/any.rb +32 -12
  9. data/lib/dry/types/array.rb +19 -6
  10. data/lib/dry/types/array/constructor.rb +32 -0
  11. data/lib/dry/types/array/member.rb +75 -16
  12. data/lib/dry/types/builder.rb +131 -15
  13. data/lib/dry/types/builder_methods.rb +49 -20
  14. data/lib/dry/types/coercions.rb +76 -22
  15. data/lib/dry/types/coercions/json.rb +43 -7
  16. data/lib/dry/types/coercions/params.rb +118 -31
  17. data/lib/dry/types/compat.rb +0 -2
  18. data/lib/dry/types/compiler.rb +56 -41
  19. data/lib/dry/types/constrained.rb +81 -32
  20. data/lib/dry/types/constrained/coercible.rb +36 -6
  21. data/lib/dry/types/constraints.rb +18 -4
  22. data/lib/dry/types/constructor.rb +127 -54
  23. data/lib/dry/types/constructor/function.rb +216 -0
  24. data/lib/dry/types/constructor/wrapper.rb +94 -0
  25. data/lib/dry/types/container.rb +7 -0
  26. data/lib/dry/types/core.rb +54 -21
  27. data/lib/dry/types/decorator.rb +38 -17
  28. data/lib/dry/types/default.rb +61 -16
  29. data/lib/dry/types/enum.rb +43 -20
  30. data/lib/dry/types/errors.rb +75 -9
  31. data/lib/dry/types/extensions.rb +7 -1
  32. data/lib/dry/types/extensions/maybe.rb +74 -16
  33. data/lib/dry/types/extensions/monads.rb +29 -0
  34. data/lib/dry/types/fn_container.rb +6 -1
  35. data/lib/dry/types/hash.rb +86 -67
  36. data/lib/dry/types/hash/constructor.rb +33 -0
  37. data/lib/dry/types/inflector.rb +3 -1
  38. data/lib/dry/types/json.rb +18 -16
  39. data/lib/dry/types/lax.rb +75 -0
  40. data/lib/dry/types/map.rb +76 -33
  41. data/lib/dry/types/meta.rb +51 -0
  42. data/lib/dry/types/module.rb +120 -0
  43. data/lib/dry/types/nominal.rb +210 -0
  44. data/lib/dry/types/options.rb +13 -26
  45. data/lib/dry/types/params.rb +39 -25
  46. data/lib/dry/types/predicate_inferrer.rb +238 -0
  47. data/lib/dry/types/predicate_registry.rb +34 -0
  48. data/lib/dry/types/primitive_inferrer.rb +97 -0
  49. data/lib/dry/types/printable.rb +16 -0
  50. data/lib/dry/types/printer.rb +315 -0
  51. data/lib/dry/types/result.rb +29 -3
  52. data/lib/dry/types/schema.rb +408 -0
  53. data/lib/dry/types/schema/key.rb +156 -0
  54. data/lib/dry/types/spec/types.rb +103 -33
  55. data/lib/dry/types/sum.rb +84 -35
  56. data/lib/dry/types/type.rb +49 -0
  57. data/lib/dry/types/version.rb +3 -1
  58. metadata +68 -79
  59. data/.gitignore +0 -10
  60. data/.rspec +0 -2
  61. data/.travis.yml +0 -29
  62. data/.yardopts +0 -5
  63. data/CONTRIBUTING.md +0 -29
  64. data/Gemfile +0 -24
  65. data/Rakefile +0 -20
  66. data/benchmarks/hash_schemas.rb +0 -51
  67. data/lib/dry/types/compat/form_types.rb +0 -27
  68. data/lib/dry/types/compat/int.rb +0 -14
  69. data/lib/dry/types/definition.rb +0 -113
  70. data/lib/dry/types/hash/schema.rb +0 -199
  71. data/lib/dry/types/hash/schema_builder.rb +0 -75
  72. 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"
@@ -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 Definition#initialize
8
- def initialize(*args, meta: EMPTY_HASH, **options)
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
- # @overload meta(data)
24
- # @param [Hash] new metadata to merge into existing metadata
25
- # @return [Type] new type with added metadata
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
@@ -1,53 +1,67 @@
1
- require 'dry/types/coercions/params'
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/types/coercions/params"
2
4
 
3
5
  module Dry
4
6
  module Types
5
- register('params.nil') do
6
- self['nil'].constructor(Coercions::Params.method(:to_nil))
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('params.date') do
10
- self['date'].constructor(Coercions::Params.method(:to_date))
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('params.date_time') do
14
- self['date_time'].constructor(Coercions::Params.method(:to_date_time))
19
+ register("params.time") do
20
+ self["nominal.time"].constructor(Coercions::Params.method(:to_time))
15
21
  end
16
22
 
17
- register('params.time') do
18
- self['time'].constructor(Coercions::Params.method(:to_time))
23
+ register("params.true") do
24
+ self["nominal.true"].constructor(Coercions::Params.method(:to_true))
19
25
  end
20
26
 
21
- register('params.true') do
22
- self['true'].constructor(Coercions::Params.method(:to_true))
27
+ register("params.false") do
28
+ self["nominal.false"].constructor(Coercions::Params.method(:to_false))
23
29
  end
24
30
 
25
- register('params.false') do
26
- self['false'].constructor(Coercions::Params.method(:to_false))
31
+ register("params.bool") do
32
+ self["params.true"] | self["params.false"]
27
33
  end
28
34
 
29
- register('params.bool') do
30
- (self['params.true'] | self['params.false']).safe
35
+ register("params.integer") do
36
+ self["nominal.integer"].constructor(Coercions::Params.method(:to_int))
31
37
  end
32
38
 
33
- register('params.integer') do
34
- self['integer'].constructor(Coercions::Params.method(:to_int))
39
+ register("params.float") do
40
+ self["nominal.float"].constructor(Coercions::Params.method(:to_float))
35
41
  end
36
42
 
37
- register('params.float') do
38
- self['float'].constructor(Coercions::Params.method(:to_float))
43
+ register("params.decimal") do
44
+ self["nominal.decimal"].constructor(Coercions::Params.method(:to_decimal))
39
45
  end
40
46
 
41
- register('params.decimal') do
42
- self['decimal'].constructor(Coercions::Params.method(:to_decimal))
47
+ register("params.array") do
48
+ self["nominal.array"].constructor(Coercions::Params.method(:to_ary))
43
49
  end
44
50
 
45
- register('params.array') do
46
- self['array'].constructor(Coercions::Params.method(:to_ary)).safe
51
+ register("params.hash") do
52
+ self["nominal.hash"].constructor(Coercions::Params.method(:to_hash))
47
53
  end
48
54
 
49
- register('params.hash') do
50
- self['hash'].constructor(Coercions::Params.method(:to_hash)).safe
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