dry-types 0.13.2 → 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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +763 -233
  3. data/LICENSE +17 -17
  4. data/README.md +15 -13
  5. data/dry-types.gemspec +28 -28
  6. data/lib/dry-types.rb +3 -1
  7. data/lib/dry/types.rb +156 -76
  8. data/lib/dry/types/any.rb +32 -12
  9. data/lib/dry/types/array.rb +19 -6
  10. data/lib/dry/types/array/constructor.rb +32 -0
  11. data/lib/dry/types/array/member.rb +75 -16
  12. data/lib/dry/types/builder.rb +131 -15
  13. data/lib/dry/types/builder_methods.rb +49 -20
  14. data/lib/dry/types/coercions.rb +76 -22
  15. data/lib/dry/types/coercions/json.rb +43 -7
  16. data/lib/dry/types/coercions/params.rb +118 -31
  17. data/lib/dry/types/compat.rb +0 -2
  18. data/lib/dry/types/compiler.rb +56 -41
  19. data/lib/dry/types/constrained.rb +81 -32
  20. data/lib/dry/types/constrained/coercible.rb +36 -6
  21. data/lib/dry/types/constraints.rb +18 -4
  22. data/lib/dry/types/constructor.rb +127 -54
  23. data/lib/dry/types/constructor/function.rb +216 -0
  24. data/lib/dry/types/constructor/wrapper.rb +94 -0
  25. data/lib/dry/types/container.rb +7 -0
  26. data/lib/dry/types/core.rb +54 -21
  27. data/lib/dry/types/decorator.rb +38 -17
  28. data/lib/dry/types/default.rb +61 -16
  29. data/lib/dry/types/enum.rb +43 -20
  30. data/lib/dry/types/errors.rb +75 -9
  31. data/lib/dry/types/extensions.rb +7 -1
  32. data/lib/dry/types/extensions/maybe.rb +74 -16
  33. data/lib/dry/types/extensions/monads.rb +29 -0
  34. data/lib/dry/types/fn_container.rb +6 -1
  35. data/lib/dry/types/hash.rb +86 -67
  36. data/lib/dry/types/hash/constructor.rb +33 -0
  37. data/lib/dry/types/inflector.rb +3 -1
  38. data/lib/dry/types/json.rb +18 -16
  39. data/lib/dry/types/lax.rb +75 -0
  40. data/lib/dry/types/map.rb +76 -33
  41. data/lib/dry/types/meta.rb +51 -0
  42. data/lib/dry/types/module.rb +120 -0
  43. data/lib/dry/types/nominal.rb +210 -0
  44. data/lib/dry/types/options.rb +13 -26
  45. data/lib/dry/types/params.rb +39 -25
  46. data/lib/dry/types/predicate_inferrer.rb +238 -0
  47. data/lib/dry/types/predicate_registry.rb +34 -0
  48. data/lib/dry/types/primitive_inferrer.rb +97 -0
  49. data/lib/dry/types/printable.rb +16 -0
  50. data/lib/dry/types/printer.rb +315 -0
  51. data/lib/dry/types/result.rb +29 -3
  52. data/lib/dry/types/schema.rb +408 -0
  53. data/lib/dry/types/schema/key.rb +156 -0
  54. data/lib/dry/types/spec/types.rb +103 -33
  55. data/lib/dry/types/sum.rb +84 -35
  56. data/lib/dry/types/type.rb +49 -0
  57. data/lib/dry/types/version.rb +3 -1
  58. metadata +68 -79
  59. data/.gitignore +0 -10
  60. data/.rspec +0 -2
  61. data/.travis.yml +0 -29
  62. data/.yardopts +0 -5
  63. data/CONTRIBUTING.md +0 -29
  64. data/Gemfile +0 -24
  65. data/Rakefile +0 -20
  66. data/benchmarks/hash_schemas.rb +0 -51
  67. data/lib/dry/types/compat/form_types.rb +0 -27
  68. data/lib/dry/types/compat/int.rb +0 -14
  69. data/lib/dry/types/definition.rb +0 -113
  70. data/lib/dry/types/hash/schema.rb +0 -199
  71. data/lib/dry/types/hash/schema_builder.rb +0 -75
  72. data/lib/dry/types/safe.rb +0 -59
