dry-types 0.8.1 → 0.9.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.
@@ -37,7 +37,7 @@ module Dry
37
37
  false
38
38
  end
39
39
 
40
- def maybe?
40
+ def constrained?
41
41
  false
42
42
  end
43
43
 
@@ -10,44 +10,37 @@ module Dry
10
10
  end
11
11
  end
12
12
 
13
- class SchemaKeyError < KeyError
13
+ SchemaKeyError = Class.new(KeyError)
14
+ private_constant(:SchemaKeyError)
15
+
16
+ class MissingKeyError < SchemaKeyError
14
17
  def initialize(key)
15
18
  super(":#{key} is missing in Hash input")
16
19
  end
17
20
  end
18
21
 
19
- StructError = Class.new(TypeError)
20
-
21
- class RepeatedAttributeError < ArgumentError
22
- def initialize(key)
23
- super("Attribute :#{key} has already been defined")
22
+ class UnknownKeysError < SchemaKeyError
23
+ def initialize(*keys)
24
+ super("unexpected keys #{keys.inspect} in Hash input")
24
25
  end
25
26
  end
26
27
 
27
28
  ConstraintError = Class.new(TypeError) do
28
- attr_reader :result
29
+ attr_reader :result, :input
29
30
 
30
- def initialize(result)
31
+ def initialize(result, input)
31
32
  @result = result
33
+ @input = input
34
+
32
35
  if result.is_a?(String)
33
- super
36
+ super(result)
34
37
  else
35
- super("#{result.input.inspect} violates constraints (#{failure_message})")
38
+ super(to_s)
36
39
  end
37
40
  end
38
41
 
39
- def input
40
- result.input
41
- end
42
-
43
- def failure_message
44
- if result.respond_to?(:rule)
45
- rule = result.rule
46
- args = rule.predicate.args - [rule.predicate.args.last]
47
- "#{rule.predicate.id}(#{args.map(&:inspect).join(', ')}) failed"
48
- else
49
- result.inspect
50
- end
42
+ def to_s
43
+ "#{input.inspect} violates constraints (#{result} failed)"
51
44
  end
52
45
  end
53
46
  end
@@ -0,0 +1,3 @@
1
+ Dry::Types.register_extension(:maybe) do
2
+ require 'dry/types/extensions/maybe'
3
+ end
@@ -0,0 +1,64 @@
1
+ require 'dry/monads/maybe'
2
+ require 'dry/types/decorator'
3
+
4
+ module Dry
5
+ module Types
6
+ class Maybe
7
+ include Dry::Equalizer(:type, :options)
8
+ include Decorator
9
+ include Builder
10
+ include Dry::Monads::Maybe::Mixin
11
+
12
+ def call(input)
13
+ input.is_a?(Dry::Monads::Maybe) ? input : Maybe(type[input])
14
+ end
15
+ alias_method :[], :call
16
+
17
+ def try(input)
18
+ Result::Success.new(Maybe(type[input]))
19
+ end
20
+
21
+ def maybe?
22
+ true
23
+ end
24
+
25
+ def default(value)
26
+ if value.nil?
27
+ raise ArgumentError, "nil cannot be used as a default of a maybe type"
28
+ else
29
+ super
30
+ end
31
+ end
32
+ end
33
+
34
+ module Builder
35
+ def maybe
36
+ Maybe.new(Types['strict.nil'] | self)
37
+ end
38
+ end
39
+
40
+ class Hash
41
+ module MaybeTypes
42
+ def resolve_missing_value(result, key, type)
43
+ if type.respond_to?(:maybe?) && type.maybe?
44
+ result[key] = type[nil]
45
+ else
46
+ super
47
+ end
48
+ end
49
+ end
50
+
51
+ Schema.include MaybeTypes
52
+ end
53
+
54
+ # Register non-coercible maybe types
55
+ NON_NIL.each_key do |name|
56
+ register("maybe.strict.#{name}", self["strict.#{name}"].maybe)
57
+ end
58
+
59
+ # Register coercible maybe types
60
+ COERCIBLE.each_key do |name|
61
+ register("maybe.coercible.#{name}", self["coercible.#{name}"].maybe)
62
+ end
63
+ end
64
+ end
@@ -19,13 +19,27 @@ module Dry
19
19
  schema(type_map, Weak)
20
20
  end
21
21
 
