99 api/problem+json(s)

Time for me to reveal why I chose this error response format with type, title and errors. Imagine if every JSON API returned the same format when things went wrong: always with these keys. That'd be pretty awesome. As API client, we'd always know what to expect and what things mean. That's a beautiful fairy tale.

In the real world, every API does whatever they want. But there are people out there working on standards for error responses, with the hope that someday, API's have some consistency.

The API Problem Details Format

One of those is called api problem details. Google for that. Ah, a boring spec document. Go ahead and read this whole thing, I'll wait... Kidding! I'll show you the good parts.

But first - click the draft ietf link. These drafts go through version, and this one has been replaced with a whole new document. And yea, these are just drafts - not official specs. But who cares? If a spec makes sense to you, why not follow it instead of making up your own format.

Scroll down to the example response. Two important things here. First, if you follow this spec, you should return a custom Content-type response header to advertise this: application/problem+json. When a client sees this - they can research it to find out what the fields in the response mean in human terms.

Second, check out the fields: the main ones are type and title. type is a unique string for what went wrong. It's supposed to be a URL - our's is just a key. We'll revisit that later. Next, this says title is a human-readable version of type and there are a bunch of other optional fields. The Extension Members section says that you can also add whatever other fields you want. We're adding an extra errors key.

Adding the application/problem+json Header

So we're already following this format - or at least we're pretty close. So I want to advertise this to our clients so they can dig into what each key means. Copy the application/problem+json Content-Type header so we can use it.

First, check for this in the test: $this->assertEquals() with application/problem+json as the expected value and $response->getHeader('Content-Type') for the actual value:

... lines 1 - 5
class ProgrammerControllerTest extends ApiTestCase
... lines 8 - 123
public function testValidationErrors()
... lines 126 - 144
$this->assertEquals('application/problem+json', $response->getHeader('Content-Type'));

Make sure it fails - run just this test:

bin/phpunit -c app --filter testValidationErrors

Yep, it fails. Now go to ProgrammerController and find createValidationErrorResponse. Instead of returning JsonResponse, set it to a $response variable and then call $response->headers->set() with Content-Type and application/problem+json. Return this:

... lines 1 - 15
class ProgrammerController extends BaseController
... lines 18 - 165
private function createValidationErrorResponse(FormInterface $form)
... lines 168 - 175
$response = new JsonResponse($data, 400);
$response->headers->set('Content-Type', 'application/problem+json');
return $response;

See if this fixed the test:

bin/phpunit -c app --filter testValidationErrors


Attempted to call function JsonResponse from namespace "AppBundle\Controller\Api"

That looks like a "Ryan" mistake - I deleted the new keyword before JsonResponse. You probably saw me do that. Put it back and now try the tests:

Beautiful green! And now we're advertising our special error format.

Leave a comment!

  • 2017-11-17 Diego Aguiar

    Hey julien moulis

    Don't worry ;) I'm glad that you could fix your problem.
    Sometimes is handy to dump some variables inside your tests, so you can actually see what's going on.

    Have a nice day

  • 2017-11-17 julien moulis

    Ok I reply myself, I guess it's a Guzzle 6 update stuff... It seems that the getHeader('Content-Type') is an array. So I added [0] to fetch the first... It works. Sorry

  • 2017-11-17 julien moulis

    I have an error in my testValidationErrors. When I add the assert ```$this->assertEquals('application/problem+json', $response->getHeader('Content-Type'));```
    My test fails with ```Array (...) does not match expected type "string".```
    I'm not sure to understand why?
    What do you think?
    Thx Julien