Skip to content

Commit

Permalink
Add support for pattern matching on Result and Error objects
Browse files Browse the repository at this point in the history
  • Loading branch information
pabloh committed Jun 20, 2022
1 parent 84d3ea0 commit 0bbc7a4
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 2 deletions.
6 changes: 5 additions & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
require "bundler/gem_tasks"
require "rspec/core/rake_task"

RSpec::Core::RakeTask.new(:spec)
RSpec::Core::RakeTask.new(:spec) do |t|
unless RUBY_VERSION =~ /^2\.7|^3\./
t.exclude_pattern = 'spec/result_pattern_matching_spec.rb'
end
end

task :default => :spec
8 changes: 8 additions & 0 deletions lib/pathway.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ def initialize(type:, message: nil, details: nil)
@details = details || {}
end

def deconstruct
[type, message, details]
end

def deconstruct_keys(_)
{ type: type, message: message, details: details }
end

private

def default_message_for(type)
Expand Down
20 changes: 20 additions & 0 deletions lib/pathway/result.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ def tee(bl=nil, &block)
follow = self.then(bl, &block)
follow.failure? ? follow : self
end

private

alias :deconstruct_value :value
end

class Failure < Result
Expand All @@ -40,6 +44,22 @@ def then(_=nil)
def tee(_=nil)
self
end

private

alias :deconstruct_value :error
end

def deconstruct
[deconstruct_value]
end

def deconstruct_keys(keys)
if deconstruct_value.respond_to?(:deconstruct_keys)
deconstruct_value.deconstruct_keys(keys)
else
{}
end
end

def failure?
Expand Down
2 changes: 1 addition & 1 deletion spec/plugins/responder_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class RespOperation < Operation

context :with

def call(_input)
def call(_)
with
end
end
Expand Down
160 changes: 160 additions & 0 deletions spec/result_pattern_matching_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# frozen_string_literal: true

require 'spec_helper'

module Pathway
class Result
describe 'Operation with pattern matching' do
class RespOperation < Operation
context :with

def call(_)
with
end
end

let(:input) { {} }
let(:context) { { with: passed_result} }

context "when calling operation using 'case'" do
context "providing a single variable name as pattern" do
let(:result) do
case RespOperation.call(context, input)
in Success(value) then "Returning: " + value
in Failure(error) then error.message
end
end

context "and the result is succesfull" do
let(:passed_result) { Result.success("VALUE") }

it "returns the success result" do
expect(result).to eq("Returning: VALUE")
end
end

context "and the result is a failure" do
let(:passed_result) { Result.failure(Error.new(type: :error, message: "AN ERROR!")) }

it "returns the failure result" do
expect(result).to eq("AN ERROR!")
end
end
end


context "providing Hash based patterns," do
context "and the underlying result does not support Hash based patterns" do
let(:passed_result) { Result.success("VALUE") }

it "raises a non matching error" do
expect {
case RespOperation.call(context, input)
in Success(value:) then value
in Failure(error) then value
end
}.to raise_error(NoMatchingPatternError)
end
end

let(:result) do
case RespOperation.call(context, input)
in Success(value) then "Returning: " + value
in Failure(type: :forbidden) then "Forbidden"
in Failure(type: :validation, details:) then "Invalid: " + details.join(", ")
in Failure(details:) then "Other: " + details.join(" ")
end
end

context "and the result is succesfull" do
let(:passed_result) { Result.success("VALUE") }

it "returns the success result" do
expect(result).to eq("Returning: VALUE")
end
end

context "the result is a failure" do
context "and the pattern is Failure with only :type specified" do
let(:passed_result) do
Result.failure(Error.new(type: :forbidden))
end

it "returns the result according to :type" do
expect(result).to eq("Forbidden")
end
end

context "and the pattern is Failure with :type and :details specified" do
let(:passed_result) do
Result.failure(Error.new(type: :validation, details: ['name missing', 'email missing']))
end

it "returns the result according to :type" do
expect(result).to eq("Invalid: name missing, email missing")
end
end

context "and the pattern is Failure with no specified :type" do
let(:passed_result) { Result.failure(Error.new(type: :misc, details: %w[some errors])) }

it "executes the least specific pattern" do
expect(result).to eq("Other: some errors")
end
end
end
end

context "providing Array based patterns," do
let(:result) do
case RespOperation.call(context, input)
in Success(value) then "Returning: " + value
in Failure([:forbidden,]) then "Forbidden"
in Failure([:validation, _, details]) then "Invalid: " + details.join(", ")
in Failure(type: :validation, details:) then "Invalid: " + details.join(", ")
in Failure([*, details]) then "Other: " + details.join(" ")
end
end

context "and the result is succesfull" do
let(:passed_result) { Result.success("VALUE") }

it "returns the success result" do
expect(result).to eq("Returning: VALUE")
end
end

context "the result is a failure" do
context "and the pattern is Failure with only :type specified" do
let(:passed_result) do
Result.failure(Error.new(type: :forbidden))
end

it "returns the result according to :type" do
expect(result).to eq("Forbidden")
end
end

context "and the pattern is Failure with :type and :details specified" do
let(:passed_result) do
Result.failure(Error.new(type: :validation, details: ['name missing', 'email missing']))
end

it "returns the result according to :type" do
expect(result).to eq("Invalid: name missing, email missing")
end
end

context "and the pattern is Failure with no specified :type" do
let(:passed_result) { Result.failure(Error.new(type: :misc, details: %w[some errors])) }

it "executes the least specific pattern" do
expect(result).to eq("Other: some errors")
end
end
end
end
end
end
end
end

0 comments on commit 0bbc7a4

Please sign in to comment.