dry-types 0.14.1 → 1.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +631 -134
  3. data/LICENSE +17 -17
  4. data/README.md +15 -13
  5. data/dry-types.gemspec +27 -30
  6. data/lib/dry/types/any.rb +32 -12
  7. data/lib/dry/types/array/constructor.rb +32 -0
  8. data/lib/dry/types/array/member.rb +75 -16
  9. data/lib/dry/types/array.rb +19 -6
  10. data/lib/dry/types/builder.rb +131 -15
  11. data/lib/dry/types/builder_methods.rb +49 -20
  12. data/lib/dry/types/coercions/json.rb +43 -7
  13. data/lib/dry/types/coercions/params.rb +118 -31
  14. data/lib/dry/types/coercions.rb +76 -22
  15. data/lib/dry/types/compat.rb +0 -2
  16. data/lib/dry/types/compiler.rb +56 -41
  17. data/lib/dry/types/constrained/coercible.rb +36 -6
  18. data/lib/dry/types/constrained.rb +81 -32
  19. data/lib/dry/types/constraints.rb +18 -4
  20. data/lib/dry/types/constructor/function.rb +216 -0
  21. data/lib/dry/types/constructor/wrapper.rb +94 -0
  22. data/lib/dry/types/constructor.rb +126 -56
  23. data/lib/dry/types/container.rb +7 -0
  24. data/lib/dry/types/core.rb +54 -21
  25. data/lib/dry/types/decorator.rb +38 -17
  26. data/lib/dry/types/default.rb +61 -16
  27. data/lib/dry/types/enum.rb +43 -20
  28. data/lib/dry/types/errors.rb +75 -9
  29. data/lib/dry/types/extensions/maybe.rb +74 -16
  30. data/lib/dry/types/extensions/monads.rb +29 -0
  31. data/lib/dry/types/extensions.rb +7 -1
  32. data/lib/dry/types/fn_container.rb +6 -1
  33. data/lib/dry/types/hash/constructor.rb +33 -0
  34. data/lib/dry/types/hash.rb +86 -67
  35. data/lib/dry/types/inflector.rb +3 -1
  36. data/lib/dry/types/json.rb +18 -16
  37. data/lib/dry/types/lax.rb +75 -0
  38. data/lib/dry/types/map.rb +76 -33
  39. data/lib/dry/types/meta.rb +51 -0
  40. data/lib/dry/types/module.rb +120 -0
  41. data/lib/dry/types/nominal.rb +210 -0
  42. data/lib/dry/types/options.rb +13 -26
  43. data/lib/dry/types/params.rb +39 -25
  44. data/lib/dry/types/predicate_inferrer.rb +238 -0
  45. data/lib/dry/types/predicate_registry.rb +34 -0
  46. data/lib/dry/types/primitive_inferrer.rb +97 -0
  47. data/lib/dry/types/printable.rb +16 -0
  48. data/lib/dry/types/printer.rb +315 -0
  49. data/lib/dry/types/result.rb +29 -3
  50. data/lib/dry/types/schema/key.rb +156 -0
  51. data/lib/dry/types/schema.rb +408 -0
  52. data/lib/dry/types/spec/types.rb +103 -33
  53. data/lib/dry/types/sum.rb +84 -35
  54. data/lib/dry/types/type.rb +49 -0
  55. data/lib/dry/types/version.rb +3 -1
  56. data/lib/dry/types.rb +156 -76
  57. data/lib/dry-types.rb +3 -1
  58. metadata +65 -78
  59. data/.gitignore +0 -10
  60. data/.rspec +0 -2
  61. data/.travis.yml +0 -27
  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
  73. data/log/.gitkeep +0 -0
data/lib/dry/types/sum.rb CHANGED
@@ -1,12 +1,21 @@
1
- require 'dry/types/options'
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/core/equalizer"
4
+ require "dry/types/options"
5
+ require "dry/types/meta"
2
6
 
3
7
  module Dry
4
8
  module Types
9
+ # Sum type
10
+ #
11
+ # @api public
5
12
  class Sum
6
13
  include Type
7
- include Dry::Equalizer(:left, :right, :options, :meta)
8
14
  include Builder
9
15
  include Options
16
+ include Meta
17
+ include Printable
18
+ include Dry::Equalizer(:left, :right, :options, :meta, inspect: false, immutable: true)
10
19
 
11
20
  # @return [Type]
12
21
  attr_reader :left
@@ -14,6 +23,7 @@ module Dry
14
23
  # @return [Type]
15
24
  attr_reader :right
16
25
 
