dry-types 0.15.0 → 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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +547 -161
  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 +23 -12
  7. data/lib/dry/types/array/constructor.rb +32 -0
  8. data/lib/dry/types/array/member.rb +74 -15
  9. data/lib/dry/types/array.rb +18 -2
  10. data/lib/dry/types/builder.rb +118 -22
  11. data/lib/dry/types/builder_methods.rb +46 -16
  12. data/lib/dry/types/coercions/json.rb +43 -7
  13. data/lib/dry/types/coercions/params.rb +117 -32
  14. data/lib/dry/types/coercions.rb +76 -22
  15. data/lib/dry/types/compiler.rb +44 -21
  16. data/lib/dry/types/constrained/coercible.rb +36 -6
  17. data/lib/dry/types/constrained.rb +79 -31
  18. data/lib/dry/types/constraints.rb +18 -4
  19. data/lib/dry/types/constructor/function.rb +216 -0
  20. data/lib/dry/types/constructor/wrapper.rb +94 -0
  21. data/lib/dry/types/constructor.rb +110 -61
  22. data/lib/dry/types/container.rb +6 -1
  23. data/lib/dry/types/core.rb +34 -11
  24. data/lib/dry/types/decorator.rb +38 -17
  25. data/lib/dry/types/default.rb +61 -16
  26. data/lib/dry/types/enum.rb +36 -20
  27. data/lib/dry/types/errors.rb +74 -8
  28. data/lib/dry/types/extensions/maybe.rb +65 -17
  29. data/lib/dry/types/extensions/monads.rb +29 -0
  30. data/lib/dry/types/extensions.rb +7 -1
  31. data/lib/dry/types/fn_container.rb +6 -1
  32. data/lib/dry/types/hash/constructor.rb +17 -4
  33. data/lib/dry/types/hash.rb +32 -20
  34. data/lib/dry/types/inflector.rb +3 -1
  35. data/lib/dry/types/json.rb +18 -16
  36. data/lib/dry/types/lax.rb +75 -0
  37. data/lib/dry/types/map.rb +70 -32
  38. data/lib/dry/types/meta.rb +51 -0
  39. data/lib/dry/types/module.rb +16 -11
  40. data/lib/dry/types/nominal.rb +113 -22
  41. data/lib/dry/types/options.rb +12 -25
  42. data/lib/dry/types/params.rb +39 -25
  43. data/lib/dry/types/predicate_inferrer.rb +238 -0
  44. data/lib/dry/types/predicate_registry.rb +34 -0
  45. data/lib/dry/types/primitive_inferrer.rb +97 -0
  46. data/lib/dry/types/printable.rb +5 -1
  47. data/lib/dry/types/printer.rb +63 -57
  48. data/lib/dry/types/result.rb +29 -3
  49. data/lib/dry/types/schema/key.rb +62 -36
  50. data/lib/dry/types/schema.rb +201 -91
  51. data/lib/dry/types/spec/types.rb +99 -37
  52. data/lib/dry/types/sum.rb +75 -25
  53. data/lib/dry/types/type.rb +49 -0
  54. data/lib/dry/types/version.rb +3 -1
  55. data/lib/dry/types.rb +106 -48
  56. data/lib/dry-types.rb +3 -1
  57. metadata +55 -78
  58. data/.codeclimate.yml +0 -15
  59. data/.gitignore +0 -10
  60. data/.rspec +0 -2
  61. data/.rubocop.yml +0 -43
  62. data/.travis.yml +0 -28
  63. data/.yardopts +0 -5
  64. data/CONTRIBUTING.md +0 -29
  65. data/Gemfile +0 -23
  66. data/Rakefile +0 -20
  67. data/benchmarks/hash_schemas.rb +0 -51
  68. data/lib/dry/types/safe.rb +0 -61
  69. data/log/.gitkeep +0 -0
data/lib/dry/types/sum.rb CHANGED
@@ -1,13 +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
14
  include Builder
8
15
  include Options
16
+ include Meta
9
17
  include Printable
