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/LICENSE CHANGED
@@ -1,20 +1,20 @@
1
- Copyright (c) 2013-2014 Piotr Solnica
1
+ The MIT License (MIT)
2
2
 
3
- Permission is hereby granted, free of charge, to any person obtaining
4
- a copy of this software and associated documentation files (the
5
- "Software"), to deal in the Software without restriction, including
6
- without limitation the rights to use, copy, modify, merge, publish,
7
- distribute, sublicense, and/or sell copies of the Software, and to
8
- permit persons to whom the Software is furnished to do so, subject to
9
- the following conditions:
3
+ Copyright (c) 2015-2021 dry-rb team
10
4
 
11
- The above copyright notice and this permission notice shall be
12
- included in all copies or substantial portions of the Software.
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
13
11
 
14
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -1,27 +1,29 @@
1
1
  [gem]: https://rubygems.org/gems/dry-types
2
- [travis]: https://travis-ci.org/dry-rb/dry-types
3
- [codeclimate]: https://codeclimate.com/github/dry-rb/dry-types
4
- [coveralls]: https://coveralls.io/r/dry-rb/dry-types
2
+ [actions]: https://github.com/dry-rb/dry-types/actions
3
+ [codacy]: https://www.codacy.com/gh/dry-rb/dry-types
4
+ [chat]: https://dry-rb.zulipchat.com
5
5
  [inchpages]: http://inch-ci.org/github/dry-rb/dry-types
6
6
 
7
- # dry-types [![Join the chat at https://gitter.im/dry-rb/chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/dry-rb/chat)
7
+ # dry-types [![Join the chat at https://dry-rb.zulipchat.com](https://img.shields.io/badge/dry--rb-join%20chat-%23346b7a.svg)][chat]
8
8
 
