plumb 0.0.2 → 0.0.4
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 +4 -4
- data/README.md +636 -129
- data/examples/concurrent_downloads.rb +3 -3
- data/examples/env_config.rb +122 -0
- data/examples/event_registry.rb +120 -0
- data/examples/weekdays.rb +1 -1
- data/lib/plumb/and.rb +4 -3
- data/lib/plumb/any_class.rb +4 -4
- data/lib/plumb/array_class.rb +8 -5
- data/lib/plumb/attributes.rb +262 -0
- data/lib/plumb/build.rb +4 -3
- data/lib/plumb/{steppable.rb → composable.rb} +85 -67
- data/lib/plumb/decorator.rb +57 -0
- data/lib/plumb/deferred.rb +1 -1
- data/lib/plumb/hash_class.rb +20 -11
- data/lib/plumb/hash_map.rb +8 -6
- data/lib/plumb/interface_class.rb +6 -2
- data/lib/plumb/json_schema_visitor.rb +97 -36
- data/lib/plumb/match_class.rb +7 -7
- data/lib/plumb/metadata.rb +5 -1
- data/lib/plumb/metadata_visitor.rb +18 -38
- data/lib/plumb/not.rb +4 -3
- data/lib/plumb/or.rb +10 -4
- data/lib/plumb/pipeline.rb +6 -5
- data/lib/plumb/policies.rb +81 -0
- data/lib/plumb/policy.rb +38 -0
- data/lib/plumb/schema.rb +13 -12
- data/lib/plumb/static_class.rb +4 -3
- data/lib/plumb/step.rb +4 -3
- data/lib/plumb/stream_class.rb +8 -7
- data/lib/plumb/tagged_hash.rb +10 -10
- data/lib/plumb/transform.rb +4 -3
- data/lib/plumb/tuple_class.rb +8 -8
- data/lib/plumb/type_registry.rb +5 -2
- data/lib/plumb/types.rb +119 -23
- data/lib/plumb/value_class.rb +4 -3
- data/lib/plumb/version.rb +1 -1
- data/lib/plumb/visitor_handlers.rb +12 -1
- data/lib/plumb.rb +59 -2
- metadata +12 -7
- data/lib/plumb/rules.rb +0 -102
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'date'
|
3
4
|
require 'plumb/visitor_handlers'
|
4
5
|
|
5
6
|
module Plumb
|
@@ -12,17 +13,26 @@ module Plumb
|
|
12
13
|
DEFAULT = 'default'
|
13
14
|
ANY_OF = 'anyOf'
|
14
15
|
ALL_OF = 'allOf'
|
16
|
+
NOT = 'not'
|
15
17
|
ENUM = 'enum'
|
16
18
|
CONST = 'const'
|
17
19
|
ITEMS = 'items'
|
18
20
|
PATTERN = 'pattern'
|
19
21
|
MINIMUM = 'minimum'
|
20
22
|
MAXIMUM = 'maximum'
|
23
|
+
MIN_ITEMS = 'minItems'
|
24
|
+
MAX_ITEMS = 'maxItems'
|
25
|
+
MIN_LENGTH = 'minLength'
|
26
|
+
MAX_LENGTH = 'maxLength'
|
27
|
+
ENVELOPE = {
|
28
|
+
'$schema' => 'https://json-schema.org/draft-08/schema#'
|
29
|
+
}.freeze
|
21
30
|
|
22
|
-
def self.call(node)
|
23
|
-
|
24
|
-
|
25
|
-
|
31
|
+
def self.call(node, root: true)
|
32
|
+
data = new.visit(node)
|
33
|
+
return data unless root
|
34
|
+
|
35
|
+
ENVELOPE.merge(data)
|
26
36
|
end
|
27
37
|
|
28
38
|
private def stringify_keys(hash) = hash.transform_keys(&:to_s)
|
@@ -32,7 +42,7 @@ module Plumb
|
|
32
42
|
end
|
33
43
|
|
34
44
|
on(:pipeline) do |node, props|
|
35
|
-
|
45
|
+
visit_children(node, props)
|
36
46
|
end
|
37
47
|
|
38
48
|
on(:step) do |node, props|
|
@@ -53,9 +63,12 @@ module Plumb
|
|
53
63
|
)
|
54
64
|
end
|
55
65
|
|
66
|
+
on(:data) do |node, props|
|
67
|
+
visit_name :hash, node._schema, props
|
68
|
+
end
|
69
|
+
|
56
70
|
on(:and) do |node, props|
|
57
|
-
left = visit(
|
58
|
-
right = visit(node.right)
|
71
|
+
left, right = node.children.map { |c| visit(c) }
|
59
72
|
type = right[TYPE] || left[TYPE]
|
60
73
|
props = props.merge(left).merge(right)
|
61
74
|
props = props.merge(TYPE => type) if type
|
@@ -64,11 +77,10 @@ module Plumb
|
|
64
77
|
|
65
78
|
# A "default" value is usually an "or" of expected_value | (undefined >> static_value)
|
66
79
|
on(:or) do |node, props|
|
67
|
-
left = visit(
|
68
|
-
|
69
|
-
any_of = [left, right].uniq
|
80
|
+
left, right = node.children.map { |c| visit(c) }
|
81
|
+
any_of = [left, right].uniq.filter(&:any?)
|
70
82
|
if any_of.size == 1
|
71
|
-
props.merge(
|
83
|
+
props.merge(any_of.first)
|
72
84
|
elsif any_of.size == 2 && (defidx = any_of.index { |p| p.key?(DEFAULT) })
|
73
85
|
val = any_of[defidx.zero? ? 1 : 0]
|
74
86
|
props.merge(val).merge(DEFAULT => any_of[defidx][DEFAULT])
|
@@ -78,22 +90,23 @@ module Plumb
|
|
78
90
|
end
|
79
91
|
|
80
92
|
on(:not) do |node, props|
|
81
|
-
props.merge(
|
93
|
+
props.merge(NOT => visit_children(node))
|
82
94
|
end
|
83
95
|
|
84
96
|
on(:value) do |node, props|
|
85
|
-
|
97
|
+
value = node.children.first
|
98
|
+
props = case value
|
86
99
|
when ::String, ::Symbol, ::Numeric
|
87
|
-
props.merge(CONST =>
|
100
|
+
props.merge(CONST => value)
|
88
101
|
else
|
89
102
|
props
|
90
103
|
end
|
91
104
|
|
92
|
-
visit(
|
105
|
+
visit(value, props)
|
93
106
|
end
|
94
107
|
|
95
108
|
on(:transform) do |node, props|
|
96
|
-
|
109
|
+
visit_children(node, props)
|
97
110
|
end
|
98
111
|
|
99
112
|
on(:undefined) do |_node, props|
|
@@ -103,36 +116,76 @@ module Plumb
|
|
103
116
|
on(:static) do |node, props|
|
104
117
|
# Set const AND default
|
105
118
|
# to emulate static values
|
106
|
-
|
119
|
+
value = node.children.first
|
120
|
+
props = case value
|
107
121
|
when ::String, ::Symbol, ::Numeric
|
108
|
-
props.merge(CONST =>
|
122
|
+
props.merge(CONST => value, DEFAULT => value)
|
109
123
|
else
|
110
124
|
props
|
111
125
|
end
|
112
126
|
|
113
|
-
visit(
|
127
|
+
visit(value, props)
|
114
128
|
end
|
115
129
|
|
116
|
-
on(:
|
117
|
-
|
118
|
-
|
130
|
+
on(:policy) do |node, props|
|
131
|
+
props = visit_children(node, props)
|
132
|
+
method_name = :"visit_#{node.policy_name}_policy"
|
133
|
+
if respond_to?(method_name)
|
134
|
+
send(method_name, node, props)
|
135
|
+
else
|
136
|
+
props
|
119
137
|
end
|
120
138
|
end
|
121
139
|
|
122
|
-
on(:
|
123
|
-
props.merge(ENUM => node.
|
140
|
+
on(:options_policy) do |node, props|
|
141
|
+
props.merge(ENUM => node.arg)
|
142
|
+
end
|
143
|
+
|
144
|
+
on(:size_policy) do |node, props|
|
145
|
+
opts = {}
|
146
|
+
case props[TYPE]
|
147
|
+
when 'array'
|
148
|
+
case node.arg
|
149
|
+
when Range
|
150
|
+
opts[MIN_ITEMS] = node.arg.min if node.arg.begin
|
151
|
+
opts[MAX_ITEMS] = node.arg.max if node.arg.end
|
152
|
+
when Numeric
|
153
|
+
opts[MIN_ITEMS] = node.arg
|
154
|
+
opts[MAX_ITEMS] = node.arg
|
155
|
+
end
|
156
|
+
when 'string'
|
157
|
+
case node.arg
|
158
|
+
when Range
|
159
|
+
opts[MIN_LENGTH] = node.arg.min if node.arg.begin
|
160
|
+
opts[MAX_LENGTH] = node.arg.max if node.arg.end
|
161
|
+
when Numeric
|
162
|
+
opts[MIN_LENGTH] = node.arg
|
163
|
+
opts[MAX_LENGTH] = node.arg
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
props.merge(opts)
|
168
|
+
end
|
169
|
+
|
170
|
+
on(:excluded_from_policy) do |node, props|
|
171
|
+
props.merge(NOT => { ENUM => node.arg })
|
172
|
+
end
|
173
|
+
|
174
|
+
on(Proc) do |_node, props|
|
175
|
+
props
|
124
176
|
end
|
125
177
|
|
126
178
|
on(:match) do |node, props|
|
127
179
|
# Set const if primitive
|
128
|
-
|
180
|
+
matcher = node.children.first
|
181
|
+
props = case matcher
|
129
182
|
when ::String, ::Symbol, ::Numeric
|
130
|
-
props.merge(CONST =>
|
183
|
+
props.merge(CONST => matcher)
|
131
184
|
else
|
132
185
|
props
|
133
186
|
end
|
134
187
|
|
135
|
-
visit(
|
188
|
+
visit(matcher, props)
|
136
189
|
end
|
137
190
|
|
138
191
|
on(:boolean) do |_node, props|
|
@@ -185,6 +238,14 @@ module Plumb
|
|
185
238
|
props.merge(opts)
|
186
239
|
end
|
187
240
|
|
241
|
+
on(::Time) do |_node, props|
|
242
|
+
props.merge(TYPE => 'string', 'format' => 'date-time')
|
243
|
+
end
|
244
|
+
|
245
|
+
on(::Date) do |_node, props|
|
246
|
+
props.merge(TYPE => 'string', 'format' => 'date')
|
247
|
+
end
|
248
|
+
|
188
249
|
on(::Hash) do |_node, props|
|
189
250
|
props.merge(TYPE => 'object')
|
190
251
|
end
|
@@ -202,7 +263,7 @@ module Plumb
|
|
202
263
|
{
|
203
264
|
TYPE => 'object',
|
204
265
|
'patternProperties' => {
|
205
|
-
'.*' => visit(node.
|
266
|
+
'.*' => visit(node.children[1])
|
206
267
|
}
|
207
268
|
}
|
208
269
|
end
|
@@ -211,27 +272,27 @@ module Plumb
|
|
211
272
|
{
|
212
273
|
TYPE => 'object',
|
213
274
|
'patternProperties' => {
|
214
|
-
'.*' => visit(node.
|
275
|
+
'.*' => visit(node.children[1])
|
215
276
|
}
|
216
277
|
}
|
217
278
|
end
|
218
279
|
|
219
280
|
on(:build) do |node, props|
|
220
|
-
|
281
|
+
visit_children(node, props)
|
221
282
|
end
|
222
283
|
|
223
284
|
on(:array) do |node, _props|
|
224
|
-
|
225
|
-
{ TYPE => 'array', ITEMS =>
|
285
|
+
items_props = visit_children(node)
|
286
|
+
{ TYPE => 'array', ITEMS => items_props }
|
226
287
|
end
|
227
288
|
|
228
289
|
on(:stream) do |node, _props|
|
229
|
-
|
230
|
-
{ TYPE => 'array', ITEMS =>
|
290
|
+
items_props = visit_children(node)
|
291
|
+
{ TYPE => 'array', ITEMS => items_props }
|
231
292
|
end
|
232
293
|
|
233
294
|
on(:tuple) do |node, _props|
|
234
|
-
items = node.
|
295
|
+
items = node.children.map { |t| visit(t) }
|
235
296
|
{ TYPE => 'array', 'prefixItems' => items }
|
236
297
|
end
|
237
298
|
|
@@ -243,7 +304,7 @@ module Plumb
|
|
243
304
|
}
|
244
305
|
|
245
306
|
key = node.key.to_s
|
246
|
-
children = node.
|
307
|
+
children = node.children.map { |c| visit(c) }
|
247
308
|
key_enum = children.map { |c| c[PROPERTIES][key][CONST] }
|
248
309
|
key_type = children.map { |c| c[PROPERTIES][key][TYPE] }
|
249
310
|
required << key
|
data/lib/plumb/match_class.rb
CHANGED
@@ -1,18 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'plumb/
|
3
|
+
require 'plumb/composable'
|
4
4
|
|
5
5
|
module Plumb
|
6
6
|
class MatchClass
|
7
|
-
include
|
7
|
+
include Composable
|
8
8
|
|
9
|
-
attr_reader :
|
9
|
+
attr_reader :children
|
10
10
|
|
11
|
-
def initialize(matcher = Undefined, error: nil)
|
11
|
+
def initialize(matcher = Undefined, error: nil, label: nil)
|
12
12
|
raise TypeError 'matcher must respond to #===' unless matcher.respond_to?(:===)
|
13
13
|
|
14
14
|
@matcher = matcher
|
15
15
|
@error = error.nil? ? build_error(matcher) : (error % matcher)
|
16
|
+
@label = matcher.is_a?(Class) ? matcher.inspect : "Match(#{label || @matcher.inspect})"
|
17
|
+
@children = [matcher].freeze
|
16
18
|
freeze
|
17
19
|
end
|
18
20
|
|
@@ -22,9 +24,7 @@ module Plumb
|
|
22
24
|
|
23
25
|
private
|
24
26
|
|
25
|
-
def _inspect
|
26
|
-
@matcher.inspect
|
27
|
-
end
|
27
|
+
def _inspect = @label
|
28
28
|
|
29
29
|
def build_error(matcher)
|
30
30
|
case matcher
|
data/lib/plumb/metadata.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Plumb
|
4
4
|
class Metadata
|
5
|
-
include
|
5
|
+
include Composable
|
6
6
|
|
7
7
|
attr_reader :metadata
|
8
8
|
|
@@ -11,6 +11,10 @@ module Plumb
|
|
11
11
|
freeze
|
12
12
|
end
|
13
13
|
|
14
|
+
def ==(other)
|
15
|
+
other.is_a?(self.class) && @metadata == other.metadata
|
16
|
+
end
|
17
|
+
|
14
18
|
def call(result) = result
|
15
19
|
|
16
20
|
private def _inspect = "Metadata[#{@metadata.inspect}]"
|
@@ -10,23 +10,14 @@ module Plumb
|
|
10
10
|
new.visit(node)
|
11
11
|
end
|
12
12
|
|
13
|
-
def on_missing_handler(node, props,
|
13
|
+
def on_missing_handler(node, props, _method_name)
|
14
14
|
return props.merge(type: node) if node.instance_of?(Class)
|
15
15
|
|
16
|
-
|
17
|
-
props
|
18
|
-
end
|
19
|
-
|
20
|
-
on(:undefined) do |_node, props|
|
21
|
-
props
|
22
|
-
end
|
16
|
+
return props unless node.respond_to?(:children)
|
23
17
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
on(:pipeline) do |node, props|
|
29
|
-
visit(node.type, props)
|
18
|
+
node.children.reduce(props) do |acc, child|
|
19
|
+
visit(child, acc)
|
20
|
+
end
|
30
21
|
end
|
31
22
|
|
32
23
|
on(:step) do |node, props|
|
@@ -42,17 +33,12 @@ module Plumb
|
|
42
33
|
props.merge(match: node, type:)
|
43
34
|
end
|
44
35
|
|
45
|
-
on(:match) do |node, props|
|
46
|
-
visit(node.matcher, props)
|
47
|
-
end
|
48
|
-
|
49
36
|
on(:hash) do |_node, props|
|
50
37
|
props.merge(type: Hash)
|
51
38
|
end
|
52
39
|
|
53
40
|
on(:and) do |node, props|
|
54
|
-
left = visit(
|
55
|
-
right = visit(node.right)
|
41
|
+
left, right = node.children.map { |child| visit(child) }
|
56
42
|
type = right[:type] || left[:type]
|
57
43
|
props = props.merge(left).merge(right)
|
58
44
|
props = props.merge(type:) if type
|
@@ -60,7 +46,7 @@ module Plumb
|
|
60
46
|
end
|
61
47
|
|
62
48
|
on(:or) do |node, props|
|
63
|
-
child_metas =
|
49
|
+
child_metas = node.children.map { |child| visit(child) }
|
64
50
|
types = child_metas.map { |child| child[:type] }.flatten.compact
|
65
51
|
types = types.first if types.size == 1
|
66
52
|
child_metas.reduce(props) do |acc, child|
|
@@ -68,22 +54,16 @@ module Plumb
|
|
68
54
|
end.merge(type: types)
|
69
55
|
end
|
70
56
|
|
71
|
-
on(:value) do |node, props|
|
72
|
-
visit(node.value, props)
|
73
|
-
end
|
74
|
-
|
75
|
-
on(:transform) do |node, props|
|
76
|
-
props.merge(type: node.target_type)
|
77
|
-
end
|
78
|
-
|
79
57
|
on(:static) do |node, props|
|
80
|
-
|
58
|
+
value = node.children[0]
|
59
|
+
type = value.is_a?(Class) ? value : value.class
|
60
|
+
props.merge(static: value, type:)
|
81
61
|
end
|
82
62
|
|
83
|
-
on(:
|
84
|
-
|
85
|
-
|
86
|
-
|
63
|
+
on(:policy) do |node, props|
|
64
|
+
props = visit(node.children[0], props)
|
65
|
+
props = props.merge(node.policy_name => node.arg) unless node.arg == Plumb::Undefined
|
66
|
+
props
|
87
67
|
end
|
88
68
|
|
89
69
|
on(:boolean) do |_node, props|
|
@@ -98,10 +78,6 @@ module Plumb
|
|
98
78
|
props.merge(type: Hash)
|
99
79
|
end
|
100
80
|
|
101
|
-
on(:build) do |node, props|
|
102
|
-
visit(node.type, props)
|
103
|
-
end
|
104
|
-
|
105
81
|
on(:array) do |_node, props|
|
106
82
|
props.merge(type: Array)
|
107
83
|
end
|
@@ -117,5 +93,9 @@ module Plumb
|
|
117
93
|
on(:tagged_hash) do |_node, props|
|
118
94
|
props.merge(type: Hash)
|
119
95
|
end
|
96
|
+
|
97
|
+
on(Proc) do |_node, props|
|
98
|
+
props
|
99
|
+
end
|
120
100
|
end
|
121
101
|
end
|
data/lib/plumb/not.rb
CHANGED
@@ -1,16 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'plumb/
|
3
|
+
require 'plumb/composable'
|
4
4
|
|
5
5
|
module Plumb
|
6
6
|
class Not
|
7
|
-
include
|
7
|
+
include Composable
|
8
8
|
|
9
|
-
attr_reader :
|
9
|
+
attr_reader :children, :errors
|
10
10
|
|
11
11
|
def initialize(step, errors: nil)
|
12
12
|
@step = step
|
13
13
|
@errors = errors
|
14
|
+
@children = [step].freeze
|
14
15
|
freeze
|
15
16
|
end
|
16
17
|
|
data/lib/plumb/or.rb
CHANGED
@@ -1,16 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'plumb/
|
3
|
+
require 'plumb/composable'
|
4
4
|
|
5
5
|
module Plumb
|
6
6
|
class Or
|
7
|
-
include
|
7
|
+
include Composable
|
8
8
|
|
9
|
-
attr_reader :
|
9
|
+
attr_reader :children
|
10
10
|
|
11
11
|
def initialize(left, right)
|
12
12
|
@left = left
|
13
13
|
@right = right
|
14
|
+
@children = [left, right].freeze
|
14
15
|
freeze
|
15
16
|
end
|
16
17
|
|
@@ -23,7 +24,12 @@ module Plumb
|
|
23
24
|
return left_result if left_result.valid?
|
24
25
|
|
25
26
|
right_result = @right.call(result)
|
26
|
-
right_result.valid?
|
27
|
+
if right_result.valid?
|
28
|
+
right_result
|
29
|
+
else
|
30
|
+
right_result.invalid(errors: [left_result.errors,
|
31
|
+
right_result.errors].flatten)
|
32
|
+
end
|
27
33
|
end
|
28
34
|
end
|
29
35
|
end
|
data/lib/plumb/pipeline.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'plumb/
|
3
|
+
require 'plumb/composable'
|
4
4
|
|
5
5
|
module Plumb
|
6
6
|
class Pipeline
|
7
|
-
include
|
7
|
+
include Composable
|
8
8
|
|
9
9
|
class AroundStep
|
10
|
-
include
|
10
|
+
include Composable
|
11
11
|
|
12
12
|
def initialize(step, block)
|
13
13
|
@step = step
|
@@ -19,10 +19,11 @@ module Plumb
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
-
attr_reader :
|
22
|
+
attr_reader :children
|
23
23
|
|
24
24
|
def initialize(type = Types::Any, &setup)
|
25
25
|
@type = type
|
26
|
+
@children = [type].freeze
|
26
27
|
@around_blocks = []
|
27
28
|
return unless block_given?
|
28
29
|
|
@@ -38,7 +39,7 @@ module Plumb
|
|
38
39
|
callable ||= block
|
39
40
|
unless is_a_step?(callable)
|
40
41
|
raise ArgumentError,
|
41
|
-
|
42
|
+
"#step expects an interface #call(Result) Result, but got #{callable.inspect}"
|
42
43
|
end
|
43
44
|
|
44
45
|
callable = @around_blocks.reduce(callable) { |cl, bl| AroundStep.new(cl, bl) } if @around_blocks.any?
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'plumb/policies'
|
4
|
+
|
5
|
+
module Plumb
|
6
|
+
# A policy registry for Plumb
|
7
|
+
# It holds and gets registered policies.
|
8
|
+
# Policies are callable objects that act as factories for type compositions.
|
9
|
+
class Policies
|
10
|
+
UnknownPolicyError = Class.new(StandardError)
|
11
|
+
MethodAlreadyDefinedError = Class.new(StandardError)
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@policies = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
# Register a policy for all or specific outpyt types.
|
18
|
+
# Example for a policy that works for all types:
|
19
|
+
# #register(Object, :my_policy, ->(node, arg) { ... })
|
20
|
+
# Example for a policy that works for a specific type:
|
21
|
+
# #register(String, :my_policy, ->(node, arg) { ... })
|
22
|
+
# Example for a policy that works for a specific interface:
|
23
|
+
# #register(:size, :my_policy, ->(node, arg) { ... })
|
24
|
+
#
|
25
|
+
# The policy callable takes the step it is applied to, a policy argument (if any) and a policy block (if any).
|
26
|
+
# Example for a policy #default(default_value = Undefined) { 'some-default-value' }
|
27
|
+
# policy = proc do |type, default_value = Undefined, &block|
|
28
|
+
# type | (Plumb::Types::Undefined >> Plumb::Types::Static[default_value])
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# @param for_type [Class, Symbol] the type the policy is for.
|
32
|
+
# @param name [Symbol] the name of the policy.
|
33
|
+
# @param policy [Proc] the policy to register.
|
34
|
+
def register(for_type, name, policy)
|
35
|
+
@policies[name] ||= {}
|
36
|
+
@policies[name][for_type] = policy
|
37
|
+
end
|
38
|
+
|
39
|
+
# Get a policy for a given type.
|
40
|
+
# @param types [Array<Class>] the types
|
41
|
+
# @param name [Symbol] the policy name
|
42
|
+
# @return [#call] the policy callable
|
43
|
+
# @raise [UnknownPolicyError] if the policy is not registered for the given types
|
44
|
+
def get(types, name)
|
45
|
+
if (pol = resolve_shared_policy(types, name))
|
46
|
+
pol
|
47
|
+
elsif (pol = @policies.dig(name, Object))
|
48
|
+
raise UnknownPolicyError, "Unknown policy #{name} for #{types.inspect}" unless pol
|
49
|
+
|
50
|
+
pol
|
51
|
+
else
|
52
|
+
raise UnknownPolicyError, "Unknown or incompatible policy #{name} for #{types.inspect}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def resolve_shared_policy(types, name)
|
59
|
+
pols = types.map do |type|
|
60
|
+
resolve_policy(type, name)
|
61
|
+
end.uniq
|
62
|
+
pols.size == 1 ? pols.first : nil
|
63
|
+
end
|
64
|
+
|
65
|
+
def resolve_policy(type, name)
|
66
|
+
policies = @policies[name]
|
67
|
+
return nil unless policies
|
68
|
+
|
69
|
+
# { Object => policy1, String => policy2, size: policy3 }
|
70
|
+
#
|
71
|
+
policies.find do |for_type, _pol|
|
72
|
+
case for_type
|
73
|
+
when Symbol # :size
|
74
|
+
type.instance_methods.include?(for_type)
|
75
|
+
when Class # String
|
76
|
+
for_type == type
|
77
|
+
end
|
78
|
+
end&.last
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/lib/plumb/policy.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'plumb/composable'
|
4
|
+
|
5
|
+
module Plumb
|
6
|
+
# Wrap a policy composition ("step") in a Policy object.
|
7
|
+
# So that visitors such as JSONSchema and Metadata visitors
|
8
|
+
# can define dedicated handlers for policies, if they need to.
|
9
|
+
class Policy
|
10
|
+
include Composable
|
11
|
+
|
12
|
+
attr_reader :policy_name, :arg, :children
|
13
|
+
|
14
|
+
# @param policy_name [Symbol]
|
15
|
+
# @param arg [Object, nil] the argument to the policy, if any.
|
16
|
+
# @param step [Step] the step composition wrapped by this policy.
|
17
|
+
def initialize(policy_name, arg, step)
|
18
|
+
@policy_name = policy_name
|
19
|
+
@arg = arg
|
20
|
+
@step = step
|
21
|
+
@children = [step].freeze
|
22
|
+
freeze
|
23
|
+
end
|
24
|
+
|
25
|
+
def ==(other)
|
26
|
+
other.is_a?(self.class) &&
|
27
|
+
policy_name == other.policy_name &&
|
28
|
+
arg == other.arg
|
29
|
+
end
|
30
|
+
|
31
|
+
# The standard Step interface.
|
32
|
+
# @param result [Result::Valid]
|
33
|
+
# @return [Result::Valid, Result::Invalid]
|
34
|
+
def call(result) = @step.call(result)
|
35
|
+
|
36
|
+
private def _inspect = @step.inspect
|
37
|
+
end
|
38
|
+
end
|