plumb 0.0.7 → 0.0.8

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 82cbbcfabbe2240d11a1957c7d06f59e314d8013c1459ecdd6339404f0c3208e
4
- data.tar.gz: a17828a33c296ba50bffbb629a806f732bc3a9f73d974617e4526bfd74065d42
3
+ metadata.gz: 7e5d0ac088968506a61d63da75f4010b050579c873d79604a25dfe0b133d3726
4
+ data.tar.gz: d559c1a3964544184258043e14d0bf9e3b41ff743a8117eccee09be2c22b084a
5
5
  SHA512:
6
- metadata.gz: 28ce9c69dcfa1f5d1129745301164400211c1df5707b851a2457f4361b59ed0ad73472851ae2993267f3d74389a6477ded4a60ce3eaed70472c041fad03df7bd
7
- data.tar.gz: 45e8f649c646b3236c6e7e5c3c86ed7493b88fc544585e5dc168675c69660b7ca217b5508a834b2dfa1820f97ca87b4edd9b988d17effb82697da28273545e2d
6
+ metadata.gz: 1207b06d90baa9833cf39737f2697fb6daf9423d94ca531a99307de561b70415bb6c9ec7fb2394af6c1f8b3f86015996ebfde5db6dc3fa2aadd5284fb1867cb2
7
+ data.tar.gz: 19131a0a289b8c5082ceb3a9cd716cdabb2d12466bba76ecc5d20c2efd6db3fbb9c04d13c8706996225189de50d81c5d8325fc5527ecc8395c492430fd17393d
data/README.md CHANGED
@@ -294,6 +294,22 @@ NotEmail.parse('hello') # "hello"
294
294
  NotEmail.parse('hello@server.com') # error
295
295
  ```
296
296
 
297
+ `#not` can also be given a type as argument, which might read better:
298
+
299
+ ```ruby
300
+ Types::Any.not(nil)
301
+ Types::Any.not(Types::Email)
302
+ ```
303
+
304
+ Finally, you can use `Types::Not` for the same effect.
305
+
306
+ ```ruby
307
+ NotNil = Types::Not[nil]
308
+ NotNil.parse(1) # 1
309
+ NotNil.parse('hello') # 'hello'
310
+ NotNil.parse(nil) # error
311
+ ```
312
+
297
313
  #### `#options`
298
314
 
299
315
  Sets allowed options for value.
@@ -506,7 +522,7 @@ So, normally you'd only use this attached to primitive types without further pro
506
522
  Passing a proc will evaluate the proc on every invocation. Use this for generated values.
507
523
 
508
524
  ```ruby
509
- random_number = Types::Numeric.static { rand }
525
+ random_number = Types::Numeric.generate { rand }
510
526
  random_number.parse # 0.32332
511
527
  random_number.parse('foo') # 0.54322 etc
512
528
  ```
@@ -514,7 +530,7 @@ random_number.parse('foo') # 0.54322 etc
514
530
  Note that the type of generated value must match the initial step's type, validated at invocation.
515
531
 
516
532
  ```ruby
517
- random_number = Types::String.static { rand } # this won't raise an error here
533
+ random_number = Types::String.generate { rand } # this won't raise an error here
518
534
  random_number.parse # raises Plumb::ParseError because `rand` is not a String
519
535
  ```
520
536
 
@@ -1200,6 +1216,30 @@ Payload = Types::Hash[
1200
1216
  ]
1201
1217
  ```
1202
1218
 
1219
+ #### Attribute writers
1220
+
1221
+ By default `Types::Data` classes are inmutable, but you can define attribute writers to allow for mutation using the `writer: true` option.
1222
+
1223
+ ```ruby
1224
+ class DBConfig < Types::Data
1225
+ attribute :host, Types::String.default('localhost'), writer: true
1226
+ end
1227
+
1228
+ class Config < Types::Data
1229
+ attribute :host, Types::Forms::URI::HTTP, writer: true
1230
+ attribute :port, Types::Integer.default(80), writer: true
1231
+
1232
+ # Nested structs can have writers too
1233
+ attribute :db, DBConfig.default(DBConfig.new)
1234
+ end
1235
+
1236
+ config = Config.new
1237
+ config.host = 'http://localhost'
1238
+ config.db.host = 'db.local'
1239
+ config.valid? # true
1240
+ config.errors # {}
1241
+ ```
1242
+
1203
1243
  #### Recursive struct definitions
1204
1244
 
1205
1245
  You can use `#defer`. See [recursive types](#recursive-types).
@@ -117,6 +117,7 @@ module Plumb
117
117
 
118
118
  def initialize(attrs = {})
119
119
  assign_attributes(attrs)
120
+ freeze
120
121
  end
121
122
 
122
123
  def ==(other)
@@ -140,13 +141,18 @@ module Plumb
140
141
 
