plumb 0.0.6 → 0.0.7
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 +65 -0
- data/examples/event_registry.rb +1 -4
- data/lib/plumb/array_class.rb +8 -8
- data/lib/plumb/attributes.rb +3 -1
- data/lib/plumb/composable.rb +56 -17
- data/lib/plumb/hash_class.rb +1 -1
- data/lib/plumb/pipeline.rb +2 -2
- data/lib/plumb/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 82cbbcfabbe2240d11a1957c7d06f59e314d8013c1459ecdd6339404f0c3208e
|
4
|
+
data.tar.gz: a17828a33c296ba50bffbb629a806f732bc3a9f73d974617e4526bfd74065d42
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 28ce9c69dcfa1f5d1129745301164400211c1df5707b851a2457f4361b59ed0ad73472851ae2993267f3d74389a6477ded4a60ce3eaed70472c041fad03df7bd
|
7
|
+
data.tar.gz: 45e8f649c646b3236c6e7e5c3c86ed7493b88fc544585e5dc168675c69660b7ca217b5508a834b2dfa1820f97ca87b4edd9b988d17effb82697da28273545e2d
|
data/README.md
CHANGED
@@ -455,6 +455,71 @@ All scalar types support this:
|
|
455
455
|
ten = Types::Integer.value(10)
|
456
456
|
```
|
457
457
|
|
458
|
+
#### `#static`
|
459
|
+
|
460
|
+
A type that always returns a valid, static value, regardless of input.
|
461
|
+
|
462
|
+
```ruby
|
463
|
+
ten = Types::Integer.static(10)
|
464
|
+
ten.parse(10) # => 10
|
465
|
+
ten.parse(100) # => 10
|
466
|
+
ten.parse('hello') # => 10
|
467
|
+
ten.parse() # => 10
|
468
|
+
ten.metadata[:type] # => Integer
|
469
|
+
```
|
470
|
+
|
471
|
+
Useful for data structures where some fields shouldn't change. Example:
|
472
|
+
|
473
|
+
```ruby
|
474
|
+
CreateUserEvent = Types::Hash[
|
475
|
+
type: Types::String.static('CreateUser'),
|
476
|
+
name: String,
|
477
|
+
age: Integer
|
478
|
+
]
|
479
|
+
```
|
480
|
+
|
481
|
+
Note that the value must be of the same type as the starting step's target type.
|
482
|
+
|
483
|
+
```ruby
|
484
|
+
Types::Integer.static('nope') # raises ArgumentError
|
485
|
+
```
|
486
|
+
|
487
|
+
This usage is similar as using `Types::Static['hello']`directly.
|
488
|
+
|
489
|
+
This helper is shorthand for the following composition:
|
490
|
+
|
491
|
+
```ruby
|
492
|
+
Types::Static[value] >> step
|
493
|
+
```
|
494
|
+
|
495
|
+
This means that validations and coercions in the original step are still applied to the static value.
|
496
|
+
|
497
|
+
```ruby
|
498
|
+
ten = Types::Integer[100..].static(10)
|
499
|
+
ten.parse # => Plumb::ParseError "Must be within 100..."
|
500
|
+
```
|
501
|
+
|
502
|
+
So, normally you'd only use this attached to primitive types without further processing (but your use case may vary).
|
503
|
+
|
504
|
+
#### `#generate`
|
505
|
+
|
506
|
+
Passing a proc will evaluate the proc on every invocation. Use this for generated values.
|
507
|
+
|
508
|
+
```ruby
|
509
|
+
random_number = Types::Numeric.static { rand }
|
510
|
+
random_number.parse # 0.32332
|
511
|
+
random_number.parse('foo') # 0.54322 etc
|
512
|
+
```
|
513
|
+
|
514
|
+
Note that the type of generated value must match the initial step's type, validated at invocation.
|
515
|
+
|
516
|
+
```ruby
|
517
|
+
random_number = Types::String.static { rand } # this won't raise an error here
|
518
|
+
random_number.parse # raises Plumb::ParseError because `rand` is not a String
|
519
|
+
```
|
520
|
+
|
521
|
+
You can also pass any `#call() => Object` interface as a generator, instead of a proc.
|
522
|
+
|
458
523
|
#### `#metadata`
|
459
524
|
|
460
525
|
Add metadata to a type
|
data/examples/event_registry.rb
CHANGED
@@ -14,9 +14,6 @@ module Types
|
|
14
14
|
# Turn an ISO8601 string into a Time object
|
15
15
|
ISOTime = String.build(::Time, :parse).policy(:rescue, ArgumentError)
|
16
16
|
|
17
|
-
# A type that can be a Time object or an ISO8601 string >> Time
|
18
|
-
Time = Any[::Time] | ISOTime
|
19
|
-
|
20
17
|
# A UUID string, or generate a new one
|
21
18
|
AutoUUID = UUID::V4.default { SecureRandom.uuid }
|
22
19
|
end
|
@@ -60,7 +57,7 @@ class Event < Types::Data
|
|
60
57
|
attribute :id, Types::AutoUUID
|
61
58
|
attribute :stream_id, Types::String.present
|
62
59
|
attribute :type, Types::String
|
63
|
-
attribute(:created_at, Types::Time.default { ::Time.now })
|
60
|
+
attribute(:created_at, Types::Forms::Time.default { ::Time.now })
|
64
61
|
attribute? :causation_id, Types::UUID::V4
|
65
62
|
attribute? :correlation_id, Types::UUID::V4
|
66
63
|
attribute :payload, Types::Static[nil]
|
data/lib/plumb/array_class.rb
CHANGED
@@ -45,7 +45,7 @@ module Plumb
|
|
45
45
|
def call(result)
|
46
46
|
return result.invalid(errors: 'is not an Array') unless ::Array === result.value
|
47
47
|
|
48
|
-
values, errors = map_array_elements(result
|
48
|
+
values, errors = map_array_elements(result)
|
49
49
|
return result.valid(values) unless errors.any?
|
50
50
|
|
51
51
|
result.invalid(values, errors:)
|
@@ -59,14 +59,14 @@ module Plumb
|
|
59
59
|
%(Array[#{element_type}])
|
60
60
|
end
|
61
61
|
|
62
|
-
def map_array_elements(
|
62
|
+
def map_array_elements(result)
|
63
63
|
# Reuse the same result object for each element
|
64
64
|
# to decrease object allocation.
|
65
65
|
# Steps might return the same result instance, so we map the values directly
|
66
66
|
# separate from the errors.
|
67
|
-
element_result =
|
67
|
+
element_result = result.dup
|
68
68
|
errors = {}
|
69
|
-
values =
|
69
|
+
values = result.value.map.with_index do |e, idx|
|
70
70
|
re = element_type.call(element_result.reset(e))
|
71
71
|
errors[idx] = re.errors unless re.valid?
|
72
72
|
re.value
|
@@ -78,12 +78,12 @@ module Plumb
|
|
78
78
|
class ConcurrentArrayClass < self
|
79
79
|
private
|
80
80
|
|
81
|
-
def map_array_elements(
|
81
|
+
def map_array_elements(result)
|
82
82
|
errors = {}
|
83
83
|
|
84
|
-
values =
|
85
|
-
|
86
|
-
|
84
|
+
values = result.value
|
85
|
+
.map { |e| Concurrent::Future.execute { element_type.resolve(e) } }
|
86
|
+
.map.with_index do |f, idx|
|
87
87
|
re = f.value
|
88
88
|
errors[idx] = f.reason if f.rejected?
|
89
89
|
re.value
|
data/lib/plumb/attributes.rb
CHANGED
@@ -160,10 +160,12 @@ module Plumb
|
|
160
160
|
|
161
161
|
@errors = BLANK_HASH
|
162
162
|
result = self.class._schema.resolve(attrs.to_h)
|
163
|
-
@attributes = result.value
|
163
|
+
@attributes = prepare_attributes(result.value)
|
164
164
|
@errors = result.errors unless result.valid?
|
165
165
|
end
|
166
166
|
|
167
|
+
def prepare_attributes(attrs) = attrs
|
168
|
+
|
167
169
|
module ClassMethods
|
168
170
|
def _schema
|
169
171
|
@_schema ||= HashClass.new
|
data/lib/plumb/composable.rb
CHANGED
@@ -84,6 +84,26 @@ module Plumb
|
|
84
84
|
def node_name = self.class.name.split('::').last.to_sym
|
85
85
|
end
|
86
86
|
|
87
|
+
# Override #=== and #== for Composable instances.
|
88
|
+
# but only when included in classes, not extended.
|
89
|
+
module Equality
|
90
|
+
# `#===` equality. So that Plumb steps can be used in case statements and pattern matching.
|
91
|
+
# @param other [Object]
|
92
|
+
# @return [Boolean]
|
93
|
+
def ===(other)
|
94
|
+
case other
|
95
|
+
when Composable
|
96
|
+
other == self
|
97
|
+
else
|
98
|
+
resolve(other).valid?
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def ==(other)
|
103
|
+
other.is_a?(self.class) && other.respond_to?(:children) && other.children == children
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
87
107
|
# Composable mixes in composition methods to classes.
|
88
108
|
# such as #>>, #|, #not, and others.
|
89
109
|
# Any Composable class can participate in Plumb compositions.
|
@@ -95,6 +115,7 @@ module Plumb
|
|
95
115
|
# not extending classes with it.
|
96
116
|
def self.included(base)
|
97
117
|
base.send(:include, Naming)
|
118
|
+
base.send(:include, Equality)
|
98
119
|
end
|
99
120
|
|
100
121
|
# Wrap an object in a Composable instance.
|
@@ -304,22 +325,6 @@ module Plumb
|
|
304
325
|
end
|
305
326
|
end
|
306
327
|
|
307
|
-
# `#===` equality. So that Plumb steps can be used in case statements and pattern matching.
|
308
|
-
# @param other [Object]
|
309
|
-
# @return [Boolean]
|
310
|
-
def ===(other)
|
311
|
-
case other
|
312
|
-
when Composable
|
313
|
-
other == self
|
314
|
-
else
|
315
|
-
resolve(other).valid?
|
316
|
-
end
|
317
|
-
end
|
318
|
-
|
319
|
-
def ==(other)
|
320
|
-
other.is_a?(self.class) && other.respond_to?(:children) && other.children == children
|
321
|
-
end
|
322
|
-
|
323
328
|
# Visitors expect a #node_name and #children interface.
|
324
329
|
# @return [Array<Composable>]
|
325
330
|
def children = BLANK_ARRAY
|
@@ -341,6 +346,40 @@ module Plumb
|
|
341
346
|
self >> Build.new(cns, factory_method:, &block)
|
342
347
|
end
|
343
348
|
|
349
|
+
# Always return a static value, regardless of the input.
|
350
|
+
# @example
|
351
|
+
# type = Types::Integer.static(10)
|
352
|
+
# type.parse(10) # => 10
|
353
|
+
# type.parse(100) # => 10
|
354
|
+
# type.parse # => 10
|
355
|
+
#
|
356
|
+
# @param value [Object]
|
357
|
+
# @return [And]
|
358
|
+
def static(value)
|
359
|
+
my_type = Array(metadata[:type]).first
|
360
|
+
unless my_type.nil? || value.instance_of?(my_type)
|
361
|
+
raise ArgumentError,
|
362
|
+
"can't set a static #{value.class} value for a #{my_type} step"
|
363
|
+
end
|
364
|
+
|
365
|
+
StaticClass.new(value) >> self
|
366
|
+
end
|
367
|
+
|
368
|
+
# Return the output of a block or #call interface, regardless of input.
|
369
|
+
# The block will be called to get the value, on every invocation.
|
370
|
+
# @example
|
371
|
+
# now = Types::Integer.generate { Time.now.to_i }
|
372
|
+
#
|
373
|
+
# @param generator [#call, nil] a callable that will be applied to the value, or nil if block
|
374
|
+
# @param block [Proc] a block that will be applied to the value, or nil if callable
|
375
|
+
# @return [And]
|
376
|
+
def generate(generator = nil, &block)
|
377
|
+
generator ||= block
|
378
|
+
raise ArgumentError, 'expected a generator' unless generator.respond_to?(:call)
|
379
|
+
|
380
|
+
Step.new(->(r) { r.valid(generator.call) }, 'generator') >> self
|
381
|
+
end
|
382
|
+
|
344
383
|
# Build a Plumb::Pipeline with this object as the starting step.
|
345
384
|
# @example
|
346
385
|
# pipe = Types::Data[name: String].pipeline do |pl|
|
@@ -351,7 +390,7 @@ module Plumb
|
|
351
390
|
#
|
352
391
|
# @return [Pipeline]
|
353
392
|
def pipeline(&block)
|
354
|
-
Pipeline.new(self, &block)
|
393
|
+
Pipeline.new(type: self, &block)
|
355
394
|
end
|
356
395
|
|
357
396
|
def to_s
|
data/lib/plumb/hash_class.rb
CHANGED
@@ -109,7 +109,7 @@ module Plumb
|
|
109
109
|
|
110
110
|
input = result.value
|
111
111
|
errors = {}
|
112
|
-
field_result =
|
112
|
+
field_result = result.dup
|
113
113
|
initial = {}
|
114
114
|
initial = initial.merge(input) if @inclusive
|
115
115
|
output = _schema.each.with_object(initial) do |(key, field), ret|
|
data/lib/plumb/pipeline.rb
CHANGED
@@ -37,14 +37,14 @@ module Plumb
|
|
37
37
|
|
38
38
|
attr_reader :children
|
39
39
|
|
40
|
-
def initialize(type
|
40
|
+
def initialize(type: Types::Any, freeze_after: true, &setup)
|
41
41
|
@type = type
|
42
42
|
@children = [type].freeze
|
43
43
|
@around_blocks = self.class.around_blocks.dup
|
44
44
|
return unless block_given?
|
45
45
|
|
46
46
|
configure(&setup)
|
47
|
-
freeze
|
47
|
+
freeze if freeze_after
|
48
48
|
end
|
49
49
|
|
50
50
|
def call(result)
|
data/lib/plumb/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: plumb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ismael Celis
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-09-
|
11
|
+
date: 2024-09-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bigdecimal
|