26
+ # @api private
17
27
  class Constrained < Sum
18
28
  # @return [Dry::Logic::Operations::Or]
19
29
  def rule
@@ -24,68 +34,81 @@ module Dry
24
34
  def constrained?
25
35
  true
26
36
  end
27
-
28
- # @param [Object] input
29
- # @return [Object]
30
- # @raise [ConstraintError] if given +input+ not passing {#try}
31
- def call(input)
32
- try(input) do |result|
33
- raise ConstraintError.new(result, input)
34
- end.input
35
- end
36
- alias_method :[], :call
37
37
  end
38
38
 
39
39
  # @param [Type] left
40
40
  # @param [Type] right
41
41
  # @param [Hash] options
42
- def initialize(left, right, options = {})
42
+ #
43
+ # @api private
44
+ def initialize(left, right, **options)
43
45
  super
44
46
  @left, @right = left, right
45
47
  freeze
46
48
  end
47
49
 
48
50
  # @return [String]
51
+ #
52
+ # @api public
49
53
  def name
50
- [left, right].map(&:name).join(' | ')
54
+ [left, right].map(&:name).join(" | ")
51
55
  end
52
56
 
53
57
  # @return [false]
58
+ #
59
+ # @api public
54
60
  def default?
55
61
  false
56
62
  end
57
63
 
58
64
  # @return [false]
65
+ #
66
+ # @api public
59
67
  def constrained?
60
68
  false
61
69
  end
62
70
 
63
71
  # @return [Boolean]
72
+ #
73
+ # @api public
64
74
  def optional?
65
- left == Types['strict.nil']
75
+ primitive?(nil)
66
76
  end
67
77
 
68
78
  # @param [Object] input
79
+ #
69
80
  # @return [Object]
70
- def call(input)
71
- try(input).input
81
+ #
82
+ # @api private
83
+ def call_unsafe(input)
84
+ left.call_safe(input) { right.call_unsafe(input) }
72
85
  end
73
- alias_method :[], :call
74
-
75
- def try(input, &block)
76
- result = left.try(input) do
77
- right.try(input)
78
- end
79
86
 
80
- return result if result.success?
87
+ # @param [Object] input
88
+ #
89
+ # @return [Object]
90
+ #
91
+ # @api private
92
+ def call_safe(input, &block)
93
+ left.call_safe(input) { right.call_safe(input, &block) }
94
+ end
81
95
 
82
- if block
83
- yield(result)
84
- else
85
- result
96
+ # @param [Object] input
97
+ #
98
+ # @api public
99
+ def try(input)
100
+ left.try(input) do
101
+ right.try(input) do |failure|
102
+ if block_given?
103
+ yield(failure)
104
+ else
105
+ failure
106
+ end
107
+ end
86
108
  end
87
109
  end
88
110
 
111
+ # @api private
89
112
  def success(input)
90
113
  if left.valid?(input)
91
114
  left.success(input)
@@ -96,6 +119,7 @@ module Dry
96
119
  end
97
120
  end
98
121
 
122
+ # @api private
99
123
  def failure(input, _error = nil)
100
124
  if !left.valid?(input)
101
125
  left.failure(input, left.try(input).error)
@@ -105,28 +129,44 @@ module Dry
105
129
  end
106
130
 
107
131
  # @param [Object] value
132
+ #
108
133
  # @return [Boolean]
134
+ #
135
+ # @api private
109
136
  def primitive?(value)
110
137
  left.primitive?(value) || right.primitive?(value)
111
138
  end
112
139
 
113
- # @param [Object] value
114
- # @return [Boolean]
115
- def valid?(value)
116
- left.valid?(value) || right.valid?(value)
140
+ # Manage metadata to the type. If the type is an optional, #meta delegates
141
+ # to the right branch
142
+ #
143
+ # @see [Meta#meta]
144
+ #
145
+ # @api public
146
+ def meta(data = Undefined)
147
+ if Undefined.equal?(data)
148
+ optional? ? right.meta : super
149
+ elsif optional?
150
+ self.class.new(left, right.meta(data), **options)
151
+ else
152
+ super
153
+ end
117
154
  end
118
- alias_method :===, :valid?
119
155
 
120
- # @api public
156
+ # @see Nominal#to_ast
121
157
  #
122
- # @see Definition#to_ast
158
+ # @api public
123
159
  def to_ast(meta: true)
124
160
  [:sum, [left.to_ast(meta: meta), right.to_ast(meta: meta), meta ? self.meta : EMPTY_HASH]]
125
161
  end
126
162
 
