Where do you REST your head?
by Sean Cribbs
Sure, it's nearly effortless to add a new XML-based API to your application using the latest stuff in Rails Edge, but what do you do when the Sandman comes? (Sorry about all the puns!)
Lately, I've been very interested in the stuff coming out in Rails Edge and eventually 1.2, emphasizing the CRUD design technique and using REST based services. A lot of this has been spurred on by DHH's keynote at RailsConf that I watched via the web.
The technique is liberating! All your controllers do basically the same things, which are the major CRUD operations (index/show, create, update, destroy) and the typical special representations (edit, new). However, when it comes down to the nitty gritty, there are a few concerns I still have.
Just to test my understanding of the domain and technique, I set out prototyping a new application this morning. It's based on an old PHP app we have that is used to manage the supplemental online databases that KCKCC Library owns or subscribes to. The basic format is very simple: Databases belong to a Category and each have several simple fields, with a few validations.
I started with the CategoriesController
to see if I
could figure everything out. I created the seven basic actions and
added the default respond_to
blocks. Here's a simple
example:
That one was pretty straightforward. What became a brain-twister
were the methods that actually modified something. Ideally, in an XML
API you don't have to print out a big response when a record was
created, deleted or updated. The way you do that is with status
codes, and it is something that showed up in DHH's talk. Here's how I
would do the create
action:
Simple, right? You just send back the location of the new record in the header, and return a 201 status code (Created). This is all well and good until you have some misbehaving clients. What if the new record wasn't created because it failed validation? With our HTML templates, we might do this:
But what should we do with the XML API? We need to refer to the HTTP 1.1 status codes, of course, specifically the 4xx ones, which refer to errors on the client-side. Here are our options (from here):
- 400 Bad Request
- This is primarily for malformed requests. Personally, I think this is inappropriate because it doesn't say that the record submitted failed validations, but that it had bad syntax -- not exactly the same thing.
- 406 Not Acceptable
- At first, I thought this would be it, but actually it refers specifically to the Accept: header and that the resource cannot be returned in an acceptable format.
- 409 Conflict
- Finally, a good candidate! Quoth W3C, "The request could not be completed due to a conflict with the current state of the resource. This code is only allowed in situations where it is expected that the user might be able to resolve the conflict and resubmit the request." They go on to talk about potential uses for versioned resources, etc. Most importantly, however, they mention that the response should give adequate information about how to resolve the conflict.
- 412 Precondition failed, 417 Expectation failed
- These both looked good at first, but of course they refer to specific HTTP headers just like 406 does. Sorry, boys, you're out.
So in the end, I decided 409 was the best for
the create
action. Now, we need to provide information
about "how to resolve the conflict". Why not return the errors that
were added to the object?
Obviously, there are other errors to deal with, but for
the create
and update
actions, 409 will work
just fine.
I'd love to hear your thoughts on how you tackled this issue, or what you would use for status codes in certain situations.