plumb 0.0.15 → 0.0.17
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/.rubocop.yml +7 -1
- data/README.md +49 -11
- data/Rakefile +2 -0
- data/examples/concurrent_downloads.rb +1 -1
- data/examples/csv_stream.rb +1 -1
- data/lib/plumb/composable.rb +9 -3
- data/lib/plumb/interface_class.rb +28 -0
- data/lib/plumb/json_schema_visitor.rb +7 -0
- data/lib/plumb/pipeline.rb +1 -2
- data/lib/plumb/schema.rb +2 -2
- data/lib/plumb/version.rb +1 -1
- metadata +7 -9
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 24a6b9a58d29c5aa9e443586d0d529aaf79fcf91a5ee724366d9153174c73fa9
|
|
4
|
+
data.tar.gz: c6200d6bc762aecf985e359c5326c6014003950f998406dabbfaa9c7aca8fc10
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d683a2a89fc717f406efcc16b884cafa10dafa148be86ffc1810a377b4f065e1e40e0d21719fe70752e67d5c4eef86843076fa9ffd1513a2d345f666b705577f
|
|
7
|
+
data.tar.gz: 5788b7b83dfb2c342a9c59f1080b30e3db93f9ff7b3c3f6eda0060021275ae648406559bb9bbee96fe204463a3715571e7e042401771e98c9836f5d5306a4087
|
data/.rubocop.yml
CHANGED
data/README.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
**This library is work in progress!**
|
|
4
4
|
|
|
5
|
-
Composable data validation, coercion and processing in Ruby. Takes over from https://github.com/ismasan/parametric
|
|
5
|
+
Composable data validation, coercion and processing in Ruby. Takes over from [https://github.com/ismasan/parametric](https://github.com/ismasan/parametric)
|
|
6
6
|
|
|
7
|
-
This library takes ideas from the excellent https://dry-rb.org ecosystem, with some of the features offered by Dry-Types, Dry-Schema, Dry-Struct. However, I'm aiming at a subset of the functionality with a (hopefully) smaller API surface and fewer concepts, focusing on lessons learned after using Parametric in production for many years.
|
|
7
|
+
This library takes ideas from the excellent [https://dry-rb.org](https://dry-rb.org) ecosystem, with some of the features offered by Dry-Types, Dry-Schema, Dry-Struct. However, I'm aiming at a subset of the functionality with a (hopefully) smaller API surface and fewer concepts, focusing on lessons learned after using Parametric in production for many years.
|
|
8
8
|
|
|
9
9
|
If you're after raw performance and versatility I strongly recommend you use the Dry gems.
|
|
10
10
|
|
|
@@ -135,7 +135,7 @@ joe = User.parse({ name: 'Joe', email: 'joe@email.com', age: 20}) # returns vali
|
|
|
135
135
|
Users.parse([joe]) # returns valid array of user hashes
|
|
136
136
|
```
|
|
137
137
|
|
|
138
|
-
More about [Types::Hash](#typeshash) and [Types::Array](#typesarray). There's also [tuples](#typestuple), [hash maps](#
|
|
138
|
+
More about [Types::Hash](#typeshash) and [Types::Array](#typesarray). There's also [tuples](#typestuple), [hash maps](#maps), [data structs](#typesdata) and [streams](#typesstream), and it's possible to create your own composite types.
|
|
139
139
|
|
|
140
140
|
### Type composition
|
|
141
141
|
|
|
@@ -329,26 +329,26 @@ type.resolve(['a', 'a', 'b']) # Valid
|
|
|
329
329
|
type.resolve(['a', 'x', 'b']) # Failure
|
|
330
330
|
```
|
|
331
331
|
|
|
332
|
-
### `#
|
|
332
|
+
### `#where`
|
|
333
333
|
|
|
334
|
-
The `#
|
|
334
|
+
The `#where` helper matches attributes of the object with values, using `#===`.
|
|
335
335
|
|
|
336
336
|
```ruby
|
|
337
|
-
LimitedArray = Types::Array[String].
|
|
338
|
-
LimitedString = Types::String.
|
|
339
|
-
LimitedSet = Types::Any[Set].
|
|
337
|
+
LimitedArray = Types::Array[String].where(size: 10)
|
|
338
|
+
LimitedString = Types::String.where(size: 10)
|
|
339
|
+
LimitedSet = Types::Any[Set].where(size: 10)
|
|
340
340
|
```
|
|
341
341
|
|
|
342
342
|
The size is matched via `#===`, so ranges also work.
|
|
343
343
|
|
|
344
344
|
```ruby
|
|
345
|
-
Password = Types::String.
|
|
345
|
+
Password = Types::String.where(bytesize: 10..20)
|
|
346
346
|
```
|
|
347
347
|
|
|
348
348
|
The helper accepts multiple attribute/value pairs
|
|
349
349
|
|
|
350
350
|
```ruby
|
|
351
|
-
JoeBloggs = Types::Any[User].
|
|
351
|
+
JoeBloggs = Types::Any[User].where(first_name: 'Joe', last_name: 'Bloggs')
|
|
352
352
|
```
|
|
353
353
|
|
|
354
354
|
#### `#transform`
|
|
@@ -669,6 +669,40 @@ when Readable
|
|
|
669
669
|
end
|
|
670
670
|
```
|
|
671
671
|
|
|
672
|
+
Or pattern matching
|
|
673
|
+
|
|
674
|
+
```ruby
|
|
675
|
+
case args
|
|
676
|
+
in [Iterable => list, String => id]
|
|
677
|
+
# etc
|
|
678
|
+
in [Resolvable => r]
|
|
679
|
+
# etc
|
|
680
|
+
end
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
#### Merging interfaces
|
|
684
|
+
|
|
685
|
+
Use the `+` operator to merge two interfaces into a new one that must support both sets of method names.
|
|
686
|
+
|
|
687
|
+
```ruby
|
|
688
|
+
Iterable = Types::Interface[:each, :map]
|
|
689
|
+
Countable = Types::Interface[:size]
|
|
690
|
+
# This one expects objects with methods :each, :map and :size
|
|
691
|
+
CountableIterable = Iterable + Countable
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
#### Intersecting interfaces
|
|
695
|
+
|
|
696
|
+
Use the `&` operator to produce a new interface with the intersection of method names
|
|
697
|
+
|
|
698
|
+
```ruby
|
|
699
|
+
I1 = Types::Interface[:a, :b, :c]
|
|
700
|
+
I2 = Types::Interface[:b, :c, :d]
|
|
701
|
+
# This one expects methods :b and :c
|
|
702
|
+
I3 = Types::Interface[:b, :c]
|
|
703
|
+
```
|
|
704
|
+
|
|
705
|
+
|
|
672
706
|
TODO: make this a bit more advanced. Check for method arity.
|
|
673
707
|
|
|
674
708
|
### `Types::Hash`
|
|
@@ -1039,7 +1073,7 @@ end
|
|
|
1039
1073
|
|
|
1040
1074
|
### Types::Data
|
|
1041
1075
|
|
|
1042
|
-
`Types::Data` provides a superclass to define **
|
|
1076
|
+
`Types::Data` provides a superclass to define **immutable** structs or value objects with typed / coercible attributes.
|
|
1043
1077
|
|
|
1044
1078
|
#### `[]` Syntax
|
|
1045
1079
|
|
|
@@ -1816,3 +1850,7 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/ismasa
|
|
|
1816
1850
|
## License
|
|
1817
1851
|
|
|
1818
1852
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
1853
|
+
|
|
1854
|
+
## Credits
|
|
1855
|
+
|
|
1856
|
+
Created by [Ismael Celis](https://ismaelcelis.com)
|
data/Rakefile
CHANGED
|
@@ -54,7 +54,7 @@ module Types
|
|
|
54
54
|
image = result.value
|
|
55
55
|
path = path_for(image.url)
|
|
56
56
|
File.open(path, 'wb') { |f| f.write(image.io.read) }
|
|
57
|
-
result.valid image.
|
|
57
|
+
result.valid image.where(url: path, io: File.new(path))
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
def path_for(url)
|
data/examples/csv_stream.rb
CHANGED
|
@@ -22,7 +22,7 @@ module Types
|
|
|
22
22
|
.transform(::Enumerator, &:each)
|
|
23
23
|
|
|
24
24
|
# Turn a string file path into a CSV stream
|
|
25
|
-
# ex. csv_enum =
|
|
25
|
+
# ex. csv_enum = StringToCSV.parse('./files/data.csv') #=> Enumerator
|
|
26
26
|
StringToCSV = OpenFile >> FileToCSV
|
|
27
27
|
end
|
|
28
28
|
|
data/lib/plumb/composable.rb
CHANGED
|
@@ -311,16 +311,22 @@ module Plumb
|
|
|
311
311
|
|
|
312
312
|
# Check attributes of an object against values, using #===
|
|
313
313
|
# @example
|
|
314
|
-
# type = Types::Array.
|
|
315
|
-
# type = Types::String.
|
|
314
|
+
# type = Types::Array.where(size: 1..10)
|
|
315
|
+
# type = Types::String.where(bytesize: 1..10)
|
|
316
316
|
#
|
|
317
317
|
# @param attrs [Hash]
|
|
318
|
-
def
|
|
318
|
+
def where(attrs)
|
|
319
319
|
attrs.reduce(self) do |t, (name, value)|
|
|
320
320
|
t >> AttributeValueMatch.new(t, name, value)
|
|
321
321
|
end
|
|
322
322
|
end
|
|
323
323
|
|
|
324
|
+
# @deprecated User {#where} instead
|
|
325
|
+
def with(...)
|
|
326
|
+
warn 'Composable#with() is deprecated. Use #where() instead. #with is reserved to make copies of Data structs'
|
|
327
|
+
where(...)
|
|
328
|
+
end
|
|
329
|
+
|
|
324
330
|
# Register a policy for this step.
|
|
325
331
|
# Mode 1.a: #policy(:name, arg) a single policy with an argument
|
|
326
332
|
# Mode 1.b: #policy(:name) a single policy without an argument
|
|
@@ -28,6 +28,34 @@ module Plumb
|
|
|
28
28
|
|
|
29
29
|
alias [] of
|
|
30
30
|
|
|
31
|
+
# Merge two interfaces into a new one with the method names of both
|
|
32
|
+
# @example
|
|
33
|
+
# i1 = Types::Interface[:foo]
|
|
34
|
+
# i2 = Types::Interface[:bar, :lol]
|
|
35
|
+
# i3 = i1 + i2 # expects objects with methods :foo, :bar, :lol
|
|
36
|
+
#
|
|
37
|
+
# @param other [InterfaceClass]
|
|
38
|
+
# @return [InterfaceClass]
|
|
39
|
+
def +(other)
|
|
40
|
+
raise ArgumentError, "expected another Types::Interface, but got #{other.inspect}" unless other.is_a?(self.class)
|
|
41
|
+
|
|
42
|
+
self.class.new((method_names + other.method_names).uniq)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Produce a new Interface with the intersection of two interfaces
|
|
46
|
+
# @example
|
|
47
|
+
# i1 = Types::Interface[:foo, :bar]
|
|
48
|
+
# i2 = Types::Interface[:bar, :lol]
|
|
49
|
+
# i3 = i1 + i2 # expects objects with methods :bar
|
|
50
|
+
#
|
|
51
|
+
# @param other [InterfaceClass]
|
|
52
|
+
# @return [InterfaceClass]
|
|
53
|
+
def &(other)
|
|
54
|
+
raise ArgumentError, "expected another Types::Interface, but got #{other.inspect}" unless other.is_a?(self.class)
|
|
55
|
+
|
|
56
|
+
self.class.new(method_names & other.method_names)
|
|
57
|
+
end
|
|
58
|
+
|
|
31
59
|
def call(result)
|
|
32
60
|
obj = result.value
|
|
33
61
|
missing_methods = @method_names.reject { |m| obj.respond_to?(m) }
|
|
@@ -61,6 +61,13 @@ module Plumb
|
|
|
61
61
|
props
|
|
62
62
|
end
|
|
63
63
|
|
|
64
|
+
# Trying to visit the deferred could go into infinite recursion
|
|
65
|
+
# if a type is deferring to itself
|
|
66
|
+
# Not clear what deferred types would mean for JSON Schema anyway.
|
|
67
|
+
on(:deferred) do |node, props|
|
|
68
|
+
props
|
|
69
|
+
end
|
|
70
|
+
|
|
64
71
|
on(:hash) do |node, props|
|
|
65
72
|
props.merge(
|
|
66
73
|
TYPE => 'object',
|
data/lib/plumb/pipeline.rb
CHANGED
data/lib/plumb/schema.rb
CHANGED
data/lib/plumb/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
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.17
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ismael Celis
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: bigdecimal
|
|
@@ -38,7 +37,7 @@ dependencies:
|
|
|
38
37
|
- - ">="
|
|
39
38
|
- !ruby/object:Gem::Version
|
|
40
39
|
version: '0'
|
|
41
|
-
description: Data validation and
|
|
40
|
+
description: Data structures, validation, coercion and processing toolkit for Ruby
|
|
42
41
|
email:
|
|
43
42
|
- ismaelct@gmail.com
|
|
44
43
|
executables: []
|
|
@@ -97,11 +96,11 @@ files:
|
|
|
97
96
|
- lib/plumb/value_class.rb
|
|
98
97
|
- lib/plumb/version.rb
|
|
99
98
|
- lib/plumb/visitor_handlers.rb
|
|
100
|
-
homepage: https://github.
|
|
99
|
+
homepage: https://ismasan.github.io/plumb
|
|
101
100
|
licenses:
|
|
102
101
|
- MIT
|
|
103
|
-
metadata:
|
|
104
|
-
|
|
102
|
+
metadata:
|
|
103
|
+
source_code_uri: https://github.com/ismasan/plumb
|
|
105
104
|
rdoc_options: []
|
|
106
105
|
require_paths:
|
|
107
106
|
- lib
|
|
@@ -116,8 +115,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
116
115
|
- !ruby/object:Gem::Version
|
|
117
116
|
version: '0'
|
|
118
117
|
requirements: []
|
|
119
|
-
rubygems_version: 3.
|
|
120
|
-
signing_key:
|
|
118
|
+
rubygems_version: 3.6.9
|
|
121
119
|
specification_version: 4
|
|
122
120
|
summary: Data validation and transformation library.
|
|
123
121
|
test_files: []
|