127
163
  # @param [Hash] options
164
+ #
128
165
  # @return [Constrained,Sum]
166
+ #
129
167
  # @see Builder#constrained
168
+ #
169
+ # @api public
130
170
  def constrained(options)
131
171
  if optional?
132
172
  right.constrained(options).optional
@@ -134,6 +174,15 @@ module Dry
134
174
  super
135
175
  end
136
176
  end
177
+
178
+ # Wrap the type with a proc
179
+ #
180
+ # @return [Proc]
181
+ #
182
+ # @api public
183
+ def to_proc
184
+ proc { |value| self.(value) }
185
+ end
137
186
  end
138
187
  end
139
188
  end
@@ -1,6 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/core/deprecations"
4
+
1
5
  module Dry
2
6
  module Types
7
+ # Common Type module denoting an object is a Type
8
+ #
9
+ # @api public
3
10
  module Type
11
+ extend ::Dry::Core::Deprecations[:'dry-types']
12
+
13
+ deprecate(:safe, :lax)
14
+
15
+ # Whether a value is a valid member of the type
16
+ #
17
+ # @return [Boolean]
18
+ #
19
+ # @api private
20
+ def valid?(input = Undefined)
21
+ call_safe(input) { return false }
22
+ true
23
+ end
24
+ # Anything can be coerced matches
25
+ alias_method :===, :valid?
26
+
27
+ # Apply type to a value
28
+ #
29
+ # @overload call(input = Undefined)
30
+ # Possibly unsafe coercion attempt. If a value doesn't
31
+ # match the type, an exception will be raised.
32
+ #
33
+ # @param [Object] input
34
+ # @return [Object]
35
+ #
36
+ # @overload call(input = Undefined)
37
+ # When a block is passed, {#call} will never throw an exception on
38
+ # failed coercion, instead it will call the block.
39
+ #
40
+ # @param [Object] input
41
+ # @yieldparam [Object] output Partially coerced value
42
+ # @return [Object]
43
+ #
44
+ # @api public
45
+ def call(input = Undefined, &block)
46
+ if block_given?
47
+ call_safe(input, &block)
48
+ else
49
+ call_unsafe(input)
50
+ end
51
+ end
52
+ alias_method :[], :call
4
53
  end
5
54
  end
6
55
  end
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Dry
2
4
  module Types
3
- VERSION = '0.14.1'.freeze
5
+ VERSION = "1.5.1"
4
6
  end
5
7
  end
data/lib/dry/types.rb CHANGED
@@ -1,81 +1,94 @@
1
- require 'bigdecimal'
2
- require 'date'
3
- require 'set'
1
+ # frozen_string_literal: true
4
2
 
5
- require 'concurrent'
3
+ require "bigdecimal"
4
+ require "date"
5
+ require "set"
6
6
 
7
- require 'dry-container'
8
- require 'dry-equalizer'
9
- require 'dry/core/extensions'
10
- require 'dry/core/constants'
11
- require 'dry/core/class_attributes'
7
+ require "concurrent/map"
12
8
 
13
- require 'dry/types/version'
14
- require 'dry/types/container'
15
- require 'dry/types/inflector'
16
- require 'dry/types/type'
17
- require 'dry/types/definition'
18
- require 'dry/types/constructor'
19
- require 'dry/types/builder_methods'
9
+ require "dry/container"
10
+ require "dry/core/extensions"
11
+ require "dry/core/constants"
12
+ require "dry/core/class_attributes"
20
13
 
21
- require 'dry/types/errors'
14
+ require "dry/types/version"
15
+ require "dry/types/container"
16
+ require "dry/types/inflector"
17
+ require "dry/types/type"
18
+ require "dry/types/printable"
19
+ require "dry/types/nominal"
20
+ require "dry/types/constructor"
21
+ require "dry/types/module"
22
+
23
+ require "dry/types/errors"
22
24
 
23
25
  module Dry
26
+ # Main library namespace
27
+ #
28
+ # @api public
24
29
  module Types
25
30
  extend Dry::Core::Extensions
26
31
  extend Dry::Core::ClassAttributes
32
+ extend Dry::Core::Deprecations[:'dry-types']
27
33
  include Dry::Core::Constants
28
34
 
29
- # @!attribute [r] namespace
30
- # @return [Container{String => Definition}]
31
- defines :namespace
32
-
33
- namespace self
34
-
35
- TYPE_SPEC_REGEX = %r[(.+)<(.+)>].freeze
35
+ TYPE_SPEC_REGEX = /(.+)<(.+)>/.freeze
36
36
 
