Daniel Doubrovkine bio photo

Daniel Doubrovkine

aka dB., CTO at artsy.net, fun at playplay.io, NYC

Email Twitter LinkedIn Github

We’ve been using Grape to provide a RESTful API. Grape is a micro-framework for Ruby that makes it really easy.

I’ve recently had to deal with code that raises exceptions in a bunch of unpredictable places. This causes Rails to produce an HTML error page, including when making JSON API calls. We want to have some control of this and wrap all calls to return an error message or maybe even a JSON error message. To do so we’ll write the following exception handler. It traps all exceptions in a rescue block and re-throws a specific :error that Grape expects.

# trap all exceptions and fail gracefuly with a 500 and a proper message
class ApiErrorHandler < Grape::Middleware::Base
  def call!(env)
    @env = env
    begin
      @app.call(@env)
    rescue Exception => e
      throw :error, :message => e.message || options[:default_message], :status => 500
    end
  end
end

This can be injected into the middleware stack, a construct I find quite elegant.

require 'api_error_handler'

class Api_v1 < Grape::API
  prefix 'api'
  version 'v1'

  use ApiErrorHandler

  ...
end

To be good citizens we’ll write an RSpec test, heavily inspired by Grape’s specs.

require 'spec_helper'

describe "ApiErrorHandler" do

  subject { Class.new(Grape::API) }
  def app; subject end

  describe "error" do

    before do
      subject.prefix 'api'
      subject.use ApiErrorHandler
      subject.get :error do
        raise "api error"
      end
      subject.get :hello do
        { hello: "world" }
      end
    end

    it "should return world when asked hello" do
      get '/api/hello'
      JSON.parse(response.body).should == { "hello" => "world" }
      response.status.should == 200
    end

    it "should return a 500 when an exception is raised" do
      get '/api/error'
      response.status.should == 500
      response.body.should == "api error"
    end

  end
end

I couldn’t figure out how to wrap it up to return JSON, I keep having to raise an :error to abort all subsequent middleware processors. Maybe someone can suggest a solution or a better approach altogether?