@@ -1,30 +1,46 @@
1
- require 'dry/types/decorator'
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/core/equalizer"
4
+ require "dry/types/decorator"
2
5
 
3
6
  module Dry
4
7
  module Types
8
+ # Default types are useful when a missing value should be replaced by a default one
9
+ #
10
+ # @api public
5
11
  class Default
6
- include Type
7
- include Dry::Equalizer(:type, :options, :value)
8
- include Decorator
9
- include Builder
10
-
12
+ # @api private
11
13
  class Callable < Default
12
- include Dry::Equalizer(:type, :options)
14
+ include Dry::Equalizer(:type, inspect: false, immutable: true)
13
15
 
14
16
  # Evaluates given callable
15
17
  # @return [Object]
16
18
  def evaluate
17
19
  value.call(type)
18
20
  end
21
+
22
+ # @return [true]
23
+ def callable?
24
+ true
25
+ end
19
26
  end
20
27
 
28
+ include Type
29
+ include Decorator
30
+ include Builder
31
+ include Printable
32
+ include Dry::Equalizer(:type, :value, inspect: false, immutable: true)
33
+
21
34
  # @return [Object]
22
35
  attr_reader :value
23
36
 
24
37
  alias_method :evaluate, :value
25
38
 
26
39
  # @param [Object, #call] value
40
+ #
27
41
  # @return [Class] {Default} or {Default::Callable}
42
+ #
43
+ # @api private
28
44
  def self.[](value)
29
45
  if value.respond_to?(:call)
30
46
  Callable
@@ -35,49 +51,78 @@ module Dry
35
51
 
36
52
  # @param [Type] type
37
53
  # @param [Object] value
54
+ #
55
+ # @api private
38
56
  def initialize(type, value, **options)
39
57
  super
40
58
  @value = value
41
59
  end
42
60
 
61
+ # Build a constrained type
62
+ #
43
63
  # @param [Array] args see {Dry::Types::Builder#constrained}
64
+ #
44
65
  # @return [Default]
66
+ #
67
+ # @api public
45
68
  def constrained(*args)
46
69
  type.constrained(*args).default(value)
47
70
  end
48
71
 
49
72
  # @return [true]
73
+ #
74
+ # @api public
50
75
  def default?
51
76
  true
52
77
  end
53
78
 
54
79
  # @param [Object] input
80
+ #
55
81
  # @return [Result::Success]
82
+ #
83
+ # @api public
56
84
  def try(input)
57
85
  success(call(input))
58
86
  end
59
87
 
88
+ # @return [Boolean]
89
+ #
90
+ # @api public
60
91
  def valid?(value = Undefined)
61
- value.equal?(Undefined) || super
92
+ Undefined.equal?(value) || super
62
93
  end
63
94
 
64
95
  # @param [Object] input
96
+ #
65
97
  # @return [Object] value passed through {#type} or {#default} value
66
- def call(input = Undefined)
98
+ #
99
+ # @api private
100
+ def call_unsafe(input = Undefined)
67
101
  if input.equal?(Undefined)
68
102
  evaluate
69
103
  else
70
- output = type[input]
71
- output.nil? ? evaluate : output
104
+ Undefined.default(type.call_unsafe(input)) { evaluate }
72
105
  end
73
106
  end
74
- alias_method :[], :call
75
107
 
76
- private
108
+ # @param [Object] input
109
+ #
110
+ # @return [Object] value passed through {#type} or {#default} value
111
+ #
112
+ # @api private
113
+ def call_safe(input = Undefined, &block)
114
+ if input.equal?(Undefined)
115
+ evaluate
116
+ else
117
+ Undefined.default(type.call_safe(input, &block)) { evaluate }
118
+ end
119
+ end
77
120
 
