dry-types 1.4.0 → 1.5.0

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