plumb 0.0.17 → 0.2.0.beta.1
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 +834 -63
- data/bench/compare_dry_schema.rb +79 -0
- data/bench/compare_dry_types.rb +37 -0
- data/bench/compare_parametric_schema.rb +2 -80
- data/bench/dry_schema_hash.rb +103 -0
- data/bench/dry_types_hash.rb +125 -0
- data/bench/json_schema_profile.rb +107 -0
- data/bench/plumb_hash.rb +17 -11
- data/bench/results_allocations.rb +137 -0
- data/bench/sample_data.rb +78 -0
- data/examples/command_objects.rb +1 -1
- data/examples/concurrent_downloads.rb +16 -9
- data/examples/event_registry.rb +6 -1
- data/examples/weekdays.rb +1 -1
- data/lib/plumb/and.rb +63 -6
- data/lib/plumb/any_class.rb +12 -2
- data/lib/plumb/array_class.rb +64 -19
- data/lib/plumb/attribute_value_match.rb +41 -1
- data/lib/plumb/attributes.rb +62 -20
- data/lib/plumb/codec.rb +795 -0
- data/lib/plumb/composable.rb +463 -39
- data/lib/plumb/conjunction.rb +50 -0
- data/lib/plumb/constraint.rb +234 -0
- data/lib/plumb/covariant_fusion.rb +46 -0
- data/lib/plumb/decorator.rb +12 -22
- data/lib/plumb/deferred.rb +13 -5
- data/lib/plumb/disjunction.rb +112 -0
- data/lib/plumb/encoder.rb +207 -0
- data/lib/plumb/function.rb +347 -0
- data/lib/plumb/hash_class.rb +350 -38
- data/lib/plumb/hash_map.rb +62 -14
- data/lib/plumb/implementation.rb +247 -0
- data/lib/plumb/interface_class.rb +21 -2
- data/lib/plumb/intersection.rb +47 -0
- data/lib/plumb/json_schema_visitor.rb +255 -36
- data/lib/plumb/key.rb +63 -13
- data/lib/plumb/mermaid_visitor.rb +129 -0
- data/lib/plumb/metadata.rb +10 -1
- data/lib/plumb/metadata_visitor.rb +36 -34
- data/lib/plumb/never_class.rb +38 -0
- data/lib/plumb/node_mapper.rb +97 -0
- data/lib/plumb/not.rb +34 -2
- data/lib/plumb/optimizer.rb +444 -0
- data/lib/plumb/or.rb +25 -23
- data/lib/plumb/pipeline.rb +99 -11
- data/lib/plumb/policy.rb +17 -4
- data/lib/plumb/range_class.rb +46 -0
- data/lib/plumb/relation.rb +57 -0
- data/lib/plumb/result.rb +55 -23
- data/lib/plumb/semantic_matcher.rb +393 -0
- data/lib/plumb/static_class.rb +20 -1
- data/lib/plumb/stream_class.rb +32 -6
- data/lib/plumb/subtyping.rb +461 -0
- data/lib/plumb/tagged_hash.rb +45 -4
- data/lib/plumb/tuple_class.rb +21 -4
- data/lib/plumb/type_cache.rb +41 -0
- data/lib/plumb/type_registry.rb +71 -0
- data/lib/plumb/typed_step.rb +67 -0
- data/lib/plumb/types.rb +27 -43
- data/lib/plumb/union.rb +30 -0
- data/lib/plumb/value_class.rb +20 -1
- data/lib/plumb/version.rb +1 -1
- data/lib/plumb/visitor_handlers.rb +20 -4
- data/lib/plumb.rb +90 -3
- metadata +30 -8
- data/lib/plumb/build.rb +0 -22
- data/lib/plumb/match_class.rb +0 -42
- data/lib/plumb/schema.rb +0 -195
- data/lib/plumb/step.rb +0 -27
- data/lib/plumb/transform.rb +0 -26
data/lib/plumb/attributes.rb
CHANGED
|
@@ -113,6 +113,25 @@ module Plumb
|
|
|
113
113
|
base.define_singleton_method(:__plumb_struct_class__) { base }
|
|
114
114
|
end
|
|
115
115
|
|
|
116
|
+
# The struct class behind `node`, or nil. Structs appear in two shapes: a
|
|
117
|
+
# Plumb::Attributes class itself (Types::Data subclasses are Composable
|
|
118
|
+
# classes), or the Function that Composable.wrap builds around a plain
|
|
119
|
+
# `include Plumb::Attributes` class — the class is the wrapped callable, so
|
|
120
|
+
# it is reached via #fn (a Function's #children are its types). This is the
|
|
121
|
+
# one owner of that representation fact — used by #build_nested and Codec's
|
|
122
|
+
# rewriter.
|
|
123
|
+
#
|
|
124
|
+
# Keyed on Function#wraps_callable? rather than on the types the node declares:
|
|
125
|
+
# boundary absorption can move a neighbouring type into a wrapper's slot (see
|
|
126
|
+
# Function#absorb_input), and it wraps the same class either way.
|
|
127
|
+
def self.struct_class(node)
|
|
128
|
+
return node if node.is_a?(::Class) && node <= Attributes
|
|
129
|
+
return nil unless node.is_a?(Plumb::Function) && node.wraps_callable?
|
|
130
|
+
|
|
131
|
+
callable = node.fn
|
|
132
|
+
callable.is_a?(::Class) && callable <= Attributes ? callable : nil
|
|
133
|
+
end
|
|
134
|
+
|
|
116
135
|
attr_reader :errors, :attributes
|
|
117
136
|
|
|
118
137
|
def initialize(attrs = {})
|
|
@@ -186,7 +205,7 @@ module Plumb
|
|
|
186
205
|
# Add a step to the processing pipeline that runs before attribute validation.
|
|
187
206
|
# This allows you to transform or validate the input data before it's assigned to attributes.
|
|
188
207
|
#
|
|
189
|
-
# @param st [Plumb::
|
|
208
|
+
# @param st [Plumb::Composable, #call, nil] A step object to add to the pipeline
|
|
190
209
|
# @param block [Proc, nil] A block to use as a step (if st is nil)
|
|
191
210
|
# @return [Class] Returns self for method chaining
|
|
192
211
|
#
|
|
@@ -228,14 +247,23 @@ module Plumb
|
|
|
228
247
|
super
|
|
229
248
|
end
|
|
230
249
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
#
|
|
250
|
+
MUST_BE_HASH = ['Must be a Hash of attributes'].freeze
|
|
251
|
+
|
|
252
|
+
# The Plumb::Callable interface
|
|
253
|
+
# @param result [Plumb::Result]
|
|
254
|
+
# @return [Plumb::Result]
|
|
234
255
|
def call(result)
|
|
235
256
|
return result if result.value.is_a?(self)
|
|
236
|
-
return result.invalid(errors:
|
|
257
|
+
return result.invalid(errors: MUST_BE_HASH) unless result.value.respond_to?(:to_h)
|
|
237
258
|
|
|
238
|
-
|
|
259
|
+
# Some #to_h implementations reject malformed contents; treat that as invalid input.
|
|
260
|
+
begin
|
|
261
|
+
attributes = result.value.to_h
|
|
262
|
+
rescue ::TypeError, ::ArgumentError
|
|
263
|
+
return result.invalid(errors: MUST_BE_HASH)
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
instance = new(attributes)
|
|
239
267
|
instance.valid? ? result.valid(instance) : result.invalid(instance, errors: instance.errors.to_h)
|
|
240
268
|
end
|
|
241
269
|
|
|
@@ -252,6 +280,22 @@ module Plumb
|
|
|
252
280
|
# node name for visitors
|
|
253
281
|
def node_name = :data
|
|
254
282
|
|
|
283
|
+
# A Data type joins Composable via `extend`, so it doesn't pick up the
|
|
284
|
+
# Equality hooks the subtype engine calls. Structural subtyping delegates
|
|
285
|
+
# to the underlying schema (a HashClass): a Data type is a subtype of
|
|
286
|
+
# `other` exactly when its schema is. `other` may be another Data type
|
|
287
|
+
# (compared schema-to-schema), a HashClass, or any Plumb type. Recurse via
|
|
288
|
+
# Plumb::Subtyping.subtype?, never #<= (which on a Class is Ruby's own
|
|
289
|
+
# class-hierarchy operator).
|
|
290
|
+
def subtype_of?(other)
|
|
291
|
+
other = other._schema if other.respond_to?(:node_name) && other.node_name == :data
|
|
292
|
+
Plumb::Subtyping.subtype?(_schema, other)
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# Mirror hook (see Composable#supertype_of?). A Data type claims a subtype
|
|
296
|
+
# only where its schema does — which by default is nothing.
|
|
297
|
+
def supertype_of?(other) = _schema.supertype_of?(other)
|
|
298
|
+
|
|
255
299
|
# attribute(:friend) { attribute(:name, String) }
|
|
256
300
|
# attribute(:friend, MyStruct) { attribute(:name, String) }
|
|
257
301
|
# attribute(:name, String)
|
|
@@ -275,9 +319,12 @@ module Plumb
|
|
|
275
319
|
child = node.children.first
|
|
276
320
|
child = __plumb_struct_class__ if child == Types::Any
|
|
277
321
|
Types::Array[build_nested(name, child, &block)]
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
322
|
+
# A wrapper function holds a caller-supplied callable — possibly a struct
|
|
323
|
+
# class, possibly not; #build_nested raises if it isn't. Deliberately NOT
|
|
324
|
+
# every Function: a #transform / #build / coercion wraps a lambda Plumb
|
|
325
|
+
# built, which a nested-attributes block has no business rewriting.
|
|
326
|
+
elsif (node.is_a?(Plumb::Function) && node.wraps_callable?) ||
|
|
327
|
+
(node.is_a?(Class) && node <= Plumb::Attributes)
|
|
281
328
|
build_nested(name, node, &block)
|
|
282
329
|
else
|
|
283
330
|
node
|
|
@@ -315,19 +362,14 @@ module Plumb
|
|
|
315
362
|
end
|
|
316
363
|
|
|
317
364
|
def build_nested(name, node, &block)
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
365
|
+
klass = Plumb::Attributes.struct_class(node)
|
|
366
|
+
unless klass
|
|
367
|
+
raise ArgumentError,
|
|
368
|
+
"attribute #{name.inspect} was given a nested-attributes block, " \
|
|
369
|
+
"but its type #{node.inspect} is not a struct class"
|
|
323
370
|
end
|
|
324
371
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
child = node.children.first
|
|
328
|
-
return node unless child <= Plumb::Attributes
|
|
329
|
-
|
|
330
|
-
sub = Class.new(child)
|
|
372
|
+
sub = Class.new(klass)
|
|
331
373
|
sub.instance_exec(&block)
|
|
332
374
|
__set_nested_class__(name, sub)
|
|
333
375
|
Composable.wrap(sub)
|