super-smart-tool 0.0.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.
- checksums.yaml +7 -0
- data/dry-types-1.9.1/CHANGELOG.md +1145 -0
- data/dry-types-1.9.1/LICENSE +20 -0
- data/dry-types-1.9.1/README.md +17 -0
- data/dry-types-1.9.1/dry-types.gemspec +45 -0
- data/dry-types-1.9.1/lib/dry/types/any.rb +41 -0
- data/dry-types-1.9.1/lib/dry/types/array/constructor.rb +24 -0
- data/dry-types-1.9.1/lib/dry/types/array/member.rb +118 -0
- data/dry-types-1.9.1/lib/dry/types/array.rb +32 -0
- data/dry-types-1.9.1/lib/dry/types/builder.rb +221 -0
- data/dry-types-1.9.1/lib/dry/types/builder_methods.rb +138 -0
- data/dry-types-1.9.1/lib/dry/types/coercions/json.rb +57 -0
- data/dry-types-1.9.1/lib/dry/types/coercions/params.rb +168 -0
- data/dry-types-1.9.1/lib/dry/types/coercions.rb +105 -0
- data/dry-types-1.9.1/lib/dry/types/compat.rb +1 -0
- data/dry-types-1.9.1/lib/dry/types/compiler.rb +138 -0
- data/dry-types-1.9.1/lib/dry/types/composition.rb +138 -0
- data/dry-types-1.9.1/lib/dry/types/constrained/coercible.rb +59 -0
- data/dry-types-1.9.1/lib/dry/types/constrained.rb +146 -0
- data/dry-types-1.9.1/lib/dry/types/constraints.rb +30 -0
- data/dry-types-1.9.1/lib/dry/types/constructor/function.rb +203 -0
- data/dry-types-1.9.1/lib/dry/types/constructor/wrapper.rb +88 -0
- data/dry-types-1.9.1/lib/dry/types/constructor.rb +195 -0
- data/dry-types-1.9.1/lib/dry/types/container.rb +12 -0
- data/dry-types-1.9.1/lib/dry/types/core.rb +106 -0
- data/dry-types-1.9.1/lib/dry/types/decorator.rb +94 -0
- data/dry-types-1.9.1/lib/dry/types/default.rb +122 -0
- data/dry-types-1.9.1/lib/dry/types/enum.rb +121 -0
- data/dry-types-1.9.1/lib/dry/types/errors.rb +138 -0
- data/dry-types-1.9.1/lib/dry/types/extensions/maybe.rb +128 -0
- data/dry-types-1.9.1/lib/dry/types/extensions/monads.rb +34 -0
- data/dry-types-1.9.1/lib/dry/types/extensions.rb +9 -0
- data/dry-types-1.9.1/lib/dry/types/fn_container.rb +37 -0
- data/dry-types-1.9.1/lib/dry/types/hash/constructor.rb +25 -0
- data/dry-types-1.9.1/lib/dry/types/hash.rb +134 -0
- data/dry-types-1.9.1/lib/dry/types/implication.rb +64 -0
- data/dry-types-1.9.1/lib/dry/types/inflector.rb +9 -0
- data/dry-types-1.9.1/lib/dry/types/intersection.rb +102 -0
- data/dry-types-1.9.1/lib/dry/types/json.rb +35 -0
- data/dry-types-1.9.1/lib/dry/types/lax.rb +66 -0
- data/dry-types-1.9.1/lib/dry/types/map.rb +134 -0
- data/dry-types-1.9.1/lib/dry/types/meta.rb +53 -0
- data/dry-types-1.9.1/lib/dry/types/module.rb +127 -0
- data/dry-types-1.9.1/lib/dry/types/nominal.rb +201 -0
- data/dry-types-1.9.1/lib/dry/types/options.rb +34 -0
- data/dry-types-1.9.1/lib/dry/types/params.rb +38 -0
- data/dry-types-1.9.1/lib/dry/types/predicate_inferrer.rb +235 -0
- data/dry-types-1.9.1/lib/dry/types/predicate_registry.rb +32 -0
- data/dry-types-1.9.1/lib/dry/types/primitive_inferrer.rb +75 -0
- data/dry-types-1.9.1/lib/dry/types/printable.rb +14 -0
- data/dry-types-1.9.1/lib/dry/types/printer/composition.rb +44 -0
- data/dry-types-1.9.1/lib/dry/types/printer.rb +313 -0
- data/dry-types-1.9.1/lib/dry/types/result.rb +72 -0
- data/dry-types-1.9.1/lib/dry/types/schema/key.rb +138 -0
- data/dry-types-1.9.1/lib/dry/types/schema.rb +408 -0
- data/dry-types-1.9.1/lib/dry/types/spec/types.rb +164 -0
- data/dry-types-1.9.1/lib/dry/types/sum.rb +105 -0
- data/dry-types-1.9.1/lib/dry/types/type.rb +53 -0
- data/dry-types-1.9.1/lib/dry/types/version.rb +7 -0
- data/dry-types-1.9.1/lib/dry/types.rb +259 -0
- data/dry-types-1.9.1/lib/dry-types.rb +3 -0
- data/super-smart-tool.gemspec +11 -0
- metadata +101 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dry
|
|
4
|
+
module Types
|
|
5
|
+
# Primitives with {Kernel} coercion methods
|
|
6
|
+
KERNEL_COERCIBLE = {
|
|
7
|
+
string: String,
|
|
8
|
+
integer: Integer,
|
|
9
|
+
float: Float,
|
|
10
|
+
decimal: BigDecimal,
|
|
11
|
+
array: ::Array,
|
|
12
|
+
hash: ::Hash
|
|
13
|
+
}.freeze
|
|
14
|
+
|
|
15
|
+
# Primitives with coercions through by convention `to_*` methods
|
|
16
|
+
METHOD_COERCIBLE = {
|
|
17
|
+
symbol: Symbol
|
|
18
|
+
}.freeze
|
|
19
|
+
|
|
20
|
+
# By convention methods to coerce {METHOD_COERCIBLE} primitives
|
|
21
|
+
METHOD_COERCIBLE_METHODS = {
|
|
22
|
+
symbol: :to_sym
|
|
23
|
+
}.freeze
|
|
24
|
+
|
|
25
|
+
# Primitives that are non-coercible
|
|
26
|
+
NON_COERCIBLE = {
|
|
27
|
+
nil: NilClass,
|
|
28
|
+
class: Class,
|
|
29
|
+
true: TrueClass,
|
|
30
|
+
false: FalseClass,
|
|
31
|
+
date: Date,
|
|
32
|
+
date_time: DateTime,
|
|
33
|
+
time: Time,
|
|
34
|
+
range: Range
|
|
35
|
+
}.freeze
|
|
36
|
+
|
|
37
|
+
# All built-in primitives
|
|
38
|
+
ALL_PRIMITIVES = [
|
|
39
|
+
KERNEL_COERCIBLE, METHOD_COERCIBLE, NON_COERCIBLE
|
|
40
|
+
].reduce(&:merge).freeze
|
|
41
|
+
|
|
42
|
+
# All coercible types
|
|
43
|
+
COERCIBLE = KERNEL_COERCIBLE.merge(METHOD_COERCIBLE).freeze
|
|
44
|
+
|
|
45
|
+
# All built-in primitives except {NilClass}
|
|
46
|
+
NON_NIL = ALL_PRIMITIVES.except(:nil).freeze
|
|
47
|
+
|
|
48
|
+
# Register generic types for {ALL_PRIMITIVES}
|
|
49
|
+
ALL_PRIMITIVES.each do |name, primitive|
|
|
50
|
+
type = Nominal[primitive].new(primitive)
|
|
51
|
+
register("nominal.#{name}", type)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Register strict types for {ALL_PRIMITIVES}
|
|
55
|
+
ALL_PRIMITIVES.each do |name, primitive|
|
|
56
|
+
type = self["nominal.#{name}"].constrained(type: primitive)
|
|
57
|
+
register(name.to_s, type)
|
|
58
|
+
register("strict.#{name}", type)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Register {KERNEL_COERCIBLE} types
|
|
62
|
+
KERNEL_COERCIBLE.each do |name, primitive|
|
|
63
|
+
register(
|
|
64
|
+
"coercible.#{name}",
|
|
65
|
+
self["nominal.#{name}"].constructor(::Kernel.method(primitive.name))
|
|
66
|
+
)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Register {METHOD_COERCIBLE} types
|
|
70
|
+
METHOD_COERCIBLE.each_key do |name|
|
|
71
|
+
register(
|
|
72
|
+
"coercible.#{name}",
|
|
73
|
+
self["nominal.#{name}"].constructor(&METHOD_COERCIBLE_METHODS[name])
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Register optional strict {NON_NIL} types
|
|
78
|
+
NON_NIL.each_key do |name|
|
|
79
|
+
type = self[name.to_s].optional
|
|
80
|
+
register("optional.strict.#{name}", type)
|
|
81
|
+
register("optional.#{name}", type)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Register optional {COERCIBLE} types
|
|
85
|
+
COERCIBLE.each_key do |name|
|
|
86
|
+
register(
|
|
87
|
+
"optional.coercible.#{name}",
|
|
88
|
+
self["coercible.#{name}"].optional
|
|
89
|
+
)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Register `:bool` since it's common and not a built-in Ruby type :(
|
|
93
|
+
register("nominal.bool", self["nominal.true"] | self["nominal.false"])
|
|
94
|
+
bool = self["strict.true"] | self["strict.false"]
|
|
95
|
+
register("strict.bool", bool)
|
|
96
|
+
register("bool", bool)
|
|
97
|
+
|
|
98
|
+
register("any", Any)
|
|
99
|
+
register("nominal.any", Any)
|
|
100
|
+
register("strict.any", Any)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
require "dry/types/coercions"
|
|
105
|
+
require "dry/types/params"
|
|
106
|
+
require "dry/types/json"
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dry
|
|
4
|
+
module Types
|
|
5
|
+
# Common API for types
|
|
6
|
+
#
|
|
7
|
+
# @api public
|
|
8
|
+
module Decorator
|
|
9
|
+
include Options
|
|
10
|
+
|
|
11
|
+
# @return [Type]
|
|
12
|
+
attr_reader :type
|
|
13
|
+
|
|
14
|
+
# @param [Type] type
|
|
15
|
+
def initialize(type, *, **)
|
|
16
|
+
super
|
|
17
|
+
@type = type
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @param [Object] input
|
|
21
|
+
# @param [#call, nil] block
|
|
22
|
+
#
|
|
23
|
+
# @return [Result,Logic::Result]
|
|
24
|
+
# @return [Object] if block given and try fails
|
|
25
|
+
#
|
|
26
|
+
# @api public
|
|
27
|
+
def try(input, &) = type.try(input, &)
|
|
28
|
+
|
|
29
|
+
# @return [Boolean]
|
|
30
|
+
#
|
|
31
|
+
# @api public
|
|
32
|
+
def default? = type.default?
|
|
33
|
+
|
|
34
|
+
# @return [Boolean]
|
|
35
|
+
#
|
|
36
|
+
# @api public
|
|
37
|
+
def constrained? = type.constrained?
|
|
38
|
+
|
|
39
|
+
# @param [Symbol] meth
|
|
40
|
+
# @param [Boolean] include_private
|
|
41
|
+
#
|
|
42
|
+
# @return [Boolean]
|
|
43
|
+
#
|
|
44
|
+
# @api public
|
|
45
|
+
def respond_to_missing?(meth, include_private = false)
|
|
46
|
+
super || type.respond_to?(meth)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Wrap the type with a proc
|
|
50
|
+
#
|
|
51
|
+
# @return [Proc]
|
|
52
|
+
#
|
|
53
|
+
# @api public
|
|
54
|
+
def to_proc = proc { |value| self.(value) }
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
# @param [Object] response
|
|
59
|
+
#
|
|
60
|
+
# @return [Boolean]
|
|
61
|
+
#
|
|
62
|
+
# @api private
|
|
63
|
+
def decorate?(response) = response.is_a?(type.class)
|
|
64
|
+
|
|
65
|
+
# Delegates missing methods to {#type}
|
|
66
|
+
#
|
|
67
|
+
# @param [Symbol] meth
|
|
68
|
+
# @param [Array] args
|
|
69
|
+
# @param [#call, nil] block
|
|
70
|
+
#
|
|
71
|
+
# @api private
|
|
72
|
+
def method_missing(meth, ...)
|
|
73
|
+
if type.respond_to?(meth)
|
|
74
|
+
response = type.public_send(meth, ...)
|
|
75
|
+
|
|
76
|
+
if decorate?(response)
|
|
77
|
+
__new__(response)
|
|
78
|
+
else
|
|
79
|
+
response
|
|
80
|
+
end
|
|
81
|
+
else
|
|
82
|
+
super
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Replace underlying type
|
|
87
|
+
#
|
|
88
|
+
# @api private
|
|
89
|
+
def __new__(type)
|
|
90
|
+
self.class.new(type, *@__args__.drop(1), **@options)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dry
|
|
4
|
+
module Types
|
|
5
|
+
# Default types are useful when a missing value should be replaced by a default one
|
|
6
|
+
#
|
|
7
|
+
# @api public
|
|
8
|
+
class Default
|
|
9
|
+
# @api private
|
|
10
|
+
class Callable < Default
|
|
11
|
+
include ::Dry::Equalizer(:type, inspect: false, immutable: true)
|
|
12
|
+
|
|
13
|
+
# Evaluates given callable
|
|
14
|
+
# @return [Object]
|
|
15
|
+
def evaluate
|
|
16
|
+
value.call(type)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @return [true]
|
|
20
|
+
def callable?
|
|
21
|
+
true
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
include Type
|
|
26
|
+
include Decorator
|
|
27
|
+
include Builder
|
|
28
|
+
include Printable
|
|
29
|
+
include ::Dry::Equalizer(:type, :value, inspect: false, immutable: true)
|
|
30
|
+
|
|
31
|
+
# @return [Object]
|
|
32
|
+
attr_reader :value
|
|
33
|
+
|
|
34
|
+
alias_method :evaluate, :value
|
|
35
|
+
|
|
36
|
+
# @param [Object, #call] value
|
|
37
|
+
#
|
|
38
|
+
# @return [Class] {Default} or {Default::Callable}
|
|
39
|
+
#
|
|
40
|
+
# @api private
|
|
41
|
+
def self.[](value)
|
|
42
|
+
if value.respond_to?(:call)
|
|
43
|
+
Callable
|
|
44
|
+
else
|
|
45
|
+
self
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @param [Type] type
|
|
50
|
+
# @param [Object] value
|
|
51
|
+
#
|
|
52
|
+
# @api private
|
|
53
|
+
def initialize(type, value, **)
|
|
54
|
+
super
|
|
55
|
+
@value = value
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Build a constrained type
|
|
59
|
+
#
|
|
60
|
+
# @param [Array] args see {Dry::Types::Builder#constrained}
|
|
61
|
+
#
|
|
62
|
+
# @return [Default]
|
|
63
|
+
#
|
|
64
|
+
# @api public
|
|
65
|
+
def constrained(...) = type.constrained(...).default(value)
|
|
66
|
+
|
|
67
|
+
# @return [true]
|
|
68
|
+
#
|
|
69
|
+
# @api public
|
|
70
|
+
def default? = true
|
|
71
|
+
|
|
72
|
+
# @param [Object] input
|
|
73
|
+
#
|
|
74
|
+
# @return [Result::Success]
|
|
75
|
+
#
|
|
76
|
+
# @api public
|
|
77
|
+
def try(input = Undefined, &)
|
|
78
|
+
if input.equal?(Undefined)
|
|
79
|
+
success(evaluate)
|
|
80
|
+
else
|
|
81
|
+
type.try(input)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# @return [Boolean]
|
|
86
|
+
#
|
|
87
|
+
# @api public
|
|
88
|
+
def valid?(value = Undefined) = Undefined.equal?(value) || super
|
|
89
|
+
|
|
90
|
+
# @param [Object] input
|
|
91
|
+
#
|
|
92
|
+
# @return [Object] value passed through {#type} or {#default} value
|
|
93
|
+
#
|
|
94
|
+
# @api private
|
|
95
|
+
def call_unsafe(input = Undefined)
|
|
96
|
+
if input.equal?(Undefined)
|
|
97
|
+
evaluate
|
|
98
|
+
else
|
|
99
|
+
Undefined.default(type.call_unsafe(input)) { evaluate }
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# @param [Object] input
|
|
104
|
+
#
|
|
105
|
+
# @return [Object] value passed through {#type} or {#default} value
|
|
106
|
+
#
|
|
107
|
+
# @api private
|
|
108
|
+
def call_safe(input = Undefined, &)
|
|
109
|
+
if input.equal?(Undefined)
|
|
110
|
+
evaluate
|
|
111
|
+
else
|
|
112
|
+
Undefined.default(type.call_safe(input, &)) { evaluate }
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# @return [false]
|
|
117
|
+
#
|
|
118
|
+
# @api private
|
|
119
|
+
def callable? = false
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dry
|
|
4
|
+
module Types
|
|
5
|
+
# Enum types can be used to define an enum on top of an existing type
|
|
6
|
+
#
|
|
7
|
+
# @api public
|
|
8
|
+
class Enum
|
|
9
|
+
include Type
|
|
10
|
+
include ::Dry::Equalizer(:type, :mapping, inspect: false, immutable: true)
|
|
11
|
+
include Decorator
|
|
12
|
+
include Builder
|
|
13
|
+
|
|
14
|
+
# @return [Array]
|
|
15
|
+
attr_reader :values
|
|
16
|
+
|
|
17
|
+
# @return [Hash]
|
|
18
|
+
attr_reader :mapping
|
|
19
|
+
|
|
20
|
+
# @return [Hash]
|
|
21
|
+
attr_reader :inverted_mapping
|
|
22
|
+
|
|
23
|
+
# @param [Type] type
|
|
24
|
+
# @param [Hash] options
|
|
25
|
+
# @option options [Array] :values
|
|
26
|
+
#
|
|
27
|
+
# @api private
|
|
28
|
+
def initialize(type, **options)
|
|
29
|
+
super
|
|
30
|
+
@mapping = options.fetch(:mapping).freeze
|
|
31
|
+
@values = @mapping.keys.freeze
|
|
32
|
+
@inverted_mapping = @mapping.invert.freeze
|
|
33
|
+
freeze
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# @return [Object]
|
|
37
|
+
#
|
|
38
|
+
# @api private
|
|
39
|
+
def call_unsafe(input) = type.call_unsafe(map_value(input))
|
|
40
|
+
|
|
41
|
+
# @return [Object]
|
|
42
|
+
#
|
|
43
|
+
# @api private
|
|
44
|
+
def call_safe(input, &) = type.call_safe(map_value(input), &)
|
|
45
|
+
|
|
46
|
+
# @see Dry::Types::Constrained#try
|
|
47
|
+
#
|
|
48
|
+
# @api public
|
|
49
|
+
def try(input, &) = super(map_value(input))
|
|
50
|
+
|
|
51
|
+
# @api private
|
|
52
|
+
def default(*)
|
|
53
|
+
raise ".enum(*values).default(value) is not supported. Call " \
|
|
54
|
+
".default(value).enum(*values) instead"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Check whether a value is in the enum
|
|
58
|
+
alias_method :include?, :valid?
|
|
59
|
+
|
|
60
|
+
# @see Nominal#to_ast
|
|
61
|
+
#
|
|
62
|
+
# @api public
|
|
63
|
+
def to_ast(meta: true)
|
|
64
|
+
[:enum, [type.to_ast(meta: meta), mapping]]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# @return [String]
|
|
68
|
+
#
|
|
69
|
+
# @api public
|
|
70
|
+
def to_s = PRINTER.(self)
|
|
71
|
+
|
|
72
|
+
# Iterate over each enum value
|
|
73
|
+
#
|
|
74
|
+
# @return [Array, Enumerator]
|
|
75
|
+
#
|
|
76
|
+
# @api public
|
|
77
|
+
def each_value(&)
|
|
78
|
+
values.each(&)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
alias_method :inspect, :to_s
|
|
82
|
+
|
|
83
|
+
# @return [String]
|
|
84
|
+
#
|
|
85
|
+
# @api public
|
|
86
|
+
def name = "#{super}(#{joined_values})"
|
|
87
|
+
|
|
88
|
+
# @return [String]
|
|
89
|
+
#
|
|
90
|
+
# @api private
|
|
91
|
+
def joined_values
|
|
92
|
+
mapping.keys.map { |value|
|
|
93
|
+
if value.is_a?(::String)
|
|
94
|
+
value
|
|
95
|
+
else
|
|
96
|
+
value.inspect
|
|
97
|
+
end
|
|
98
|
+
}.join("|")
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
private
|
|
102
|
+
|
|
103
|
+
# Maps a value
|
|
104
|
+
#
|
|
105
|
+
# @param [Object] input
|
|
106
|
+
#
|
|
107
|
+
# @return [Object]
|
|
108
|
+
#
|
|
109
|
+
# @api private
|
|
110
|
+
def map_value(input)
|
|
111
|
+
if input.equal?(Undefined)
|
|
112
|
+
type.call
|
|
113
|
+
elsif mapping.key?(input)
|
|
114
|
+
input
|
|
115
|
+
else
|
|
116
|
+
inverted_mapping.fetch(input, input)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dry
|
|
4
|
+
module Types
|
|
5
|
+
extend ::Dry::Core::ClassAttributes
|
|
6
|
+
|
|
7
|
+
# @!attribute [r] namespace
|
|
8
|
+
# @return [Container{String => Nominal}]
|
|
9
|
+
defines :namespace
|
|
10
|
+
|
|
11
|
+
namespace self
|
|
12
|
+
|
|
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
|
+
super("")
|
|
55
|
+
@errors = errors
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# @return string
|
|
59
|
+
def message = errors.map(&:message).join(", ")
|
|
60
|
+
|
|
61
|
+
# @return [Array]
|
|
62
|
+
def meta = errors.map(&:meta)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
class SchemaError < CoercionError
|
|
66
|
+
# @return [String, Symbol]
|
|
67
|
+
attr_reader :key
|
|
68
|
+
|
|
69
|
+
# @return [Object]
|
|
70
|
+
attr_reader :value
|
|
71
|
+
|
|
72
|
+
# @param [String,Symbol] key
|
|
73
|
+
# @param [Object] value
|
|
74
|
+
# @param [String, #to_s] result
|
|
75
|
+
def initialize(key, value, result)
|
|
76
|
+
@key = key
|
|
77
|
+
@value = value
|
|
78
|
+
super(
|
|
79
|
+
"#{value.inspect} (#{value.class}) has invalid type " \
|
|
80
|
+
"for :#{key} violates constraints (#{result} failed)"
|
|
81
|
+
)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
MapError = ::Class.new(CoercionError)
|
|
86
|
+
|
|
87
|
+
SchemaKeyError = ::Class.new(CoercionError)
|
|
88
|
+
private_constant(:SchemaKeyError)
|
|
89
|
+
|
|
90
|
+
class MissingKeyError < SchemaKeyError
|
|
91
|
+
# @return [Symbol]
|
|
92
|
+
attr_reader :key
|
|
93
|
+
|
|
94
|
+
# @param [String,Symbol] key
|
|
95
|
+
def initialize(key)
|
|
96
|
+
@key = key
|
|
97
|
+
super("#{key.inspect} is missing in Hash input")
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
class UnknownKeysError < SchemaKeyError
|
|
102
|
+
# @return [Array<Symbol>]
|
|
103
|
+
attr_reader :keys
|
|
104
|
+
|
|
105
|
+
# @param [<String, Symbol>] keys
|
|
106
|
+
def initialize(keys)
|
|
107
|
+
@keys = keys
|
|
108
|
+
super("unexpected keys #{keys.inspect} in Hash input")
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
class ConstraintError < CoercionError
|
|
113
|
+
# @return [String, #to_s]
|
|
114
|
+
attr_reader :result
|
|
115
|
+
# @return [Object]
|
|
116
|
+
attr_reader :input
|
|
117
|
+
|
|
118
|
+
# @param [String, #to_s] result
|
|
119
|
+
# @param [Object] input
|
|
120
|
+
def initialize(result, input)
|
|
121
|
+
@result = result
|
|
122
|
+
@input = input
|
|
123
|
+
|
|
124
|
+
if result.is_a?(::String)
|
|
125
|
+
super(result)
|
|
126
|
+
else
|
|
127
|
+
super(to_s)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# @return [String]
|
|
132
|
+
def message
|
|
133
|
+
"#{input.inspect} violates constraints (#{result} failed)"
|
|
134
|
+
end
|
|
135
|
+
alias_method :to_s, :message
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry/monads"
|
|
4
|
+
require "dry/monads/version"
|
|
5
|
+
|
|
6
|
+
if Gem::Version.new(Dry::Monads::VERSION) < Gem::Version.new("1.5.0")
|
|
7
|
+
raise "dry-types requires dry-monads >= 1.5.0"
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
module Dry
|
|
11
|
+
module Types
|
|
12
|
+
# Maybe extension provides Maybe types where values are wrapped using `Either` monad
|
|
13
|
+
#
|
|
14
|
+
# @api public
|
|
15
|
+
class Maybe
|
|
16
|
+
include Type
|
|
17
|
+
include ::Dry::Equalizer(:type, :options, inspect: false, immutable: true)
|
|
18
|
+
include Decorator
|
|
19
|
+
include Builder
|
|
20
|
+
include Printable
|
|
21
|
+
include ::Dry::Monads[:maybe]
|
|
22
|
+
|
|
23
|
+
# @param [Dry::Monads::Maybe, Object] input
|
|
24
|
+
#
|
|
25
|
+
# @return [Dry::Monads::Maybe]
|
|
26
|
+
#
|
|
27
|
+
# @api private
|
|
28
|
+
def call_unsafe(input = Undefined)
|
|
29
|
+
case input
|
|
30
|
+
when ::Dry::Monads::Maybe
|
|
31
|
+
input
|
|
32
|
+
when Undefined
|
|
33
|
+
None()
|
|
34
|
+
else
|
|
35
|
+
Maybe(type.call_unsafe(input))
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @param [Dry::Monads::Maybe, Object] input
|
|
40
|
+
#
|
|
41
|
+
# @return [Dry::Monads::Maybe]
|
|
42
|
+
#
|
|
43
|
+
# @api private
|
|
44
|
+
def call_safe(input = Undefined)
|
|
45
|
+
case input
|
|
46
|
+
when ::Dry::Monads::Maybe
|
|
47
|
+
input
|
|
48
|
+
when Undefined
|
|
49
|
+
None()
|
|
50
|
+
else
|
|
51
|
+
Maybe(type.call_safe(input) { |output = input| return yield(output) })
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# @param [Object] input
|
|
56
|
+
#
|
|
57
|
+
# @return [Result::Success]
|
|
58
|
+
#
|
|
59
|
+
# @api public
|
|
60
|
+
def try(input = Undefined, &)
|
|
61
|
+
result = type.try(input)
|
|
62
|
+
|
|
63
|
+
if result.success?
|
|
64
|
+
Result::Success.new(Maybe(result.input))
|
|
65
|
+
else
|
|
66
|
+
result
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# @return [true]
|
|
71
|
+
#
|
|
72
|
+
# @api public
|
|
73
|
+
def default? = true
|
|
74
|
+
|
|
75
|
+
# @param [Object] value
|
|
76
|
+
#
|
|
77
|
+
# @see Dry::Types::Builder#default
|
|
78
|
+
#
|
|
79
|
+
# @raise [ArgumentError] if nil provided as default value
|
|
80
|
+
#
|
|
81
|
+
# @api public
|
|
82
|
+
def default(value)
|
|
83
|
+
if value.nil?
|
|
84
|
+
raise ::ArgumentError, "nil cannot be used as a default of a maybe type"
|
|
85
|
+
else
|
|
86
|
+
super
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
module Builder
|
|
92
|
+
# Turn a type into a maybe type
|
|
93
|
+
#
|
|
94
|
+
# @return [Maybe]
|
|
95
|
+
#
|
|
96
|
+
# @api public
|
|
97
|
+
def maybe = Maybe.new(Types["nil"] | self)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# @api private
|
|
101
|
+
class Schema::Key # rubocop:disable Style/ClassAndModuleChildren
|
|
102
|
+
# @api private
|
|
103
|
+
def maybe = __new__(type.maybe)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# @api private
|
|
107
|
+
class Printer
|
|
108
|
+
MAPPING[Maybe] = :visit_maybe
|
|
109
|
+
|
|
110
|
+
# @api private
|
|
111
|
+
def visit_maybe(maybe)
|
|
112
|
+
visit(maybe.type) do |type|
|
|
113
|
+
yield "Maybe<#{type}>"
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Register non-coercible maybe types
|
|
119
|
+
NON_NIL.each_key do |name|
|
|
120
|
+
register("maybe.strict.#{name}", self[name.to_s].maybe)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Register coercible maybe types
|
|
124
|
+
COERCIBLE.each_key do |name|
|
|
125
|
+
register("maybe.coercible.#{name}", self["coercible.#{name}"].maybe)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|