dry-types 0.14.1 → 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 (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"