Skip to content

Commit

Permalink
Add limoncello
Browse files Browse the repository at this point in the history
  • Loading branch information
neomerx committed Nov 25, 2015
1 parent 1fac290 commit 88ff2e6
Show file tree
Hide file tree
Showing 59 changed files with 3,130 additions and 57 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ APP_ENV=local
APP_DEBUG=true
APP_KEY=SomeRandomString

DB_CONNECTION=sqlite
DB_HOST=localhost
DB_DATABASE=homestead
DB_USERNAME=homestead
Expand Down
8 changes: 6 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
/vendor
/node_modules
vendor/
node_modules/
.idea/
Homestead.yaml
Homestead.json
.env
composer.lock
storage/database.sqlite
_ide_helper.php
15 changes: 15 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
language: php
php:
- 5.5
- 5.6
- hhvm
before_script:
- travis_retry composer self-update
- travis_retry composer install --no-interaction --prefer-dist
- cp .env.example .env
- php artisan key:generate
- touch database/database.sqlite
- php artisan migrate --force
- php artisan db:seed --force
script:
- phpunit
247 changes: 225 additions & 22 deletions app/Exceptions/Handler.php
Original file line number Diff line number Diff line change
@@ -1,51 +1,254 @@
<?php
<?php namespace App\Exceptions;

namespace App\Exceptions;
use \Psr\Log\LoggerInterface;
use \Illuminate\Http\Request;
use \Illuminate\Http\Response;
use \Neomerx\JsonApi\Document\Error;
use \Neomerx\Limoncello\Errors\RendererContainer;
use \Neomerx\JsonApi\Parameters\Headers\MediaType;
use \Neomerx\Cors\Contracts\AnalysisResultInterface;
use \App\Http\Controllers\JsonApi\LaravelIntegration;
use \Neomerx\Limoncello\Contracts\IntegrationInterface;
use \Neomerx\JsonApi\Contracts\Exceptions\RendererInterface;
use \Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use \Neomerx\JsonApi\Contracts\Exceptions\RendererContainerInterface;
use \Neomerx\JsonApi\Contracts\Parameters\SupportedExtensionsInterface;

use Exception;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use \Exception;
use \UnexpectedValueException;

use \Firebase\JWT\ExpiredException;
use \Firebase\JWT\SignatureInvalidException;

use \Illuminate\Contracts\Validation\ValidationException;
use \Illuminate\Database\Eloquent\ModelNotFoundException;
use \Illuminate\Database\Eloquent\MassAssignmentException;

use \Symfony\Component\HttpKernel\Exception\GoneHttpException;
use \Symfony\Component\HttpKernel\Exception\ConflictHttpException;
use \Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use \Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use \Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
use \Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException;
use \Symfony\Component\HttpKernel\Exception\LengthRequiredHttpException;
use \Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException;
use \Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use \Symfony\Component\HttpKernel\Exception\PreconditionFailedHttpException;
use \Symfony\Component\HttpKernel\Exception\PreconditionRequiredHttpException;
use \Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException;

