dry-types 1.6.1 → 1.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/dry-types.gemspec +3 -5
- data/lib/dry/types/builder.rb +39 -2
- data/lib/dry/types/composition.rb +152 -0
- data/lib/dry/types/container.rb +1 -3
- data/lib/dry/types/extensions/maybe.rb +7 -2
- data/lib/dry/types/extensions/monads.rb +7 -2
- data/lib/dry/types/implication.rb +66 -0
- data/lib/dry/types/intersection.rb +108 -0
- data/lib/dry/types/predicate_registry.rb +7 -20
- data/lib/dry/types/printer/composition.rb +44 -0
- data/lib/dry/types/printer.rb +110 -129
- data/lib/dry/types/sum.rb +3 -91
- data/lib/dry/types/version.rb +1 -1
- data/lib/dry/types.rb +1 -1
- metadata +26 -50
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fdd7e08f86f3137ead67bb6170e91de625227e97783be335b982e5a4c313b32e
|
4
|
+
data.tar.gz: faa51ae68eb4942cb64520eed7d6c4dbf5b46af384a91acdfc94105f32298839
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 588feadceca555b168266b6e6ee542d71b154152ea56a88f7b601c33a79b116c90b86356e03bc8ca273e4df5d38e4056277c88502387e836bba4af8409fede14
|
7
|
+
data.tar.gz: 72326eb3f32d33cbc95d1e20d6bec4f1bc40a66fa6cac818d35b8ff2214d3932f3350d5e6cf36a6a49ea3ffd862d7422a49622d5a98937a0be6f914d0f390965
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,16 @@
|
|
1
1
|
<!--- DO NOT EDIT THIS FILE - IT'S AUTOMATICALLY GENERATED VIA DEVTOOLS --->
|
2
2
|
|
3
|
+
## 1.7.0 2022-11-04
|
4
|
+
|
5
|
+
|
6
|
+
### Changed
|
7
|
+
|
8
|
+
- This version is compatible with recently released dry-rb dependencies (@flash-gordon)
|
9
|
+
- Updated to dry-core 1.0 (@flash-gordon + @solnic)
|
10
|
+
- Dependency on dry-container was dropped (@flash-gordon)
|
11
|
+
|
12
|
+
[Compare v1.6.1...v1.7.0](https://github.com/dry-rb/dry-types/compare/v1.6.1...v1.7.0)
|
13
|
+
|
3
14
|
## 1.6.1 2022-10-15
|
4
15
|
|
5
16
|
|
data/dry-types.gemspec
CHANGED
@@ -30,14 +30,12 @@ Gem::Specification.new do |spec|
|
|
30
30
|
|
31
31
|
# to update dependencies edit project.yml
|
32
32
|
spec.add_runtime_dependency "concurrent-ruby", "~> 1.0"
|
33
|
-
spec.add_runtime_dependency "dry-
|
34
|
-
spec.add_runtime_dependency "dry-
|
35
|
-
spec.add_runtime_dependency "dry-
|
36
|
-
spec.add_runtime_dependency "dry-logic", "~> 1.3", ">= 1.3"
|
33
|
+
spec.add_runtime_dependency "dry-core", "~> 1.0", "< 2"
|
34
|
+
spec.add_runtime_dependency "dry-inflector", "~> 1.0", "< 2"
|
35
|
+
spec.add_runtime_dependency "dry-logic", ">= 1.4", "< 2"
|
37
36
|
spec.add_runtime_dependency "zeitwerk", "~> 2.6"
|
38
37
|
|
39
38
|
spec.add_development_dependency "bundler"
|
40
|
-
spec.add_development_dependency "dry-monads", "~> 1.0"
|
41
39
|
spec.add_development_dependency "rake"
|
42
40
|
spec.add_development_dependency "rspec"
|
43
41
|
spec.add_development_dependency "yard"
|
data/lib/dry/types/builder.rb
CHANGED
@@ -5,6 +5,7 @@ module Dry
|
|
5
5
|
# Common API for building types and composition
|
6
6
|
#
|
7
7
|
# @api public
|
8
|
+
# rubocop:disable Metrics/ModuleLength
|
8
9
|
module Builder
|
9
10
|
include Dry::Core::Constants
|
10
11
|
|
@@ -30,8 +31,29 @@ module Dry
|
|
30
31
|
#
|
31
32
|
# @api private
|
32
33
|
def |(other)
|
33
|
-
|
34
|
-
|
34
|
+
compose(other, Sum)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Compose two types into an Intersection type
|
38
|
+
#
|
39
|
+
# @param [Type] other
|
40
|
+
#
|
41
|
+
# @return [Intersection, Intersection::Constrained]
|
42
|
+
#
|
43
|
+
# @api private
|
44
|
+
def &(other)
|
45
|
+
compose(other, Intersection)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Compose two types into an Implication type
|
49
|
+
#
|
50
|
+
# @param [Type] other
|
51
|
+
#
|
52
|
+
# @return [Implication, Implication::Constrained]
|
53
|
+
#
|
54
|
+
# @api private
|
55
|
+
def >(other)
|
56
|
+
compose(other, Implication)
|
35
57
|
end
|
36
58
|
|
37
59
|
# Turn a type into an optional type
|
@@ -179,6 +201,21 @@ module Dry
|
|
179
201
|
end
|
180
202
|
end
|
181
203
|
end
|
204
|
+
|
205
|
+
private
|
206
|
+
|
207
|
+
# @api private
|
208
|
+
def compose(other, composition_class)
|
209
|
+
klass =
|
210
|
+
if constrained? && other.constrained?
|
211
|
+
composition_class::Constrained
|
212
|
+
else
|
213
|
+
composition_class
|
214
|
+
end
|
215
|
+
|
216
|
+
klass.new(self, other)
|
217
|
+
end
|
182
218
|
end
|
219
|
+
# rubocop:enable Metrics/ModuleLength
|
183
220
|
end
|
184
221
|
end
|
@@ -0,0 +1,152 @@
|
|
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
|
+
module Composition
|
10
|
+
include Type
|
11
|
+
include Builder
|
12
|
+
include Options
|
13
|
+
include Meta
|
14
|
+
include Printable
|
15
|
+
include Dry::Equalizer(:left, :right, :options, :meta, inspect: false, immutable: true)
|
16
|
+
|
17
|
+
# @return [Type]
|
18
|
+
attr_reader :left
|
19
|
+
|
20
|
+
# @return [Type]
|
21
|
+
attr_reader :right
|
22
|
+
|
23
|
+
module Constrained
|
24
|
+
def rule
|
25
|
+
left.rule.public_send(self.class.operator, right.rule)
|
26
|
+
end
|
27
|
+
|
28
|
+
def constrained?
|
29
|
+
true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.included(base)
|
34
|
+
composition_name = Inflector.demodulize(base)
|
35
|
+
ast_type = Inflector.underscore(composition_name).to_sym
|
36
|
+
base.define_singleton_method(:ast_type) { ast_type }
|
37
|
+
base.define_singleton_method(:composition_name) { composition_name }
|
38
|
+
base.const_set("Constrained", Class.new(base) { include Constrained })
|
39
|
+
end
|
40
|
+
|
41
|
+
# @param [Type] left
|
42
|
+
# @param [Type] right
|
43
|
+
# @param [Hash] options
|
44
|
+
#
|
45
|
+
# @api private
|
46
|
+
def initialize(left, right, **options)
|
47
|
+
super
|
48
|
+
@left, @right = left, right
|
49
|
+
freeze
|
50
|
+
end
|
51
|
+
|
52
|
+
# @return [String]
|
53
|
+
#
|
54
|
+
# @api public
|
55
|
+
def name
|
56
|
+
[left, right].map(&:name).join(" #{self.class.operator} ")
|
57
|
+
end
|
58
|
+
|
59
|
+
# @return [false]
|
60
|
+
#
|
61
|
+
# @api public
|
62
|
+
def default?
|
63
|
+
false
|
64
|
+
end
|
65
|
+
|
66
|
+
# @return [false]
|
67
|
+
#
|
68
|
+
# @api public
|
69
|
+
def constrained?
|
70
|
+
false
|
71
|
+
end
|
72
|
+
|
73
|
+
# @return [Boolean]
|
74
|
+
#
|
75
|
+
# @api public
|
76
|
+
def optional?
|
77
|
+
false
|
78
|
+
end
|
79
|
+
|
80
|
+
# @param [Object] input
|
81
|
+
#
|
82
|
+
# @return [Object]
|
83
|
+
#
|
84
|
+
# @api private
|
85
|
+
def call_unsafe(input)
|
86
|
+
raise NotImplementedError
|
87
|
+
end
|
88
|
+
|
89
|
+
# @param [Object] input
|
90
|
+
#
|
91
|
+
# @return [Object]
|
92
|
+
#
|
93
|
+
# @api private
|
94
|
+
def call_safe(input, &block)
|
95
|
+
raise NotImplementedError
|
96
|
+
end
|
97
|
+
|
98
|
+
# @param [Object] input
|
99
|
+
#
|
100
|
+
# @api public
|
101
|
+
def try(input)
|
102
|
+
raise NotImplementedError
|
103
|
+
end
|
104
|
+
|
105
|
+
# @api private
|
106
|
+
def success(input)
|
107
|
+
result = try(input)
|
108
|
+
if result.success?
|
109
|
+
result
|
110
|
+
else
|
111
|
+
raise ArgumentError, "Invalid success value '#{input}' for #{inspect}"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# @api private
|
116
|
+
def failure(input, _error = nil)
|
117
|
+
result = try(input)
|
118
|
+
if result.failure?
|
119
|
+
result
|
120
|
+
else
|
121
|
+
raise ArgumentError, "Invalid failure value '#{input}' for #{inspect}"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# @param [Object] value
|
126
|
+
#
|
127
|
+
# @return [Boolean]
|
128
|
+
#
|
129
|
+
# @api private
|
130
|
+
def primitive?(value)
|
131
|
+
raise NotImplementedError
|
132
|
+
end
|
133
|
+
|
134
|
+
# @see Nominal#to_ast
|
135
|
+
#
|
136
|
+
# @api public
|
137
|
+
def to_ast(meta: true)
|
138
|
+
[self.class.ast_type,
|
139
|
+
[left.to_ast(meta: meta), right.to_ast(meta: meta), meta ? self.meta : EMPTY_HASH]]
|
140
|
+
end
|
141
|
+
|
142
|
+
# Wrap the type with a proc
|
143
|
+
#
|
144
|
+
# @return [Proc]
|
145
|
+
#
|
146
|
+
# @api public
|
147
|
+
def to_proc
|
148
|
+
proc { |value| self.(value) }
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
data/lib/dry/types/container.rb
CHANGED
@@ -1,14 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "dry/container"
|
4
|
-
|
5
3
|
module Dry
|
6
4
|
module Types
|
7
5
|
# Internal container for the built-in types
|
8
6
|
#
|
9
7
|
# @api private
|
10
8
|
class Container
|
11
|
-
include
|
9
|
+
include Core::Container::Mixin
|
12
10
|
end
|
13
11
|
end
|
14
12
|
end
|
@@ -1,6 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "dry/monads
|
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
|
4
9
|
|
5
10
|
module Dry
|
6
11
|
module Types
|
@@ -13,7 +18,7 @@ module Dry
|
|
13
18
|
include Decorator
|
14
19
|
include Builder
|
15
20
|
include Printable
|
16
|
-
include ::Dry::Monads
|
21
|
+
include ::Dry::Monads[:maybe]
|
17
22
|
|
18
23
|
# @param [Dry::Monads::Maybe, Object] input
|
19
24
|
#
|
@@ -1,6 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "dry/monads
|
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
|
4
9
|
|
5
10
|
module Dry
|
6
11
|
module Types
|
@@ -8,7 +13,7 @@ module Dry
|
|
8
13
|
#
|
9
14
|
# @api public
|
10
15
|
class Result
|
11
|
-
include Dry::Monads
|
16
|
+
include ::Dry::Monads[:result]
|
12
17
|
|
13
18
|
# Turn result into a monad
|
14
19
|
#
|
@@ -0,0 +1,66 @@
|
|
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
|
+
end
|
14
|
+
|
15
|
+
# @param [Object] input
|
16
|
+
#
|
17
|
+
# @return [Object]
|
18
|
+
#
|
19
|
+
# @api private
|
20
|
+
def call_unsafe(input)
|
21
|
+
if left.try(input).success?
|
22
|
+
right.call_unsafe(input)
|
23
|
+
else
|
24
|
+
input
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# @param [Object] input
|
29
|
+
#
|
30
|
+
# @return [Object]
|
31
|
+
#
|
32
|
+
# @api private
|
33
|
+
def call_safe(input, &block)
|
34
|
+
if left.try(input).success?
|
35
|
+
right.call_safe(input, &block)
|
36
|
+
else
|
37
|
+
input
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# @param [Object] input
|
42
|
+
#
|
43
|
+
# @api public
|
44
|
+
def try(input)
|
45
|
+
if left.try(input).success?
|
46
|
+
right.try(input)
|
47
|
+
else
|
48
|
+
Result::Success.new(input)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# @param [Object] value
|
53
|
+
#
|
54
|
+
# @return [Boolean]
|
55
|
+
#
|
56
|
+
# @api private
|
57
|
+
def primitive?(value)
|
58
|
+
if left.primitive?(value)
|
59
|
+
right.primitive?(value)
|
60
|
+
else
|
61
|
+
true
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,108 @@
|
|
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
|
+
end
|
18
|
+
|
19
|
+
# @param [Object] input
|
20
|
+
#
|
21
|
+
# @return [Object]
|
22
|
+
#
|
23
|
+
# @api private
|
24
|
+
def call_unsafe(input)
|
25
|
+
merge_results(left.call_unsafe(input), right.call_unsafe(input))
|
26
|
+
end
|
27
|
+
|
28
|
+
# @param [Object] input
|
29
|
+
#
|
30
|
+
# @return [Object]
|
31
|
+
#
|
32
|
+
# @api private
|
33
|
+
def call_safe(input, &block)
|
34
|
+
try_sides(input, &block).input
|
35
|
+
end
|
36
|
+
|
37
|
+
# @param [Object] input
|
38
|
+
#
|
39
|
+
# @api public
|
40
|
+
def try(input)
|
41
|
+
try_sides(input) do |failure|
|
42
|
+
if block_given?
|
43
|
+
yield(failure)
|
44
|
+
else
|
45
|
+
failure
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# @param [Object] value
|
51
|
+
#
|
52
|
+
# @return [Boolean]
|
53
|
+
#
|
54
|
+
# @api private
|
55
|
+
def primitive?(value)
|
56
|
+
left.primitive?(value) && right.primitive?(value)
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
# @api private
|
62
|
+
def try_sides(input, &block)
|
63
|
+
results = []
|
64
|
+
|
65
|
+
[left, right].each do |side|
|
66
|
+
result = try_side(side, input, &block)
|
67
|
+
return result if result.failure?
|
68
|
+
|
69
|
+
results << result
|
70
|
+
end
|
71
|
+
|
72
|
+
Result::Success.new(merge_results(*results.map(&:input)))
|
73
|
+
end
|
74
|
+
|
75
|
+
# @api private
|
76
|
+
def try_side(side, input)
|
77
|
+
failure = nil
|
78
|
+
|
79
|
+
result = side.try(input) do |f|
|
80
|
+
failure = f
|
81
|
+
yield(f)
|
82
|
+
end
|
83
|
+
|
84
|
+
if result.is_a?(Result)
|
85
|
+
result
|
86
|
+
elsif failure
|
87
|
+
Result::Failure.new(result, failure)
|
88
|
+
else
|
89
|
+
Result::Success.new(result)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# @api private
|
94
|
+
def merge_results(left_result, right_result)
|
95
|
+
case left_result
|
96
|
+
when ::Array
|
97
|
+
left_result
|
98
|
+
.zip(right_result)
|
99
|
+
.map { |lhs, rhs| merge_results(lhs, rhs) }
|
100
|
+
when ::Hash
|
101
|
+
left_result.merge(right_result)
|
102
|
+
else
|
103
|
+
left_result
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -15,27 +15,14 @@ module Dry
|
|
15
15
|
KERNEL_RESPOND_TO = ::Kernel.instance_method(:respond_to?)
|
16
16
|
private_constant(:KERNEL_RESPOND_TO)
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
end
|
23
|
-
|
24
|
-
# @api private
|
25
|
-
def key?(name)
|
26
|
-
KERNEL_RESPOND_TO.bind_call(@predicates, name)
|
27
|
-
end
|
28
|
-
else
|
29
|
-
# @api private
|
30
|
-
def initialize(predicates = Logic::Predicates)
|
31
|
-
@predicates = predicates
|
32
|
-
@has_predicate = KERNEL_RESPOND_TO.bind(@predicates)
|
33
|
-
end
|
18
|
+
# @api private
|
19
|
+
def initialize(predicates = Logic::Predicates)
|
20
|
+
@predicates = predicates
|
21
|
+
end
|
34
22
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
end
|
23
|
+
# @api private
|
24
|
+
def key?(name)
|
25
|
+
KERNEL_RESPOND_TO.bind_call(@predicates, name)
|
39
26
|
end
|
40
27
|
|
41
28
|
# @api private
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Types
|
5
|
+
# @api private
|
6
|
+
class Printer
|
7
|
+
# @api private
|
8
|
+
class Composition
|
9
|
+
def initialize(printer, composition_class)
|
10
|
+
@printer = printer
|
11
|
+
@composition_class = composition_class
|
12
|
+
freeze
|
13
|
+
end
|
14
|
+
|
15
|
+
def visit(composition)
|
16
|
+
visit_constructors(composition) do |constructors|
|
17
|
+
@printer.visit_options(composition.options, composition.meta) do |opts|
|
18
|
+
yield "#{@composition_class.composition_name}<#{constructors}#{opts}>"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def visit_constructors(composition)
|
26
|
+
visit_constructor(composition.left) do |left|
|
27
|
+
visit_constructor(composition.right) do |right|
|
28
|
+
yield "#{left} #{@composition_class.operator} #{right}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def visit_constructor(type, &block)
|
34
|
+
case type
|
35
|
+
when @composition_class
|
36
|
+
visit_constructors(type, &block)
|
37
|
+
else
|
38
|
+
@printer.visit(type, &block)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/dry/types/printer.rb
CHANGED
@@ -1,17 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "dry/types/printer/composition"
|
4
|
+
|
3
5
|
module Dry
|
4
6
|
# rubocop:disable Metrics/AbcSize
|
5
7
|
# rubocop:disable Metrics/PerceivedComplexity
|
6
8
|
module Types
|
7
9
|
# @api private
|
8
10
|
class Printer
|
9
|
-
MAPPING = {
|
11
|
+
MAPPING = {
|
10
12
|
Nominal => :visit_nominal,
|
11
13
|
Constructor => :visit_constructor,
|
12
|
-
|
13
|
-
|
14
|
-
|
14
|
+
[
|
15
|
+
Constrained,
|
16
|
+
Constrained::Coercible
|
17
|
+
] => :visit_constrained,
|
18
|
+
Types::Hash => :visit_hash,
|
15
19
|
Schema => :visit_schema,
|
16
20
|
Schema::Key => :visit_key,
|
17
21
|
Map => :visit_map,
|
@@ -19,12 +23,22 @@ module Dry
|
|
19
23
|
Array::Member => :visit_array_member,
|
20
24
|
Lax => :visit_lax,
|
21
25
|
Enum => :visit_enum,
|
22
|
-
Default => :visit_default,
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
+
[Default, Default::Callable] => :visit_default,
|
27
|
+
[
|
28
|
+
Sum,
|
29
|
+
Sum::Constrained,
|
30
|
+
Intersection,
|
31
|
+
Intersection::Constrained,
|
32
|
+
Implication,
|
33
|
+
Implication::Constrained
|
34
|
+
] => :visit_composition,
|
26
35
|
Any.class => :visit_any
|
27
|
-
}
|
36
|
+
}.flat_map { |k, v| Array(k).map { |kk| [kk, v] } }.to_h
|
37
|
+
|
38
|
+
def initialize
|
39
|
+
@composition_printers = {}
|
40
|
+
freeze
|
41
|
+
end
|
28
42
|
|
29
43
|
def call(type)
|
30
44
|
output = "".dup
|
@@ -87,106 +101,10 @@ module Dry
|
|
87
101
|
end
|
88
102
|
end
|
89
103
|
|
90
|
-
def
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
type_fn_str = ""
|
95
|
-
strict_str = ""
|
96
|
-
|
97
|
-
strict_str = "strict " if options.delete(:strict)
|
98
|
-
|
99
|
-
if (key_fn = options.delete(:key_transform_fn))
|
100
|
-
visit_callable(key_fn) do |fn|
|
101
|
-
key_fn_str = "key_fn=#{fn} "
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
if (type_fn = options.delete(:type_transform_fn))
|
106
|
-
visit_callable(type_fn) do |fn|
|
107
|
-
type_fn_str = "type_fn=#{fn} "
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
keys = options.delete(:keys)
|
112
|
-
|
113
|
-
visit_options(options, schema.meta) do |opts|
|
114
|
-
opts = "#{opts[1..]} " unless opts.empty?
|
115
|
-
schema_parameters = "#{key_fn_str}#{type_fn_str}#{strict_str}#{opts}"
|
116
|
-
|
117
|
-
header = "Schema<#{schema_parameters}keys={"
|
118
|
-
|
119
|
-
if size.zero?
|
120
|
-
yield "#{header}}>"
|
121
|
-
else
|
122
|
-
yield header.dup << keys.map { |key|
|
123
|
-
visit(key) { |type| type }
|
124
|
-
}.join(" ") << "}>"
|
125
|
-
end
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
def visit_map(map)
|
130
|
-
visit(map.key_type) do |key|
|
131
|
-
visit(map.value_type) do |value|
|
132
|
-
options = map.options.dup
|
133
|
-
options.delete(:key_type)
|
134
|
-
options.delete(:value_type)
|
135
|
-
|
136
|
-
visit_options(options) do |_opts|
|
137
|
-
yield "Map<#{key} => #{value}>"
|
138
|
-
end
|
139
|
-
end
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
def visit_key(key)
|
144
|
-
visit(key.type) do |type|
|
145
|
-
if key.required?
|
146
|
-
yield "#{key.name}: #{type}"
|
147
|
-
else
|
148
|
-
yield "#{key.name}?: #{type}"
|
149
|
-
end
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
def visit_sum(sum)
|
154
|
-
visit_sum_constructors(sum) do |constructors|
|
155
|
-
visit_options(sum.options, sum.meta) do |opts|
|
156
|
-
yield "Sum<#{constructors}#{opts}>"
|
157
|
-
end
|
158
|
-
end
|
159
|
-
end
|
160
|
-
|
161
|
-
def visit_sum_constructors(sum)
|
162
|
-
case sum.left
|
163
|
-
when Sum
|
164
|
-
visit_sum_constructors(sum.left) do |left|
|
165
|
-
case sum.right
|
166
|
-
when Sum
|
167
|
-
visit_sum_constructors(sum.right) do |right|
|
168
|
-
yield "#{left} | #{right}"
|
169
|
-
end
|
170
|
-
else
|
171
|
-
visit(sum.right) do |right|
|
172
|
-
yield "#{left} | #{right}"
|
173
|
-
end
|
174
|
-
end
|
175
|
-
end
|
176
|
-
else
|
177
|
-
visit(sum.left) do |left|
|
178
|
-
case sum.right
|
179
|
-
when Sum
|
180
|
-
visit_sum_constructors(sum.right) do |right|
|
181
|
-
yield "#{left} | #{right}"
|
182
|
-
end
|
183
|
-
else
|
184
|
-
visit(sum.right) do |right|
|
185
|
-
yield "#{left} | #{right}"
|
186
|
-
end
|
187
|
-
end
|
188
|
-
end
|
189
|
-
end
|
104
|
+
def visit_composition(composition, &block)
|
105
|
+
klass = composition.class
|
106
|
+
@composition_printers[klass] = Composition.new(self, klass)
|
107
|
+
@composition_printers[klass].visit(composition, &block)
|
190
108
|
end
|
191
109
|
|
192
110
|
def visit_enum(enum)
|
@@ -234,25 +152,6 @@ module Dry
|
|
234
152
|
end
|
235
153
|
end
|
236
154
|
|
237
|
-
def visit_hash(hash)
|
238
|
-
options = hash.options.dup
|
239
|
-
type_fn_str = ""
|
240
|
-
|
241
|
-
if (type_fn = options.delete(:type_transform_fn))
|
242
|
-
visit_callable(type_fn) do |fn|
|
243
|
-
type_fn_str = "type_fn=#{fn}"
|
244
|
-
end
|
245
|
-
end
|
246
|
-
|
247
|
-
visit_options(options, hash.meta) do |opts|
|
248
|
-
if opts.empty? && type_fn_str.empty?
|
249
|
-
yield "Hash"
|
250
|
-
else
|
251
|
-
yield "Hash<#{type_fn_str}#{opts}>"
|
252
|
-
end
|
253
|
-
end
|
254
|
-
end
|
255
|
-
|
256
155
|
def visit_callable(callable)
|
257
156
|
fn = callable.is_a?(String) ? FnContainer[callable] : callable
|
258
157
|
|
@@ -288,6 +187,88 @@ module Dry
|
|
288
187
|
end
|
289
188
|
end
|
290
189
|
|
190
|
+
def visit_schema(schema)
|
191
|
+
options = schema.options.dup
|
192
|
+
size = schema.count
|
193
|
+
key_fn_str = ""
|
194
|
+
type_fn_str = ""
|
195
|
+
strict_str = ""
|
196
|
+
|
197
|
+
strict_str = "strict " if options.delete(:strict)
|
198
|
+
|
199
|
+
if (key_fn = options.delete(:key_transform_fn))
|
200
|
+
visit_callable(key_fn) do |fn|
|
201
|
+
key_fn_str = "key_fn=#{fn} "
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
if (type_fn = options.delete(:type_transform_fn))
|
206
|
+
visit_callable(type_fn) do |fn|
|
207
|
+
type_fn_str = "type_fn=#{fn} "
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
keys = options.delete(:keys)
|
212
|
+
|
213
|
+
visit_options(options, schema.meta) do |opts|
|
214
|
+
opts = "#{opts[1..]} " unless opts.empty?
|
215
|
+
schema_parameters = "#{key_fn_str}#{type_fn_str}#{strict_str}#{opts}"
|
216
|
+
|
217
|
+
header = "Schema<#{schema_parameters}keys={"
|
218
|
+
|
219
|
+
if size.zero?
|
220
|
+
yield "#{header}}>"
|
221
|
+
else
|
222
|
+
yield header.dup << keys.map { |key|
|
223
|
+
visit(key) { |type| type }
|
224
|
+
}.join(" ") << "}>"
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def visit_map(map)
|
230
|
+
visit(map.key_type) do |key|
|
231
|
+
visit(map.value_type) do |value|
|
232
|
+
options = map.options.dup
|
233
|
+
options.delete(:key_type)
|
234
|
+
options.delete(:value_type)
|
235
|
+
|
236
|
+
visit_options(options) do |_opts|
|
237
|
+
yield "Map<#{key} => #{value}>"
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
def visit_key(key)
|
244
|
+
visit(key.type) do |type|
|
245
|
+
if key.required?
|
246
|
+
yield "#{key.name}: #{type}"
|
247
|
+
else
|
248
|
+
yield "#{key.name}?: #{type}"
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
def visit_hash(hash)
|
254
|
+
options = hash.options.dup
|
255
|
+
type_fn_str = ""
|
256
|
+
|
257
|
+
if (type_fn = options.delete(:type_transform_fn))
|
258
|
+
visit_callable(type_fn) do |fn|
|
259
|
+
type_fn_str = "type_fn=#{fn}"
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
visit_options(options, hash.meta) do |opts|
|
264
|
+
if opts.empty? && type_fn_str.empty?
|
265
|
+
yield "Hash"
|
266
|
+
else
|
267
|
+
yield "Hash<#{type_fn_str}#{opts}>"
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
291
272
|
def visit_options(options, meta = EMPTY_HASH) # rubocop:disable Metrics/PerceivedComplexity
|
292
273
|
if options.empty? && meta.empty?
|
293
274
|
yield ""
|
@@ -312,7 +293,7 @@ module Dry
|
|
312
293
|
end
|
313
294
|
end
|
314
295
|
|
315
|
-
PRINTER = Printer.new
|
296
|
+
PRINTER = Printer.new
|
316
297
|
end
|
317
298
|
# rubocop:enable Metrics/AbcSize
|
318
299
|
# rubocop:enable Metrics/PerceivedComplexity
|
data/lib/dry/types/sum.rb
CHANGED
@@ -6,62 +6,10 @@ module Dry
|
|
6
6
|
#
|
7
7
|
# @api public
|
8
8
|
class Sum
|
9
|
-
include
|
10
|
-
include Builder
|
11
|
-
include Options
|
12
|
-
include Meta
|
13
|
-
include Printable
|
14
|
-
include Dry::Equalizer(:left, :right, :options, :meta, inspect: false, immutable: true)
|
9
|
+
include Composition
|
15
10
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
# @return [Type]
|
20
|
-
attr_reader :right
|
21
|
-
|
22
|
-
# @api private
|
23
|
-
class Constrained < Sum
|
24
|
-
# @return [Dry::Logic::Operations::Or]
|
25
|
-
def rule
|
26
|
-
left.rule | right.rule
|
27
|
-
end
|
28
|
-
|
29
|
-
# @return [true]
|
30
|
-
def constrained?
|
31
|
-
true
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
# @param [Type] left
|
36
|
-
# @param [Type] right
|
37
|
-
# @param [Hash] options
|
38
|
-
#
|
39
|
-
# @api private
|
40
|
-
def initialize(left, right, **options)
|
41
|
-
super
|
42
|
-
@left, @right = left, right
|
43
|
-
freeze
|
44
|
-
end
|
45
|
-
|
46
|
-
# @return [String]
|
47
|
-
#
|
48
|
-
# @api public
|
49
|
-
def name
|
50
|
-
[left, right].map(&:name).join(" | ")
|
51
|
-
end
|
52
|
-
|
53
|
-
# @return [false]
|
54
|
-
#
|
55
|
-
# @api public
|
56
|
-
def default?
|
57
|
-
false
|
58
|
-
end
|
59
|
-
|
60
|
-
# @return [false]
|
61
|
-
#
|
62
|
-
# @api public
|
63
|
-
def constrained?
|
64
|
-
false
|
11
|
+
def self.operator
|
12
|
+
:|
|
65
13
|
end
|
66
14
|
|
67
15
|
# @return [Boolean]
|
@@ -104,26 +52,6 @@ module Dry
|
|
104
52
|
end
|
105
53
|
end
|
106
54
|
|
107
|
-
# @api private
|
108
|
-
def success(input)
|
109
|
-
if left.valid?(input)
|
110
|
-
left.success(input)
|
111
|
-
elsif right.valid?(input)
|
112
|
-
right.success(input)
|
113
|
-
else
|
114
|
-
raise ArgumentError, "Invalid success value '#{input}' for #{inspect}"
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
# @api private
|
119
|
-
def failure(input, _error = nil)
|
120
|
-
if left.valid?(input)
|
121
|
-
right.failure(input, right.try(input).error)
|
122
|
-
else
|
123
|
-
left.failure(input, left.try(input).error)
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
55
|
# @param [Object] value
|
128
56
|
#
|
129
57
|
# @return [Boolean]
|
@@ -149,13 +77,6 @@ module Dry
|
|
149
77
|
end
|
150
78
|
end
|
151
79
|
|
152
|
-
# @see Nominal#to_ast
|
153
|
-
#
|
154
|
-
# @api public
|
155
|
-
def to_ast(meta: true)
|
156
|
-
[:sum, [left.to_ast(meta: meta), right.to_ast(meta: meta), meta ? self.meta : EMPTY_HASH]]
|
157
|
-
end
|
158
|
-
|
159
80
|
# @param [Hash] options
|
160
81
|
#
|
161
82
|
# @return [Constrained,Sum]
|
@@ -170,15 +91,6 @@ module Dry
|
|
170
91
|
super
|
171
92
|
end
|
172
93
|
end
|
173
|
-
|
174
|
-
# Wrap the type with a proc
|
175
|
-
#
|
176
|
-
# @return [Proc]
|
177
|
-
#
|
178
|
-
# @api public
|
179
|
-
def to_proc
|
180
|
-
proc { |value| self.(value) }
|
181
|
-
end
|
182
94
|
end
|
183
95
|
end
|
184
96
|
end
|
data/lib/dry/types/version.rb
CHANGED
data/lib/dry/types.rb
CHANGED
@@ -8,7 +8,6 @@ require "zeitwerk"
|
|
8
8
|
require "concurrent/map"
|
9
9
|
|
10
10
|
require "dry/core"
|
11
|
-
require "dry/container"
|
12
11
|
require "dry/logic"
|
13
12
|
|
14
13
|
require "dry/types/constraints"
|
@@ -41,6 +40,7 @@ module Dry
|
|
41
40
|
loader.ignore(
|
42
41
|
"#{root}/dry-types.rb",
|
43
42
|
"#{root}/dry/types/extensions",
|
43
|
+
"#{root}/dry/types/printer",
|
44
44
|
"#{root}/dry/types/spec/types.rb",
|
45
45
|
"#{root}/dry/types/{#{%w[
|
46
46
|
compat
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dry-types
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Piotr Solnica
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-11-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|
@@ -24,80 +24,66 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.0'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: dry-container
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - "~>"
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '0.3'
|
34
|
-
type: :runtime
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - "~>"
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '0.3'
|
41
27
|
- !ruby/object:Gem::Dependency
|
42
28
|
name: dry-core
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
44
30
|
requirements:
|
45
31
|
- - "~>"
|
46
32
|
- !ruby/object:Gem::Version
|
47
|
-
version: '0
|
48
|
-
- - "
|
33
|
+
version: '1.0'
|
34
|
+
- - "<"
|
49
35
|
- !ruby/object:Gem::Version
|
50
|
-
version: '
|
36
|
+
version: '2'
|
51
37
|
type: :runtime
|
52
38
|
prerelease: false
|
53
39
|
version_requirements: !ruby/object:Gem::Requirement
|
54
40
|
requirements:
|
55
41
|
- - "~>"
|
56
42
|
- !ruby/object:Gem::Version
|
57
|
-
version: '0
|
58
|
-
- - "
|
43
|
+
version: '1.0'
|
44
|
+
- - "<"
|
59
45
|
- !ruby/object:Gem::Version
|
60
|
-
version: '
|
46
|
+
version: '2'
|
61
47
|
- !ruby/object:Gem::Dependency
|
62
48
|
name: dry-inflector
|
63
49
|
requirement: !ruby/object:Gem::Requirement
|
64
50
|
requirements:
|
65
51
|
- - "~>"
|
66
52
|
- !ruby/object:Gem::Version
|
67
|
-
version: '0
|
68
|
-
- - "
|
53
|
+
version: '1.0'
|
54
|
+
- - "<"
|
69
55
|
- !ruby/object:Gem::Version
|
70
|
-
version:
|
56
|
+
version: '2'
|
71
57
|
type: :runtime
|
72
58
|
prerelease: false
|
73
59
|
version_requirements: !ruby/object:Gem::Requirement
|
74
60
|
requirements:
|
75
61
|
- - "~>"
|
76
62
|
- !ruby/object:Gem::Version
|
77
|
-
version: '0
|
78
|
-
- - "
|
63
|
+
version: '1.0'
|
64
|
+
- - "<"
|
79
65
|
- !ruby/object:Gem::Version
|
80
|
-
version:
|
66
|
+
version: '2'
|
81
67
|
- !ruby/object:Gem::Dependency
|
82
68
|
name: dry-logic
|
83
69
|
requirement: !ruby/object:Gem::Requirement
|
84
70
|
requirements:
|
85
|
-
- - "~>"
|
86
|
-
- !ruby/object:Gem::Version
|
87
|
-
version: '1.3'
|
88
71
|
- - ">="
|
89
72
|
- !ruby/object:Gem::Version
|
90
|
-
version: '1.
|
73
|
+
version: '1.4'
|
74
|
+
- - "<"
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '2'
|
91
77
|
type: :runtime
|
92
78
|
prerelease: false
|
93
79
|
version_requirements: !ruby/object:Gem::Requirement
|
94
80
|
requirements:
|
95
|
-
- - "~>"
|
96
|
-
- !ruby/object:Gem::Version
|
97
|
-
version: '1.3'
|
98
81
|
- - ">="
|
99
82
|
- !ruby/object:Gem::Version
|
100
|
-
version: '1.
|
83
|
+
version: '1.4'
|
84
|
+
- - "<"
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '2'
|
101
87
|
- !ruby/object:Gem::Dependency
|
102
88
|
name: zeitwerk
|
103
89
|
requirement: !ruby/object:Gem::Requirement
|
@@ -126,20 +112,6 @@ dependencies:
|
|
126
112
|
- - ">="
|
127
113
|
- !ruby/object:Gem::Version
|
128
114
|
version: '0'
|
129
|
-
- !ruby/object:Gem::Dependency
|
130
|
-
name: dry-monads
|
131
|
-
requirement: !ruby/object:Gem::Requirement
|
132
|
-
requirements:
|
133
|
-
- - "~>"
|
134
|
-
- !ruby/object:Gem::Version
|
135
|
-
version: '1.0'
|
136
|
-
type: :development
|
137
|
-
prerelease: false
|
138
|
-
version_requirements: !ruby/object:Gem::Requirement
|
139
|
-
requirements:
|
140
|
-
- - "~>"
|
141
|
-
- !ruby/object:Gem::Version
|
142
|
-
version: '1.0'
|
143
115
|
- !ruby/object:Gem::Dependency
|
144
116
|
name: rake
|
145
117
|
requirement: !ruby/object:Gem::Requirement
|
@@ -207,6 +179,7 @@ files:
|
|
207
179
|
- lib/dry/types/coercions/params.rb
|
208
180
|
- lib/dry/types/compat.rb
|
209
181
|
- lib/dry/types/compiler.rb
|
182
|
+
- lib/dry/types/composition.rb
|
210
183
|
- lib/dry/types/constrained.rb
|
211
184
|
- lib/dry/types/constrained/coercible.rb
|
212
185
|
- lib/dry/types/constraints.rb
|
@@ -225,7 +198,9 @@ files:
|
|
225
198
|
- lib/dry/types/fn_container.rb
|
226
199
|
- lib/dry/types/hash.rb
|
227
200
|
- lib/dry/types/hash/constructor.rb
|
201
|
+
- lib/dry/types/implication.rb
|
228
202
|
- lib/dry/types/inflector.rb
|
203
|
+
- lib/dry/types/intersection.rb
|
229
204
|
- lib/dry/types/json.rb
|
230
205
|
- lib/dry/types/lax.rb
|
231
206
|
- lib/dry/types/map.rb
|
@@ -239,6 +214,7 @@ files:
|
|
239
214
|
- lib/dry/types/primitive_inferrer.rb
|
240
215
|
- lib/dry/types/printable.rb
|
241
216
|
- lib/dry/types/printer.rb
|
217
|
+
- lib/dry/types/printer/composition.rb
|
242
218
|
- lib/dry/types/result.rb
|
243
219
|
- lib/dry/types/schema.rb
|
244
220
|
- lib/dry/types/schema/key.rb
|