Skip to content

Commit

Permalink
Merge pull request #837 from PennyDreadfulMTG/validation
Browse files Browse the repository at this point in the history
Validation comes to Gatherling, a little
  • Loading branch information
bakert authored Nov 11, 2024
2 parents d164519 + d8cc072 commit ae23569
Show file tree
Hide file tree
Showing 30 changed files with 660 additions and 94 deletions.
17 changes: 17 additions & 0 deletions gatherling/Exceptions/BadRequestException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

declare(strict_types=1);

namespace Gatherling\Exceptions;

use Throwable;

abstract class BadRequestException extends GatherlingException
{
public function __construct(string $message = "", int $code = 0, ?Throwable $previous = null, int $httpStatusCode = 400)
{
parent::__construct($message, $code, $previous, $httpStatusCode);
}

abstract public function getUserMessage(): string;
}
14 changes: 13 additions & 1 deletion gatherling/Exceptions/GatherlingException.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,19 @@
namespace Gatherling\Exceptions;

use Exception;
use Throwable;

class GatherlingException extends Exception
abstract class GatherlingException extends Exception
{
public readonly int $httpStatusCode;

public function __construct(
string $message = "",
int $code = 0,
?Throwable $previous = null,
int $httpStatusCode = 500,
) {
parent::__construct($message, $code, $previous);
$this->httpStatusCode = $httpStatusCode;
}
}
16 changes: 11 additions & 5 deletions gatherling/Exceptions/MarshalException.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,18 @@

namespace Gatherling\Exceptions;

use Gatherling\Helpers\Types\Type;
use Gatherling\Helpers\Types\TypeMismatch;

