Skip to content
This repository was archived by the owner on Feb 14, 2023. It is now read-only.

Bug fixes for hiding private data when handling errors #61

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
2 changes: 1 addition & 1 deletion src/Concerns/Exceptions/Laravel/Api.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ trait Api
{
protected function convertValidationExceptionToResponse(ValidationException $e, $request)
{
return $this->response($e);
return $this->prepareJsonResponse($request, $e);
}
}
2 changes: 2 additions & 0 deletions src/Contracts/Responsable.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

interface Responsable
{
public static function hidePrivate(bool $hide): void;

public static function allowWith(): void;

public static function withoutWith(): void;
Expand Down
25 changes: 23 additions & 2 deletions src/Exceptions/Laravel/BaseHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Helldar\ApiResponse\Exceptions\Laravel;

use Helldar\ApiResponse\Services\Response;
use Helldar\Support\Facades\Helpers\Arr;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
Expand Down Expand Up @@ -40,14 +41,14 @@ protected function prepareJsonResponse($request, Throwable $e)

protected function getExceptionMessage(Throwable $e)
{
$converted = parent::convertExceptionToArray($e);
$converted = $this->convertExceptionToArray($e);

return Arr::get($converted, 'message');
}

protected function getExceptionTrace(Throwable $e)
{
$converted = parent::convertExceptionToArray($e);
$converted = $this->convertExceptionToArray($e);

return ['info' => Arr::except($converted, 'message')];
}
Expand All @@ -68,4 +69,24 @@ protected function response($data, int $status_code = null, array $with = [], ar

return api_response($data, $status_code, $with, $headers);
}

protected function convertExceptionToArray(Throwable $e)
{
return Response::$hide_private
? ['message' => $this->isHttpException($e) ? $e->getMessage() : 'Server Error']
: [
'message' => $e->getMessage(),
'exception' => get_class($e),
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => $this->getTrace($e),
];
}

protected function getTrace(Throwable $e): array
{
return collect($e->getTrace())->map(static function ($trace) {
return Arr::except($trace, ['args']);
})->all();
}
}
5 changes: 1 addition & 4 deletions src/Exceptions/Laravel/Eight/ApiHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
use Helldar\ApiResponse\Exceptions\Laravel\BaseHandler;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Contracts\Support\Responsable;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Routing\Router;
use Illuminate\Validation\ValidationException;
use Throwable;
Expand Down Expand Up @@ -37,9 +36,7 @@ public function render($request, Throwable $e)
}
}

