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?
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.
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!
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!
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
π