37
- # @return [Module]
38
- def self.module
39
- namespace = Module.new
40
- define_constants(namespace, type_keys)
41
- namespace.extend(BuilderMethods)
42
- namespace
37
+ # @see Dry.Types
38
+ def self.module(*namespaces, default: :nominal, **aliases)
39
+ ::Module.new(container, *namespaces, default: default, **aliases)
43
40
  end
41
+ deprecate_class_method :module, message: <<~DEPRECATION
42
+ Use Dry.Types() instead. Beware, it exports strict types by default, for old behavior use Dry.Types(default: :nominal). See more options in the changelog
43
+ DEPRECATION
44
44
 
45
- # @deprecated Include {Dry::Types.module} instead
46
- def self.finalize
47
- warn 'Dry::Types.finalize and configuring namespace is deprecated. Just'\
48
- ' do `include Dry::Types.module` in places where you want to have access'\
49
- ' to built-in types'
50
-
51
- define_constants(self.namespace, type_keys)
45
+ # @api private
46
+ def self.included(*)
47
+ raise "Import Dry.Types, not Dry::Types"
52
48
  end
53
49
 
54
- # @return [Container{String => Definition}]
50
+ # Return container with registered built-in type objects
51
+ #
52
+ # @return [Container{String => Nominal}]
53
+ #
54
+ # @api private
55
55
  def self.container
56
56
  @container ||= Container.new
57
57
  end
58
58
 
59
+ # Check if a give type is registered
60
+ #
61
+ # @return [Boolean]
62
+ #
59
63
  # @api private
60
64
  def self.registered?(class_or_identifier)
61
65
  container.key?(identifier(class_or_identifier))
62
66
  end
63
67
 
68
+ # Register a new built-in type
69
+ #
64
70
  # @param [String] name
65
71
  # @param [Type] type
66
72
  # @param [#call,nil] block
67
- # @return [Container{String => Definition}]
73
+ #
74
+ # @return [Container{String => Nominal}]
75
+ #
68
76
  # @api private
69
77
  def self.register(name, type = nil, &block)
70
78
  container.register(name, type || block.call)
71
79
  end
72
80
 
81
+ # Get a built-in type by its name
82
+ #
73
83
  # @param [String,Class] name
84
+ #
74
85
  # @return [Type,Class]
86
+ #
87
+ # @api public
75
88
  def self.[](name)
76
89
  type_map.fetch_or_store(name) do
77
90
  case name
78
- when String
91
+ when ::String
79
92
  result = name.match(TYPE_SPEC_REGEX)
80
93
 
81
94
  if result
@@ -84,7 +97,12 @@ module Dry
84
97
  else
85
98
  container[name]
86
99
  end
87
- when Class
100
+ when ::Class
101
+ warn(<<~DEPRECATION)
102
+ Using Dry::Types.[] with a class is deprecated, please use string identifiers: Dry::Types[Integer] -> Dry::Types['integer'].
103
+ If you're using dry-struct this means changing `attribute :counter, Integer` to `attribute :counter, Dry::Types['integer']` or to `attribute :counter, 'integer'`.
104
+ DEPRECATION
105
+
88
106
  type_name = identifier(name)
89
107
 
90
108
  if container.key?(type_name)
@@ -96,58 +114,120 @@ module Dry
96
114
  end
97
115
  end
98
116
 
99
- # @param [Module] namespace
100
- # @param [<String>] identifiers
101
- # @return [<Definition>]
102
- def self.define_constants(namespace, identifiers)
103
- names = identifiers.map do |id|
104
- parts = id.split('.')
105
- [Inflector.camelize(parts.pop), parts.map(&Inflector.method(:camelize))]
106
- end
107
-
108
- names.map do |(klass, parts)|
109
- mod = parts.reduce(namespace) do |a, e|
110
- a.constants.include?(e.to_sym) ? a.const_get(e) : a.const_set(e, Module.new)
111
- end
112
-
113
- mod.const_set(klass, self[identifier((parts + [klass]).join('::'))])
114
- end
115
- end
116
-
117
+ # Infer a type identifier from the provided class
118
+ #
117
119
  # @param [#to_s] klass
120
+ #
118
121
  # @return [String]
119
122
  def self.identifier(klass)
120
- Inflector.underscore(klass).tr('/', '.')
123
+ Inflector.underscore(klass).tr("/", ".")
121
124
  end
122
125
 
126
+ # Cached type map
127
+ #
123
128
  # @return [Concurrent::Map]
129
+ #
130
+ # @api private
124
131
  def self.type_map