/**
* @package Neomerx\LimoncelloCollins
*/
class Handler extends ExceptionHandler
{
/**
* @var RendererContainerInterface
*/
private $renderContainer;

/**
* @var IntegrationInterface
*/
private $integration;

/**
* @param LoggerInterface $log
*/
public function __construct(LoggerInterface $log)
{
parent::__construct($log);

$this->integration = new LaravelIntegration();
$this->renderContainer = new RendererContainer($this->integration);

$this->registerCustomExceptions();
}

/**
* A list of the exception types that should not be reported.
*
* @var array
*/
protected $dontReport = [
HttpException::class,
ExpiredException::class,
GoneHttpException::class,
ValidationException::class,
ConflictHttpException::class,
NotFoundHttpException::class,
ModelNotFoundException::class,
BadRequestHttpException::class,
UnexpectedValueException::class,
AccessDeniedHttpException::class,
SignatureInvalidException::class,
UnauthorizedHttpException::class,
NotAcceptableHttpException::class,
LengthRequiredHttpException::class,
TooManyRequestsHttpException::class,
MethodNotAllowedHttpException::class,
PreconditionFailedHttpException::class,
PreconditionRequiredHttpException::class,
UnsupportedMediaTypeHttpException::class,
];

/**
* Report or log an exception.
* Render an exception into an HTTP response.
*
* This is a great spot to send exceptions to Sentry, Bugsnag, etc.
* @param Request $request
* @param Exception $exception
*
* @param \Exception $e
* @return void
* @return Response
*/
public function report(Exception $e)
public function render($request, Exception $exception)
{
return parent::report($e);
$request ?: null;

$render = $this->renderContainer->getRenderer(get_class($exception));

if (($supportedExtensions = $this->getSupportedExtensions()) !== null) {
$render->withSupportedExtensions($supportedExtensions);
}

$corsHeaders = $this->mergeCorsHeadersTo();
$mediaType = new MediaType(MediaType::JSON_API_TYPE, MediaType::JSON_API_SUB_TYPE);

return $render->withHeaders($corsHeaders)->withMediaType($mediaType)->render($exception);
}

/**
* Render an exception into an HTTP response.
* Here you can add 'exception -> HTTP code' mapping or custom exception renders.
*/
private function registerCustomExceptions()
{
$this->renderContainer->registerHttpCodeMapping([

MassAssignmentException::class => Response::HTTP_FORBIDDEN,
ExpiredException::class => Response::HTTP_UNAUTHORIZED,
SignatureInvalidException::class => Response::HTTP_UNAUTHORIZED,
UnexpectedValueException::class => Response::HTTP_BAD_REQUEST,

]);

//
// That's an example of how to create custom response with JSON API Error.
//
$custom404render = $this->getCustom404Render();

// Another example how Eloquent ValidationException could be used.
// You can use validation as simple as this
//
// /** @var \Illuminate\Validation\Validator $validator */
// if ($validator->fails()) {
// throw new ValidationException($validator);
// }
//
// and it will return JSON-API error(s) from your API service
$customValidationRender = $this->getCustomValidationRender();

// This render is interesting because it takes HTTP Headers from exception and
// adds them to HTTP Response (via render parameter $headers)
$customTooManyRequestsRender = $this->getCustomTooManyRequestsRender();

$this->renderContainer->registerRenderer(ModelNotFoundException::class, $custom404render);
$this->renderContainer->registerRenderer(ValidationException::class, $customValidationRender);
$this->renderContainer->registerRenderer(TooManyRequestsHttpException::class, $customTooManyRequestsRender);
}

/**
* @return RendererInterface
*/
private function getCustom404Render()
{
$converter = function (ModelNotFoundException $exception) {
$exception ?: null;

// Prepare Error object (e.g. take info from the exception)
$title = 'Requested item not found';
$error = new Error(null, null, null, null, $title);

return $error;
};

$renderer = $this->renderContainer->createConvertContentRenderer(Response::HTTP_NOT_FOUND, $converter);

return $renderer;
}

/**
* @return RendererInterface
*/
private function getCustomValidationRender()
{
$converter = function (ValidationException $exception) {
// Prepare Error object (e.g. take info from the exception)
$title = 'Validation fails';

$errors = [];
foreach ($exception->errors()->all() as $validationMessage) {
$errors[] = new Error(null, null, null, null, $title, $validationMessage);
}

return $errors;
};

$renderer = $this->renderContainer->createConvertContentRenderer(Response::HTTP_BAD_REQUEST, $converter);

return $renderer;
}

/**
* @return RendererInterface
*/
private function getCustomTooManyRequestsRender()
{
/** @var RendererInterface $renderer */
$renderer = null;
$converter = function (TooManyRequestsHttpException $exception) use (&$renderer) {
// Prepare Error object (e.g. take info from the exception)
$title = 'Validation fails';
$message = $exception->getMessage();
$error = new Error(null, null, null, null, $title, $message);

$headers = $exception->getHeaders();
$renderer->withHeaders($headers);

return $error;
};

$renderer = $this->renderContainer->createConvertContentRenderer(Response::HTTP_TOO_MANY_REQUESTS, $converter);

return $renderer;
}

/**
* @param array $headers
*
* @param \Illuminate\Http\Request $request
* @param \Exception $e
* @return \Illuminate\Http\Response
* @return array
*/
public function render($request, Exception $e)
private function mergeCorsHeadersTo(array $headers = [])
{
if ($e instanceof ModelNotFoundException) {
$e = new NotFoundHttpException($e->getMessage(), $e);
$resultHeaders = $headers;
if (app()->resolved(AnalysisResultInterface::class) === true) {
/** @var AnalysisResultInterface|null $result */
$result = app(AnalysisResultInterface::class);
if ($result !== null) {
$resultHeaders = array_merge($headers, $result->getResponseHeaders());
}
}

return parent::render($request, $e);
return $resultHeaders;
}

/**
* @return SupportedExtensionsInterface|null
*/
private function getSupportedExtensions()
{
/** @var SupportedExtensionsInterface|null $supportedExtensions */
$supportedExtensions = app()->resolved(SupportedExtensionsInterface::class) === false ? null :
app()->make(SupportedExtensionsInterface::class);

return $supportedExtensions;
}
}
Loading

0 comments on commit 88ff2e6

Please sign in to comment.