Skip to content

Commit e5a3fa1

Browse files
committed
Add limoncello
1 parent f40e72f commit e5a3fa1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+3006
-62
lines changed

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ APP_ENV=local
22
APP_DEBUG=true
33
APP_KEY=SomeRandomString
44

5+
DB_CONNECTION=sqlite
56
DB_HOST=localhost
67
DB_DATABASE=homestead
78
DB_USERNAME=homestead

.gitignore

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1-
/vendor
2-
/node_modules
1+
vendor/
2+
node_modules/
3+
.idea/
34
Homestead.yaml
45
Homestead.json
56
.env
7+
composer.lock
8+
storage/database.sqlite
9+
_ide_helper.php

.travis.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
language: php
2+
php:
3+
- 5.5
4+
- 5.6
5+
- hhvm
6+
before_script:
7+
- travis_retry composer self-update
8+
- travis_retry composer install --no-interaction --prefer-dist
9+
- cp .env.example .env
10+
- php artisan key:generate
11+
- touch storage/database.sqlite
12+
- php artisan migrate --force
13+
- php artisan db:seed --force
14+
script:
15+
- phpunit

app/Exceptions/Handler.php

Lines changed: 231 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,260 @@
1-
<?php
1+
<?php namespace App\Exceptions;
22

3-
namespace App\Exceptions;
3+
use \Closure;
4+
use \Psr\Log\LoggerInterface;
5+
use \Illuminate\Http\Request;
6+
use \Illuminate\Http\Response;
7+
use \Neomerx\JsonApi\Document\Error;
8+
use \Neomerx\JsonApi\Factories\Factory;
9+
use \Neomerx\Limoncello\Config\Config as C;
10+
use \Neomerx\JsonApi\Encoder\EncoderOptions;
11+
use \Neomerx\Limoncello\Errors\RenderContainer;
12+
use \Neomerx\Cors\Contracts\AnalysisResultInterface;
13+
use \App\Http\Controllers\JsonApi\LaravelIntegration;
14+
use \Neomerx\Limoncello\Contracts\IntegrationInterface;
15+
use \Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
16+
use \Neomerx\JsonApi\Contracts\Exceptions\RenderContainerInterface;
17+
use \Neomerx\JsonApi\Contracts\Parameters\SupportedExtensionsInterface;
418

5-
use Exception;
6-
use Illuminate\Database\Eloquent\ModelNotFoundException;
7-
use Symfony\Component\HttpKernel\Exception\HttpException;
8-
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
9-
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
19+
use \Exception;
20+
use \UnexpectedValueException;
21+
22+
use \Firebase\JWT\ExpiredException;
23+
use \Firebase\JWT\SignatureInvalidException;
24+
25+
use \Illuminate\Contracts\Validation\ValidationException;
26+
use \Illuminate\Database\Eloquent\ModelNotFoundException;
27+
use \Illuminate\Database\Eloquent\MassAssignmentException;
28+
29+
use \Symfony\Component\HttpKernel\Exception\GoneHttpException;
30+
use \Symfony\Component\HttpKernel\Exception\ConflictHttpException;
31+
use \Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
32+
use \Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
33+
use \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
34+
use \Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
35+
use \Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException;
36+
use \Symfony\Component\HttpKernel\Exception\LengthRequiredHttpException;
37+
use \Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException;
38+
use \Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
39+
use \Symfony\Component\HttpKernel\Exception\PreconditionFailedHttpException;
40+
use \Symfony\Component\HttpKernel\Exception\PreconditionRequiredHttpException;
41+
use \Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException;
1042