10
- include Dry::Equalizer(:left, :right, :options, :meta, inspect: false)
18
+ include Dry::Equalizer(:left, :right, :options, :meta, inspect: false, immutable: true)
11
19
 
12
20
  # @return [Type]
13
21
  attr_reader :left
@@ -15,6 +23,7 @@ module Dry
15
23
  # @return [Type]
16
24
  attr_reader :right
17
25
 
26
+ # @api private
18
27
  class Constrained < Sum
19
28
  # @return [Dry::Logic::Operations::Or]
20
29
  def rule
@@ -25,55 +34,69 @@ module Dry
25
34
  def constrained?
26
35
  true
27
36
  end
28
-
29
- # @param [Object] input
30
- # @return [Object]
31
- # @raise [ConstraintError] if given +input+ not passing {#try}
32
- def call(input)
33
- try(input) { |result|
34
- raise ConstraintError.new(result, input)
35
- }.input
36
- end
37
- alias_method :[], :call
38
37
  end
39
38
 
40
39
  # @param [Type] left
41
40
  # @param [Type] right
42
41
  # @param [Hash] options
43
- def initialize(left, right, options = {})
42
+ #
43
+ # @api private
44
+ def initialize(left, right, **options)
44
45
  super
45
46
  @left, @right = left, right
46
47
  freeze
47
48
  end
48
49
 
49
50
  # @return [String]
51
+ #
52
+ # @api public
50
53
  def name
51
- [left, right].map(&:name).join(' | ')
54
+ [left, right].map(&:name).join(" | ")
52
55
  end
53
56
 
54
57
  # @return [false]
58
+ #
59
+ # @api public
55
60
  def default?
56
61
  false
57
62
  end
58
63
 
59
64
  # @return [false]
65
+ #
66
+ # @api public
60
67
  def constrained?
61
68
  false
62
69
  end
63
70
 
64
71
  # @return [Boolean]
72
+ #
73
+ # @api public
65
74
  def optional?
66
75
  primitive?(nil)
67
76
  end
68
77
 
69
78
  # @param [Object] input
79
+ #
70
80
  # @return [Object]
71
- def call(input)
72
- try(input).input
81
+ #
82
+ # @api private
83
+ def call_unsafe(input)
84
+ left.call_safe(input) { right.call_unsafe(input) }
73
85
  end
74
- alias_method :[], :call
75
86
 
76
- def try(input, &block)
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
95
+
96
+ # @param [Object] input
97
+ #
98
+ # @api public
99
+ def try(input)
77
100
  left.try(input) do
78
101
  right.try(input) do |failure|
79
102
  if block_given?
@@ -85,6 +108,7 @@ module Dry
85
108
  end
86
109
  end
87
110
 
111
+ # @api private
88
112
  def success(input)
89
113
  if left.valid?(input)
90
114
  left.success(input)
@@ -95,6 +119,7 @@ module Dry
95
119
  end
96
120
  end
97
121
 
122
+ # @api private
98
123
  def failure(input, _error = nil)
99
124
  if !left.valid?(input)
100
125
  left.failure(input, left.try(input).error)
@@ -104,28 +129,44 @@ module Dry
104
129
  end
105
130
 
106
131
  # @param [Object] value
132
+ #
107
133
  # @return [Boolean]
134
+ #
135
+ # @api private
108
136
  def primitive?(value)
109
137
  left.primitive?(value) || right.primitive?(value)
110
138
  end
111
139
 
112
- # @param [Object] value
113
- # @return [Boolean]
114
- def valid?(value)
115
- 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
116
154
  end
117
- alias_method :===, :valid?
118
155
 
119
- # @api public
120
- #
121
156
  # @see Nominal#to_ast
157
+ #
158
+ # @api public
122
159
  def to_ast(meta: true)
123
160
  [:sum, [left.to_ast(meta: meta), right.to_ast(meta: meta), meta ? self.meta : EMPTY_HASH]]
124
161
  end
125
162
 