class MarshalException extends GatherlingException
{
public function __construct(public mixed $value, public string $typeRequested)
{
$type = gettype($this->value);
$repr = var_export($this->value, true);
parent::__construct("Unable to marshal variable of type $type as $this->typeRequested: $repr");
public function __construct(
public readonly mixed $value,
public readonly Type $expectedType,
public readonly TypeMismatch $typeMismatch
) {
$type = gettype($value);
$repr = var_export($value, true);
parent::__construct("Unable to marshal variable of gettype $type as {$expectedType->getTypeName()} due to $typeMismatch->value: $repr");
}
}
13 changes: 12 additions & 1 deletion gatherling/Exceptions/NotFoundException.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@

namespace Gatherling\Exceptions;

class NotFoundException extends GatherlingException
use Throwable;

class NotFoundException extends BadRequestException
{
public function __construct(string $message = "", int $code = 0, ?Throwable $previous = null)
{
parent::__construct($message, $code, $previous, 404);
}

public function getUserMessage(): string
{
return 'The requested resource was not found';
}
}
36 changes: 36 additions & 0 deletions gatherling/Exceptions/RequestException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace Gatherling\Exceptions;

use Gatherling\Helpers\Types\Type;
use Throwable;

class RequestException extends BadRequestException
{
public function __construct(
private string $key,
private Type $expectedType,
private mixed $value,
Throwable $previous
) {
$s = "Error processing request parameter '{$key}', expecting {$expectedType->getTypeName()} but got " . var_export($value, true);
parent::__construct($s, 0, $previous);
}

public function getUserMessage(): string
{
$s = "The {$this->key} field requires {$this->expectedType->getDisplayName($this->value)} but it was ";
if ($this->value === null) {
$s .= "missing";
} elseif ($this->value === '') {
$s .= "blank";
} elseif (is_scalar($this->value)) {
$s .= "'{$this->value}'";
} else {
$s .= "invalid";
}
return $s;
}
}
13 changes: 13 additions & 0 deletions gatherling/Exceptions/ValidationException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Gatherling\Exceptions;

class ValidationException extends BadRequestException
{
public function getUserMessage(): string
{
return $this->getMessage();
}
}
36 changes: 36 additions & 0 deletions gatherling/Helpers/ErrorHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace Gatherling\Helpers;

use Gatherling\Exceptions\BadRequestException;
use Gatherling\Exceptions\GatherlingException;
use Gatherling\Views\Pages\Error;
use Throwable;

class ErrorHandler
{
public function handle(Throwable $e): never
{
$requestId = Request::getRequestId();

$message = (string)$e;

if ($e instanceof BadRequestException) {
logger()->warning($message);
$userMessage = $e->getUserMessage();
} else {
logger()->error($message);
$userMessage = "Oops! Something went wrong.";
}

if ($e instanceof GatherlingException) {
$httpStatusCode = $e->httpStatusCode;
} else {
$httpStatusCode = 500;
}
$page = new Error($requestId, $userMessage, $httpStatusCode);
$page->send();
}
}
46 changes: 25 additions & 21 deletions gatherling/Helpers/Marshaller.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
namespace Gatherling\Helpers;

use Gatherling\Exceptions\MarshalException;
use Gatherling\Helpers\Types\DictType;
use Gatherling\Helpers\Types\ListType;
use Gatherling\Helpers\Types\SimpleType;
use Gatherling\Helpers\Types\TypeMismatch;

class Marshaller
{
Expand All @@ -19,7 +23,7 @@ public function int(int|false $default = false): int
if ($default !== false) {
return $default;
}
throw new MarshalException($this->value, 'int');
throw new MarshalException($this->value, SimpleType::INT, TypeMismatch::NULL);
}
return $value;
}
Expand All @@ -41,7 +45,7 @@ public function string(string|false $default = false): string
if ($default !== false) {
return $default;
}
throw new MarshalException($this->value, 'string');
throw new MarshalException($this->value, SimpleType::STRING, TypeMismatch::NULL);
}
return $value;
}
Expand All @@ -52,7 +56,7 @@ public function optionalString(): ?string
return null;
}
if (!is_string($this->value)) {
throw new MarshalException($this->value, 'optionalString');
throw new MarshalException($this->value, SimpleType::STRING, TypeMismatch::NOT_OF_TYPE);
}
return $this->value;
}
Expand All @@ -64,7 +68,7 @@ public function float(float|false $default = false): float
if ($default !== false) {
return $default;
}
throw new MarshalException($this->value, 'float');
throw new MarshalException($this->value, SimpleType::FLOAT, TypeMismatch::NULL);
}
return $value;
}
Expand All @@ -75,7 +79,7 @@ public function optionalFloat(): ?float
return null;
}
if (!is_numeric($this->value)) {
throw new MarshalException($this->value, 'optionalFloat');
throw new MarshalException($this->value, SimpleType::FLOAT, TypeMismatch::NOT_OF_TYPE);
}
return (float) $this->value;
}
Expand All @@ -87,12 +91,12 @@ public function ints(): array
return [];
}
if (!is_array($this->value)) {
throw new MarshalException($this->value, 'listInt');
throw new MarshalException($this->value, ListType::int(), TypeMismatch::NOT_ARRAY);
}
$result = [];
foreach ($this->value as $value) {
if (!is_numeric($value)) {
throw new MarshalException($value, 'listIntEntry');
throw new MarshalException($value, ListType::int(), TypeMismatch::INVALID_VALUE_TYPE);
}
$this->strictIntCheck($value);
$result[] = (int) $value;
Expand All @@ -108,12 +112,12 @@ public function strings(): array
return [];
}
if (!is_array($this->value)) {
throw new MarshalException($this->value, 'listString');
throw new MarshalException($this->value, ListType::string(), TypeMismatch::NOT_ARRAY);
}
$result = [];
foreach ($this->value as $value) {
if (!is_string($value)) {
throw new MarshalException($value, 'listStringEntry');
throw new MarshalException($value, ListType::string(), TypeMismatch::INVALID_VALUE_TYPE);
}
$result[] = $value;
}
Expand All @@ -127,7 +131,7 @@ public function dictIntOrString(): array
return [];
}
if (!is_array($this->value)) {
throw new MarshalException($this->value, 'dictIntOrString');
throw new MarshalException($this->value, DictType::intOrString(), TypeMismatch::NOT_ARRAY);
}
$result = [];
foreach ($this->value as $key => $value) {
Expand All @@ -143,7 +147,7 @@ public function dictIntOrString(): array
}
}
}
throw new MarshalException($value, 'dictIntOrStringEntry');
throw new MarshalException($value, DictType::intOrString(), TypeMismatch::INVALID_VALUE_TYPE);
}
return $result;
}
Expand All @@ -155,15 +159,15 @@ public function dictInt(): array
return [];
}
if (!is_array($this->value)) {
throw new MarshalException($this->value, 'dictInt');
throw new MarshalException($this->value, DictType::int(), TypeMismatch::NOT_ARRAY);
}
$result = [];
foreach ($this->value as $key => $value) {
if (!is_string($key)) {
throw new MarshalException($key, 'dictIntKey');
throw new MarshalException($key, DictType::int(), TypeMismatch::INVALID_KEY_TYPE);
}
if (!is_int($value)) {
throw new MarshalException($value, 'dictIntValue');
throw new MarshalException($value, DictType::int(), TypeMismatch::INVALID_VALUE_TYPE);
}
$result[$key] = $value;
}
Expand All @@ -177,15 +181,15 @@ public function dictString(): array
return [];
}
if (!is_array($this->value)) {
throw new MarshalException($this->value, 'dictString');
throw new MarshalException($this->value, DictType::string(), TypeMismatch::NOT_ARRAY);
}
$result = [];
foreach ($this->value as $key => $value) {
if (!is_string($key)) {
throw new MarshalException($key, 'dictStringKey');
throw new MarshalException($key, DictType::string(), TypeMismatch::INVALID_KEY_TYPE);
}
if (!is_string($value)) {
throw new MarshalException($value, 'dictStringValue');
throw new MarshalException($value, DictType::string(), TypeMismatch::INVALID_VALUE_TYPE);
}
$result[$key] = $value;
}
Expand All @@ -194,15 +198,15 @@ public function dictString(): array

private function strictIntCheck(mixed $value): void
{
if (!is_scalar($value) && !is_null($value)) {
throw new MarshalException($value, 'strictIntCheckScalar');
if (!is_scalar($value) && $value !== null) {
throw new MarshalException($value, SimpleType::INT, TypeMismatch::NOT_SCALAR);
}
$canBeInt = is_int($value) || is_float($value) || (is_string($value) && is_numeric($value));
if (!$canBeInt) {
throw new MarshalException($value, 'strictIntCheckInt');
throw new MarshalException($value, SimpleType::INT, TypeMismatch::NOT_INT_COMPATIBLE);
}
if ((string)(int) $value !== (string) $value) {
throw new MarshalException($value, 'strictIntCheckDetail');
throw new MarshalException($value, SimpleType::INT, TypeMismatch::NOT_WHOLE_NUMBER);
}
}
}
Loading

0 comments on commit ae23569

Please sign in to comment.