78
- # Replace underlying type
79
- def __new__(type)
80
- self.class.new(type, value, options)
121
+ # @return [false]
122
+ #
123
+ # @api private
124
+ def callable?
125
+ false
81
126
  end
82
127
  end
83
128
  end
@@ -1,11 +1,18 @@
1
- require 'dry/types/decorator'
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/core/equalizer"
4
+ require "dry/types/decorator"
2
5
 
3
6
  module Dry
4
7
  module Types
8
+ # Enum types can be used to define an enum on top of an existing type
9
+ #
10
+ # @api public
5
11
  class Enum
6
12
  include Type
7
- include Dry::Equalizer(:type, :options, :mapping)
13
+ include Dry::Equalizer(:type, :mapping, inspect: false, immutable: true)
8
14
  include Decorator
15
+ include Builder
9
16
 
10
17
  # @return [Array]
11
18
  attr_reader :values
@@ -19,7 +26,9 @@ module Dry
19
26
  # @param [Type] type
20
27
  # @param [Hash] options
21
28
  # @option options [Array] :values
22
- def initialize(type, options)
29
+ #
30
+ # @api private
31
+ def initialize(type, **options)
23
32
  super
24
33
  @mapping = options.fetch(:mapping).freeze
25
34
  @values = @mapping.keys.freeze
@@ -27,45 +36,59 @@ module Dry
27
36
  freeze
28
37
  end
29
38
 
30
- # @param [Object] input
31
39
  # @return [Object]
32
- def call(input = Undefined)
33
- type[map_value(input)]
40
+ #
41
+ # @api private
42
+ def call_unsafe(input)
43
+ type.call_unsafe(map_value(input))
44
+ end
45
+
46
+ # @return [Object]
47
+ #
48
+ # @api private
49
+ def call_safe(input, &block)
50
+ type.call_safe(map_value(input), &block)
34
51
  end
35
- alias_method :[], :call
36
52
 
37
- # @param [Object] input
38
- # @yieldparam [Failure] failure
39
- # @yieldreturn [Result]
40
- # @return [Logic::Result]
41
- # @return [Object] if coercion fails and a block is given
53
+ # @see Dry::Types::Constrained#try
54
+ #
55
+ # @api public
42
56
  def try(input)
43
57
  super(map_value(input))
44
58
  end
45
59
 
60
+ # @api private
46
61
  def default(*)
47
- raise '.enum(*values).default(value) is not supported. Call '\
48
- '.default(value).enum(*values) instead'
62
+ raise ".enum(*values).default(value) is not supported. Call "\
63
+ ".default(value).enum(*values) instead"
49
64
  end
50
65
 
51
66
  # Check whether a value is in the enum
52
67
  alias_method :include?, :valid?
53
68
 
54
- # @api public
69
+ # @see Nominal#to_ast
55
70
  #
56
- # @see Definition#to_ast
71
+ # @api public
57
72
  def to_ast(meta: true)
58
- [:enum, [type.to_ast(meta: meta),
59
- mapping,
60
- meta ? self.meta : EMPTY_HASH]]
73
+ [:enum, [type.to_ast(meta: meta), mapping]]
74
+ end
75
+
76
+ # @return [String]
77
+ #
78
+ # @api public
79
+ def to_s
80
+ PRINTER.(self)
61
81
  end
82
+ alias_method :inspect, :to_s
62
83
 
63
84
  private
64
85
 
65
86
  # Maps a value
66
87
  #
67
- # @params [Object]
88
+ # @param [Object] input
89
+ #
68
90
  # @return [Object]
91
+ #
69
92
  # @api private
70
93
  def map_value(input)
71
94
  if input.equal?(Undefined)
@@ -1,14 +1,71 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Dry
2
4
  module Types
3
- extend Dry::Core::ClassAttributes
5
+ extend ::Dry::Core::ClassAttributes
4
6
 
5
7
  # @!attribute [r] namespace
6
- # @return [Container{String => Definition}]
8
+ # @return [Container{String => Nominal}]
7
9
  defines :namespace
8
10
 
9
11
  namespace self
10
12
 
