Skip to content

Commit

Permalink
Add limoncello
Browse files Browse the repository at this point in the history
  • Loading branch information
neomerx committed Aug 22, 2015
1 parent f40e72f commit e0bec8d
Show file tree
Hide file tree
Showing 58 changed files with 3,023 additions and 62 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 storage/database.sqlite
- php artisan migrate --force
- php artisan db:seed --force
script:
- phpunit
253 changes: 231 additions & 22 deletions app/Exceptions/Handler.php
Original file line number Diff line number Diff line change
@@ -1,51 +1,260 @@
<?php
<?php namespace App\Exceptions;

namespace App\Exceptions;
use \Closure;
use \Psr\Log\LoggerInterface;
use \Illuminate\Http\Request;
use \Illuminate\Http\Response;
use \Neomerx\JsonApi\Document\Error;
use \Neomerx\JsonApi\Factories\Factory;
use \Neomerx\Limoncello\Config\Config as C;
use \Neomerx\JsonApi\Encoder\EncoderOptions;
use \Neomerx\Limoncello\Errors\RenderContainer;
use \Neomerx\Cors\Contracts\AnalysisResultInterface;
use \App\Http\Controllers\JsonApi\LaravelIntegration;
use \Neomerx\Limoncello\Contracts\IntegrationInterface;
use \Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use \Neomerx\JsonApi\Contracts\Exceptions\RenderContainerInterface;
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;

class Handler extends ExceptionHandler
{
/**
* @var RenderContainerInterface
*/
private $renderContainer;

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

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

$this->integration = new LaravelIntegration();

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

$this->renderContainer = new RenderContainer(new Factory(), $this->integration, $extensionsClosure);

$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);
$render = $this->renderContainer->getRender($exception);
$corsHeaders = $this->mergeCorsHeadersTo();

return $render($request, $exception, $corsHeaders);
}

/**
* 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->registerRender(ModelNotFoundException::class, $custom404render);
$this->renderContainer->registerRender(ValidationException::class, $customValidationRender);
$this->renderContainer->registerRender(TooManyRequestsHttpException::class, $customTooManyRequestsRender);
}

/**
* @return Closure
*/
private function getCustom404Render()
{
$custom404render = function (/*Request $request, ModelNotFoundException $exception*/) {
// This render can convert JSON API Error to Response
$jsonApiErrorRender = $this->renderContainer->getErrorsRender(Response::HTTP_NOT_FOUND);

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

// Convert error (note it accepts array of errors) to HTTP response
return $jsonApiErrorRender([$error], $this->getEncoderOptions(), $this->mergeCorsHeadersTo());
};

return $custom404render;
}

/**
* @return Closure
*/
private function getCustomValidationRender()
{
$customValidationRender = function (Request $request, ValidationException $exception) {
$request ?: null; // avoid 'unused' warning

// This render can convert JSON API Error to Response
$jsonApiErrorRender = $this->renderContainer->getErrorsRender(Response::HTTP_BAD_REQUEST);

// 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);
}

// Convert error (note it accepts array of errors) to HTTP response
return $jsonApiErrorRender($errors, $this->getEncoderOptions(), $this->mergeCorsHeadersTo());
};

return $customValidationRender;
}

/**
* @return Closure
*/
private function getCustomTooManyRequestsRender()
{
$customTooManyRequestsRender = function (Request $request, TooManyRequestsHttpException $exception) {
$request ?: null; // avoid 'unused' warning

// This render can convert JSON API Error to Response
$jsonApiErrorRender = $this->renderContainer->getErrorsRender(Response::HTTP_TOO_MANY_REQUESTS);

// Prepare Error object (e.g. take info from the exception)
$title = 'Validation fails';
$message = $exception->getMessage();
$headers = $exception->getHeaders();
$error = new Error(null, null, null, null, $title, $message);

// Convert error (note it accepts array of errors) to HTTP response
return $jsonApiErrorRender([$error], $this->getEncoderOptions(), $this->mergeCorsHeadersTo($headers));
};

return $customTooManyRequestsRender;
}

/**
* @return EncoderOptions
*/
private function getEncoderOptions()
{
// Load JSON formatting options from config
$options = array_get(
$this->integration->getConfig(),
C::JSON . '.' . C::JSON_OPTIONS,
C::JSON_OPTIONS_DEFAULT
);
$encodeOptions = new EncoderOptions($options);

return $encodeOptions;
}

/**
* @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;
}
}
Loading

0 comments on commit e0bec8d

Please sign in to comment.