Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 34 additions & 44 deletions src/Schema/AbstractObjectSchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
use Chubbyphp\Parsing\Errors;
use Chubbyphp\Parsing\ErrorsException;

abstract class AbstractObjectSchema extends AbstractSchema implements ObjectSchemaInterface
abstract class AbstractObjectSchema extends AbstractSchemaV2 implements ObjectSchemaInterface
{
public const string ERROR_TYPE_CODE = 'abstract_object.type';
public const string ERROR_TYPE_TEMPLATE = 'Type should be "array|\stdClass|\Traversable", {{given}} given';
Expand Down Expand Up @@ -64,54 +64,18 @@ public function __construct(array $fieldToSchema)
}

$this->fieldToSchema = $typeCheckedFieldToSchema;
}

final public function parse(mixed $input): mixed
{
if ($input instanceof \stdClass || $input instanceof \Traversable) {
$input = (array) $input;
}

if ($input instanceof \JsonSerializable) {
$input = $input->jsonSerialize();
}

try {
$input = $this->dispatchPreParses($input);

if (null === $input && $this->nullable) {
return null;
$this->preParses[] = static function (mixed $input) {
if ($input instanceof \stdClass || $input instanceof \Traversable) {
return (array) $input;
}

if (!\is_array($input)) {
throw new ErrorsException(
new Error(
static::ERROR_TYPE_CODE,
static::ERROR_TYPE_TEMPLATE,
['given' => $this->getDataType($input)]
)
);
}

/** @var array<string, mixed> $input */
$childrenErrors = new Errors();

$this->unknownFields($input, $childrenErrors);

$output = $this->parseFields($input, $childrenErrors);

if ($childrenErrors->has()) {
throw new ErrorsException($childrenErrors);
if ($input instanceof \JsonSerializable) {
return $input->jsonSerialize();
}

return $this->dispatchPostParses($output);
} catch (ErrorsException $e) {
if ($this->catch) {
return ($this->catch)($input, $e);
}

throw $e;
}
return $input;
};
}

/**
Expand Down Expand Up @@ -146,6 +110,32 @@ final public function optional(array $optional = []): static
return $clone;
}

protected function innerParse(mixed $input): mixed
{
if (!\is_array($input)) {
throw new ErrorsException(
new Error(
static::ERROR_TYPE_CODE,
static::ERROR_TYPE_TEMPLATE,
['given' => $this->getDataType($input)]
)
);
}

/** @var array<string, mixed> $input */
$childrenErrors = new Errors();

$this->unknownFields($input, $childrenErrors);

$output = $this->parseFields($input, $childrenErrors);

if ($childrenErrors->has()) {
throw new ErrorsException($childrenErrors);
}

return $output;
}

/**
* @param array<string, mixed> $input
*/
Expand Down
3 changes: 3 additions & 0 deletions src/Schema/AbstractSchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
use Chubbyphp\Parsing\ErrorsException;
use Chubbyphp\Parsing\Result;

/**
* @deprecated use Chubbyphp\Parsing\Schem\AbstractSchemaV2
*/
abstract class AbstractSchema implements SchemaInterface
{
protected bool $nullable = false;
Expand Down
126 changes: 126 additions & 0 deletions src/Schema/AbstractSchemaV2.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
<?php

declare(strict_types=1);

namespace Chubbyphp\Parsing\Schema;

use Chubbyphp\Parsing\ErrorsException;
use Chubbyphp\Parsing\Result;

abstract class AbstractSchemaV2 implements SchemaInterface
{
protected bool $nullable = false;

/**
* @var array<\Closure(mixed): mixed>
*/
protected array $preParses = [];

/**
* @var array<\Closure>
*/
protected array $postParses = [];

/**
* @var \Closure(mixed, ErrorsException): mixed
*/
protected ?\Closure $catch = null;

final public function nullable(bool $nullable = true): static
{
$clone = clone $this;
$clone->nullable = $nullable;

return $clone;
}

final public function default(mixed $default): static
{
return $this->preParse(static fn (mixed $input) => $input ?? $default);
}

/**
* @param \Closure(mixed $input): mixed $preParse
*/
final public function preParse(\Closure $preParse): static
{
$clone = clone $this;
$clone->preParses[] = $preParse;

return $clone;
}

final public function postParse(\Closure $postParse): static
{
$clone = clone $this;
$clone->postParses[] = $postParse;

return $clone;
}

final public function parse(mixed $input): mixed
{
try {
$input = $this->dispatchPreParses($input);

if (null === $input && $this->nullable) {
return null;
}

$output = $this->innerParse($input);

return $this->dispatchPostParses($output);
} catch (ErrorsException $e) {
if ($this->catch) {
return ($this->catch)($input, $e);
}

throw $e;
}
}

final public function safeParse(mixed $input): Result
{
try {
return new Result($this->parse($input), null);
} catch (ErrorsException $e) {
return new Result(null, $e);
}
}

/**
* @param \Closure(mixed $input, ErrorsException $e): mixed $catch
*/
final public function catch(\Closure $catch): static
{
$clone = clone $this;
$clone->catch = $catch;

return $clone;
}

abstract protected function innerParse(mixed $input): mixed;

final protected function dispatchPreParses(mixed $data): mixed
{
return array_reduce(
$this->preParses,
static fn (mixed $currentData, \Closure $preParse) => $preParse($currentData),
$data
);
}

final protected function dispatchPostParses(mixed $data): mixed
{
return array_reduce(
$this->postParses,
static fn (mixed $currentData, \Closure $postParse) => $postParse($currentData),
$data
);
}

final protected function getDataType(mixed $input): string
{
return \is_object($input) ? $input::class : \gettype($input);
}
}
78 changes: 32 additions & 46 deletions src/Schema/ArraySchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
use Chubbyphp\Parsing\Errors;
use Chubbyphp\Parsing\ErrorsException;

final class ArraySchema extends AbstractSchema implements SchemaInterface
final class ArraySchema extends AbstractSchemaV2 implements SchemaInterface
{
public const string ERROR_TYPE_CODE = 'array.type';
public const string ERROR_TYPE_TEMPLATE = 'Type should be "array", {{given}} given';
Expand All @@ -27,51 +27,6 @@ final class ArraySchema extends AbstractSchema implements SchemaInterface

public function __construct(private SchemaInterface $itemSchema) {}

public function parse(mixed $input): mixed
{
try {
$input = $this->dispatchPreParses($input);

if (null === $input && $this->nullable) {
return null;
}

if (!\is_array($input)) {
throw new ErrorsException(
new Error(
self::ERROR_TYPE_CODE,
self::ERROR_TYPE_TEMPLATE,
['given' => $this->getDataType($input)]
)
);
}

$array = [];

$childrenErrors = new Errors();

foreach ($input as $i => $item) {
try {
$array[$i] = $this->itemSchema->parse($item);
} catch (ErrorsException $e) {
$childrenErrors->add($e->errors, (string) $i);
}
}

if ($childrenErrors->has()) {
throw new ErrorsException($childrenErrors);
}

return $this->dispatchPostParses($array);
} catch (ErrorsException $e) {
if ($this->catch) {
return ($this->catch)($input, $e);
}

throw $e;
}
}

public function length(int $length): static
{
return $this->postParse(static function (array $array) use ($length) {
Expand Down Expand Up @@ -187,4 +142,35 @@ public function reduce(\Closure $reduce, mixed $initial = null): static
{
return $this->postParse(static fn (array $array) => array_reduce($array, $reduce, $initial));
}

protected function innerParse(mixed $input): mixed
{
if (!\is_array($input)) {
throw new ErrorsException(
new Error(
self::ERROR_TYPE_CODE,
self::ERROR_TYPE_TEMPLATE,
['given' => $this->getDataType($input)]
)
);
}

$output = [];

$childrenErrors = new Errors();

foreach ($input as $i => $item) {
try {
$output[$i] = $this->itemSchema->parse($item);
} catch (ErrorsException $e) {
$childrenErrors->add($e->errors, (string) $i);
}
}

if ($childrenErrors->has()) {
throw new ErrorsException($childrenErrors);
}

return $output;
}
}
Loading