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.
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