126
163
  # @param [Hash] options
164
+ #
127
165
  # @return [Constrained,Sum]
166
+ #
128
167
  # @see Builder#constrained
168
+ #
169
+ # @api public
129
170
  def constrained(options)
130
171
  if optional?
131
172
  right.constrained(options).optional
@@ -133,6 +174,15 @@ module Dry
133
174
  super
134
175
  end
135
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
136
186
  end
137
187
  end
138
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.15.0'.freeze
5
+ VERSION = "1.5.1"
4
6
  end
5
7
  end
data/lib/dry/types.rb CHANGED
@@ -1,38 +1,42 @@
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/printable'
18
- require 'dry/types/nominal'
19
- require 'dry/types/constructor'
20
- require 'dry/types/module'
9
+ require "dry/container"
10
+ require "dry/core/extensions"
11
+ require "dry/core/constants"
12
+ require "dry/core/class_attributes"
21
13
 
22
- 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"
23
24
 
24
25
  module Dry
26
+ # Main library namespace
27
+ #
28
+ # @api public
25
29
  module Types
26
30
  extend Dry::Core::Extensions
27
31
  extend Dry::Core::ClassAttributes
28
32
  extend Dry::Core::Deprecations[:'dry-types']
29
33
  include Dry::Core::Constants
30
34
 
31
- TYPE_SPEC_REGEX = %r[(.+)<(.+)>].freeze
35
+ TYPE_SPEC_REGEX = /(.+)<(.+)>/.freeze
32
36
 
33
37
  # @see Dry.Types
34
38
  def self.module(*namespaces, default: :nominal, **aliases)
35
- Module.new(container, *namespaces, default: default, **aliases)
39
+ ::Module.new(container, *namespaces, default: default, **aliases)
36
40
  end
37
41
  deprecate_class_method :module, message: <<~DEPRECATION
38
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
@@ -40,34 +44,51 @@ module Dry
40
44
 
41
45
  # @api private
42
46
  def self.included(*)
43
- raise RuntimeError, "Import Dry.Types, not Dry::Types"
47
+ raise "Import Dry.Types, not Dry::Types"
44
48
  end
45
49
 
50
+ # Return container with registered built-in type objects
51
+ #
46
52
  # @return [Container{String => Nominal}]
53
+ #
54
+ # @api private
47
55
  def self.container
48
56
  @container ||= Container.new
49
57
  end
50
58
 
59
+ # Check if a give type is registered
60
+ #
61
+ # @return [Boolean]
62
+ #
51
63
  # @api private
52
64
  def self.registered?(class_or_identifier)
53
65
  container.key?(identifier(class_or_identifier))
54
66
  end
55
67
 
68
+ # Register a new built-in type
69
+ #
56
70
  # @param [String] name
57
71
  # @param [Type] type
58
72
  # @param [#call,nil] block
73
+ #
59
74
  # @return [Container{String => Nominal}]
75
+ #
60
76
  # @api private
61
77
  def self.register(name, type = nil, &block)
62
78
  container.register(name, type || block.call)
63
79
  end
64
80
 
81
+ # Get a built-in type by its name
82
+ #
65
83
  # @param [String,Class] name
84
+ #
66
85
  # @return [Type,Class]
86
+ #
87
+ # @api public
67
88
  def self.[](name)
68
89
  type_map.fetch_or_store(name) do
69
90
  case name
70
- when String
91
+ when ::String
71
92
  result = name.match(TYPE_SPEC_REGEX)
72
93
 
73
94
  if result
@@ -76,7 +97,12 @@ module Dry
76
97
  else
77
98
  container[name]
78
99
  end
79
- 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
+
80
106
  type_name = identifier(name)
81
107
 
82
108
  if container.key?(type_name)
@@ -88,36 +114,64 @@ module Dry
88
114
  end
89
115
  end
90
116
 
117
+ # Infer a type identifier from the provided class
118
+ #
91
119
  # @param [#to_s] klass
120
+ #
92
121
  # @return [String]
