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,34 @@
|
|
|
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
|
+
# Monad extension for Result
|
|
13
|
+
#
|
|
14
|
+
# @api public
|
|
15
|
+
class Result
|
|
16
|
+
include ::Dry::Monads[:result]
|
|
17
|
+
|
|
18
|
+
# Turn result into a monad
|
|
19
|
+
#
|
|
20
|
+
# This makes result objects work with dry-monads (or anything with a compatible interface)
|
|
21
|
+
#
|
|
22
|
+
# @return [Dry::Monads::Success,Dry::Monads::Failure]
|
|
23
|
+
#
|
|
24
|
+
# @api public
|
|
25
|
+
def to_monad
|
|
26
|
+
if success?
|
|
27
|
+
Success(input)
|
|
28
|
+
else
|
|
29
|
+
Failure([error, input])
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dry
|
|
4
|
+
module Types
|
|
5
|
+
# Internal container for constructor functions used by the built-in types
|
|
6
|
+
#
|
|
7
|
+
# @api private
|
|
8
|
+
class FnContainer
|
|
9
|
+
# @api private
|
|
10
|
+
def self.container
|
|
11
|
+
@container ||= Container.new
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# @api private
|
|
15
|
+
def self.register(function = ::Dry::Core::Constants::Undefined, &block)
|
|
16
|
+
fn = ::Dry::Core::Constants::Undefined.default(function, block)
|
|
17
|
+
fn_name = register_name(fn)
|
|
18
|
+
container.register(fn_name, fn) unless container.key?(fn_name)
|
|
19
|
+
fn_name
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @api private
|
|
23
|
+
def self.[](fn_name)
|
|
24
|
+
if container.key?(fn_name)
|
|
25
|
+
container[fn_name]
|
|
26
|
+
else
|
|
27
|
+
fn_name
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @api private
|
|
32
|
+
def self.register_name(function)
|
|
33
|
+
"fn_#{function.__id__}"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dry
|
|
4
|
+
module Types
|
|
5
|
+
# Hash type exposes additional APIs for working with schema hashes
|
|
6
|
+
#
|
|
7
|
+
# @api public
|
|
8
|
+
class Hash < Nominal
|
|
9
|
+
class Constructor < ::Dry::Types::Constructor
|
|
10
|
+
# @api private
|
|
11
|
+
def constructor_type = ::Dry::Types::Hash::Constructor
|
|
12
|
+
|
|
13
|
+
# @return [Lax]
|
|
14
|
+
#
|
|
15
|
+
# @api public
|
|
16
|
+
def lax = Lax.new(type.lax.constructor(fn, meta: meta))
|
|
17
|
+
|
|
18
|
+
# @see Dry::Types::Array#of
|
|
19
|
+
#
|
|
20
|
+
# @api public
|
|
21
|
+
def schema(...) = type.schema(...).constructor(fn, meta: meta)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dry
|
|
4
|
+
module Types
|
|
5
|
+
# Hash types can be used to define maps and schemas
|
|
6
|
+
#
|
|
7
|
+
# @api public
|
|
8
|
+
class Hash < Nominal
|
|
9
|
+
NOT_REQUIRED = {required: false}.freeze
|
|
10
|
+
|
|
11
|
+
# @overload schema(type_map, meta = EMPTY_HASH)
|
|
12
|
+
# @param [{Symbol => Dry::Types::Nominal}] type_map
|
|
13
|
+
# @param [Hash] meta
|
|
14
|
+
# @return [Dry::Types::Schema]
|
|
15
|
+
#
|
|
16
|
+
# @overload schema(keys)
|
|
17
|
+
# @param [Array<Dry::Types::Schema::Key>] key List of schema keys
|
|
18
|
+
# @param [Hash] meta
|
|
19
|
+
# @return [Dry::Types::Schema]
|
|
20
|
+
#
|
|
21
|
+
# @api public
|
|
22
|
+
def schema(keys_or_map, meta = EMPTY_HASH)
|
|
23
|
+
if keys_or_map.is_a?(::Array)
|
|
24
|
+
keys = keys_or_map
|
|
25
|
+
else
|
|
26
|
+
keys = build_keys(keys_or_map)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
Schema.new(primitive, keys: keys, **options, meta: self.meta.merge(meta))
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Build a map type
|
|
33
|
+
#
|
|
34
|
+
# @param [Type] key_type
|
|
35
|
+
# @param [Type] value_type
|
|
36
|
+
#
|
|
37
|
+
# @return [Map]
|
|
38
|
+
#
|
|
39
|
+
# @api public
|
|
40
|
+
def map(key_type, value_type)
|
|
41
|
+
Map.new(
|
|
42
|
+
primitive,
|
|
43
|
+
key_type: resolve_type(key_type),
|
|
44
|
+
value_type: resolve_type(value_type),
|
|
45
|
+
meta: meta
|
|
46
|
+
)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @api private
|
|
50
|
+
def weak(*)
|
|
51
|
+
raise "Support for old hash schemas was removed, please refer to the CHANGELOG " \
|
|
52
|
+
"on how to proceed with the new API https://github.com/dry-rb/dry-types/blob/main/CHANGELOG.md"
|
|
53
|
+
end
|
|
54
|
+
alias_method :permissive, :weak
|
|
55
|
+
alias_method :strict, :weak
|
|
56
|
+
alias_method :strict_with_defaults, :weak
|
|
57
|
+
alias_method :symbolized, :weak
|
|
58
|
+
|
|
59
|
+
# Injects a type transformation function for building schemas
|
|
60
|
+
#
|
|
61
|
+
# @param [#call,nil] proc
|
|
62
|
+
# @param [#call,nil] block
|
|
63
|
+
#
|
|
64
|
+
# @return [Hash]
|
|
65
|
+
#
|
|
66
|
+
# @api public
|
|
67
|
+
def with_type_transform(proc = nil, &block)
|
|
68
|
+
fn = proc || block
|
|
69
|
+
|
|
70
|
+
raise ::ArgumentError, "a block or callable argument is required" if fn.nil?
|
|
71
|
+
|
|
72
|
+
handle = ::Dry::Types::FnContainer.register(fn)
|
|
73
|
+
with(type_transform_fn: handle)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# @api private
|
|
77
|
+
def constructor_type
|
|
78
|
+
::Dry::Types::Hash::Constructor
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Whether the type transforms types of schemas created by {Dry::Types::Hash#schema}
|
|
82
|
+
#
|
|
83
|
+
# @return [Boolean]
|
|
84
|
+
#
|
|
85
|
+
# @api public
|
|
86
|
+
def transform_types?
|
|
87
|
+
!options[:type_transform_fn].nil?
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# @param meta [Boolean] Whether to dump the meta to the AST
|
|
91
|
+
#
|
|
92
|
+
# @return [Array] An AST representation
|
|
93
|
+
#
|
|
94
|
+
# @api public
|
|
95
|
+
def to_ast(meta: true)
|
|
96
|
+
[:hash,
|
|
97
|
+
[options.slice(:type_transform_fn, :namespace),
|
|
98
|
+
meta_ast(meta)]]
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
private
|
|
102
|
+
|
|
103
|
+
# @api private
|
|
104
|
+
def build_keys(type_map)
|
|
105
|
+
type_fn = options.fetch(:type_transform_fn, Schema::NO_TRANSFORM)
|
|
106
|
+
type_transform = ::Dry::Types::FnContainer[type_fn]
|
|
107
|
+
|
|
108
|
+
type_map.map do |map_key, type|
|
|
109
|
+
name, options = key_name(map_key)
|
|
110
|
+
key = Schema::Key.new(resolve_type(type), name, **options)
|
|
111
|
+
type_transform.(key)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# @api private
|
|
116
|
+
def resolve_type(type)
|
|
117
|
+
case type
|
|
118
|
+
when Type then type
|
|
119
|
+
when ::Class, ::String then Types[type]
|
|
120
|
+
else type
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# @api private
|
|
125
|
+
def key_name(key)
|
|
126
|
+
if key.to_s.end_with?("?")
|
|
127
|
+
[key.to_s.chop.to_sym, NOT_REQUIRED]
|
|
128
|
+
else
|
|
129
|
+
[key, EMPTY_HASH]
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dry
|
|
4
|
+
module Types
|
|
5
|
+
# Implication type
|
|
6
|
+
#
|
|
7
|
+
# @api public
|
|
8
|
+
class Implication
|
|
9
|
+
include Composition
|
|
10
|
+
|
|
11
|
+
def self.operator = :>
|
|
12
|
+
|
|
13
|
+
# @param [Object] input
|
|
14
|
+
#
|
|
15
|
+
# @return [Object]
|
|
16
|
+
#
|
|
17
|
+
# @api private
|
|
18
|
+
def call_unsafe(input)
|
|
19
|
+
if left.try(input).success?
|
|
20
|
+
right.call_unsafe(input)
|
|
21
|
+
else
|
|
22
|
+
input
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @param [Object] input
|
|
27
|
+
#
|
|
28
|
+
# @return [Object]
|
|
29
|
+
#
|
|
30
|
+
# @api private
|
|
31
|
+
def call_safe(input, &)
|
|
32
|
+
if left.try(input).success?
|
|
33
|
+
right.call_safe(input, &)
|
|
34
|
+
else
|
|
35
|
+
input
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @param [Object] input
|
|
40
|
+
#
|
|
41
|
+
# @api public
|
|
42
|
+
def try(input, &)
|
|
43
|
+
if left.try(input).success?
|
|
44
|
+
right.try(input, &)
|
|
45
|
+
else
|
|
46
|
+
Result::Success.new(input)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @param [Object] value
|
|
51
|
+
#
|
|
52
|
+
# @return [Boolean]
|
|
53
|
+
#
|
|
54
|
+
# @api private
|
|
55
|
+
def primitive?(value)
|
|
56
|
+
if left.primitive?(value)
|
|
57
|
+
right.primitive?(value)
|
|
58
|
+
else
|
|
59
|
+
true
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry/core/equalizer"
|
|
4
|
+
require "dry/types/options"
|
|
5
|
+
require "dry/types/meta"
|
|
6
|
+
|
|
7
|
+
module Dry
|
|
8
|
+
module Types
|
|
9
|
+
# Intersection type
|
|
10
|
+
#
|
|
11
|
+
# @api public
|
|
12
|
+
class Intersection
|
|
13
|
+
include Composition
|
|
14
|
+
|
|
15
|
+
def self.operator = :&
|
|
16
|
+
|
|
17
|
+
# @param [Object] input
|
|
18
|
+
#
|
|
19
|
+
# @return [Object]
|
|
20
|
+
#
|
|
21
|
+
# @api private
|
|
22
|
+
def call_unsafe(input)
|
|
23
|
+
merge_results(left.call_unsafe(input), right.call_unsafe(input))
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @param [Object] input
|
|
27
|
+
#
|
|
28
|
+
# @return [Object]
|
|
29
|
+
#
|
|
30
|
+
# @api private
|
|
31
|
+
def call_safe(input, &) = try_sides(input, &).input
|
|
32
|
+
|
|
33
|
+
# @param [Object] input
|
|
34
|
+
#
|
|
35
|
+
# @api public
|
|
36
|
+
def try(input)
|
|
37
|
+
try_sides(input) do |failure|
|
|
38
|
+
if block_given?
|
|
39
|
+
yield(failure)
|
|
40
|
+
else
|
|
41
|
+
failure
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @param [Object] value
|
|
47
|
+
#
|
|
48
|
+
# @return [Boolean]
|
|
49
|
+
#
|
|
50
|
+
# @api private
|
|
51
|
+
def primitive?(value)
|
|
52
|
+
left.primitive?(value) && right.primitive?(value)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
# @api private
|
|
58
|
+
def try_sides(input, &block)
|
|
59
|
+
results = []
|
|
60
|
+
|
|
61
|
+
[left, right].each do |side|
|
|
62
|
+
result = try_side(side, input, &block)
|
|
63
|
+
return result if result.failure?
|
|
64
|
+
|
|
65
|
+
results << result
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
Result::Success.new(merge_results(*results.map(&:input)))
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# @api private
|
|
72
|
+
def try_side(side, input)
|
|
73
|
+
failure = nil
|
|
74
|
+
|
|
75
|
+
result = side.try(input) do |f|
|
|
76
|
+
failure = f
|
|
77
|
+
yield(f)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
if result.is_a?(Result)
|
|
81
|
+
result
|
|
82
|
+
elsif failure
|
|
83
|
+
Result::Failure.new(result, failure)
|
|
84
|
+
else
|
|
85
|
+
Result::Success.new(result)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# @api private
|
|
90
|
+
def merge_results(left_result, right_result)
|
|
91
|
+
case left_result
|
|
92
|
+
when ::Array
|
|
93
|
+
left_result.zip(right_result).map { merge_results(_1, _2) }
|
|
94
|
+
when ::Hash
|
|
95
|
+
left_result.merge(right_result)
|
|
96
|
+
else
|
|
97
|
+
left_result
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry/types/coercions/json"
|
|
4
|
+
|
|
5
|
+
module Dry
|
|
6
|
+
module Types
|
|
7
|
+
register("json.nil") do
|
|
8
|
+
self["nominal.nil"].constructor(Coercions::JSON.method(:to_nil))
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
register("json.date") do
|
|
12
|
+
self["nominal.date"].constructor(Coercions::JSON.method(:to_date))
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
register("json.date_time") do
|
|
16
|
+
self["nominal.date_time"].constructor(Coercions::JSON.method(:to_date_time))
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
register("json.time") do
|
|
20
|
+
self["nominal.time"].constructor(Coercions::JSON.method(:to_time))
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
register("json.decimal") do
|
|
24
|
+
self["nominal.decimal"].constructor(Coercions::JSON.method(:to_decimal))
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
register("json.symbol") do
|
|
28
|
+
self["nominal.symbol"].constructor(Coercions::JSON.method(:to_symbol))
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
register("json.array") { self["array"] }
|
|
32
|
+
|
|
33
|
+
register("json.hash") { self["hash"] }
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dry
|
|
4
|
+
module Types
|
|
5
|
+
# Lax types rescue from type-related errors when constructors fail
|
|
6
|
+
#
|
|
7
|
+
# @api public
|
|
8
|
+
class Lax
|
|
9
|
+
include Type
|
|
10
|
+
include Decorator
|
|
11
|
+
include Builder
|
|
12
|
+
include Printable
|
|
13
|
+
include ::Dry::Equalizer(:type, inspect: false, immutable: true)
|
|
14
|
+
|
|
15
|
+
undef :options, :constructor, :<<, :>>, :prepend, :append
|
|
16
|
+
|
|
17
|
+
# @param [Object] input
|
|
18
|
+
#
|
|
19
|
+
# @return [Object]
|
|
20
|
+
#
|
|
21
|
+
# @api public
|
|
22
|
+
def call(input, &)
|
|
23
|
+
type.call_safe(input) { |output = input| output }
|
|
24
|
+
end
|
|
25
|
+
alias_method :[], :call
|
|
26
|
+
alias_method :call_safe, :call
|
|
27
|
+
alias_method :call_unsafe, :call
|
|
28
|
+
|
|
29
|
+
# @param [Object] input
|
|
30
|
+
# @param [#call,nil] block
|
|
31
|
+
#
|
|
32
|
+
# @yieldparam [Failure] failure
|
|
33
|
+
# @yieldreturn [Result]
|
|
34
|
+
#
|
|
35
|
+
# @return [Result,Logic::Result]
|
|
36
|
+
#
|
|
37
|
+
# @api public
|
|
38
|
+
def try(input, &) = type.try(input, &)
|
|
39
|
+
|
|
40
|
+
# @see Nominal#to_ast
|
|
41
|
+
#
|
|
42
|
+
# @api public
|
|
43
|
+
def to_ast(meta: true) = [:lax, type.to_ast(meta: meta)]
|
|
44
|
+
|
|
45
|
+
# @return [Lax]
|
|
46
|
+
#
|
|
47
|
+
# @api public
|
|
48
|
+
def lax = self
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
# @param [Object, Dry::Types::Constructor] response
|
|
53
|
+
#
|
|
54
|
+
# @return [Boolean]
|
|
55
|
+
#
|
|
56
|
+
# @api private
|
|
57
|
+
def decorate?(response)
|
|
58
|
+
super || response.is_a?(type.constructor_type)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
extend ::Dry::Core::Deprecations[:"dry-types"]
|
|
63
|
+
Safe = Lax
|
|
64
|
+
deprecate_constant(:Safe)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dry
|
|
4
|
+
module Types
|
|
5
|
+
# Homogeneous mapping. It describes a hash with unknown keys that match a certain type.
|
|
6
|
+
#
|
|
7
|
+
# @example
|
|
8
|
+
# type = Dry::Types['hash'].map(
|
|
9
|
+
# Dry::Types['integer'].constrained(gteq: 1, lteq: 10),
|
|
10
|
+
# Dry::Types['string']
|
|
11
|
+
# )
|
|
12
|
+
#
|
|
13
|
+
# type.(1 => 'right')
|
|
14
|
+
# # => {1 => 'right'}
|
|
15
|
+
#
|
|
16
|
+
# type.('1' => 'wrong')
|
|
17
|
+
# # Dry::Types::MapError: "1" violates constraints (type?(Integer, "1")
|
|
18
|
+
# # AND gteq?(1, "1")
|
|
19
|
+
# # AND lteq?(10, "1") failed)
|
|
20
|
+
#
|
|
21
|
+
# type.(11 => 'wrong')
|
|
22
|
+
# # Dry::Types::MapError: 11 violates constraints (lteq?(10, 11) failed)
|
|
23
|
+
#
|
|
24
|
+
# @api public
|
|
25
|
+
class Map < Nominal
|
|
26
|
+
def initialize(primitive, key_type: Types["any"], value_type: Types["any"], meta: EMPTY_HASH)
|
|
27
|
+
super
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @return [Type]
|
|
31
|
+
#
|
|
32
|
+
# @api public
|
|
33
|
+
def key_type = options[:key_type]
|
|
34
|
+
|
|
35
|
+
# @return [Type]
|
|
36
|
+
#
|
|
37
|
+
# @api public
|
|
38
|
+
def value_type = options[:value_type]
|
|
39
|
+
|
|
40
|
+
# @return [String]
|
|
41
|
+
#
|
|
42
|
+
# @api public
|
|
43
|
+
def name = "Map"
|
|
44
|
+
|
|
45
|
+
# @param [Hash] hash
|
|
46
|
+
#
|
|
47
|
+
# @return [Hash]
|
|
48
|
+
#
|
|
49
|
+
# @api private
|
|
50
|
+
def call_unsafe(hash)
|
|
51
|
+
try(hash) { |failure|
|
|
52
|
+
raise MapError, failure.error.message
|
|
53
|
+
}.input
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# @param [Hash] hash
|
|
57
|
+
#
|
|
58
|
+
# @return [Hash]
|
|
59
|
+
#
|
|
60
|
+
# @api private
|
|
61
|
+
def call_safe(hash) = try(hash) { return yield }.input
|
|
62
|
+
|
|
63
|
+
# @param [Hash] hash
|
|
64
|
+
#
|
|
65
|
+
# @return [Result]
|
|
66
|
+
#
|
|
67
|
+
# @api public
|
|
68
|
+
def try(hash)
|
|
69
|
+
result = coerce(hash)
|
|
70
|
+
return result if result.success? || !block_given?
|
|
71
|
+
|
|
72
|
+
yield(result)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# @param meta [Boolean] Whether to dump the meta to the AST
|
|
76
|
+
#
|
|
77
|
+
# @return [Array] An AST representation
|
|
78
|
+
#
|
|
79
|
+
# @api public
|
|
80
|
+
def to_ast(meta: true)
|
|
81
|
+
[:map,
|
|
82
|
+
[key_type.to_ast(meta: true),
|
|
83
|
+
value_type.to_ast(meta: true),
|
|
84
|
+
meta_ast(meta)]]
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# @return [Boolean]
|
|
88
|
+
#
|
|
89
|
+
# @api public
|
|
90
|
+
def constrained? = value_type.constrained?
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
# @api private
|
|
95
|
+
# rubocop:disable Metrics/AbcSize
|
|
96
|
+
def coerce(input)
|
|
97
|
+
assert_primitive(input) do
|
|
98
|
+
output = {}
|
|
99
|
+
failures = []
|
|
100
|
+
|
|
101
|
+
input.each do |k, v|
|
|
102
|
+
res_k = key_type.try(k)
|
|
103
|
+
res_v = value_type.try(v)
|
|
104
|
+
|
|
105
|
+
if res_k.failure?
|
|
106
|
+
failures << res_k.error
|
|
107
|
+
elsif output.key?(res_k.input)
|
|
108
|
+
failures << CoercionError.new("duplicate coerced hash key #{res_k.input.inspect}")
|
|
109
|
+
elsif res_v.failure?
|
|
110
|
+
failures << res_v.error
|
|
111
|
+
else
|
|
112
|
+
output[res_k.input] = res_v.input
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
if failures.empty?
|
|
117
|
+
success(output)
|
|
118
|
+
else
|
|
119
|
+
failure(input, MultipleError.new(failures))
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
# rubocop:enable Metrics/AbcSize
|
|
124
|
+
|
|
125
|
+
def assert_primitive(input)
|
|
126
|
+
if primitive?(input)
|
|
127
|
+
yield
|
|
128
|
+
else
|
|
129
|
+
failure(input, CoercionError.new("#{input.inspect} must be an instance of #{primitive}"))
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dry
|
|
4
|
+
module Types
|
|
5
|
+
# Storage for meta-data
|
|
6
|
+
#
|
|
7
|
+
# @api public
|
|
8
|
+
module Meta
|
|
9
|
+
def initialize(*args, meta: EMPTY_HASH, **options)
|
|
10
|
+
super(*args, **options)
|
|
11
|
+
@meta = meta.freeze
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# @param options [Hash] new_options
|
|
15
|
+
#
|
|
16
|
+
# @return [Type]
|
|
17
|
+
#
|
|
18
|
+
# @api public
|
|
19
|
+
def with(**options)
|
|
20
|
+
if options.empty?
|
|
21
|
+
self
|
|
22
|
+
else
|
|
23
|
+
super(meta: @meta, **options)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @overload meta
|
|
28
|
+
# @return [Hash] metadata associated with type
|
|
29
|
+
#
|
|
30
|
+
# @overload meta(data)
|
|
31
|
+
# @param [Hash] new metadata to merge into existing metadata
|
|
32
|
+
# @return [Type] new type with added metadata
|
|
33
|
+
#
|
|
34
|
+
# @api public
|
|
35
|
+
def meta(data = Undefined)
|
|
36
|
+
if Undefined.equal?(data)
|
|
37
|
+
@meta
|
|
38
|
+
elsif data.empty?
|
|
39
|
+
self
|
|
40
|
+
else
|
|
41
|
+
with(meta: @meta.merge(data))
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Resets meta
|
|
46
|
+
#
|
|
47
|
+
# @return [Dry::Types::Type]
|
|
48
|
+
#
|
|
49
|
+
# @api public
|
|
50
|
+
def pristine = with(meta: EMPTY_HASH)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|