if ($e instanceof HttpResponseException) {
return $this->response($e);
} elseif ($e instanceof AuthenticationException) {
if ($e instanceof AuthenticationException) {
return $this->response($e, 403);
} elseif ($e instanceof ValidationException) {
return $this->convertValidationExceptionToResponse($e, $request);
Expand Down
32 changes: 30 additions & 2 deletions src/Services/Response.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,22 @@
use Helldar\ApiResponse\Contracts\Parseable;
use Helldar\ApiResponse\Contracts\Resolver as ResolverContract;
use Helldar\ApiResponse\Contracts\Responsable;
use Helldar\ApiResponse\Contracts\Wrapper;
use Helldar\ApiResponse\Support\Parser;
use Helldar\ApiResponse\Wrappers\Error;
use Helldar\ApiResponse\Wrappers\Resolver;
use Helldar\ApiResponse\Wrappers\Success;
use Helldar\Support\Concerns\Makeable;
use Illuminate\Http\Resources\Json\JsonResource;
use Symfony\Component\HttpFoundation\JsonResponse;

final class Response implements Responsable
{
use Makeable;

/** @var bool */
public static $hide_private = true;

/** @var bool */
public static $allow_with = true;

Expand All @@ -34,6 +39,11 @@ final class Response implements Responsable
/** @var array */
protected $headers = [];

public static function hidePrivate(bool $hide = true): void
{
self::$hide_private = $hide;
}

public static function allowWith(): void
{
self::$allow_with = true;
Expand All @@ -47,11 +57,19 @@ public static function withoutWith(): void
public static function wrapped(): void
{
self::$wrap = true;

if (class_exists(JsonResource::class)) {
JsonResource::wrap('data');
}
}

public static function withoutWrap(): void
{
self::$wrap = false;

if (class_exists(JsonResource::class)) {
JsonResource::withoutWrapping();
}
}

public function with(array $with = []): Responsable
Expand Down Expand Up @@ -103,13 +121,13 @@ protected function getParser(): Parseable
->resolve();
}

protected function getWrapped(Parseable $parser)
protected function getWrapped(Parseable $parser): Wrapper
{
$wrapper = $this->getWrapper($parser);

return $wrapper::make()
->wrap(self::$wrap)
->allowWith(self::$allow_with)
->allowWith(! $this->isHidePrivate($parser) && $this->isAllowWith())
->parser($parser)
->resolver($this->resolver());
}
Expand All @@ -128,4 +146,14 @@ protected function resolver(): ResolverContract
{
return Resolver::make();
}

protected function isHidePrivate(Parseable $parser): bool
{
return $parser->isError() && self::$hide_private;
}

protected function isAllowWith(): bool
{
return self::$allow_with;
}
}
12 changes: 0 additions & 12 deletions tests/Fixtures/Concerns/Laravel/Exceptionable.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

namespace Tests\Fixtures\Concerns\Laravel;

use Exception;
use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode;
use Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance;

Expand All @@ -28,17 +27,6 @@ protected function removeDownFile(): void
@unlink(storage_path('framework/down'));
}

protected function setRoutes($app): void
{
$app['router']->get('/foo', static function () {
return api_response('ok');
})->middleware($this->getMaintenanceMiddleware());

$app['router']->get('/bar', static function () {
throw new Exception('Foo Bar');
})->middleware($this->getMaintenanceMiddleware());
}

protected function getMaintenanceMiddleware(): string
{
return $this->isSeven()
Expand Down
5 changes: 5 additions & 0 deletions tests/Fixtures/Concerns/Laravel/Requests.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ protected function requestBar(): Response
return $this->request('/bar');
}

protected function requestBaz(): Response
{
return $this->request('/baz');
}

protected function request(string $uri): Response
{
return $this->response(
Expand Down
17 changes: 17 additions & 0 deletions tests/Fixtures/Concerns/Responsable.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,28 @@

namespace Tests\Fixtures\Concerns;

use Exception;
use Tests\Fixtures\Entities\Response;
use Tests\Fixtures\Exceptions\FooHttpException;

/** @mixin \Tests\Laravel\TestCase */
trait Responsable
{
protected function setRoutes($app): void
{
$app['router']->get('/foo', static function () {
return api_response('ok', null, ['foo' => 'Foo']);
})->middleware($this->getMaintenanceMiddleware());

$app['router']->get('/bar', static function () {
throw new Exception('Foo Bar');
})->middleware($this->getMaintenanceMiddleware());

$app['router']->get('/baz', static function () {
throw new FooHttpException('Foo Http');
})->middleware($this->getMaintenanceMiddleware());
}

protected function response($data = null, int $status_code = null, array $with = []): Response
{
return new Response($data, $status_code, $with);
Expand Down
14 changes: 0 additions & 14 deletions tests/Fixtures/Contracts/Parserable.php

This file was deleted.

16 changes: 16 additions & 0 deletions tests/Fixtures/Contracts/Testable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Tests\Fixtures\Contracts;

interface Testable
{
public function testInstance();

public function testType();

public function testStructureSuccess();

public function testStructureErrors();

public function testStatusCode();
}
5 changes: 4 additions & 1 deletion tests/Fixtures/Entities/Response.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ public function getStatusCode(): int
return $this->response->getStatusCode();
}

public function instance(): JsonResponse
/**
* @return mixed|\Symfony\Component\HttpFoundation\JsonResponse|\Illuminate\Testing\TestResponse||null
*/
public function instance()
{
return $this->response;
}
Expand Down
2 changes: 1 addition & 1 deletion tests/Fixtures/Exceptions/FooException.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ final class FooException extends Exception
{
public function __construct(string $message)
{
parent::__construct($message, 405);
parent::__construct($message, 502);
}
}
13 changes: 13 additions & 0 deletions tests/Fixtures/Exceptions/FooHttpException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Tests\Fixtures\Exceptions;

use Symfony\Component\HttpKernel\Exception\HttpException;

final class FooHttpException extends HttpException
{
public function __construct(string $message, int $status_code = 403)
{
parent::__construct($status_code, $message);
}
}
5 changes: 5 additions & 0 deletions tests/Fixtures/Laravel/Resources/Bar.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,9 @@ public function toArray($request)
'qwerty' => $this->bar,
];
}

public function with($request)
{
return ['baz' => 'Baz'];
}
}
6 changes: 6 additions & 0 deletions tests/Fixtures/Laravel/Resources/Foo.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ public function toArray($request)
'foo' => $this->foo,

'bar' => Bar::make($this->bar),
];
}

public function with($request)
{
return [
'baz' => Bar::make($this->whenLoaded('bar')),
];
}
Expand Down
53 changes: 53 additions & 0 deletions tests/Laravel/Parsers/Exception/NoWithDataHideTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

namespace Tests\Laravel\Parsers\Exception;

use Illuminate\Testing\TestResponse;
use Tests\Fixtures\Concerns\Laravel\Requests;
use Tests\Laravel\TestCase;

final class NoWithDataHideTest extends TestCase
{
use Requests;

protected $with = false;

public function testInstance()
{
$this->assertTrue($this->requestFoo()->instance() instanceof TestResponse);
$this->assertTrue($this->requestBar()->instance() instanceof TestResponse);
$this->assertTrue($this->requestBaz()->instance() instanceof TestResponse);
}

public function testType()
{
$this->assertJson($this->requestFoo()->getRaw());
$this->assertJson($this->requestBar()->getRaw());
$this->assertJson($this->requestBaz()->getRaw());
}

public function testStructureSuccess()
{
$this->assertSame(['data' => 'ok'], $this->requestFoo()->getJson());
}

public function testStructureErrors()
{
$this->assertSame(
['error' => ['type' => 'Exception', 'data' => 'Whoops! Something went wrong.']],
$this->requestBar()->getJson()
);

$this->assertSame(
['error' => ['type' => 'FooHttpException', 'data' => 'Foo Http']],
$this->requestBaz()->getJson()
);
}

public function testStatusCode()
{
$this->assertSame(200, $this->requestFoo()->getStatusCode());
$this->assertSame(500, $this->requestBar()->getStatusCode());
$this->assertSame(403, $this->requestBaz()->getStatusCode());
}
}
Loading