141
142
  # @return [Hash]
142
143
  def to_h
143
- attributes.transform_values do |value|
144
- case value
145
- when ::Array
146
- value.map { |v| v.respond_to?(:to_h) ? v.to_h : v }
147
- else
148
- value.respond_to?(:to_h) ? value.to_h : value
149
- end
144
+ self.class._schema._schema.keys.each.with_object({}) do |key, memo|
145
+ key = key.to_sym
146
+ value = attributes[key]
147
+ val = case value
148
+ when ::Array
149
+ value.map { |v| v.respond_to?(:to_h) ? v.to_h : v }
150
+ when ::NilClass
151
+ nil
152
+ else
153
+ value.respond_to?(:to_h) ? value.to_h : value
154
+ end
155
+ memo[key] = val
150
156
  end
151
157
  end
152
158
 
@@ -158,7 +164,7 @@ module Plumb
158
164
  def assign_attributes(attrs = BLANK_HASH)
159
165
  raise ArgumentError, 'Must be a Hash of attributes' unless attrs.respond_to?(:to_h)
160
166
 
161
- @errors = BLANK_HASH
167
+ @errors = {}
162
168
  result = self.class._schema.resolve(attrs.to_h)
163
169
  @attributes = prepare_attributes(result.value)
164
170
  @errors = result.errors unless result.valid?
@@ -211,7 +217,7 @@ module Plumb
211
217
  # attribute(:friends, Types::Array[Person])
212
218
  # attribute(:friends, [Person])
213
219
  #
214
- def attribute(name, type = Types::Any, &block)
220
+ def attribute(name, type = Types::Any, writer: false, &block)
215
221
  key = Key.wrap(name)
216
222
  name = key.to_sym
217
223
  type = Composable.wrap(type)
@@ -234,13 +240,30 @@ module Plumb
234
240
  end
235
241
 
236
242
  @_schema = _schema + { key => type }
237
- __plumb_define_attribute_method__(name)
243
+ __plumb_define_attribute_reader_method__(name)
244
+ return name unless writer
245
+
246
+ __plumb_define_attribute_writer_method__(name)
238
247
  end
239
248
 
240
- def __plumb_define_attribute_method__(name)
249
+ def __plumb_define_attribute_reader_method__(name)
241
250
  define_method(name) { @attributes[name] }
242
251
  end
243
252
 
253
+ def __plumb_define_attribute_writer_method__(name)
254
+ define_method("#{name}=") do |value|
255
+ type = self.class._schema.at_key(name)
256
+ result = type.resolve(value)
257
+ @attributes[name] = result.value
258
+ if result.valid?
259
+ @errors.delete(name)
260
+ else
261
+ @errors.merge!(name => result.errors)
262
+ end
263
+ result.value
264
+ end
265
+ end
266
+
244
267
  def attribute?(name, *args, &block)
245
268
  attribute(Key.new(name, optional: true), *args, &block)
246
269
  end
data/lib/plumb/not.rb CHANGED
@@ -8,13 +8,19 @@ module Plumb
8
8
 
9
9
  attr_reader :children, :errors
10
10
 
11
- def initialize(step, errors: nil)
12
- @step = step
13
- @errors = errors
11
+ def initialize(step = nil, errors: nil)
12
+ @step = Composable.wrap(step)
13
+ @errors = errors || "must not be #{step.inspect}"
14
14
  @children = [step].freeze
15
15
  freeze
16
16
  end
17
17
 
18
+ # @param step [Object]
19
+ # @return [Not]
20
+ def [](step)
21
+ self.class.new(step)
22
+ end
23
+
18
24
  private def _inspect
19
25
  %(Not(#{@step.inspect}))
20
26
  end
data/lib/plumb/types.rb CHANGED
@@ -159,6 +159,7 @@ module Plumb
159
159
  Stream = StreamClass.new
160
160
  Tuple = TupleClass.new
161
161
  Hash = HashClass.new
162
+ Not = Plumb::Not.new
162
163
  Interface = InterfaceClass.new
163
164
  Email = String[URI::MailTo::EMAIL_REGEXP].as_node(:email)
164
165
  Date = Any[::Date]
data/lib/plumb/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Plumb
4
- VERSION = '0.0.7'
4
+ VERSION = '0.0.8'
5
5
  end
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.7
4
+ version: 0.0.8
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-25 00:00:00.000000000 Z
11
+ date: 2024-10-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bigdecimal
@@ -115,7 +115,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
115
115
  - !ruby/object:Gem::Version
116
116
  version: '0'
117
117
  requirements: []
118
- rubygems_version: 3.5.11
118
+ rubygems_version: 3.5.21
119
119
  signing_key:
120
120
  specification_version: 4
121
121
  summary: Data validation and transformation library.