dry-types 1.4.0 → 1.5.0

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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +68 -0
  3. data/LICENSE +1 -1
  4. data/README.md +1 -1
  5. data/dry-types.gemspec +2 -3
  6. data/lib/dry-types.rb +1 -1
  7. data/lib/dry/types.rb +55 -31
  8. data/lib/dry/types/any.rb +2 -2
  9. data/lib/dry/types/array.rb +2 -2
  10. data/lib/dry/types/array/constructor.rb +1 -1
  11. data/lib/dry/types/array/member.rb +1 -1
  12. data/lib/dry/types/builder.rb +66 -18
  13. data/lib/dry/types/builder_methods.rb +1 -2
  14. data/lib/dry/types/coercions/json.rb +5 -5
  15. data/lib/dry/types/coercions/params.rb +3 -3
  16. data/lib/dry/types/compiler.rb +10 -10
  17. data/lib/dry/types/constrained.rb +5 -9
  18. data/lib/dry/types/constraints.rb +3 -3
  19. data/lib/dry/types/constructor.rb +39 -6
  20. data/lib/dry/types/constructor/function.rb +31 -2
  21. data/lib/dry/types/constructor/wrapper.rb +94 -0
  22. data/lib/dry/types/container.rb +1 -1
  23. data/lib/dry/types/core.rb +12 -12
  24. data/lib/dry/types/decorator.rb +2 -2
  25. data/lib/dry/types/default.rb +13 -1
  26. data/lib/dry/types/enum.rb +3 -3
  27. data/lib/dry/types/errors.rb +1 -1
  28. data/lib/dry/types/extensions.rb +2 -2
  29. data/lib/dry/types/extensions/maybe.rb +4 -4
  30. data/lib/dry/types/extensions/monads.rb +1 -1
  31. data/lib/dry/types/fn_container.rb +1 -1
  32. data/lib/dry/types/hash.rb +9 -15
  33. data/lib/dry/types/hash/constructor.rb +1 -1
  34. data/lib/dry/types/inflector.rb +1 -1
  35. data/lib/dry/types/json.rb +15 -15
  36. data/lib/dry/types/lax.rb +2 -2
  37. data/lib/dry/types/map.rb +2 -2
  38. data/lib/dry/types/meta.rb +1 -1
  39. data/lib/dry/types/module.rb +6 -6
  40. data/lib/dry/types/nominal.rb +10 -11
  41. data/lib/dry/types/params.rb +30 -28
  42. data/lib/dry/types/predicate_inferrer.rb +51 -11
  43. data/lib/dry/types/predicate_registry.rb +1 -1
  44. data/lib/dry/types/primitive_inferrer.rb +1 -1
  45. data/lib/dry/types/printer.rb +25 -25
  46. data/lib/dry/types/result.rb +1 -1
  47. data/lib/dry/types/schema.rb +5 -13
  48. data/lib/dry/types/schema/key.rb +4 -4
  49. data/lib/dry/types/spec/types.rb +57 -45
  50. data/lib/dry/types/sum.rb +3 -3
  51. data/lib/dry/types/type.rb +1 -1
  52. data/lib/dry/types/version.rb +1 -1
  53. metadata +9 -22
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f734c560178356c131ffab93f54a26d5e3cd48c0493c5defba166773fa17d992
4
- data.tar.gz: 3920839f32bbe474d2e99776dc631bfa079139b099aeb9cbb3c00c45f0e56cbf
3
+ metadata.gz: 8ffbb2a3ad9a5c532228eccc2c56690e1e7ea8808b579a140f804a46a5263d69
4
+ data.tar.gz: 04e2f92495290236be6daf9be2963649f5350c38f69c16a992649610986bbe5e
5
5
  SHA512:
6
- metadata.gz: 86397faca01543a8836b9d48f6c5bb70084c51219651f137e308d968de42e1f04cb2a8f3b53e3aa362537a35818a0ba54c3fa731521a87e33e62afc2f5ac94d8
7
- data.tar.gz: 8b97e1b888936595ef5808a6d5ad6ef5d9e0cf0f52c44f5802831e8dbd1c6bbba2ef4a60ad896b505ed086869ba8977bc87a8053aff10b582ab16316f5829900
6
+ metadata.gz: 53f6b6604552a3dacfbfdb64713e8cfdbc47e51b0b0061b6ab186c948ec207b7159c2a076dd17dead3f4d55e4cc300d2133831590f6b56105ac0754bbfd36e99
7
+ data.tar.gz: d7050f2070155e72c560f35392121d0de222da270495cbf7f65f0b7c9dcb91f9d9c3db20b119b9a9f1a62c8fae58e49474beaebf81b7df47095cf518607df25c
@@ -1,3 +1,71 @@
1
+ <!--- DO NOT EDIT THIS FILE - IT'S AUTOMATICALLY GENERATED VIA DEVTOOLS --->
2
+
3
+ ## 1.5.0 2020-01-21
4
+
5
+
6
+ ### Added
7
+
8
+ - Wrapping constructor types :tada: (@flash-gordon)
9
+
10
+ Constructor blocks can have a second argument.
11
+ The second argument is the underlying type itself:
12
+ ```ruby
13
+ age_from_year = Dry::Types['coercible.integer'].constructor do |input, type|
14
+ Date.today.year - type.(input)
15
+ end
16
+ age_from_year.('2000') # => 21
17
+ ```
18
+ With wrapping constructors you have control over "type application". You can even
19
+ run it more than once:
20
+ ```ruby
21
+ inc = Dry::Types['integer'].constructor(&:succ)
22
+ inc2x = inc.constructor { _2.(_2.(_2.(_1))) }
23
+ inc2x.(10) # => 13
24
+ ```
25
+ - Fallbacks :tada: (@flash-gordon)
26
+
27
+ ```ruby
28
+ age = Dry::Types['coercible.ineger'].fallback(18)
29
+ age.('10') # => 10
30
+ age.('20') # => 20
31
+ age.('abc') # => 18
32
+ ```
33
+
34
+ Fallbacks are different from default values: the later will be evaluated
35
+ only when *no input* provided.
36
+
37
+ Under the hood, `.fallback` creates a wrapping constructor.
38
+ - `params.string` as an alias for `strict.string`. This addition should be non-breaking (@flash-gordon)
39
+ - API for defining custom type builders similar to `.default`, `.constructor`, or `.optional` (@flash-gordon)
40
+
41
+ ```ruby
42
+ # Making an alias for `.fallback`
43
+ Dry::Types.define_builder(:or) { |type, v| type.fallback(v) }
44
+
45
+ # Using new builder
46
+ type = Dry::Types['integer'].or(-273)
47
+ type.(:invalid) # => -273
48
+ ```
49
+
50
+ ### Changed
51
+
52
+ - Inferring predicates from class names is deprecated. It's very unlikely your code depends on it,
53
+ however, if it does, you'll get an exception with instructions. (@flash-gordon)
54
+
55
+ If you don't rely on inferring, just disable it with:
56
+
57
+ ```ruby
58
+ Dry::Types::PredicateInferrer::Compiler.infer_predicate_by_class_name false
59
+ ```
60
+
61
+ Otherwise, enable it explicitly:
62
+
63
+ ```ruby
64
+ Dry::Types::PredicateInferrer::Compiler.infer_predicate_by_class_name true
65
+ ```
66
+
67
+ [Compare v1.4.0...v1.5.0](https://github.com/dry-rb/dry-types/compare/v1.4.0...v1.5.0)
68
+
1
69
  ## 1.4.0 2020-03-09
2
70
 
3
71
 
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2015-2020 dry-rb team
3
+ Copyright (c) 2015-2021 dry-rb team
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy of
6
6
  this software and associated documentation files (the "Software"), to deal in
data/README.md CHANGED
@@ -21,7 +21,7 @@
21
21
 
22
22
  This library officially supports the following Ruby versions:
23
23
 
24
- * MRI >= `2.4`
24
+ * MRI >= `2.5`
25
25
  * jruby >= `9.2`
26
26
 
27
27
  ## License
@@ -25,13 +25,12 @@ Gem::Specification.new do |spec|
25
25
  spec.metadata['source_code_uri'] = 'https://github.com/dry-rb/dry-types'
26
26
  spec.metadata['bug_tracker_uri'] = 'https://github.com/dry-rb/dry-types/issues'
27
27
 
28
- spec.required_ruby_version = ">= 2.4.0"
28
+ spec.required_ruby_version = ">= 2.5.0"
29
29
 
30
30
  # to update dependencies edit project.yml
31
31
  spec.add_runtime_dependency "concurrent-ruby", "~> 1.0"
32
32
  spec.add_runtime_dependency "dry-container", "~> 0.3"
33
- spec.add_runtime_dependency "dry-core", "~> 0.4", ">= 0.4.4"
34
- spec.add_runtime_dependency "dry-equalizer", "~> 0.3"
33
+ spec.add_runtime_dependency "dry-core", "~> 0.5", ">= 0.5"
35
34
  spec.add_runtime_dependency "dry-inflector", "~> 0.1", ">= 0.1.2"
36
35
  spec.add_runtime_dependency "dry-logic", "~> 1.0", ">= 1.0.2"
37
36
 
@@ -1,3 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/types'
3
+ require "dry/types"
@@ -1,27 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'bigdecimal'
4
- require 'date'
5
- require 'set'
3
+ require "bigdecimal"
4
+ require "date"
5
+ require "set"
6
6
 
7
- require 'concurrent'
7
+ require "concurrent/map"
8
8
 
9
- require 'dry-container'
10
- require 'dry-equalizer'
11
- require 'dry/core/extensions'
12
- require 'dry/core/constants'
13
- require 'dry/core/class_attributes'
9
+ require "dry/container"
10
+ require "dry/core/extensions"
11
+ require "dry/core/constants"
12
+ require "dry/core/class_attributes"
14
13
 
15
- require 'dry/types/version'
16
- require 'dry/types/container'
17
- require 'dry/types/inflector'
18
- require 'dry/types/type'
19
- require 'dry/types/printable'
20
- require 'dry/types/nominal'
21
- require 'dry/types/constructor'
22
- require 'dry/types/module'
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"
23
22
 
24
- require 'dry/types/errors'
23
+ require "dry/types/errors"
25
24
 
26
25
  module Dry
27
26
  # Main library namespace
@@ -37,7 +36,7 @@ module Dry
37
36
 
38
37
  # @see Dry.Types
39
38
  def self.module(*namespaces, default: :nominal, **aliases)
40
- Module.new(container, *namespaces, default: default, **aliases)
39
+ ::Module.new(container, *namespaces, default: default, **aliases)
41
40
  end
42
41
  deprecate_class_method :module, message: <<~DEPRECATION
43
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
@@ -45,7 +44,7 @@ module Dry
45
44
 
46
45
  # @api private
47
46
  def self.included(*)
48
- raise 'Import Dry.Types, not Dry::Types'
47
+ raise "Import Dry.Types, not Dry::Types"
49
48
  end
50
49
 
51
50
  # Return container with registered built-in type objects
@@ -121,7 +120,7 @@ module Dry
121
120
  #
122
121
  # @return [String]
123
122
  def self.identifier(klass)
124
- Inflector.underscore(klass).tr('/', '.')
123
+ Inflector.underscore(klass).tr("/", ".")
125
124
  end
126
125
 
127
126
  # Cached type map
@@ -130,22 +129,49 @@ module Dry
130
129
  #
131
130
  # @api private
132
131
  def self.type_map
133
- @type_map ||= Concurrent::Map.new
132
+ @type_map ||= ::Concurrent::Map.new
134
133
  end
135
134
 
136
135
  # @api private
137
136
  def self.const_missing(const)
138
137
  underscored = Inflector.underscore(const)
139
138
 
140
- if container.keys.any? { |key| key.split('.')[0] == underscored }
141
- raise NameError,
142
- 'dry-types does not define constants for default types. '\
139
+ if container.keys.any? { |key| key.split(".")[0] == underscored }
140
+ raise ::NameError,
141
+ "dry-types does not define constants for default types. "\
143
142
  'You can access the predefined types with [], e.g. Dry::Types["integer"] '\
144
- 'or generate a module with types using Dry.Types()'
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
149
175
  end
150
176
 
151
177
  # Export registered types as a module with constants
@@ -197,13 +223,11 @@ module Dry
197
223
  #
198
224
  # @api public
199
225
  #
200
- # rubocop:disable Naming/MethodName
201
226
  def self.Types(*namespaces, default: Types::Undefined, **aliases)
202
227
  Types::Module.new(Types.container, *namespaces, default: default, **aliases)
203
228
  end
204
- # rubocop:enable Naming/MethodName
205
229
  end
206
230
 
207
- require 'dry/types/core' # load built-in types
208
- require 'dry/types/extensions'
209
- require 'dry/types/printer'
231
+ require "dry/types/core" # load built-in types
232
+ require "dry/types/extensions"
233
+ require "dry/types/printer"
@@ -10,7 +10,7 @@ module Dry
10
10
  # @api public
11
11
  class AnyClass < Nominal
12
12
  def self.name
13
- 'Any'
13
+ "Any"
14
14
  end
15
15
 
16
16
  # @api private
@@ -22,7 +22,7 @@ module Dry
22
22
  #
23
23
  # @api public
24
24
  def name
25
- 'Any'
25
+ "Any"
26
26
  end
27
27
 
28
28
  # @param [Hash] new_options
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/types/array/member'
4
- require 'dry/types/array/constructor'
3
+ require "dry/types/array/member"
4
+ require "dry/types/array/constructor"
5
5
 
6
6
  module Dry
7
7
  module Types
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/types/constructor'
3
+ require "dry/types/constructor"
4
4
 
5
5
  module Dry
6
6
  module Types
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/types/array/constructor'
3
+ require "dry/types/array/constructor"
4
4
 
5
5
  module Dry
6
6
  module Types
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/core/deprecations'
3
+ require "dry/core/deprecations"
4
4
 
5
5
  module Dry
6
6
  module Types
@@ -42,7 +42,7 @@ module Dry
42
42
  #
43
43
  # @api public
44
44
  def optional
45
- Types['strict.nil'] | self
45
+ Types["nil"] | self
46
46
  end
47
47
 
48
48
  # Turn a type into a constrained type
@@ -59,7 +59,7 @@ module Dry
59
59
  # Turn a type into a type with a default value
60
60
  #
61
61
  # @param [Object] input
62
- # @param [Hash] options
62
+ # @option [Boolean] shared Whether it's safe to share the value across type applications
63
63
  # @param [#call,nil] block
64
64
  #
65
65
  # @raise [ConstraintError]
@@ -69,23 +69,27 @@ module Dry
69
69
  # @api public
70
70
  def default(input = Undefined, options = EMPTY_HASH, &block)
71
71
  unless input.frozen? || options[:shared]
72
- where = Dry::Core::Deprecations::STACK.()
73
- Dry::Core::Deprecations.warn(
72
+ where = Core::Deprecations::STACK.()
73
+ Core::Deprecations.warn(
74
74
  "#{input.inspect} is mutable."\
75
- ' Be careful: types will return the same instance of the default'\
76
- ' value every time. Call `.freeze` when setting the default'\
77
- ' or pass `shared: true` to discard this warning.'\
75
+ " Be careful: types will return the same instance of the default"\
76
+ " value every time. Call `.freeze` when setting the default"\
77
+ " or pass `shared: true` to discard this warning."\
78
78
  "\n#{where}",
79
79
  tag: :'dry-types'
80
80
  )
81
81
  end
82
82
 
83
- value = input.equal?(Undefined) ? block : input
83
+ value = Undefined.default(input, block)
84
+ type = Default[value].new(self, value)
84
85
 
85
- if value.respond_to?(:call) || valid?(value)
86
- Default[value].new(self, value)
86
+ if !type.callable? && !valid?(value)
87
+ raise ConstraintError.new(
88
+ "default value #{value.inspect} violates constraints",
89
+ value
90
+ )
87
91
  else
88
- raise ConstraintError.new("default value #{value.inspect} violates constraints", value)
92
+ type
89
93
  end
90
94
  end
91
95
 
@@ -127,18 +131,62 @@ module Dry
127
131
  #
128
132
  # @api public
129
133
  def constructor(constructor = nil, **options, &block)
130
- constructor_type.new(with(**options), fn: constructor || block)
134
+ constructor_type[with(**options), fn: constructor || block]
131
135
  end
132
136
  alias_method :append, :constructor
133
137
  alias_method :prepend, :constructor
134
138
  alias_method :>>, :constructor
135
139
  alias_method :<<, :constructor
140
+
141
+ # Use the given value on type mismatch
142
+ #
143
+ # @param [Object] value
144
+ # @option [Boolean] shared Whether it's safe to share the value across type applications
145
+ # @param [#call,nil] fallback
146
+ #
147
+ # @return [Constructor]
148
+ #
149
+ # @api public
150
+ def fallback(value = Undefined, shared: false, &_fallback)
151
+ if Undefined.equal?(value) && !block_given?
152
+ raise ::ArgumentError, "fallback value or a block must be given"
153
+ end
154
+
155
+ if !block_given? && !valid?(value)
156
+ raise ConstraintError.new(
157
+ "fallback value #{value.inspect} violates constraints",
158
+ value
159
+ )
160
+ end
161
+
162
+ unless value.frozen? || shared
163
+ where = Core::Deprecations::STACK.()
164
+ Core::Deprecations.warn(
165
+ "#{value.inspect} is mutable."\
166
+ " Be careful: types will return the same instance of the fallback"\
167
+ " value every time. Call `.freeze` when setting the fallback"\
168
+ " or pass `shared: true` to discard this warning."\
169
+ "\n#{where}",
170
+ tag: :'dry-types'
171
+ )
172
+ end
173
+
174
+ constructor do |input, type, &_block|
175
+ type.(input) do |output = input|
176
+ if block_given?
177
+ yield(output)
178
+ else
179
+ value
180
+ end
181
+ end
182
+ end
183
+ end
136
184
  end
137
185
  end
138
186
  end
139
187
 
140
- require 'dry/types/default'
141
- require 'dry/types/constrained'
142
- require 'dry/types/enum'
143
- require 'dry/types/lax'
144
- require 'dry/types/sum'
188
+ require "dry/types/default"
189
+ require "dry/types/constrained"
190
+ require "dry/types/enum"
191
+ require "dry/types/lax"
192
+ require "dry/types/sum"
@@ -4,7 +4,6 @@ module Dry
4
4
  module Types
5
5
  # Common API for building type objects in a convenient way
6
6
  #
7
- # rubocop:disable Naming/MethodName
8
7
  #
9
8
  # @api public
10
9
  module BuilderMethods
@@ -133,7 +132,7 @@ module Dry
133
132
  #
134
133
  # @return [Dry::Types::Contrained]
135
134
  def Interface(*methods)
136
- methods.reduce(Types['nominal.any']) do |type, method|
135
+ methods.reduce(Types["nominal.any"]) do |type, method|
137
136
  type.constrained(respond_to: method)
138
137
  end
139
138
  end