11
- class SchemaError < TypeError
13
+ # Base class for coercion errors raise by dry-types
14
+ #
15
+ class CoercionError < ::StandardError
16
+ # @api private
17
+ def self.handle(exception, meta: Undefined)
18
+ if block_given?
19
+ yield
20
+ else
21
+ raise new(
22
+ exception.message,
23
+ meta: meta,
24
+ backtrace: exception.backtrace
25
+ )
26
+ end
27
+ end
28
+
29
+ # Metadata associated with the error
30
+ #
31
+ # @return [Object]
32
+ attr_reader :meta
33
+
34
+ # @api private
35
+ def initialize(message, meta: Undefined, backtrace: Undefined)
36
+ unless message.is_a?(::String)
37
+ raise ::ArgumentError, "message must be a string, #{message.class} given"
38
+ end
39
+
40
+ super(message)
41
+ @meta = Undefined.default(meta, nil)
42
+ set_backtrace(backtrace) unless Undefined.equal?(backtrace)
43
+ end
44
+ end
45
+
46
+ # Collection of multiple errors
47
+ #
48
+ class MultipleError < CoercionError
49
+ # @return [Array<CoercionError>]
50
+ attr_reader :errors
51
+
52
+ # @param [Array<CoercionError>] errors
53
+ def initialize(errors)
54
+ @errors = errors
55
+ end
56
+
57
+ # @return string
58
+ def message
59
+ errors.map(&:message).join(", ")
60
+ end
61
+
62
+ # @return [Array]
63
+ def meta
64
+ errors.map(&:meta)
65
+ end
66
+ end
67
+
68
+ class SchemaError < CoercionError
12
69
  # @param [String,Symbol] key
13
70
  # @param [Object] value
14
71
  # @param [String, #to_s] result
@@ -17,26 +74,34 @@ module Dry
17
74
  end
18
75
  end
19
76
 
20
- MapError = Class.new(TypeError)
77
+ MapError = ::Class.new(CoercionError)
21
78
 
22
- SchemaKeyError = Class.new(KeyError)
79
+ SchemaKeyError = ::Class.new(CoercionError)
23
80
  private_constant(:SchemaKeyError)
24
81
 
25
82
  class MissingKeyError < SchemaKeyError
83
+ # @return [Symbol]
84
+ attr_reader :key
85
+
26
86
  # @param [String,Symbol] key
27
87
  def initialize(key)
28
- super(":#{key} is missing in Hash input")
88
+ @key = key
89
+ super("#{key.inspect} is missing in Hash input")
29
90
  end
30
91
  end
31
92
 
32
93
  class UnknownKeysError < SchemaKeyError
94
+ # @return [Array<Symbol>]
95
+ attr_reader :keys
96
+
33
97
  # @param [<String, Symbol>] keys
34
- def initialize(*keys)
98
+ def initialize(keys)
99
+ @keys = keys
35
100
  super("unexpected keys #{keys.inspect} in Hash input")
36
101
  end
37
102
  end
38
103
 
39
- class ConstraintError < TypeError
104
+ class ConstraintError < CoercionError
40
105
  # @return [String, #to_s]
41
106
  attr_reader :result
42
107
  # @return [Object]
@@ -56,9 +121,10 @@ module Dry
56
121
  end
57
122
 
58
123
  # @return [String]
59
- def to_s
124
+ def message
60
125
  "#{input.inspect} violates constraints (#{result} failed)"
61
126
  end
127
+ alias_method :to_s, :message
62
128
  end
63
129
  end
64
130
  end
@@ -1,3 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  Dry::Types.register_extension(:maybe) do
2
- require 'dry/types/extensions/maybe'
4
+ require "dry/types/extensions/maybe"
5
+ end
6
+
7
+ Dry::Types.register_extension(:monads) do
8
+ require "dry/types/extensions/monads"
3
9
  end
@@ -1,49 +1,83 @@
1
- require 'dry/monads/maybe'
2
- require 'dry/types/decorator'
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/core/equalizer"
4
+ require "dry/monads/maybe"
5
+ require "dry/types/decorator"
3
6
 