93
122
  def self.identifier(klass)
94
- Inflector.underscore(klass).tr('/', '.')
123
+ Inflector.underscore(klass).tr("/", ".")
95
124
  end
96
125
 
126
+ # Cached type map
127
+ #
97
128
  # @return [Concurrent::Map]
129
+ #
130
+ # @api private
98
131
  def self.type_map
99
- @type_map ||= Concurrent::Map.new
100
- end
101
-
102
- # List of type keys defined in {Dry::Types.container}
103
- # @return [<String>]
104
- def self.type_keys
105
- container.keys
132
+ @type_map ||= ::Concurrent::Map.new
106
133
  end
107
134
 
108
135
  # @api private
109
136
  def self.const_missing(const)
110
137
  underscored = Inflector.underscore(const)
111
138
 
112
- if container.keys.any? { |key| key.split('.')[0] == underscored }
113
- raise NameError,
114
- 'dry-types does not define constants for default types. '\
115
- 'You can access the predefined types with [], e.g. Dry::Types["strict.integer"] '\
116
- '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()"
117
144
  else
118
145
  super
119
146
  end
120
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
121
175
  end
122
176
 
123
177
  # Export registered types as a module with constants
@@ -126,26 +180,26 @@ module Dry
126
180
  #
127
181
  # module Types
128
182
  # # imports all types as constants, uses modules for namespaces
129
- # include Dry::Types.module
183
+ # include Dry::Types()
130
184
  # end
131
- # # nominal types are exported by default
185
+ # # strict types are exported by default
132
186
  # Types::Integer
133
- # # => #<Dry::Types[Nominal<Integer>]>
134
- # Types::Strict::Integer
135
187
  # # => #<Dry::Types[Constrained<Nominal<Integer> rule=[type?(Integer)]>]>
188
+ # Types::Nominal::Integer
189
+ # # => #<Dry::Types[Nominal<Integer>]>
136
190
  #
137
191
  # @example changing default types
138
192
  #
139
193
  # module Types
140
- # include Dry::Types(default: :strict)
194
+ # include Dry::Types(default: :nominal)
141
195
  # end
142
196
  # Types::Integer
143
- # # => #<Dry::Types[Constrained<Nominal<Integer> rule=[type?(Integer)]>]>
197
+ # # => #<Dry::Types[Nominal<Integer>]>
144
198
  #
145
199
  # @example cherry-picking namespaces
146
200
  #
147
201
  # module Types
148
- # include Dry::Types.module(:strict, :coercible)
202
+ # include Dry::Types(:strict, :coercible)
149
203
  # end
150
204
  # # cherry-picking discards default types,
151
205
  # # provide the :default option along with the list of
@@ -154,7 +208,7 @@ module Dry
154
208
  #
155
209
  # @example custom names
156
210
  # module Types
157
- # include Dry::Types.module(coercible: :Kernel)
211
+ # include Dry::Types(coercible: :Kernel)
158
212
  # end
159
213
  # Types::Kernel::Integer
160
214
  # # => #<Dry::Types[Constructor<Nominal<Integer> fn=Kernel.Integer>]>
@@ -162,14 +216,18 @@ module Dry
162
216
  # @param [Array<Symbol>] namespaces List of type namespaces to export
163
217
  # @param [Symbol] default Default namespace to export
164
218
  # @param [Hash{Symbol => Symbol}] aliases Optional renamings, like strict: :Draconian
219
+ #
165
220
  # @return [Dry::Types::Module]
166
221
  #
167
- # @see Dry::types::Module
222
+ # @see Dry::Types::Module
223
+ #
224
+ # @api public
225
+ #
168
226
  def self.Types(*namespaces, default: Types::Undefined, **aliases)
169
227
  Types::Module.new(Types.container, *namespaces, default: default, **aliases)
170
228
  end
171
229
  end
172
230
 
173
- require 'dry/types/core' # load built-in types
174
- require 'dry/types/extensions'
175
- require 'dry/types/printer'
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"