22
+ def permissive(type_map)
23
+ schema(type_map, Permissive)
24
+ end
25
+
22
26
  def strict(type_map)
23
27
  schema(type_map, Strict)
24
28
  end
25
29
 
30
+ def strict_with_defaults(type_map)
31
+ schema(type_map, StrictWithDefaults)
32
+ end
33
+
26
34
  def symbolized(type_map)
27
35
  schema(type_map, Symbolized)
28
36
  end
37
+
38
+ private
39
+
40
+ def resolve_missing_value(_result, _key, _type)
41
+ # noop
42
+ end
29
43
  end
30
44
  end
31
45
  end
@@ -4,27 +4,33 @@ module Dry
4
4
  class Schema < Hash
5
5
  attr_reader :member_types
6
6
 
7
- def initialize(primitive, options = {})
7
+ def initialize(_primitive, options)
8
8
  @member_types = options.fetch(:member_types)
9
9
  super
10
10
  end
11
11
 
12
- def call(hash, meth = :call)
13
- member_types.each_with_object({}) do |(key, type), result|
14
- if hash.key?(key)
15
- result[key] = type.__send__(meth, hash[key])
16
- else
17
- resolve_missing_value(result, key, type)
18
- end
19
- end
12
+ def call(hash)
13
+ coerce(hash)
20
14
  end
21
15
  alias_method :[], :call
22
16
 
23
17
  def try(hash, &block)
24
- result = call(hash, :try)
25
- output = result.each_with_object({}) { |(key, res), h| h[key] = res.input }
18
+ success = true
19
+ output = {}
20
+
21
+ begin
22
+ result = try_coerce(hash) do |key, member_result|
23
+ success &&= member_result.success?
24
+ output[key] = member_result.input
25
+
26
+ member_result
27
+ end
28
+ rescue ConstraintError, UnknownKeysError, SchemaError => e
29
+ success = false
30
+ result = e
31
+ end
26
32
 
27
- if result.values.all?(&:success?)
33
+ if success
28
34
  success(output)
29
35
  else
30
36
  failure = failure(output, result)
@@ -34,42 +40,87 @@ module Dry
34
40
 
35
41
  private
36
42
 
43
+ def try_coerce(hash)
44
+ resolve(hash) do |type, key, value|
45
+ yield(key, type.try(value))
46
+ end
47
+ end
48
+
49
+ def coerce(hash)
50
+ resolve(hash) do |type, key, value|
51
+ begin
52
+ type.call(value)
53
+ rescue ConstraintError
54
+ raise SchemaError.new(key, value)
55
+ end
56
+ end
57
+ end
58
+
59
+ def resolve(hash)
60
+ member_types.each_with_object({}) do |(key, type), result|
61
+ if hash.key?(key)
62
+ result[key] = yield(type, key, hash[key])
63
+ else
64
+ resolve_missing_value(result, key, type)
65
+ end
66
+ end
67
+ end
68
+
37
69
  def resolve_missing_value(result, key, type)
38
70
  if type.default?
39
71
  result[key] = type.evaluate
40
- elsif type.maybe?
41
- result[key] = type[nil]
72
+ else
73
+ super
42
74
  end
43
75
  end
44
76
  end
45
77
 
46
- class Strict < Schema
47
- def call(hash, meth = :call)
48
- member_types.each_with_object({}) do |(key, type), result|
49
- begin
50
- value = hash.fetch(key)
51
- result[key] = type.__send__(meth, value)
52
- rescue TypeError
53
- raise SchemaError.new(key, value)
54
- rescue KeyError
55
- raise SchemaKeyError.new(key)
56
- end
78
+ class Permissive < Schema
79
+ private
80
+
81
+ def resolve_missing_value(_, key, _)
82
+ raise MissingKeyError, key
83
+ end
84
+ end
85
+
86
+ class Strict < Permissive
87
+ private
88
+
89
+ def resolve(hash)
90
+ unexpected = hash.keys - member_types.keys
91
+ raise UnknownKeysError.new(*unexpected) unless unexpected.empty?
92
+
93
+ super do |member_type, key, value|
94
+ type = member_type.default? ? member_type.type : member_type
95
+
96
+ yield(type, key, value)
97
+ end
98
+ end
99
+ end
100
+
101
+ class StrictWithDefaults < Strict
102
+ private
103
+
104
+ def resolve_missing_value(result, key, type)
105
+ if type.default?
106
+ result[key] = type.value
107
+ else
108
+ super
57
109
  end