4
7
  module Dry
5
8
  module Types
9
+ # Maybe extension provides Maybe types where values are wrapped using `Either` monad
10
+ #
11
+ # @api public
6
12
  class Maybe
7
13
  include Type
8
- include Dry::Equalizer(:type, :options)
14
+ include ::Dry::Equalizer(:type, :options, inspect: false, immutable: true)
9
15
  include Decorator
10
16
  include Builder
11
- include Dry::Monads::Maybe::Mixin
17
+ include Printable
18
+ include ::Dry::Monads::Maybe::Mixin
19
+
20
+ # @param [Dry::Monads::Maybe, Object] input
21
+ #
22
+ # @return [Dry::Monads::Maybe]
23
+ #
24
+ # @api private
25
+ def call_unsafe(input = Undefined)
26
+ case input
27
+ when ::Dry::Monads::Maybe
28
+ input
29
+ when Undefined
30
+ None()
31
+ else
32
+ Maybe(type.call_unsafe(input))
33
+ end
34
+ end
12
35
 
13
36
  # @param [Dry::Monads::Maybe, Object] input
37
+ #
14
38
  # @return [Dry::Monads::Maybe]
15
- def call(input = Undefined)
39
+ #
40
+ # @api private
41
+ def call_safe(input = Undefined)
16
42
  case input
17
- when Dry::Monads::Maybe
43
+ when ::Dry::Monads::Maybe
18
44
  input
19
45
  when Undefined
20
46
  None()
21
47
  else
22
- Maybe(type[input])
48
+ Maybe(type.call_safe(input) { |output = input| return yield(output) })
23
49
  end
24
50
  end
25
- alias_method :[], :call
26
51
 
27
52
  # @param [Object] input
53
+ #
28
54
  # @return [Result::Success]
55
+ #
56
+ # @api public
29
57
  def try(input = Undefined)
30
- res = if input.equal?(Undefined)
31
- None()
32
- else
33
- Maybe(type[input])
34
- end
58
+ result = type.try(input)
35
59
 
36
- Result::Success.new(res)
60
+ if result.success?
61
+ Result::Success.new(Maybe(result.input))
62
+ else
63
+ result
64
+ end
37
65
  end
38
66
 
39
67
  # @return [true]
68
+ #
69
+ # @api public
40
70
  def default?
41
71
  true
42
72
  end
43
73
 
44
74
  # @param [Object] value
75
+ #
45
76
  # @see Dry::Types::Builder#default
77
+ #
46
78
  # @raise [ArgumentError] if nil provided as default value
79
+ #
80
+ # @api public
47
81
  def default(value)
48
82
  if value.nil?
49
83
  raise ArgumentError, "nil cannot be used as a default of a maybe type"
@@ -54,15 +88,39 @@ module Dry
54
88
  end
55
89
 
56
90
  module Builder
91
+ # Turn a type into a maybe type
92
+ #
57
93
  # @return [Maybe]
94
+ #
95
+ # @api public
96
+ def maybe
97
+ Maybe.new(Types["nil"] | self)
98
+ end
99
+ end
100
+
101
+ # @api private
102
+ class Schema::Key
103
+ # @api private
58
104
  def maybe
59
- Maybe.new(Types['strict.nil'] | self)
105
+ __new__(type.maybe)
106
+ end
107
+ end
108
+
109
+ # @api private
110
+ class Printer
111
+ MAPPING[Maybe] = :visit_maybe
112
+
113
+ # @api private
114
+ def visit_maybe(maybe)
115
+ visit(maybe.type) do |type|
116
+ yield "Maybe<#{type}>"
117
+ end
60
118
  end
61
119
  end
62
120
 
63
121
  # Register non-coercible maybe types
64
122
  NON_NIL.each_key do |name|
65
- register("maybe.strict.#{name}", self["strict.#{name}"].maybe)
123
+ register("maybe.strict.#{name}", self[name.to_s].maybe)
66
124
  end
67
125
 
68
126
  # Register coercible maybe types