Skip to content

Commit

Permalink
Merge pull request #11 from KentarouTakeda/refactor-error-renerer
Browse files Browse the repository at this point in the history
Refactor error renerer and add to README
  • Loading branch information
KentarouTakeda authored Jan 1, 2024
2 parents 0891966 + 35a1bb9 commit cd585a9
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 24 deletions.
56 changes: 56 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,62 @@ Check the comments in [config/openapi-validator.php](config/openapi-validator.ph
];
```
### Error responses and customization
By default, it is formatted according to
[RFC 7807 - Problem Details for HTTP APIs](https://datatracker.ietf.org/doc/html/rfc7807).
Validation errors and stack traces can also be included depending
on your settings. For example, it might look like this:
```json
{
"title": "NoResponseCode",
"detail": "OpenAPI spec contains no such operation [/,get,201]", // Error reason
"status": 500, // Same as HTTP response code
"trace": [
{ "error": "...", "message": "...", "file": "...", "line": ... },
...
]
}
```
Here's how to change to a different format:

1. First, implement a class to generate a response. For example:

```php
class MyErrorRenderer implements ErrorRendererInterface
{
public function render(
Request $request,
\Throwable $error,
ErrorType $errorType,
): Response {
return new Response(
match ($errorType) {
ErrorType::Request => "Request Error: " . $error->getMessage(),
ErrorType::Response => "Response Error: " . $error->getMessage(),
}
);
}
}
```

2. Next, register the class to the service container.

```php
// AppServiceProvider.php

public function register(): void
{
$this->app->bind(
ErrorRendererInterface::class,
Rfc7807Renderer::class
);
}
```

## Contributing and Development

```bash
Expand Down
8 changes: 1 addition & 7 deletions src/ErrorRendererInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,10 @@ interface ErrorRendererInterface
{
/**
* Render the exception that occurred during processing as a response
*
* @param int $status Specify the response code. This *MAY* be overwritten depending on the type of exception.
* @param bool $includePointer Whether to include a validation error pointer in the response
* @param bool $includeTrace Whether to include a stack trace in the response
*/
public function render(
Request $request,
\Throwable $error,
int $status,
bool $includePointer,
bool $includeTrace,
ErrorType $errorType,
): Response;
}
18 changes: 18 additions & 0 deletions src/ErrorType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace KentarouTakeda\Laravel\OpenApiValidator;

enum ErrorType
{
/**
* Request validation error
*/
case Request;

/**
* Response validation error
*/
case Response;
}
9 changes: 3 additions & 6 deletions src/Http/Middleware/OpenApiValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Illuminate\Log\LogManager;
use KentarouTakeda\Laravel\OpenApiValidator\Config\Config;
use KentarouTakeda\Laravel\OpenApiValidator\ErrorRendererInterface;
use KentarouTakeda\Laravel\OpenApiValidator\ErrorType;
use KentarouTakeda\Laravel\OpenApiValidator\SchemaRepository\SchemaRepository;
use League\OpenAPIValidation\PSR7\Exception\NoPath;
use League\OpenAPIValidation\PSR7\Exception\ValidationFailed;
Expand Down Expand Up @@ -162,9 +163,7 @@ private function renderResponseError(Request $request, \Throwable $error): Respo
return $this->errorRenderer->render(
$request,
$error,
Response::HTTP_INTERNAL_SERVER_ERROR,
$this->config->getIncludeResErrorDetailInResponse(),
$this->config->getIncludeTraceInResponse(),
ErrorType::Response
);
}

Expand All @@ -173,9 +172,7 @@ private function renderRequestError(Request $request, \Throwable $error): Respon
return $this->errorRenderer->render(
$request,
$error,
Response::HTTP_BAD_REQUEST,
$this->config->getIncludeReqErrorDetailInResponse(),
false,
ErrorType::Request,
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
use Illuminate\Database\RecordsNotFoundException;
use Illuminate\Routing\Exceptions\BackedEnumCaseNotFoundException;
use Illuminate\Session\TokenMismatchException;
use KentarouTakeda\Laravel\OpenApiValidator\Config\Config;
use KentarouTakeda\Laravel\OpenApiValidator\ErrorRendererInterface;
use KentarouTakeda\Laravel\OpenApiValidator\ErrorType;
use League\OpenAPIValidation\Schema\Exception\SchemaMismatch;
use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException;
use Symfony\Component\HttpFoundation\Request;
Expand All @@ -19,33 +21,35 @@
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

class ErrorRenderer implements ErrorRendererInterface
class Rfc7807Renderer implements ErrorRendererInterface
{
public function __construct(
private readonly ResponseFactory $responseFactory,
private readonly Config $config,
) {
}

public function render(
Request $request,
\Throwable $error,
int $status,
bool $includePointer,
bool $includeTrace,
ErrorType $errorType,
): Response {
$error = $this->prepareException($error);

if ($error instanceof HttpException) {
$status = $error->getStatusCode();
}
$status = $error instanceof HttpException ?
$error->getStatusCode() :
match ($errorType) {
ErrorType::Request => Response::HTTP_BAD_REQUEST,
ErrorType::Response => Response::HTTP_INTERNAL_SERVER_ERROR,
};

$json = [
'title' => class_basename($error),
'detail' => $error->getMessage() ?: null,
'status' => $status,
];

if ($includePointer) {
if ($this->shouldIncludePointer($errorType)) {
$schemaMismatch = $this->findSchemaMismatch($error);

if ($schemaMismatch) {
Expand All @@ -54,7 +58,7 @@ public function render(
}
}

if ($includeTrace) {
if ($this->config->getIncludeTraceInResponse()) {
$json['trace'] = $this->extractTrace($error);
}

Expand All @@ -67,6 +71,14 @@ public function render(
);
}

private function shouldIncludePointer(ErrorType $errorType): bool
{
return match ($errorType) {
ErrorType::Request => $this->config->getIncludeReqErrorDetailInResponse(),
ErrorType::Response => $this->config->getIncludeResErrorDetailInResponse(),
};
}

private function findSchemaMismatch(\Throwable $error): ?SchemaMismatch
{
while ($error) {
Expand Down
4 changes: 2 additions & 2 deletions src/ServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
use KentarouTakeda\Laravel\OpenApiValidator\Console\Commands\CacheCommand;
use KentarouTakeda\Laravel\OpenApiValidator\Console\Commands\ClearCommand;
use KentarouTakeda\Laravel\OpenApiValidator\Console\Commands\PublishCommand;
use KentarouTakeda\Laravel\OpenApiValidator\Renderer\ErrorRenderer;
use KentarouTakeda\Laravel\OpenApiValidator\Renderer\Rfc7807Renderer;

class ServiceProvider extends BaseServiceProvider
{
Expand All @@ -18,7 +18,7 @@ public function register(): void
{
$this->mergeConfigFrom(self::CONFIG_PATH, 'openapi-validator');

$this->app->bind(ErrorRendererInterface::class, ErrorRenderer::class);
$this->app->bind(ErrorRendererInterface::class, Rfc7807Renderer::class);
}

public function boot(): void
Expand Down

0 comments on commit cd585a9

Please sign in to comment.