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 ‘Decoder` failed, try another `Decoder`.
-
#test(unknown) ⇒ Result
Use the decoder to ‘test` that an `unknown` object 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 |