dry-types 0.15.0 → 1.5.1

Sign up to get free protection for your applications and to get access to all the features.
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"