cloudcreativity / json-api

Framework agnostic JSON API serialisation and deserialisation

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

CloudCreativity\JsonApi\Error\ThrowableError should allow an independent code

nickelozz opened this issue Β· comments

I've been playing around with this package using both the CloudCreativity\JsonApi\Error\ThrowableError and the CloudCreativity\JsonApi\Error\ErrorObject classes to try implementing JWT authentication exception and error responses but I noticed a radical difference between both of em.

ThrowableError doesn't seem to use a provided "code" key in the array sent to its constructor's first parameter (CloudCreativity\JsonApi\Error\ThrowableError line: 76), I can use a separate $code parameter but it allows only null|integer values, which goes against JSON API standard as stated on their specification: code: an application-specific error code, expressed as a string value.

I believe ThrowableError should not use the same code that's being used for the exception code and allow for a different application's specific code to be added which should be treated as a string and rendered on the response.

What do you think?

@nickelozz

Thanks for the feedback!

What you've written was exactly my intention for ThrowableError but unfortunately if you overload getCode() on a class that extends Exception you get a fatal error as getCode() is final.

That will change (I believe) in PHP 7 because there's going to be a throwable interface but obviously I'll need to support PHP 5.5/6 for a while longer.

So the upshot is that ThrowableError isn't usable if you want to set a specific code. Where I want to use a code, I use the ErrorException class, passing in an ErrorObject as the first argument to the constructor. Which would look like this:

$err = new ErrorObject([
    ErrorObject::TITLE => 'Error',
    ErrorObject::STATUS => 500,
    ErrorObject::CODE => 'custom-error-code',
]);

throw new ErrorException($err);

Maybe as a thought it would be best to deprecate ThrowableError and instead allow ErrorException to accept either an Error object as its first argument, or an array (which it would convert to an instance of ErrorObject. What do you think of that?

The other option you have is that you could use your own Exception class(es) for the JWT authentication and then add an exception renderer that handles those specifically. There's some renderers in the Exceptions namespace if you want to see how those work and I'm of course happy to help if you have any questions about implementing one of those.

@lindyhopchris

I have to agree with you on deprecating ThrowableError since it's not well suited for the JSON API scenario.

I'm following your advices on both issues I opened (#13, #14) and it makes sense from the perspective of my app's structure, however I'm encountering 1 issue I haven't been able to solve and I'd like your opinion on it if you don't mind:

Currently I'm using ErrorException to throw all errors (until you add the errors reply helper cloudcreativity/laravel-json-api#1, but I assume even that helper will ultimately throw the same ErrorException).

So I need a way to determine which errors should be logged (e.g. JWT creation error) from the non loggable ones (e.g. invalid JWT). I've tried to extend your ErrorException class just to have a different named exception I can throw that'd be easily added to the $dontReport array within my laravel exception handler, however even tho it does work log-wise the response to the client gets messed up.

Am I doing something wrong here? I read the last part of your comment several times but I'm afraid I don't get how to achieve the behavour I want with the exception renderer....could you please give me an example?

Thank you very much in advance!

@nickelozz

Several options here! You won't need any of these if you wait for me to add the errors reply helper. That won't work by throwing an exception as it won't be required if the errors helper takes an ErrorInterface object (like ErrorObject). But these are the options to get it working via throwing errors...

1. Logging

In the application I have using the laravel-json-api package, I do the following in my application's Exception handler:

public function shouldReport(Exception $e)
{
    if ($e instanceof ErrorInterface) {
        return 499 < $e->getStatus();
    } elseif ($e instanceof ErrorsAwareInterface) {
        return 499 < $e->getErrors()->getStatus();
    }
    return parent::shouldReport($e);
}

You could do something else with your own logic which could test the status or the getCode() method to see if it's one of your JWT error codes.

2. Custom Rendering via Config

You could implement your own JWT exceptions that do not implement any JSON API interfaces and throw them.

In your json-api config file, you could set up an array representation of what the JSON API rendering should be for that specific JWT exception class. This line in the example config shows how:
https://github.com/cloudcreativity/laravel-json-api/blob/master/config/json-api.php#L66

3. Write Your Own Renderer

Alternatively have your own JWT exception class, and write a custom exception renderer class. This can be registered in your AppServiceProvider in exactly the same way as this:
https://github.com/cloudcreativity/laravel-json-api/blob/master/src/ServiceProvider.php#L121

I.e. you'd need to add something like this:

$container->resolving(RendererContainerInterface::class, function (RendererContainerInterface $rendererContainer) use ($container) {
    /** @var ResponsesInterface $response */
    $responses = $container->make(ResponsesInterface::class);
    $errorRenderer = new JWTExceptionRenderer($responses);
    $rendererContainer->registerRenderer(JWTException::class, $errorRenderer);
});

Your JWTExceptionRenderer would need to be implemented along the lines of
https://github.com/cloudcreativity/json-api/blob/master/src/Exceptions/ErrorRenderer.php

All you'd need is your own logic in the getContent() method that converts your JWT Exception to something that implements ErrorInterface and passes that into the encoder (you could convert it to an ErrorObject). Basically this approach means you have all the rendering logic for your JWT Exceptions encapsulated in it's own class (unit). However I'm not sure it'd be necessary if you can map JWT Exception classes to array config in option 2 above.

@lindyhopchris hey! thanks so much for your in-depth explanation.

I'll play around with the different options you've given me to find the 1 I'm most comfortable with. However I'm more interested in simply using that helper method of yours πŸ‘

If I understood correctly by using the errors reply helper there won't be an exception thrown... so no log will happen? If I can somehow help you with it please let me know I'll be more than willing to help out.

Keep up the good work!

@nickelozz

This is available on the feature/issue1 branch of the cloudcreativity/laravel-json-api package. Can you check it meets your requirements?

It adds an error and errors helper to the ResponsesHelper class.

Hello @lindyhopchris

I'm sorry I took a few days to reply, I've tested the error helper and it works just as expected! ty ...however I'm not entirely sure about how the errors one works...I tried sending it the same instance of ErrorObject but returns:

{
  "data": null
}

Is it for the MultiErrorException or I'm supposed to use it differently?

Either way, I believe you can merge the changes into master now πŸ˜„ thanks again!

The errors one takes an array of ErrorInterface objects, or an ErrorCollectionInterface object.

I'll have a look at merging tomorrow.

Alright πŸ‘

@nickelozz I've merged on laravel-json-api and tagged it as v0.2.0

πŸ‘