58
110
  end
59
- alias_method :[], :call
60
111
  end
61
112
 
62
113
  class Weak < Schema
63
- def self.new(primitive, options = {})
114
+ def self.new(primitive, options)
64
115
  member_types = options.
65
- fetch(:member_types, {}).
116
+ fetch(:member_types).
66
117
  each_with_object({}) { |(k, t), res| res[k] = t.safe }
67
118
 
68
119
  super(primitive, options.merge(member_types: member_types))
69
120
  end
70
121
 
71
122
  def try(hash, &block)
72
- if hash.is_a?(::Hash)
123
+ if hash.instance_of?(::Hash)
73
124
  super
74
125
  else
75
126
  result = failure(hash, "#{hash} must be a hash")
@@ -79,23 +130,27 @@ module Dry
79
130
  end
80
131
 
81
132
  class Symbolized < Weak
82
- def call(hash, meth = :call)
83
- member_types.each_with_object({}) do |(key, type), result|
84
- if hash.key?(key)
85
- result[key] = type.__send__(meth, hash[key])
86
- else
87
- key_name = key.to_s
133
+ private
88
134
 
89
- if hash.key?(key_name)
90
- result[key] = type.__send__(meth, hash[key_name])
91
- else
92
- resolve_missing_value(result, key, type)
135
+ def resolve(hash)
136
+ member_types.each_with_object({}) do |(key, type), result|
137
+ keyname =
138
+ if hash.key?(key)
139
+ key
140
+ elsif hash.key?(string_key = key.to_s)
141
+ string_key
93
142
  end
143
+
144
+ if keyname
145
+ result[key] = yield(type, key, hash[keyname])
146
+ else
147
+ resolve_missing_value(result, key, type)
94
148
  end
95
149
  end
96
150
  end
97
- alias_method :[], :call
98
151
  end
152
+
153
+ private_constant(*constants(false))
99
154
  end
100
155
  end
101
156
  end
@@ -3,8 +3,6 @@ module Dry
3
3
  module Options
4
4
  attr_reader :options
5
5
 
6
- attr_reader :meta
7
-
8
6
  def initialize(*args, **options)
9
7
  @__args__ = args
10
8
  @options = options
@@ -1,7 +1,17 @@
1
+ require 'dry/equalizer'
2
+
1
3
  module Dry
2
4
  module Types
3
- module Result
4
- class Success < Struct.new(:input)
5
+ class Result
6
+ include Dry::Equalizer(:input)
7
+
8
+ attr_reader :input
9
+
10
+ def initialize(input)
11
+ @input = input
12
+ end
13
+
14
+ class Success < Result
5
15
  def success?
6
16
  true
7
17
  end
@@ -11,7 +21,20 @@ module Dry
11
21
  end
12
22
  end
13
23
 
14
- class Failure < Struct.new(:input, :error)
24
+ class Failure < Result
25
+ include Dry::Equalizer(:input, :error)
26
+
27
+ attr_reader :error
28
+
29
+ def initialize(input, error)
30
+ super(input)
31
+ @error = error
32
+ end
33
+
34
+ def to_s
35
+ error.to_s
36
+ end
37
+
15
38
  def success?
16
39
  false
17
40
  end
@@ -8,7 +8,13 @@ module Dry
8
8
  include Builder
9
9
 
10
10
  def call(input)
11
- try(input).input
11
+ result = try(input)
12
+
13
+ if result.respond_to?(:input)
14
+ result.input
15
+ else
16
+ input
17
+ end
12
18
  end
13
19
  alias_method :[], :call
14
20
 
data/lib/dry/types/sum.rb CHANGED
@@ -15,6 +15,10 @@ module Dry
15
15
  def rule
16
16
  left.rule | right.rule
17
17
  end
18
+
19
+ def constrained?
20
+ true
21
+ end
18
22
  end
19
23
 
20
24
  def initialize(left, right, options = {})
@@ -35,9 +39,13 @@ module Dry
35
39
  false
36
40
  end
37
41
 
42
+ def constrained?
43
+ false
44
+ end
45
+
38
46
  def call(input)
39
47
  try(input) do |result|
40
- raise ConstraintError, result
48
+ raise ConstraintError.new(result, input)
41
49
  end.input
42
50
  end
43
51
  alias_method :[], :call