125
- @type_map ||= Concurrent::Map.new
132
+ @type_map ||= ::Concurrent::Map.new
126
133
  end
127
134
 
128
- # List of type keys defined in {Dry::Types.container}
129
- # @return [<String>]
130
- def self.type_keys
131
- container.keys
132
- end
133
-
134
- private
135
-
136
135
  # @api private
137
136
  def self.const_missing(const)
138
137
  underscored = Inflector.underscore(const)
139
138
 
140
- if type_keys.any? { |key| key.split('.')[0] == underscored }
141
- raise NameError,
142
- 'dry-types does not define constants for default types. '\
143
- 'You can access the predefined types with [], e.g. Dry::Types["strict.integer"] '\
144
- 'or generate a module with types using Dry::Types.module'
139
+ if container.keys.any? { |key| key.split(".")[0] == underscored }
140
+ raise ::NameError,
141
+ "dry-types does not define constants for default types. "\
142
+ 'You can access the predefined types with [], e.g. Dry::Types["integer"] '\
143
+ "or generate a module with types using Dry.Types()"
145
144
  else
146
145
  super
147
146
  end
148
147
  end
148
+
149
+ # Add a new type builder method. This is a public API for defining custom
150
+ # type constructors
151
+ #
152
+ # @example simple custom type constructor
153
+ # Dry::Types.define_builder(:or_nil) do |type|
154
+ # type.optional.fallback(nil)
155
+ # end
156
+ #
157
+ # Dry::Types["integer"].or_nil.("foo") # => nil
158
+ #
159
+ # @example fallback alias
160
+ # Dry::Types.define_builder(:or) do |type, fallback|
161
+ # type.fallback(fallback)
162
+ # end
163
+ #
164
+ # Dry::Types["integer"].or(100).("foo") # => 100
165
+ #
166
+ # @param [Symbol] method
167
+ # @param [#call] block
168
+ #
169
+ # @api public
170
+ def self.define_builder(method, &block)
171
+ Builder.define_method(method) do |*args|
172
+ block.(self, *args)
173
+ end
174
+ end
175
+ end
176
+
177
+ # Export registered types as a module with constants
178
+ #
179
+ # @example no options
180
+ #
181
+ # module Types
182
+ # # imports all types as constants, uses modules for namespaces
183
+ # include Dry::Types()
184
+ # end
185
+ # # strict types are exported by default
186
+ # Types::Integer
187
+ # # => #<Dry::Types[Constrained<Nominal<Integer> rule=[type?(Integer)]>]>
188
+ # Types::Nominal::Integer
189
+ # # => #<Dry::Types[Nominal<Integer>]>
190
+ #
191
+ # @example changing default types
192
+ #
193
+ # module Types
194
+ # include Dry::Types(default: :nominal)
195
+ # end
196
+ # Types::Integer
197
+ # # => #<Dry::Types[Nominal<Integer>]>
198
+ #
199
+ # @example cherry-picking namespaces
200
+ #
201
+ # module Types
202
+ # include Dry::Types(:strict, :coercible)
203
+ # end
204
+ # # cherry-picking discards default types,
205
+ # # provide the :default option along with the list of
206
+ # # namespaces if you want the to be exported
207
+ # Types.constants # => [:Coercible, :Strict]
208
+ #
209
+ # @example custom names
210
+ # module Types
211
+ # include Dry::Types(coercible: :Kernel)
212
+ # end
213
+ # Types::Kernel::Integer
214
+ # # => #<Dry::Types[Constructor<Nominal<Integer> fn=Kernel.Integer>]>
215
+ #
216
+ # @param [Array<Symbol>] namespaces List of type namespaces to export
217
+ # @param [Symbol] default Default namespace to export
218
+ # @param [Hash{Symbol => Symbol}] aliases Optional renamings, like strict: :Draconian
219
+ #
220
+ # @return [Dry::Types::Module]
221
+ #
222
+ # @see Dry::Types::Module
223
+ #
224
+ # @api public
225
+ #
226
+ def self.Types(*namespaces, default: Types::Undefined, **aliases)
227
+ Types::Module.new(Types.container, *namespaces, default: default, **aliases)
149
228
  end
150
229
  end
151
230
 
152
- require 'dry/types/core' # load built-in types
153
- require 'dry/types/extensions'
231
+ require "dry/types/core" # load built-in types
232
+ require "dry/types/extensions"
233
+ require "dry/types/printer"
data/lib/dry-types.rb CHANGED
@@ -1 +1,3 @@
1
- require 'dry/types'
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/types"