Skip to content

Commit 54b6fb5

Browse files
committed
More work on the API utilizing Laravel 5.5 exception rendering
Also corrects API format to maintain JSONAPI spec
1 parent f30f4b4 commit 54b6fb5

28 files changed

+464
-391
lines changed

app/Exceptions/DisplayException.php

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,12 @@
1111

1212
use Log;
1313
use Throwable;
14+
use Prologue\Alerts\AlertsMessageBag;
1415

1516
class DisplayException extends PterodactylException
1617
{
18+
const LEVEL_DEBUG = 'debug';
19+
const LEVEL_INFO = 'info';
1720
const LEVEL_WARNING = 'warning';
1821
const LEVEL_ERROR = 'error';
1922

@@ -32,13 +35,13 @@ class DisplayException extends PterodactylException
3235
*/
3336
public function __construct($message, Throwable $previous = null, $level = self::LEVEL_ERROR, $code = 0)
3437
{
35-
$this->level = $level;
38+
parent::__construct($message, $code, $previous);
3639

3740
if (! is_null($previous)) {
3841
Log::{$level}($previous);
3942
}
4043

41-
parent::__construct($message, $code, $previous);
44+
$this->level = $level;
4245
}
4346

4447
/**
@@ -48,4 +51,25 @@ public function getErrorLevel()
4851
{
4952
return $this->level;
5053
}
54+
55+
/**
56+
* Render the exception to the user by adding a flashed message to the session
57+
* and then redirecting them back to the page that they came from. If the
58+
* request originated from an API hit, return the error in JSONAPI spec format.
59+
*
60+
* @param \Illuminate\Http\Request $request
61+
* @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse
62+
*/
63+
public function render($request)
64+
{
65+
if ($request->expectsJson()) {
66+
return response()->json(Handler::convertToArray($this, [
67+
'detail' => $this->getMessage(),
68+
]), 500);
69+
}
70+
71+
app()->make(AlertsMessageBag::class)->danger($this->getMessage())->flash();
72+
73+
return redirect()->back()->withInput();
74+
}
5175
}

app/Exceptions/DisplayValidationException.php

Lines changed: 0 additions & 14 deletions
This file was deleted.

app/Exceptions/Handler.php

Lines changed: 82 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,11 @@
33
namespace Pterodactyl\Exceptions;
44

55
use Exception;
6-
use Prologue\Alerts\Facades\Alert;
76
use Illuminate\Auth\AuthenticationException;
87
use Illuminate\Session\TokenMismatchException;
98
use Illuminate\Validation\ValidationException;
109
use Illuminate\Auth\Access\AuthorizationException;
1110
use Illuminate\Database\Eloquent\ModelNotFoundException;
12-
use Pterodactyl\Exceptions\Model\DataValidationException;
1311
use Symfony\Component\HttpKernel\Exception\HttpException;
1412
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
1513
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
@@ -25,15 +23,23 @@ class Handler extends ExceptionHandler
2523
AuthenticationException::class,
2624
AuthorizationException::class,
2725
DisplayException::class,
28-
DataValidationException::class,
29-
DisplayValidationException::class,
3026
HttpException::class,
3127
ModelNotFoundException::class,
3228
RecordNotFoundException::class,
3329
TokenMismatchException::class,
3430
ValidationException::class,
3531
];
3632

