dry-types 0.13.2 → 1.5.1

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