plumb 0.2.0.beta.1 → 0.2.0.beta.3

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.
@@ -232,8 +232,8 @@ module Plumb
232
232
  callable
233
233
  elsif callable.respond_to?(:to_composable)
234
234
  # The context-free resolution hook for objects that become a type on
235
- # demand: an Encoder class resolves to its default (declared)
236
- # direction, a Codec class to its instance. Used in schema literals,
235
+ # demand: an Encoder class resolves to its default (declared) direction; a
236
+ # Codec raises, having no type to become. Used in schema literals,
237
237
  # `Array[Enc]`, `#/`, etc. The composition operators (#>>, #|, #&)
238
238
  # consult #to_plumb_type BEFORE wrapping, so a composed operand can
239
239
  # resolve against context and never reaches this branch.
@@ -453,11 +453,12 @@ module Plumb
453
453
  # A filtered Hash is lenient: it accepts ANY hash and drops invalid/missing/
454
454
  # extra fields (it never rejects a Hash), so as a #>> consumer it accepts any
455
455
  # hash-like value. (Its #input_type still declares the schema it relaxes.)
456
- # Memoized at the class level (instances are frozen; Types isn't loaded yet
457
- # when this file is).
458
- def self.each_pair_interface = @each_pair_interface ||= Types::Interface[:each_pair]
456
+ def accepted_type = Types::EachPair
459
457
 
460
- def accepted_type = FilteredHash.each_pair_interface
458
+ # Re-derived, NOT rebuilt around the old #fn: the filter closes over the schema it
459
+ # was built from, so Function#with_children returns a node that inspects as the
460
+ # new schema and still filters by the old one.
461
+ def with_children(children) = children.first.filtered
461
462
 
462
463
  # Neither boundary check runs — the per-field validation in HashClass#filtered
463
464
  # subsumes both. Overriding #call is also what excludes this node from fusion,
@@ -93,11 +93,7 @@ module Plumb
93
93
  # entries (it never rejects a Hash), so as a #>> consumer it accepts any
94
94
  # hash-like value — not itself. Without this, `Hash >> Hash[K, V].filtered`
95
95
  # would be flagged as an illegal narrowing.
96
- # Memoized at the class level (instances are frozen; Types isn't loaded
97
- # yet when this file is).
98
- def self.each_pair_interface = @each_pair_interface ||= Types::Interface[:each_pair]
99
-
100
- def accepted_type = FilteredHashMap.each_pair_interface
96
+ def accepted_type = Types::EachPair
101
97
 
102
98
  def call(result)
103
99
  result.invalid(errors: 'must be a Hash') unless result.value.is_a?(::Hash)
@@ -41,11 +41,7 @@ module Plumb
41
41
  # #>> composition check. This lets a producer of a raw Enumerator/Array (eg.
42
42
  # a CSV enumerator) feed a Stream. Covariant Stream[X] <= Stream[Y]
43
43
  # subtyping is unaffected (that goes through #subtype_of? / #children).
44
- # Memoized at the class level (instances are frozen; Types isn't loaded yet
45
- # when this file is) — #call hits it on every data invocation.
46
- def self.each_interface = @each_interface ||= Types::Interface[:each]
47
-
48
- def input_type = StreamClass.each_interface
44
+ def input_type = Types::Each
49
45
 
50
46
  # The [Step] interface
51
47
  # @param result [Result]
data/lib/plumb/types.rb CHANGED
@@ -96,11 +96,16 @@ module Plumb
96
96
  # Works with a block too:
97
97
  # date = Type::Any[Date].default { Date.today }
98
98
  #
99
+ # A generated default declares, and is checked against, `type`'s OUTPUT — not `type`
100
+ # itself, which would re-run a conversion on a value the block already produced
101
+ # (`Types::String.transform(::Integer).default { 10 }` generates the Integer).
102
+ # Declaring it also tells the rest of the library what fills this position: without
103
+ # it the branch resolves to `Any`, and a Codec has nothing to encode.
99
104
  policy :default, helper: true do |type, value = Undefined, &block|
100
105
  val_type = if value == Undefined
101
- Function.opaque(inspect: 'default proc', identity: [:default, block]) do |result|
102
- result.valid(block.call)
103
- end
106
+ fn = ->(result) { result.valid(block.call) }
107
+ Function.new(Types::Any, Plumb::Subtyping.resolved_output(type), fn,
108
+ inspect: 'default proc', identity: [:default, block])
104
109
  else
105
110
  Types::Static[value]
106
111
  end
@@ -112,12 +117,20 @@ module Plumb
112
117
  # Expect a specific exception class, and return an invalid result if it is raised.
113
118
  # Usage:
114
119
  # type = Types::String.build(Date, :parse).policy(:rescue, Date::Error)
120
+ #
121
+ # The guard returns what `type` returned, so it declares `type`'s OUTPUT — opaque,
122
+ # it would erase the type it wraps (the closure is invisible) and resolve to `Any`.
123
+ # Unchecked: `type` already validated what it produced. The INPUT stays `Any`, or
124
+ # `type`'s own input check runs a second time, outside the rescue.
115
125
  policy :rescue do |type, exception_class|
116
- Function.opaque(inspect: 'Rescue', identity: [:rescue, type, exception_class]) do |result|
126
+ fn = lambda do |result|
117
127
  type.call(result)
118
128
  rescue exception_class => e
119
129
  result.invalid(errors: e.message)
120
130
  end
131
+
132
+ GuaranteedFunction.new(Types::Any, Plumb::Subtyping.resolved_output(type), fn,
133
+ inspect: 'Rescue', identity: [:rescue, type, exception_class])
121
134
  end
122
135
 
123
136
  # Split a string into an array. Default separator is /\s*,\s*/
@@ -168,6 +181,10 @@ module Plumb
168
181
  Email = String[URI::MailTo::EMAIL_REGEXP].as_node(:email)
169
182
  Date = Any[::Date]
170
183
  Time = Any[::Time]
184
+ # What a lenient container accepts (a Stream, a `.filtered` Array/Hash/HashMap).
185
+ # Here, not beside those classes, because Types is loaded after all of them.
186
+ Each = Interface[:each]
187
+ EachPair = Interface[:each_pair]
171
188
 
172
189
  # A type that recursively converts string keys to symbols in nested hashes.
173
190
  # This is commonly used for normalizing payload data in commands and events.
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.2.0.beta.1'
4
+ VERSION = '0.2.0.beta.3'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: plumb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0.beta.1
4
+ version: 0.2.0.beta.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ismael Celis