2009-05-04

Does XHR lead to better testing/abstraction?

I was talking with a friend of mine who is a Ruby programmer who does Rails development and I asked her how best to handle a form submission that was malformed. I am thinking of the situation when I have a URL that accepts a POST from a web page and some argument is in the wrong format or an argument is entirely missing. I wanted some way to signal the POST submission was bad directly from the HTTP status code or something. I mean you should have some clear way to know that something failed along with a message as to why it failed.

But she said it should return a 200 with response page specifying what went wrong. When she said that my TDD sensibilities along with my separation of concerns training recoiled in horror. To test the URL response I have to parse the HTML for error output?!? That means I have to inspect the view to know that an error occurred! And this is simply not testing the view to make sure an error was made visible to the user, this was to test the error was caught, period.

After I recovered from my shock at this suggested practiced and realized most sites worked like this I realized why it offended me so much. Just like everyone else I was taught that MVC was generally the best way to structure GUI applications, and in general I agree with the assessment. In my opinion an application should function regardless of what the GUI iss, making the front-end a separate component of the overall application. One should be able to swap out the "V" from MVC and have things still work. That's just good abstraction in my opinion. That means I should be able to test the core components of an application -- the M and C -- without a GUI. And yet with most web apps we do not get this separation thanks to most forms signaling a failure based on what is displayed in the reply.

With this in mind I began to think about how I could rectify this situation for my web app I was developing. And that's when I realized that using XHR to send a form's data to a URL and use the response to handle errors was, from a testing perspective, the best way to go. It might make the client-side code more complicated as a click in a form would no longer simply be the case of the web browser redirecting to a specific page but require processing a response -- probably JSON -- and handling the reply. But from a testing perspective I would be able to test the submission URL in isolation from the web page, thus separating the V from the M and C. Plus it would give me a REST API upfront and thus not require me to create it later -- if I chose to document the API and make it public.

That's when I said to myself, "OK, so how does everyone else handle signifying an error when it comes to errors in a REST API"? And that's when I found out everyone does it there own little way. About the only thing I found that was consistent is that if everything went well the URLs returned a 200 and when it went to hell they returned a 404. But from there there was no consistency. Only looking at JSON responses, some included the HTTP status code in the error message while others did not. Some had the idea of a class of error and a message while others only had a classification. Some had no answer whatsoever (I'm looking at you XML-RPC), and some went over the top (that would be SOAP).

So I began to wonder about what I would do to signify a failure for a REST call. The first thing I agreed upon was that returning 200/404 was reasonable. It does irk me slightly since it is just like returning 0/1 in C to signal an error, but it works in this situation where one does not have proper exceptions nor other status codes to use to signal different types of errors. I could inspect the returned JSON object for an 'error' attribute to realize an error object was returned, but this works just as well and I would rather dispatch on status code than have to introspect the returned value.

Having chosen a way to signify an error, I then thought about what the JSON reply would require. I thought about how Python signifies errors and almost went down the road of something like exceptions complete with inheritance. But I quickly realized that would require loading JavaScript code just for defined exceptions and I didn't like that for an exposed API; for a REST API one should be able to just read the API docs to make a call and not require including any special JavaScript code. I also realized that this was an API issue, not a programming issue. I was more interested in how Python handled errors when calling functions than how bad syntax was flagged.

That means that I wanted something like TypeError when an argument is missing and ValueError when some input is malformed. That's when I figured why can't I just do something like that? If I had a JSON object have an 'error' attribute that specified the type of error, like "TypeError" or "ValueError", I would have my TypeError/ValueError in JSOn. A 'message' attribute would work as BaseException.args[0]. And then I could tack on arbitrary attributes on this error object for error-specific information such as what argument was missing or what kind of format was expected for a field.

And so all of this is what I plan to do the next time I have any data that needs to be sent to a URL for something that is public-facing and and not a hack (although honestly, isn't everything hack and we just happen to be willing to chance having the public use it?). Yes, it complicates the client-side stuff, but actually the JS code could be made into a library to nicely flag errors, etc. if one standardized on the type of errors that would be returned. Plus it makes unit testing the controller much easier. And it forces you to have a REST API upfront so you don't have to design it later.

Oh, and if anyone says, "what about XForms?", I will say, "get it in all of the major web browsers and then we can talk".