how to handle exceptions in JSON based RESTful code?

16,992

Solution 1

(I found the answer just before I hit [Post your question]. But this might help someone else as well...)

Use ActionController's rescue_from

The answer is to use ActionController's rescue_from, as described in this Guide and documented here. In particular, you can replace the default rendering of the default 404.html and 500.html files along these lines:

class ApplicationController < ActionController::Base
  rescue_from ActiveRecord::RecordNotFound, :with => :record_not_found

private
  def record_not_found(error)
    render :json => {:error => error.message}, :status => :not_found
  end 
end

Solution 2

If it helps anyone, this is what I did as a catch all for my purely json api:

In your ApplicationController that each specific controller inherits from, add

# app/controllers/api/v1/application_controller.rb

# ...

rescue_from StandardError do |exception|
    render json: { :error => exception.message }, :status => 500
end

# ...
  • based mostly off fearless_fool's answer

Solution 3

As a developer, you will also want to see traces (preferably with useful lines, filtering out gems). And make traces invisible for production:

  rescue_from StandardError do |exception|
    # Handle only JSON requests
    raise unless request.format.json?

    err = {error: exception.message}

    err[:backtrace] = exception.backtrace.select do |line|
      # filter out non-significant lines:
      %w(/gems/ /rubygems/ /lib/ruby/).all? do |litter|
         not line.include?(litter)
      end
    end if Rails.env.development? and exception.is_a? Exception

    # duplicate exception output to console:
    STDERR.puts ['ERROR:', err[:error], '']
                    .concat(err[:backtrace] || []).join "\n"

    render :json => err, :status => 500
  end

Solution 4

There is no clear consensus on how to keep a consistent standard for writing JSON API code, but this is a some of what I practice (more than what you asked for):

  1. Keep it simple - Try to stay restful. Custom methods can make things complex fast.
  2. Have the server return native error codes, and use 'rescue_from' to capture, and
  3. in other cases, render Rails HTTP response codes, that can be specifically targeted by the client app.

In your case, you might find the Rails respond_to and respond_with handle html/json/other responses with grace. And even in your solution, it still will effectively render the HTML, but that is not what will be interpreted by your client app, which will instead read the HTTP header and get the HTTP response code, which is what is triggering your 'rescue_from'.

Share:
16,992
fearless_fool
Author by

fearless_fool

Embedded Processor Wizard, well seasoned and steeped in the MIT Media Lab culture of building cool things. For the last several decades, I've thrived on cramming lots of functionality into tiny processors. One of my specialities is exploiting the properties of single chip devices (e.g. GPIO ports, PWM timers, etc) to create robust designs with minimal parts count. My first startup, Ember Corporation (bought by Silicon Labs) ushered in the Internet of Things by releasing the first microcontrollers with embedded wireless mesh networking. Long before Ember, I made 6502, Z80 and PIC processors jump through hoops to control laser printers, environmental sensors, audio devices, lighting systems and electronic whoopee cushions. More recently, I've been working with RPi, various Arduino (including Intel Arduino 101), Freescale/NXP KL2xx, and I look forward creating new things on the ESP32, GR8, AM335x and/or nRF52 family of processors. My work doesn't stop at the microcontroller level: I use C, C++, Python, Javascript/Node, Ruby and other languages to connect the microcontrollers into cloud-based applications.

Updated on June 13, 2022

Comments

  • fearless_fool
    fearless_fool over 1 year

    I have a "software as a service" app that uses JSON communicated via a RESTful API.

    Simply stated: what are the best practices for capturing and reporting exceptions when using a RESTful API with JSON data interchange?

    My first thought was to see what Rails does by generating a scaffold, but that's clearly not right. Here's an excerpt:

    class MumblesController < ApplicationController
    
      # GET /mumbles/1
      # GET /mumbles/1.json
      def show
        @mumble = Mumble.find(params[:id])
        respond_to do |format|
          format.html # show.html.erb
          format.json { render json: @mumble }
        end
      end
    
    end
    

    In this case, if the JSON code sends a non-existent ID, e.g.

    http://www.myhost.com/mumbles/99999.json
    

    then Mumble.find() will raise ActiveRecord::RecordNotFound. ActionController will catch that and render an error page in HTML. But HTML is useless to the client that is expecting JSON.

    I could work around that by wrapping the Mumble.find() in a begin ... rescue RuntimeError block and rendering a JSON status => :unprocessable_entity or something.

    But then what if the client's app sends an invalid path, e.g.:

    http://www.myhost.com/badtypo/1.json
    

    Is a JSON based app supposed to catch that and return an error in JSON? If so, where do I capture that without digging deep into ActionDispatch?

    So overall, do I punt and let ActionController generate HTML if there's an error? That doesn't feel right...

  • AlexChaffee
    AlexChaffee over 7 years
    beware: at least in Rails 4, the order of matching in rescue_from is reversed from regular rescue: if you want a different behaviors for a subclass vs. its parent class, put the subclass after the parent :-/
  • Michael
    Michael about 7 years
    This seems like overkill responding with a 500 for any rescued error.