New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

lab42_result

Package Overview
Dependencies
Maintainers
1
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

lab42_result

  • 0.2.1
  • Rubygems
  • Socket score

Version published
Maintainers
1
Created
Source

Gem Version

Lab42::Result

A result encapsulation class, like the Either type in Haskell.

Context Quick Starting Guide

Given


    require "lab42/result/autoimport" # equivalent to: the following lines:
    # require "lab42/result"
    # Result = Lab42::Result

    let(:ok) { Result.ok(42) }
    let(:error) { Result.error("oh no!") }

Accessing a result

If OK

Then it's ok (I guess):

  expect(ok).to be_ok

And its value might be of interest

  expect(ok.value).to eq(42)

And it will execute the block passed to the if_ok method

    x = nil
    expect(ok.if_ok {x = 42}).to eq(42)
    expect(x).to eq(42)

And the value is passed in

    expect(ok.if_ok{it/2}).to eq(21)

But not the one passed to the if_error method

    x = nil
    expect(ok.if_error {x = 42}).to be_nil
    expect(x).to be_nil

And will not raise any error

  expect{ok.raise!}.not_to raise_error

But you must not access the error method

    expect{ ok.error }.to raise_error(Lab42::Result::IllegalMonitorState, /must not invoke the error method on an ok result/)

And the same holds for the exception method

    expect{ ok.exception }.to raise_error(Lab42::Result::IllegalMonitorState, /must not invoke the exception method on an ok result/)
If Error

But it is not an error

  expect(error).not_to be_ok

And its value cannot be accessed anymore

  expect{ error.value }.to raise_error(Lab42::Result::IllegalMonitorState, /must not invoke the value method on an error result/)

But of course now we can call the error methods #error and #exception

    expect(error.error).to eq("oh no!")
    expect(error.exception).to eq(RuntimeError)
    

And it will certainly raise this time

  expect{ error.raise! }.to raise_error(RuntimeError, "oh no!") 

And as often times you will match on an error case only and raise a custom exception the following shortcut comes in handy

  expect{ error.raise!(KeyError) }.to raise_error(KeyError, "oh no!") 

And also you might like to have access to the original message

    expect{ error.raise!(KeyError) { "key not found #{it}"} }
      .to raise_error(KeyError, "key not found oh no!") 

And it will execute the block passed to the if_error method

    x = nil
    expect(error.if_error {x = 42}).to eq(42)
    expect(x).to eq(42)

And the error and message are passed in

    expect(error.if_error {[_1, _2]}).to eq([RuntimeError, "oh no!"])    

But not the one passed to the if_ok method

    x = nil
    expect(error.if_ok {x = 42}).to be_nil
    expect(x).to be_nil

Context: Capturing Exceptions

Given some code which might raise exceptions

    class MyError < StandardError; end
    def maybe_raise(answer)
      raise MyError, "Not the correct answer" unless answer == 42
      "Correct answer!"
    end

Then you can rescue from the exception with

    error = Result.from_rescue{maybe_raise(73)}
    expect(error).not_to be_ok

And you can decostruct the error

    Result.from_rescue{maybe_raise(73)} => {ok: false, error:}
    expect(error).to eq("Not the correct answer")

But if you get the correct answer

    Result.from_rescue{maybe_raise(42)} => {ok: true, value:}
    expect(value).to eq("Correct answer!")

Context: More params for our constructors

Context: Ok without a value

Given an ok result with the default value

    let(:default_ok) { Result.ok }

Then we still have an ok result

    expect(default_ok).to be_ok

But its value is just nil

    expect(default_ok.value).to be_nil

Context: Error with a different exception

Given an error with an explicit exception

    let(:argument_error) { Result.error("do not do that", exception: ArgumentError) }

Then we will get that exception back

    expect{ argument_error.raise! }
      .to raise_error(ArgumentError, "do not do that")

Context: Pattern Matching and Conversions

While the Result objectr is very strict on what methods can be called depending on its status (ok?, !ok?)

A more laissez-faire approach can be achieved via Pattern Matching and Conversions

We can deconstruct a result into a hash or an array, and the deconstruction into a hash is identical to matching the result of #to_h

Given two results

    let(:my_error) { Result.error("my bad") }
    let(:my_success) { Result.ok("my good") }
    let(:error_hash) { my_error.to_h }
    let(:success_hash) { my_success.to_h }

Then we can convert them into hashes

    expect(error_hash).to eq(ok: false, value: nil, error: "my bad", exception: RuntimeError)
    expect(success_hash).to eq(ok: true, value: "my good", error: nil, exception: nil)

Then we can desconstruct the error as a hash (as seen above) ```ruby my_error in {ok: false, error: message, exception: RuntimeError} expect(message).to eq("my bad")


The deconstruction into an array however will yield two differently shaped patterns

And  therefore...
```ruby
    my_error in [false, message, RuntimeError]
    expect(message).to eq("my bad")

And the same holds for the ok result

    my_success in [true, value]
    expect(value).to eq("my good")

Context: Immutability

And last but not least, to assure that all instances of Result are frozen we have removed the default constructor (we have not - yet - shadowed Object#allocate though)

Given results

    # Placeholder needed for speculate v1.0.6

Then we do not have a default constructor

    expect{ Result.new }.to raise_error(NoMethodError)

Context: Close Integration

Although the Result class has its merits for error handling it also encourages the usage of a pattern that I call Ok iff errors.empty?

This pattern is implemented by the following module

    module OkIffErrorsEmpty
      def errors = ( @__errors__ ||= [])
      def ok? = errors.empty?
    end

As simple as that.

Now Result allows for a seamingless integration with this pattern, first of all it exposes a module implementing it, but with a (more) reasonable name Errors

While this makes for a fatter interface, if well used, can make the workflow in your class more elegant

Given a class including Errors (pun intended)

    require 'lab42/result/errors'
    class MyErrors
      include Lab42::Result::Errors
    end

    let(:my_errors) { MyErrors.new }

Then an instance of MyErrors is just ok

    expect(my_errors).to be_ok

And has no errors

    expect(my_errors.errors).to be_empty

And we can extract a result out of it

    result = my_errors.to_result
    expect(result).to be_ok

And the value of that result is (per default) my_errors

    my_errors.to_result => {value: my_errors}

But if there are errors, we get

    my_errors.errors << :error1
    my_errors.errors << :error2
    my_errors.to_result => [false, errors, _]
    expect(errors).to eq([:error1, :error2])

And eventually we get a helper to add many errors at once (always an optimist)

    my_errors.add_errors(:error3, :error4)
    my_errors.to_result => [false, errors, _]
    expect(errors).to eq([:error3, :error4])

But I kept my favorite at the end (bad misstake, the audience is sleeping already)

    great = MyErrors.new
    output = []
    great.if_ok { output << "I was completly fine" }
    great.if_error { raise "Does not happen" }
    great.add_errors("I got sick")
    great.if_ok { raise "Still not happening" }
    great.if_error { output << "But then, caught something" }
    expect(output).to eq(["I was completly fine" , "But then, caught something"]) 

Even more detailed speculations

...which allow to have an acceptance test provided test coverage and show the detailed behavior of this library can be found here

LICENSE

Copyright 2025 Robert Dober robert.dober@gmail.com

AGPL-3.0-or-later c.f LICENSE

FAQs

Package last updated on 19 Jan 2025

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc