Class: ReSorcery::Decoder
- Inherits:
-
Object
- Object
- ReSorcery::Decoder
- Includes:
- Helpers
- Defined in:
- lib/re_sorcery/decoder.rb,
lib/re_sorcery/decoder/builtin_decoders.rb
Overview
Test that an object satisfies some property
A Decoder represents a piece of logic for verifying some property. A
simple example would be a Decoder that verifies that an object
is_a?(String). A ReSorcery::Result is used to represent the result
of the logic. This can be created like:
is_string =
Decoder.new { |n| n.is_a?(String) ? ok(n) : err("Expected String, got #{n.class}") }
And then used like:
is_string.test("I'm a string") #=> ok("I'm a string")
is_string.test(:symbol) #=> err("Expected String, got Symbol")
Because returning the original object wrapped in ok is the common result
when a Decoder passes, a shorthand in the initializer is to return
true, and Decoder will wrap the object for you.
is_string =
Decoder.new { |n| n.is_a?(String) || err("Expected String, got #{n.class}") }
A similar shorthand: Since the alternative is always something wrapped in
err, if anything but true or a Result is returned, it will be wrapped
in err.
is_string =
Decoder.new { |n| n.is_a?(String) || "Expected String, got #{n.class}" }
All three of these implementations of is_string are equivalent.
Note that this library has strong opinions against using nil, so nil
will never pass Decoder#test.
Defined Under Namespace
Modules: BuiltinDecoders
Instance Method Summary collapse
-
#and ⇒ Object
Chain decoders and maintain any transformed value.
-
#and_then(decoder = nil, &block) ⇒ Object
Chain decoders.
-
#assign(key, other) ⇒ Object
Chain decoders like and_then, but use the chain to build an object.
-
#initialize(&block) ⇒ Decoder
constructor
A new instance of Decoder.
-
#map(&block) ⇒ Object
Apply some block within the context of a successful decoder.
-
#map_error(&block) ⇒ Object
Apply some block within the context of an unsuccessful decoder.
-
#or_else(decoder = nil, &block) ⇒ Object
If the
Decoderfailed, try anotherDecoder. -
#test(unknown) ⇒ Result
Use the decoder to
testthat anunknownobject satisfies some property.
Constructor Details
#initialize(&block) ⇒ Decoder
Returns a new instance of Decoder.
43 44 45 46 |
# File 'lib/re_sorcery/decoder.rb', line 43 def initialize(&block) @block = block freeze end |
Instance Method Details
#and ⇒ Object
Chain decoders and maintain any transformed value
pos = is(String, Symbol)
.map(&:to_sym)
.and { |s, u| [:a, :b, :c].include?(s) || "Invalid option: #{u.inspect}" }
pos.test(:a) == ok(:a)
pos.test("a") == ok(:a)
pos.test("d") == err('Invalid option: "d"')
111 112 113 114 115 116 117 |
# File 'lib/re_sorcery/decoder.rb', line 111 def and and_then do |value| Decoder.new do |unknown| Decoder.new { yield(value, unknown) }.test(value) end end end |
#and_then(decoder = nil, &block) ⇒ Object
Chain decoders
The second decoder can be chosen based on the (successful) result of the first decoder.
Note: the second decoder decodes the "original" unknown value, not the potentially-changed result of the first decoder.
Decoder.new { |u| u.is_a?(Symbol) || "Not symbol" }
.map(&:to_s)
.and_then { |v| Decoder.new { |u| true } } # v is a String & u is a Symbol
.test(:a_symbol)
The block must return a Decoder.
90 91 92 93 94 95 96 97 98 99 |
# File 'lib/re_sorcery/decoder.rb', line 90 def and_then(decoder = nil, &block) ArgCheck['decoder', decoder, Decoder] unless decoder.nil? Decoder.new do |unknown| test(unknown).and_then do |value| # Don't try to re-assign `decoder` here, because it's captured from outside the Decoder (decoder || ArgCheck['block.call(value)', block.call(value), Decoder]).test(unknown) end end end |
#assign(key, other) ⇒ Object
Chain decoders like and_then, but use the chain to build an object
133 134 135 136 137 138 139 140 141 142 143 144 |
# File 'lib/re_sorcery/decoder.rb', line 133 def assign(key, other) ArgCheck['key', key, Symbol] ArgCheck['other', other, Proc, Decoder] other = ->(_) { other.call } if other.is_a?(Proc) && other.arity.zero? and_then do |a| ArgCheck['decoded value', a, Hash] decoder = other.is_a?(Decoder) ? other : ArgCheck['other.call', other.call(a), Decoder] decoder.map { |b| a.merge(key => b) } end end |
#map(&block) ⇒ Object
Apply some block within the context of a successful decoder
67 68 69 |
# File 'lib/re_sorcery/decoder.rb', line 67 def map(&block) Decoder.new { |unknown| test(unknown).map(&block) } end |
#map_error(&block) ⇒ Object
Apply some block within the context of an unsuccessful decoder
72 73 74 |
# File 'lib/re_sorcery/decoder.rb', line 72 def map_error(&block) Decoder.new { |unknown| test(unknown).map_error(&block) } end |
#or_else(decoder = nil, &block) ⇒ Object
If the Decoder failed, try another Decoder
The block must return a Decoder.
122 123 124 125 126 127 128 129 130 |
# File 'lib/re_sorcery/decoder.rb', line 122 def or_else(decoder = nil, &block) ArgCheck['decoder', decoder, Decoder] unless decoder.nil? Decoder.new do |unknown| test(unknown).or_else do |value| (decoder || ArgCheck['block.call(value)', block.call(value), Decoder]).test(unknown) end end end |
#test(unknown) ⇒ Result
Use the decoder to test that an unknown object satisfies some property
Note that ok(nil) will never be returned.
54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/re_sorcery/decoder.rb', line 54 def test(unknown) block_result = @block.call(unknown) case block_result when Result::Ok, Result::Err block_result when TrueClass ok(unknown) else err(block_result) end end |