9
9
  [![Gem Version](https://badge.fury.io/rb/dry-types.svg)][gem]
10
- [![Build Status](https://travis-ci.org/dry-rb/dry-types.svg?branch=master)][travis]
11
- [![Code Climate](https://codeclimate.com/github/dry-rb/dry-types/badges/gpa.svg)][codeclimate]
12
- [![Test Coverage](https://codeclimate.com/github/dry-rb/dry-types/badges/coverage.svg)][codeclimate]
10
+ [![CI Status](https://github.com/dry-rb/dry-types/workflows/ci/badge.svg)][actions]
11
+ [![Codacy Badge](https://api.codacy.com/project/badge/Grade/f2d71613195f4da993acb9ac9d6ea336)][codacy]
12
+ [![Codacy Badge](https://api.codacy.com/project/badge/Coverage/f2d71613195f4da993acb9ac9d6ea336)][codacy]
13
13
  [![Inline docs](http://inch-ci.org/github/dry-rb/dry-types.svg?branch=master)][inchpages]
14
14
 
15
15
  ## Links
16
16
 
17
- * [Documentation](http://dry-rb.org/gems/dry-types)
17
+ * [User documentation](http://dry-rb.org/gems/dry-types)
18
+ * [API documentation](http://rubydoc.info/gems/dry-types)
18
19
 
19
- ## Development
20
+ ## Supported Ruby versions
20
21
 
21
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rake run_specs` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
22
+ This library officially supports the following Ruby versions:
22
23
 
23
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
24
+ * MRI >= `2.5`
25
+ * jruby >= `9.2`
24
26
 
25
- ## Contributing
27
+ ## License
26
28
 
27
- Bug reports and pull requests are welcome on GitHub at https://github.com/dry-rb/dry-types.
29
+ See `LICENSE` file.
data/dry-types.gemspec CHANGED
@@ -1,45 +1,42 @@
1
- lib = File.expand_path('../lib', __FILE__)
1
+ # frozen_string_literal: true
2
+ # this file is managed by dry-rb/devtools project
3
+
4
+ lib = File.expand_path('lib', __dir__)
2
5
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
6
  require 'dry/types/version'
4
7
 
5
8
  Gem::Specification.new do |spec|
6
- spec.name = "dry-types"
7
- spec.version = Dry::Types::VERSION.dup
9
+ spec.name = 'dry-types'
8
10
  spec.authors = ["Piotr Solnica"]
9
11
  spec.email = ["piotr.solnica@gmail.com"]
10
12
  spec.license = 'MIT'
13
+ spec.version = Dry::Types::VERSION.dup
11
14
 
12
- spec.summary = 'Type system for Ruby supporting coercions, constraints and complex types like structs, value objects, enums etc.'
15
+ spec.summary = "Type system for Ruby supporting coercions, constraints and complex types like structs, value objects, enums etc"
13
16
  spec.description = spec.summary
14
- spec.homepage = "https://github.com/dry-rb/dry-types"
17
+ spec.homepage = 'https://dry-rb.org/gems/dry-types'
18
+ spec.files = Dir["CHANGELOG.md", "LICENSE", "README.md", "dry-types.gemspec", "lib/**/*"]
19
+ spec.bindir = 'bin'
20
+ spec.executables = []
21
+ spec.require_paths = ['lib']
15
22
 
16
- # Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
17
- # delete this section to allow pushing this gem to any host.
18
- if spec.respond_to?(:metadata)
19
- spec.metadata['allowed_push_host'] = "https://rubygems.org"
20
- spec.metadata['changelog_uri'] = "https://github.com/dry-rb/dry-types/blob/master/CHANGELOG.md"
21
- spec.metadata['source_code_uri'] = "https://github.com/dry-rb/dry-types"
22
- spec.metadata['bug_tracker_uri'] = "https://github.com/dry-rb/dry-types/issues"
23
- else
24
- raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
25
- end
23
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
24
+ spec.metadata['changelog_uri'] = 'https://github.com/dry-rb/dry-types/blob/master/CHANGELOG.md'
25
+ spec.metadata['source_code_uri'] = 'https://github.com/dry-rb/dry-types'
26
+ spec.metadata['bug_tracker_uri'] = 'https://github.com/dry-rb/dry-types/issues'
26
27
 
27
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } - ['bin/console', 'bin/setup']
28
- spec.bindir = "exe"
29
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
30
- spec.require_paths = ["lib"]
31
- spec.required_ruby_version = ">= 2.3.0"
28
+ spec.required_ruby_version = ">= 2.5.0"
32
29
 
33
- spec.add_runtime_dependency 'concurrent-ruby', '~> 1.0'
34
- spec.add_runtime_dependency 'dry-core', '~> 0.4', '>= 0.4.4'
35
- spec.add_runtime_dependency 'dry-inflector', '~> 0.1', '>= 0.1.2'
36
- spec.add_runtime_dependency 'dry-container', '~> 0.3'
37
- spec.add_runtime_dependency 'dry-equalizer', '~> 0.2', '>= 0.2.2'
38
- spec.add_runtime_dependency 'dry-logic', '~> 0.5', '>= 0.5'
30
+ # to update dependencies edit project.yml
31
+ spec.add_runtime_dependency "concurrent-ruby", "~> 1.0"
32
+ spec.add_runtime_dependency "dry-container", "~> 0.3"
33
+ spec.add_runtime_dependency "dry-core", "~> 0.5", ">= 0.5"
34
+ spec.add_runtime_dependency "dry-inflector", "~> 0.1", ">= 0.1.2"
35
+ spec.add_runtime_dependency "dry-logic", "~> 1.0", ">= 1.0.2"
39
36
 
40
37
  spec.add_development_dependency "bundler"
41
- spec.add_development_dependency "rake", "~> 11.0"
42
- spec.add_development_dependency "rspec", "~> 3.3"
43
- spec.add_development_dependency 'dry-monads', '~> 0.2'
44
- spec.add_development_dependency 'yard', '~> 0.9.5'
38
+ spec.add_development_dependency "dry-monads", "~> 1.0"
39
+ spec.add_development_dependency "rake"
40
+ spec.add_development_dependency "rspec"
41
+ spec.add_development_dependency "yard"
45
42
  end
data/lib/dry/types/any.rb CHANGED
@@ -1,36 +1,47 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Dry
2
4
  module Types
3
- Any = Class.new(Nominal) do
5
+ # Any is a nominal type that defines Object as the primitive class
6
+ #
7
+ # This type is useful in places where you can't be specific about the type
8
+ # and anything is acceptable.
9
+ #
10
+ # @api public
11
+ class AnyClass < Nominal
4
12
  def self.name
5
- 'Any'
13
+ "Any"
6
14
  end
7
15
 
16
+ # @api private
8
17
  def initialize(**options)
9
- super(::Object, options)
18
+ super(::Object, **options)
10
19
  end
11
20
 
12
21
  # @return [String]
22
+ #
23
+ # @api public
13
24
  def name
14
- 'Any'
15
- end
16
-
17
- # @param [Object] any input is valid
18
- # @return [true]
19
- def valid?(_)
20
- true
25
+ "Any"
21
26
  end
22
- alias_method :===, :valid?
23
27
 
24
28
  # @param [Hash] new_options
29
+ #
25
30
  # @return [Type]
31
+ #
32
+ # @api public
26
33
  def with(**new_options)
27
34
  self.class.new(**options, meta: @meta, **new_options)
28
35
  end
29
36
 
30
37
  # @return [Array]
38
+ #
39
+ # @api public
31
40
  def to_ast(meta: true)
32
41
  [:any, meta ? self.meta : EMPTY_HASH]
33
42
  end
34
- end.new
43
+ end
44
+
45
+ Any = AnyClass.new
35
46
  end
36
47
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/types/constructor"
4
+
5
+ module Dry
6
+ module Types
7
+ # @api public
8
+ class Array < Nominal
9
+ # @api private
10
+ class Constructor < ::Dry::Types::Constructor
11
+ # @api private
12
+ def constructor_type
13
+ ::Dry::Types::Array::Constructor
14
+ end
15
+
16
+ # @return [Lax]
17
+ #
18
+ # @api public
19
+ def lax
20
+ Lax.new(type.lax.constructor(fn, meta: meta))
21
+ end
22
+
23
+ # @see Dry::Types::Array#of
24
+ #
25
+ # @api public
26
+ def of(member)
27
+ type.of(member).constructor(fn, meta: meta)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -1,57 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/types/array/constructor"
4
+
1
5
  module Dry
2
6
  module Types
3
7
  class Array < Nominal
8
+ # Member arrays define their member type that is applied to each element
9
+ #
10
+ # @api public
4
11
  class Member < Array
5
12
  # @return [Type]
6
13
  attr_reader :member
7
14
 
8
15
  # @param [Class] primitive
9
16
  # @param [Hash] options
17
+ #
10
18
  # @option options [Type] :member
11
- def initialize(primitive, options = {})
19
+ #
20
+ # @api private
21
+ def initialize(primitive, **options)
12
22
  @member = options.fetch(:member)
13
23
  super
14
24
  end
15
25
 
16
26
  # @param [Object] input
17
- # @param [Symbol] meth
27
+ #
18
28
  # @return [Array]
19
- def call(input, meth = :call)
20
- input.map { |el| member.__send__(meth, el) }
29
+ #
30
+ # @api private
31
+ def call_unsafe(input)
32
+ if primitive?(input)
33
+ input.each_with_object([]) do |el, output|
34
+ coerced = member.call_unsafe(el)
35
+
36
+ output << coerced unless Undefined.equal?(coerced)
37
+ end
38
+ else
39
+ super
40
+ end
21
41
  end
22
- alias_method :[], :call
23
42
 
24
- # @param [Array, #all?, Object] value
25
- # @return [Boolean]
26
- def valid?(value)
27
- super && value.all? { |el| member.valid?(el) }
43
+ # @param [Object] input
44
+ # @return [Array]
45
+ #
46
+ # @api private
47
+ def call_safe(input)
48
+ if primitive?(input)
49
+ failed = false
50
+
51
+ result = input.each_with_object([]) do |el, output|
52
+ coerced = member.call_safe(el) { |out = el|
53
+ failed = true
54
+ out
55
+ }
56
+
57
+ output << coerced unless Undefined.equal?(coerced)
58
+ end
59
+
60
+ failed ? yield(result) : result
61
+ else
62
+ yield
63
+ end
28
64
  end
29
65
 
30
66
  # @param [Array, Object] input
31
67
  # @param [#call,nil] block
68
+ #
32
69
  # @yieldparam [Failure] failure
33
70
  # @yieldreturn [Result]
71
+ #
34
72
  # @return [Result,Logic::Result]
73
+ #
74
+ # @api public
35
75
  def try(input, &block)
36
- if input.is_a?(::Array)
37
- result = call(input, :try).reject { |r| r.input.equal?(Undefined) }
38
- output = result.map(&:input)
76
+ if primitive?(input)
77
+ output = []
78
+
79
+ result = input.map { |el| member.try(el) }
80
+ result.each do |r|
81
+ output << r.input unless Undefined.equal?(r.input)
82
+ end
39
83
 
40
84
  if result.all?(&:success?)
41
85
  success(output)
42
86
  else
43
- failure = failure(output, result.select(&:failure?))
87
+ error = result.find(&:failure?).error
88
+ failure = failure(output, error)
44
89
  block ? yield(failure) : failure
45
90
  end
46
91
  else
47
- failure = failure(input, "#{input} is not an array")
92
+ failure = failure(input, CoercionError.new("#{input} is not an array"))
48
93
  block ? yield(failure) : failure
49
94
  end
50
95
  end
51
96
 
52
- # @api public
97
+ # Build a lax type
53
98
  #
99
+ # @return [Lax]
100
+ #
101
+ # @api public
102
+ def lax
103
+ Lax.new(Member.new(primitive, **options, member: member.lax, meta: meta))
104
+ end
105
+
54
106
  # @see Nominal#to_ast
107
+ #
108
+ # @api public
55
109
  def to_ast(meta: true)
56
110
  if member.respond_to?(:to_ast)
57
111
  [:array, [member.to_ast(meta: meta), meta ? self.meta : EMPTY_HASH]]
@@ -59,6 +113,11 @@ module Dry
59
113
  [:array, [member, meta ? self.meta : EMPTY_HASH]]
60
114
  end
61
115
  end
116
+
117
+ # @api private
118
+ def constructor_type
119
+ ::Dry::Types::Array::Constructor
120
+ end
62
121
  end
63
122
  end
64
123
  end
@@ -1,10 +1,21 @@
1
- require 'dry/types/array/member'
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/types/array/member"
4
+ require "dry/types/array/constructor"
2
5
 
3
6
  module Dry
4
7
  module Types
8
+ # Array type can be used to define an array with optional member type
9
+ #
10
+ # @api public
5
11
  class Array < Nominal
6
- # @param [Type] type
12
+ # Build an array type with a member type
13
+ #
14
+ # @param [Type,#call] type
15
+ #
7
16
  # @return [Array::Member]
17
+ #
18
+ # @api public
8
19
  def of(type)
9
20
  member =
10
21
  case type
@@ -14,6 +25,11 @@ module Dry
14
25
 
15
26
  Array::Member.new(primitive, **options, member: member)
16
27
  end
28
+
29
+ # @api private
30
+ def constructor_type
31
+ ::Dry::Types::Array::Constructor
32
+ end
17
33
  end
18
34
  end
19
35
  end
@@ -1,67 +1,105 @@
1
- require 'dry/core/deprecations'
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/core/deprecations"
2
4
 
3
5
  module Dry
4
6
  module Types
7
+ # Common API for building types and composition
8
+ #
9
+ # @api public
5
10
  module Builder
6
11
  include Dry::Core::Constants
7
12
 
8
13
  # @return [Class]
14
+ #
15
+ # @api private
9
16
  def constrained_type
10
17
  Constrained
11
18
  end
12
19
 
13
20
  # @return [Class]
21
+ #
22
+ # @api private
14
23
  def constructor_type
15
24
  Constructor
16
25
  end
17
26
 
27
+ # Compose two types into a Sum type
28
+ #
18
29
  # @param [Type] other
30
+ #
19
31
  # @return [Sum, Sum::Constrained]
32
+ #
33
+ # @api private
20
34
  def |(other)
21
35
  klass = constrained? && other.constrained? ? Sum::Constrained : Sum
22
36
  klass.new(self, other)
23
37
  end
24
38
 
39
+ # Turn a type into an optional type
40
+ #
25
41
  # @return [Sum]
42
+ #
43
+ # @api public
26
44
  def optional
27
- Types['strict.nil'] | self
45
+ Types["nil"] | self
28
46
  end
29
47
 
48
+ # Turn a type into a constrained type
49
+ #
30
50
  # @param [Hash] options constraining rule (see {Types.Rule})
51
+ #
31
52
  # @return [Constrained]
53
+ #
54
+ # @api public
32
55
  def constrained(options)
33
56
  constrained_type.new(self, rule: Types.Rule(options))
34
57
  end
35
58
 
59
+ # Turn a type into a type with a default value
60
+ #
36
61
  # @param [Object] input
37
- # @param [Hash] options
62
+ # @option [Boolean] shared Whether it's safe to share the value across type applications
38
63
  # @param [#call,nil] block
64
+ #
39
65
  # @raise [ConstraintError]
66
+ #
40
67
  # @return [Default]
68
+ #
69
+ # @api public
41
70
  def default(input = Undefined, options = EMPTY_HASH, &block)
42
71
  unless input.frozen? || options[:shared]
43
- where = Dry::Core::Deprecations::STACK.()
44
- Dry::Core::Deprecations.warn(
72
+ where = Core::Deprecations::STACK.()
73
+ Core::Deprecations.warn(
45
74
  "#{input.inspect} is mutable."\
46
- ' Be careful: types will return the same instance of the default'\
47
- ' value every time. Call `.freeze` when setting the default'\
48
- ' or pass `shared: true` to discard this warning.'\
49
- "\n#{ where }",
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
+ "\n#{where}",
50
79
  tag: :'dry-types'
51
80
  )
52
81
  end
53
82
 
54
- value = input.equal?(Undefined) ? block : input
83
+ value = Undefined.default(input, block)
84
+ type = Default[value].new(self, value)
55
85
 
56
- if value.respond_to?(:call) || valid?(value)
57
- 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
+ )
58
91
  else
59
- raise ConstraintError.new("default value #{value.inspect} violates constraints", value)
92
+ type
60
93
  end
61
94
  end
62
95
 
96
+ # Define an enum on top of the existing type
97
+ #
63
98
  # @param [Array] values
99
+ #
64
100
  # @return [Enum]
101
+ #
102
+ # @api public
65
103
  def enum(*values)
66
104
  mapping =
67
105
  if values.length == 1 && values[0].is_a?(::Hash)
@@ -73,24 +111,82 @@ module Dry
73
111
  Enum.new(constrained(included_in: mapping.keys), mapping: mapping)
74
112
  end
75
113
 
76
- # @return [Safe]
77
- def safe
78
- Safe.new(self)
114
+ # Turn a type into a lax type that will rescue from type-errors and
115
+ # return the original input
116
+ #
117
+ # @return [Lax]
118
+ #
119
+ # @api public
120
+ def lax
121
+ Lax.new(self)
79
122
  end
80
123
 
124
+ # Define a constructor for the type
125
+ #
81
126
  # @param [#call,nil] constructor
82
127
  # @param [Hash] options
83
128
  # @param [#call,nil] block
129
+ #
84
130
  # @return [Constructor]
131
+ #
132
+ # @api public
85
133
  def constructor(constructor = nil, **options, &block)
86
- constructor_type.new(with(options), fn: constructor || block)
134
+ constructor_type[with(**options), fn: constructor || block]
135
+ end
136
+ alias_method :append, :constructor
137
+ alias_method :prepend, :constructor
138
+ alias_method :>>, :constructor
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
87
183
  end
88
184
  end
89
185
  end
90
186
  end
91
187
 
92
- require 'dry/types/default'
93
- require 'dry/types/constrained'
94
- require 'dry/types/enum'
95
- require 'dry/types/safe'
96
- 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"