errors/cause/named.rb

tests/_examples/errors/cause/named.rb in the metorex repository.

# `raise` takes a `cause:` naming what the new error follows. It settles the
# matter: an error raised with one is not chained onto whatever was being
# handled, and an error raised a second time keeps the cause it was first
# raised with.

def show
  yield
rescue Exception => raised
  puts "#{raised.class}: #{raised.message} <- #{raised.cause.inspect}"
end

# A cause named outright, in place of the one being handled.
show do
  begin
    raise "being handled"
  rescue
    raise("the new one", cause: StandardError.new("named instead"))
  end
end

# `cause: nil` asks for none at all.
show do
  begin
    raise "being handled"
  rescue
    raise("the new one", cause: nil)
  end
end

# Naming cause alone says nothing about what to raise.
show { raise(cause: StandardError.new("only this")) }

# A cause has to be an exception.
show { raise("the new one", cause: Object.new) }

# An exception is not its own cause.
held = StandardError.new("itself")
show { raise(held, cause: held) }

# A chain that would run back on itself is refused.
show do
  begin
    raise "one"
  rescue => first
    begin
      raise "two"
    rescue
      begin
        raise "three"
      rescue => third
        raise(first, cause: third)
      end
    end
  end
end

# An error raised again keeps the cause it was raised with the first time.
show do
  begin
    raise "one"
  rescue => first
    begin
      raise "two"
    rescue
      raise first
    end
  end
end

Output:

RuntimeError: the new one <- #<StandardError: named instead>
RuntimeError: the new one <- nil
ArgumentError: only cause is given with no arguments <- nil
TypeError: exception object expected <- nil
StandardError: itself <- nil
ArgumentError: circular causes <- #<RuntimeError: three>
RuntimeError: one <- nil