33+
/**
34+
* A list of the inputs that are never flashed for validation exceptions.
35+
*
36+
* @var array
37+
*/
38+
protected $dontFlash = [
39+
'password',
40+
'password_confirmation',
41+
];
42+
3743
/**
3844
* Report or log an exception.
3945
*
@@ -53,41 +59,78 @@ public function report(Exception $exception)
5359
*
5460
* @param \Illuminate\Http\Request $request
5561
* @param \Exception $exception
56-
* @return \Illuminate\Http\JsonResponse|\Symfony\Component\HttpFoundation\Response
62+
* @return \Symfony\Component\HttpFoundation\Response
5763
*
5864
* @throws \Exception
5965
*/
6066
public function render($request, Exception $exception)
6167
{
62-
if ($request->expectsJson() || $request->isJson() || $request->is(...config('pterodactyl.json_routes'))) {
63-
$exception = $this->prepareException($exception);
68+
return parent::render($request, $exception);
69+
}
6470

65-
if (config('app.debug') || $this->isHttpException($exception) || $exception instanceof DisplayException) {
66-
$displayError = $exception->getMessage();
67-
} else {
68-
$displayError = 'An unhandled exception was encountered with this request.';
71+
/**
72+
* @param \Illuminate\Http\Request $request
73+
* @param \Illuminate\Validation\ValidationException $exception
74+
* @return \Illuminate\Http\JsonResponse
75+
*/
76+
public function invalidJson($request, ValidationException $exception)
77+
{
78+
$codes = collect($exception->validator->failed())->mapWithKeys(function ($reasons, $field) {
79+
$cleaned = [];
80+
foreach ($reasons as $reason => $attrs) {
81+
$cleaned[] = snake_case($reason);
6982
}
7083

71-
$response = response()->json(
72-
[
73-
'error' => $displayError,
74-
'type' => (! config('app.debug')) ? null : class_basename($exception),
75-
'http_code' => (method_exists($exception, 'getStatusCode')) ? $exception->getStatusCode() : 500,
76-
'trace' => (! config('app.debug')) ? null : $exception->getTrace(),
77-
],
78-
$this->isHttpException($exception) ? $exception->getStatusCode() : 500,
79-
$this->isHttpException($exception) ? $exception->getHeaders() : [],
80-
JSON_UNESCAPED_SLASHES
81-
);
84+
return [$field => $cleaned];
85+
})->toArray();
86+
87+
$errors = collect($exception->errors())->map(function ($errors, $field) use ($codes) {
88+
$response = [];
89+
foreach ($errors as $key => $error) {
90+
$response[] = [
91+
'code' => array_get($codes, $field . '.' . $key),
92+
'detail' => $error,
93+
'source' => ['field' => $field],
94+
];
95+
}
96+
97+
return $response;
98+
})->flatMap(function ($errors) {
99+
return $errors;
100+
})->toArray();
101+
102+
return response()->json(['errors' => $errors], $exception->status);
103+
}
82104

83-
parent::report($exception);
84-
} elseif ($exception instanceof DisplayException) {
85-
Alert::danger($exception->getMessage())->flash();
105+
/**
106+
* Return the exception as a JSONAPI representation for use on API requests.
107+
*
108+
* @param \Exception $exception
109+
* @param array $override
110+
* @return array
111+
*/
112+
public static function convertToArray(Exception $exception, array $override = []): array
113+
{
114+
$error = [
115+
'code' => class_basename($exception),
116+
'status' => method_exists($exception, 'getStatusCode') ? strval($exception->getStatusCode()) : '500',
117+
'detail' => 'An error was encountered while processing this request.',
118+
];
86119

87-
return redirect()->back()->withInput();
120+
if (config('app.debug')) {
121+
$error = array_merge($error, [
122+
'detail' => $exception->getMessage(),
123+
'source' => [
124+
'line' => $exception->getLine(),
125+
'file' => str_replace(base_path(), '', $exception->getFile()),
126+
],
127+
'meta' => [
128+
'trace' => explode("\n", $exception->getTraceAsString()),
129+
],
130+
]);
88131
}
89132

90-
return (isset($response)) ? $response : parent::render($request, $exception);
133+
return ['errors' => [array_merge($error, $override)]];
91134
}
92135

93136
/**
@@ -105,4 +148,16 @@ protected function unauthenticated($request, AuthenticationException $exception)
105148

106149
return redirect()->guest(route('auth.login'));
107150
}
151+
152+
/**
153+
* Converts an exception into an array to render in the response. Overrides
154+
* Laravel's built-in converter to output as a JSONAPI spec compliant object.
155+
*
156+
* @param \Exception $exception
157+
* @return array
158+
*/
159+
protected function convertExceptionToArray(Exception $exception)
160+
{
161+
return self::convertToArray($exception);
162+
}
108163
}
Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,60 @@
11
<?php
2-
/**
3-
* Pterodactyl - Panel
4-
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
5-
*
6-
* This software is licensed under the terms of the MIT license.
7-
* https://opensource.org/licenses/MIT
8-
*/
92

103
namespace Pterodactyl\Exceptions\Model;
114

125
use Illuminate\Contracts\Validation\Validator;
13-
use Illuminate\Validation\ValidationException;
6+
use Pterodactyl\Exceptions\PterodactylException;
147
use Illuminate\Contracts\Support\MessageProvider;
8+
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
159

16-
class DataValidationException extends ValidationException implements MessageProvider
10+
class DataValidationException extends PterodactylException implements HttpExceptionInterface, MessageProvider
1711
{
12+
/**
13+
* The validator instance.
14+
*
15+
* @var \Illuminate\Contracts\Validation\Validator
16+
*/
17+
public $validator;
18+
1819
/**
1920
* DataValidationException constructor.
2021
*
2122
* @param \Illuminate\Contracts\Validation\Validator $validator
2223
*/
2324
public function __construct(Validator $validator)
2425
{
25-
parent::__construct($validator);
26+
parent::__construct(
27+
'Data integrity exception encountered while performing database write operation. ' . $validator->errors()->toJson()
28+
);
29+
30+
$this->validator = $validator;
2631
}
2732

2833
/**
34+
* Return the validator message bag.
35+
*
2936
* @return \Illuminate\Support\MessageBag
3037
*/
3138
public function getMessageBag()
3239
{
3340
return $this->validator->errors();
3441
}
42+
43+
/**
44+
* Return the status code for this request.
45+
*
46+
* @return int
47+
*/
48+
public function getStatusCode()
49+
{
50+
return 500;
51+
}
52+
53+
/**
54+
* @return array
55+
*/
56+
public function getHeaders()
57+
{
58+
return [];
59+
}
3560
}

app/Exceptions/PterodactylException.php

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,4 @@
11
<?php
2-
/**
3-
* Pterodactyl - Panel
4-
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
5-
*
6-
* This software is licensed under the terms of the MIT license.
7-
* https://opensource.org/licenses/MIT
8-
*/
92

103
namespace Pterodactyl\Exceptions;
114

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
11
<?php
2-
/**
3-
* Pterodactyl - Panel
4-
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
5-
*
6-
* This software is licensed under the terms of the MIT license.
7-
* https://opensource.org/licenses/MIT
8-
*/
92

103
namespace Pterodactyl\Exceptions\Repository\Daemon;
114

12-
class InvalidPowerSignalException extends \Exception
5+
use Pterodactyl\Exceptions\Repository\RepositoryException;
6+
7+
class InvalidPowerSignalException extends RepositoryException
138
{
149
}

app/Exceptions/Repository/DuplicateDatabaseNameException.php

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,4 @@
11
<?php
2-
/**
3-
* Pterodactyl - Panel
4-
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
5-
*
6-
* This software is licensed under the terms of the MIT license.
7-
* https://opensource.org/licenses/MIT
8-
*/
92

103
namespace Pterodactyl\Exceptions\Repository;
114

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,20 @@
11
<?php
2-
/**
3-
* Pterodactyl - Panel
4-
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
5-
*
6-
* This software is licensed under the terms of the MIT license.
7-
* https://opensource.org/licenses/MIT
8-
*/
92

103
namespace Pterodactyl\Exceptions\Repository;
114

12-
class RecordNotFoundException extends \Exception
5+
class RecordNotFoundException extends RepositoryException
136
{
147
/**
15-
* @return int
8+
* Handle request to render this exception to a user. Returns the default
9+
* 404 page view.
10+
*
11+
* @param \Illuminate\Http\Request $request
12+
* @return \Illuminate\Http\Response
1613
*/
17-
public function getStatusCode()
14+
public function render($request)
1815
{
19-
return 404;
16+
if (! config('app.debug')) {
17+
return response()->view('errors.404', [], 404);
18+
}
2019
}
2120
}
Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
11
<?php
2-
/**
3-
* Pterodactyl - Panel
4-
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
5-
*
6-
* This software is licensed under the terms of the MIT license.
7-
* https://opensource.org/licenses/MIT
8-
*/
92

103
namespace Pterodactyl\Exceptions\Repository;
114

12-
class RepositoryException extends \Exception
5+
use Pterodactyl\Exceptions\PterodactylException;
6+
7+
class RepositoryException extends PterodactylException
138
{
149
}

0 commit comments

Comments
 (0)