plumb 0.0.13 → 0.0.15
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 +58 -1
- data/lib/plumb/attributes.rb +48 -4
- data/lib/plumb/hash_class.rb +1 -1
- data/lib/plumb/key.rb +15 -13
- data/lib/plumb/metadata.rb +1 -1
- data/lib/plumb/types.rb +16 -0
- 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: 104313a59a2f9187f582058b8f0f3c6dce43763079518d68fde511aef6112400
|
4
|
+
data.tar.gz: 4fc765c09d840145ebf5777cfd4e326f3705db5cfda8ee5324c5f6f4f81e0070
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2ca83f0ed1b4fbb2a83e11ff05bded28ed99550de9bfbec2d20d79943d886209c6b4cabeb2055857e24e60748642f0f7b541dcf5dc433107073772b085c5025a
|
7
|
+
data.tar.gz: 9d7b79dc0509f481730331bd3e65a1b85378d3609279b6c575ec8d77d8e786ccbe33b8dd54b0abfa826df06454793b235c1f8086a581c9a143359899cebb0318
|
data/README.md
CHANGED
@@ -232,6 +232,7 @@ You can see more use cases in [the examples directory](https://github.com/ismasa
|
|
232
232
|
* `Types::Numeric`
|
233
233
|
* `Types::String`
|
234
234
|
* `Types::Hash`
|
235
|
+
* `Types::SymbolizedHash`
|
235
236
|
* `Types::UUID::V4`
|
236
237
|
* `Types::Email`
|
237
238
|
* `Types::Date`
|
@@ -838,7 +839,19 @@ User.parse(name: 'Joe', age: 40) # => { name: 'Joe', age: 40 }
|
|
838
839
|
User.parse(name: 'Joe', age: 'nope') # => { name: 'Joe' }
|
839
840
|
```
|
840
841
|
|
841
|
-
###
|
842
|
+
### `Types::SymbolizedHash`
|
843
|
+
|
844
|
+
This type turns a hash's keys into symbols by calling `#to_sym` on them, and returning a new Hash.
|
845
|
+
|
846
|
+
```ruby
|
847
|
+
# Make sure to symbolize keys first
|
848
|
+
type = Types::SymbolizedHash > Types::Hash[name: String, age: Integer]
|
849
|
+
type.parse('name' => 'Joe', 'age' => 20) # {name: 'Joe', age: 20}
|
850
|
+
```
|
851
|
+
|
852
|
+
|
853
|
+
|
854
|
+
### maps
|
842
855
|
|
843
856
|
You can also use Hash syntax to define a hash map with specific types for all keys and values:
|
844
857
|
|
@@ -1177,7 +1190,51 @@ Using `attribute?` allows for optional attributes. If the attribute is not prese
|
|
1177
1190
|
attribute? :company, Company
|
1178
1191
|
```
|
1179
1192
|
|
1193
|
+
#### Before steps, symbolizing keys
|
1194
|
+
|
1195
|
+
The optional `.step` helper adds arbitrary Plumb steps to a Data constructor's internal pipeline.
|
1196
|
+
|
1197
|
+
This pipeline processes input data when initialising a Data instance.
|
1198
|
+
|
1199
|
+
This example adds the built-in `Types::SymbolizedHash` type to make sure struct inputs are symbolised before processing.
|
1200
|
+
|
1201
|
+
```ruby
|
1202
|
+
class Person < Types::Data
|
1203
|
+
step Types::SymbolizedHash
|
1204
|
+
|
1205
|
+
attribute :name, String
|
1206
|
+
attribute :age, Integer
|
1207
|
+
end
|
1208
|
+
|
1209
|
+
# String keys will be symbolised now
|
1210
|
+
person = Person.new('name' => 'Joe', 'age' => 40)
|
1211
|
+
person.name # 'Joe'
|
1212
|
+
person.to_h # => { name: 'Joe', age: 40 }
|
1213
|
+
```
|
1214
|
+
|
1215
|
+
Inline blocks can be registered as steps
|
1216
|
+
|
1217
|
+
```ruby
|
1218
|
+
class Person < Types::Data
|
1219
|
+
# upcase all values
|
1220
|
+
step do |r|
|
1221
|
+
upcased = r.value.transform_values(&:upcase)
|
1222
|
+
r.valid upcased
|
1223
|
+
end
|
1224
|
+
|
1225
|
+
attribute :name, String
|
1226
|
+
attribute :last_name, String
|
1227
|
+
end
|
1228
|
+
|
1229
|
+
person = Person.new(name: 'joe', last_name: 'bloggs')
|
1230
|
+
person.name # => 'JOE'
|
1231
|
+
person.last_name # => 'BLOGGS'
|
1232
|
+
```
|
1233
|
+
|
1234
|
+
A Data class steps are inherited to its child classes.
|
1235
|
+
|
1180
1236
|
#### Inheritance
|
1237
|
+
|
1181
1238
|
Data structs can inherit from other structs. This is useful for defining a base struct with common attributes.
|
1182
1239
|
|
1183
1240
|
```ruby
|
data/lib/plumb/attributes.rb
CHANGED
@@ -116,7 +116,7 @@ module Plumb
|
|
116
116
|
attr_reader :errors, :attributes
|
117
117
|
|
118
118
|
def initialize(attrs = {})
|
119
|
-
assign_attributes(attrs)
|
119
|
+
assign_attributes(self.class._pipeline.parse(attrs))
|
120
120
|
freeze
|
121
121
|
end
|
122
122
|
|
@@ -135,8 +135,8 @@ module Plumb
|
|
135
135
|
|
136
136
|
def inspect
|
137
137
|
%(#<#{self.class}:#{object_id} [#{valid? ? 'valid' : 'invalid'}] #{attributes.map do |k, v|
|
138
|
-
|
139
|
-
|
138
|
+
[k, v.inspect].join(':')
|
139
|
+
end.join(' ')}>)
|
140
140
|
end
|
141
141
|
|
142
142
|
# @return [Hash]
|
@@ -175,11 +175,53 @@ module Plumb
|
|
175
175
|
def prepare_attributes(attrs) = attrs
|
176
176
|
|
177
177
|
module ClassMethods
|
178
|
+
def _set_pipeline(pl)
|
179
|
+
@_pipeline = pl
|
180
|
+
end
|
181
|
+
|
182
|
+
def _pipeline
|
183
|
+
@_pipeline || Plumb::Types::Any
|
184
|
+
end
|
185
|
+
|
186
|
+
# Add a step to the processing pipeline that runs before attribute validation.
|
187
|
+
# This allows you to transform or validate the input data before it's assigned to attributes.
|
188
|
+
#
|
189
|
+
# @param st [Plumb::Step, nil] A step object to add to the pipeline
|
190
|
+
# @param block [Proc, nil] A block to use as a step (if st is nil)
|
191
|
+
# @return [Class] Returns self for method chaining
|
192
|
+
#
|
193
|
+
# @example Transform input before validation
|
194
|
+
# class Person
|
195
|
+
# include Plumb::Attributes
|
196
|
+
#
|
197
|
+
# step { |result| result.valid(result.value.transform_keys(&:to_sym)) }
|
198
|
+
# attribute :name, Types::String
|
199
|
+
# end
|
200
|
+
#
|
201
|
+
# @example Add custom validation
|
202
|
+
# class Person
|
203
|
+
# include Plumb::Attributes
|
204
|
+
#
|
205
|
+
# step do |result|
|
206
|
+
# if result.value[:name].nil?
|
207
|
+
# result.invalid(errors: 'Name is required')
|
208
|
+
# else
|
209
|
+
# result
|
210
|
+
# end
|
211
|
+
# end
|
212
|
+
# attribute :name, Types::String
|
213
|
+
# end
|
214
|
+
def step(st = nil, &block)
|
215
|
+
@_pipeline = _pipeline >> (st || block)
|
216
|
+
self
|
217
|
+
end
|
218
|
+
|
178
219
|
def _schema
|
179
220
|
@_schema ||= HashClass.new
|
180
221
|
end
|
181
222
|
|
182
223
|
def inherited(subclass)
|
224
|
+
subclass._set_pipeline _pipeline
|
183
225
|
_schema._schema.each do |key, type|
|
184
226
|
subclass.attribute(key, type)
|
185
227
|
end
|
@@ -220,7 +262,9 @@ module Plumb
|
|
220
262
|
# attribute(:friends, [Person])
|
221
263
|
#
|
222
264
|
def attribute(name, type = Types::Any, writer: false, &block)
|
223
|
-
|
265
|
+
# Key accepts String or Symbol, with optional '?' suffix for optional keys
|
266
|
+
# for Data structs, we always convert to Symbol keys
|
267
|
+
key = Key.wrap(name, symbolize: true)
|
224
268
|
name = key.to_sym
|
225
269
|
type = Composable.wrap(type)
|
226
270
|
|
data/lib/plumb/hash_class.rb
CHANGED
@@ -113,7 +113,7 @@ module Plumb
|
|
113
113
|
initial = {}
|
114
114
|
initial = initial.merge(input) if @inclusive
|
115
115
|
output = _schema.each.with_object(initial) do |(key, field), ret|
|
116
|
-
key_s = key.
|
116
|
+
key_s = key.to_key
|
117
117
|
if input.key?(key_s)
|
118
118
|
r = field.call(field_result.reset(input[key_s]))
|
119
119
|
errors[key_s] = r.errors unless r.valid?
|
data/lib/plumb/key.rb
CHANGED
@@ -2,28 +2,30 @@
|
|
2
2
|
|
3
3
|
module Plumb
|
4
4
|
class Key
|
5
|
-
OPTIONAL_EXP = /(\w+)(\?)?$/
|
5
|
+
# OPTIONAL_EXP = /(\w+)(\?)?$/
|
6
|
+
OPTIONAL_EXP = /(?<word>[A-Za-z0-9_$]+)(?<qmark>\?)?/
|
6
7
|
|
7
|
-
def self.wrap(key)
|
8
|
-
key.is_a?(Key) ? key : new(key)
|
8
|
+
def self.wrap(key, symbolize: false)
|
9
|
+
key.is_a?(Key) ? key : new(key, symbolize:)
|
9
10
|
end
|
10
11
|
|
11
|
-
attr_reader :to_sym, :node_name
|
12
|
+
attr_reader :to_key, :to_sym, :node_name
|
12
13
|
|
13
|
-
def initialize(key, optional: false)
|
14
|
-
|
15
|
-
match = OPTIONAL_EXP.match(
|
14
|
+
def initialize(key, optional: false, symbolize: false)
|
15
|
+
key_type = symbolize ? Symbol : key.class
|
16
|
+
match = OPTIONAL_EXP.match(key.to_s)
|
17
|
+
key = match[:word]
|
18
|
+
@to_key = key_type == Symbol ? key.to_sym : key
|
19
|
+
@to_sym = @to_key.to_sym
|
20
|
+
@optional = !match[:qmark].nil? ? true : optional
|
16
21
|
@node_name = :key
|
17
|
-
@key = match[1]
|
18
|
-
@to_sym = @key.to_sym
|
19
|
-
@optional = !match[2].nil? ? true : optional
|
20
22
|
freeze
|
21
23
|
end
|
22
24
|
|
23
|
-
def to_s = @
|
25
|
+
def to_s = @to_key.to_s
|
24
26
|
|
25
27
|
def hash
|
26
|
-
@
|
28
|
+
@to_key.hash
|
27
29
|
end
|
28
30
|
|
29
31
|
def eql?(other)
|
@@ -35,7 +37,7 @@ module Plumb
|
|
35
37
|
end
|
36
38
|
|
37
39
|
def inspect
|
38
|
-
"#{@
|
40
|
+
"#{@to_key}#{'?' if @optional}"
|
39
41
|
end
|
40
42
|
end
|
41
43
|
end
|
data/lib/plumb/metadata.rb
CHANGED
data/lib/plumb/types.rb
CHANGED
@@ -155,6 +155,22 @@ module Plumb
|
|
155
155
|
Date = Any[::Date]
|
156
156
|
Time = Any[::Time]
|
157
157
|
|
158
|
+
# A type that recursively converts string keys to symbols in nested hashes.
|
159
|
+
# This is commonly used for normalizing payload data in commands and events.
|
160
|
+
#
|
161
|
+
# @example Simple hash symbolization
|
162
|
+
# SymbolizedHash.parse({ 'name' => 'John' }) # => { name: 'John' }
|
163
|
+
# @example Nested hash symbolization
|
164
|
+
# SymbolizedHash.parse({ 'user' => { 'name' => 'John' } }) # => { user: { name: 'John' } }
|
165
|
+
# @example Mixed types preserved
|
166
|
+
# SymbolizedHash.parse({ 'count' => 1, 'active' => true }) # => { count: 1, active: true }
|
167
|
+
SymbolizedHash = Hash[
|
168
|
+
# String keys are converted to symbols, existing symbols are preserved
|
169
|
+
(Symbol | String.transform(::Symbol, &:to_sym)),
|
170
|
+
# Hash values are recursively symbolized, other types pass through unchanged
|
171
|
+
Any.defer { SymbolizedHash } | Any
|
172
|
+
]
|
173
|
+
|
158
174
|
module UUID
|
159
175
|
V4 = String[/\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/i].as_node(:uuid)
|
160
176
|
end
|
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.15
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ismael Celis
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-10-
|
11
|
+
date: 2025-10-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bigdecimal
|