1143
class Handler extends ExceptionHandler
1244
{
45+
/**
46+
* @var RenderContainerInterface
47+
*/
48+
private $renderContainer;
49+
50+
/**
51+
* @var IntegrationInterface
52+
*/
53+
private $integration;
54+
55+
/**
56+
* @param LoggerInterface $log
57+
*/
58+
public function __construct(LoggerInterface $log)
59+
{
60+
parent::__construct($log);
61+
62+
$this->integration = new LaravelIntegration();
63+
64+
$extensionsClosure = function () {
65+
/** @var SupportedExtensionsInterface $supportedExtensions */
66+
$supportedExtensions = app()->resolved(SupportedExtensionsInterface::class) === false ? null :
67+
app()->make(SupportedExtensionsInterface::class);
68+
return $supportedExtensions;
69+
};
70+
71+
$this->renderContainer = new RenderContainer(new Factory(), $this->integration, $extensionsClosure);
72+
73+
$this->registerCustomExceptions();
74+
}
75+
1376
/**
1477
* A list of the exception types that should not be reported.
1578
*
1679
* @var array
1780
*/
1881
protected $dontReport = [
19-
HttpException::class,
82+
ExpiredException::class,
83+
GoneHttpException::class,
84+
ValidationException::class,
85+
ConflictHttpException::class,
86+
NotFoundHttpException::class,
2087
ModelNotFoundException::class,
88+
BadRequestHttpException::class,
89+
UnexpectedValueException::class,
90+
AccessDeniedHttpException::class,
91+
SignatureInvalidException::class,
92+
UnauthorizedHttpException::class,
93+
NotAcceptableHttpException::class,
94+
LengthRequiredHttpException::class,
95+
TooManyRequestsHttpException::class,
96+
MethodNotAllowedHttpException::class,
97+
PreconditionFailedHttpException::class,
98+
PreconditionRequiredHttpException::class,
99+
UnsupportedMediaTypeHttpException::class,
21100
];
22101

23102
/**
24-
* Report or log an exception.
103+
* Render an exception into an HTTP response.
25104
*
26-
* This is a great spot to send exceptions to Sentry, Bugsnag, etc.
105+
* @param Request $request
106+
* @param Exception $exception
27107
*
28-
* @param \Exception $e
29-
* @return void
108+
* @return Response
30109
*/
31-
public function report(Exception $e)
110+
public function render($request, Exception $exception)
32111
{
33-
return parent::report($e);
112+
$render = $this->renderContainer->getRender($exception);
113+
$corsHeaders = $this->mergeCorsHeadersTo();
114+
115+
return $render($request, $exception, $corsHeaders);
34116
}
35117

36118
/**
37-
* Render an exception into an HTTP response.
119+
* Here you can add 'exception -> HTTP code' mapping or custom exception renders.
120+
*/
121+
private function registerCustomExceptions()
122+
{
123+
$this->renderContainer->registerHttpCodeMapping([
124+
125+
MassAssignmentException::class => Response::HTTP_FORBIDDEN,
126+
ExpiredException::class => Response::HTTP_UNAUTHORIZED,
127+
SignatureInvalidException::class => Response::HTTP_UNAUTHORIZED,
128+
UnexpectedValueException::class => Response::HTTP_BAD_REQUEST,
129+
130+
]);
131+
132+
//
133+
// That's an example of how to create custom response with JSON API Error.
134+
//
135+
$custom404render = $this->getCustom404Render();
136+
137+
// Another example how Eloquent ValidationException could be used.
138+
// You can use validation as simple as this
139+
//
140+
// /** @var \Illuminate\Validation\Validator $validator */
141+
// if ($validator->fails()) {
142+
// throw new ValidationException($validator);
143+
// }
144+
//
145+
// and it will return JSON-API error(s) from your API service
146+
$customValidationRender = $this->getCustomValidationRender();
147+
148+
// This render is interesting because it takes HTTP Headers from exception and
149+
// adds them to HTTP Response (via render parameter $headers)
150+
$customTooManyRequestsRender = $this->getCustomTooManyRequestsRender();
151+
152+
$this->renderContainer->registerRender(ModelNotFoundException::class, $custom404render);
153+
$this->renderContainer->registerRender(ValidationException::class, $customValidationRender);
154+
$this->renderContainer->registerRender(TooManyRequestsHttpException::class, $customTooManyRequestsRender);
155+
}
156+
157+
/**
158+
* @return Closure
159+
*/
160+
private function getCustom404Render()
161+
{
162+
$custom404render = function (/*Request $request, ModelNotFoundException $exception*/) {
163+
// This render can convert JSON API Error to Response
164+
$jsonApiErrorRender = $this->renderContainer->getErrorsRender(Response::HTTP_NOT_FOUND);
165+
166+
// Prepare Error object (e.g. take info from the exception)
167+
$title = 'Requested item not found';
168+
$error = new Error(null, null, null, null, $title);
169+
170+
// Convert error (note it accepts array of errors) to HTTP response
171+
return $jsonApiErrorRender([$error], $this->getEncoderOptions(), $this->mergeCorsHeadersTo());
172+
};
173+
174+
return $custom404render;
175+
}
176+
177+
/**
178+
* @return Closure
179+
*/
180+
private function getCustomValidationRender()
181+
{
182+
$customValidationRender = function (Request $request, ValidationException $exception) {
183+
$request ?: null; // avoid 'unused' warning
184+
185+
// This render can convert JSON API Error to Response
186+
$jsonApiErrorRender = $this->renderContainer->getErrorsRender(Response::HTTP_BAD_REQUEST);
187+
188+
// Prepare Error object (e.g. take info from the exception)
189+
$title = 'Validation fails';
190+
$errors = [];
191+
foreach ($exception->errors()->all() as $validationMessage) {
192+
$errors[] = new Error(null, null, null, null, $title, $validationMessage);
193+
}
194+
195+
// Convert error (note it accepts array of errors) to HTTP response
196+
return $jsonApiErrorRender($errors, $this->getEncoderOptions(), $this->mergeCorsHeadersTo());
197+
};
198+
199+
return $customValidationRender;
200+
}
201+
202+
/**
203+
* @return Closure
204+
*/
205+
private function getCustomTooManyRequestsRender()
206+
{
207+
$customTooManyRequestsRender = function (Request $request, TooManyRequestsHttpException $exception) {
208+
$request ?: null; // avoid 'unused' warning
209+
210+
// This render can convert JSON API Error to Response
211+
$jsonApiErrorRender = $this->renderContainer->getErrorsRender(Response::HTTP_TOO_MANY_REQUESTS);
212+
213+
// Prepare Error object (e.g. take info from the exception)
214+
$title = 'Validation fails';
215+
$message = $exception->getMessage();
216+
$headers = $exception->getHeaders();
217+
$error = new Error(null, null, null, null, $title, $message);
218+
219+
// Convert error (note it accepts array of errors) to HTTP response
220+
return $jsonApiErrorRender([$error], $this->getEncoderOptions(), $this->mergeCorsHeadersTo($headers));
221+
};
222+
223+
return $customTooManyRequestsRender;
224+
}
225+
226+
/**
227+
* @return EncoderOptions
228+
*/
229+
private function getEncoderOptions()
230+
{
231+
// Load JSON formatting options from config
232+
$options = array_get(
233+
$this->integration->getConfig(),
234+
C::JSON . '.' . C::JSON_OPTIONS,
235+
C::JSON_OPTIONS_DEFAULT
236+
);
237+
$encodeOptions = new EncoderOptions($options);
238+
239+
return $encodeOptions;
240+
}
241+
242+
/**
243+
* @param array $headers
38244
*
39-
* @param \Illuminate\Http\Request $request
40-
* @param \Exception $e
41-
* @return \Illuminate\Http\Response
245+
* @return array
42246
*/
43-
public function render($request, Exception $e)
247+
private function mergeCorsHeadersTo(array $headers = [])
44248
{
45-
if ($e instanceof ModelNotFoundException) {
46-
$e = new NotFoundHttpException($e->getMessage(), $e);
249+
$resultHeaders = $headers;
250+
if (app()->resolved(AnalysisResultInterface::class) === true) {
251+
/** @var AnalysisResultInterface|null $result */
252+
$result = app(AnalysisResultInterface::class);
253+
if ($result !== null) {
254+
$resultHeaders = array_merge($headers, $result->getResponseHeaders());
255+
}
47256
}
48257

49-
return parent::render($request, $e);
258+
return $resultHeaders;
50259
}
51260
}

0 commit comments

Comments
 (0)