Skip to content

Commit

Permalink
feat!: wrap node messages in proper class
Browse files Browse the repository at this point in the history
A new class `NodeMessage` is used to wrap messages added to a node
during the mapping. This class will allow further features by giving
access to useful data related to the bound node.

BREAKING CHANGE: as of now every message is wrapped into a `NodeMessage`
it is therefore not possible to check whether the message is an instance
of `Throwable` — a new method `$message->isError()` is now to be used
for such cases.
  • Loading branch information
romm committed Jan 6, 2022
1 parent 2c7e115 commit a805ba0
Show file tree
Hide file tree
Showing 19 changed files with 309 additions and 107 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ try {
// All messages bound to the node can be accessed
foreach ($node->messages() as $message) {
// Errors can be retrieved by filtering like below:
if ($message instanceof Throwable) {
if ($message->isError()) {
// Do something…
}
}
Expand Down
21 changes: 0 additions & 21 deletions src/Mapper/Object/Exception/ObjectConstructionError.php

This file was deleted.

4 changes: 2 additions & 2 deletions src/Mapper/Object/MethodObjectBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
use CuyZ\Valinor\Mapper\Object\Exception\InvalidSourceForObject;
use CuyZ\Valinor\Mapper\Object\Exception\MethodNotFound;
use CuyZ\Valinor\Mapper\Object\Exception\MissingMethodArgument;
use CuyZ\Valinor\Mapper\Object\Exception\ObjectConstructionError;
use CuyZ\Valinor\Mapper\Tree\Message\ThrowableMessage;
use CuyZ\Valinor\Type\Types\ClassType;
use Exception;

Expand Down Expand Up @@ -102,7 +102,7 @@ public function build(array $arguments): object
/** @infection-ignore-all */
return $className::$methodName(...array_values($arguments)); // @phpstan-ignore-line
} catch (Exception $exception) {
throw new ObjectConstructionError($exception);
throw ThrowableMessage::from($exception);
}
}

Expand Down
124 changes: 124 additions & 0 deletions src/Mapper/Tree/Message/NodeMessage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<?php

declare(strict_types=1);

namespace CuyZ\Valinor\Mapper\Tree\Message;

use CuyZ\Valinor\Definition\Attributes;
use CuyZ\Valinor\Mapper\Tree\Node;
use CuyZ\Valinor\Type\Type;
use Throwable;

use function sprintf;

final class NodeMessage implements Message, HasCode
{
private Node $node;

private Message $message;

public function __construct(Node $node, Message $message)
{
$this->node = $node;
$this->message = $message;
}

/**
* Performs a placeholders replace operation on the given content.
*
* The values to be replaced will be the ones given as second argument; if
* none is given these values will be used instead, in order:
*
* 1. The original code of this message
* 2. The original content of this message
* 3. A string representation of the node type
* 4. The name of the node
* 5. The path of the node
*
* See usage examples below:
*
* ```
* $content = $message->format('the previous code was: %1$s');
*
* $content = $message->format(
* '%1$s / new message content (type: %2$s)',
* 'some parameter',
* $message->type(),
* );
* ```
*/
public function format(string $content, string ...$values): string
{
return sprintf($content, ...$values ?: [
$this->code(),
(string)$this,
(string)$this->type(),
$this->name(),
$this->path(),
]);
}

public function name(): string
{
return $this->node->name();
}

public function path(): string
{
return $this->node->path();
}

public function type(): Type
{
return $this->node->type();
}

public function attributes(): Attributes
{
return $this->node->attributes();
}

/**
* @return mixed
*/
public function value()
{
if (! $this->node->isValid()) {
return null;
}

return $this->node->value();
}

public function originalMessage(): Message
{
return $this->message;
}

public function isError(): bool
{
return $this->message instanceof Throwable;
}

public function code(): string
{
if ($this->message instanceof HasCode) {
return $this->message->code();
}

if ($this->message instanceof Throwable) {
return (string)$this->message->getCode();
}

return 'unknown';
}

public function __toString(): string
{
if ($this->message instanceof Throwable) {
return $this->message->getMessage();
}

return (string)$this->message;
}
}
26 changes: 22 additions & 4 deletions src/Mapper/Tree/Message/ThrowableMessage.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,37 @@
namespace CuyZ\Valinor\Mapper\Tree\Message;

use RuntimeException;
use Throwable;

final class ThrowableMessage extends RuntimeException implements Message, HasCode
{
public function __construct(string $message, string $code)
public static function new(string $message, string $code): self
{
parent::__construct($message);
$instance = new self($message);
$instance->code = $code;

$this->code = $code;
return $instance;
}

/**
* @return Message&Throwable
*/
public static function from(Throwable $message): Message
{
if ($message instanceof Message) {
return $message;
}

/** @infection-ignore-all */
$instance = new self($message->getMessage(), 0, $message);
$instance->code = (string)$message->getCode();

return $instance;
}

public function code(): string
{
return $this->code;
return (string)$this->code;
}

public function __toString(): string
Expand Down
12 changes: 6 additions & 6 deletions src/Mapper/Tree/Node.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use CuyZ\Valinor\Mapper\Tree\Exception\DuplicatedNodeChild;
use CuyZ\Valinor\Mapper\Tree\Exception\InvalidNodeValue;
use CuyZ\Valinor\Mapper\Tree\Message\Message;
use CuyZ\Valinor\Mapper\Tree\Message\NodeMessage;
use CuyZ\Valinor\Type\Type;
use Throwable;

Expand All @@ -22,7 +23,7 @@ final class Node
/** @var array<Node> */
private array $children = [];

/** @var array<Message> */
/** @var array<NodeMessage> */
private array $messages = [];

private bool $valid = true;
Expand Down Expand Up @@ -137,18 +138,17 @@ public function children(): array

public function withMessage(Message $message): self
{
$message = new NodeMessage($this, $message);

$clone = clone $this;
$clone->messages = [...$this->messages, $message];

if ($message instanceof Throwable) {
$clone->valid = false;
}
$clone->valid = $clone->valid && ! $message->isError();

return $clone;
}

/**
* @return array<Message>
* @return array<NodeMessage>
*/
public function messages(): array
{
Expand Down
7 changes: 6 additions & 1 deletion tests/Fake/Mapper/Tree/Message/FakeErrorMessage.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,13 @@

final class FakeErrorMessage extends Exception implements Message
{
public function __construct(string $message = 'some error message')
{
parent::__construct($message);
}

public function __toString(): string
{
return 'some error message';
return $this->message;
}
}
17 changes: 15 additions & 2 deletions tests/Fake/Mapper/Tree/Message/FakeMessage.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,25 @@

namespace CuyZ\Valinor\Tests\Fake\Mapper\Tree\Message;

use CuyZ\Valinor\Mapper\Tree\Message\HasCode;
use CuyZ\Valinor\Mapper\Tree\Message\Message;

final class FakeMessage implements Message
final class FakeMessage implements Message, HasCode
{
private string $message;

public function __construct(string $message = 'some message')
{
$this->message = $message;
}

public function __toString(): string
{
return 'some message';
return $this->message;
}

public function code(): string
{
return 'some_code';
}
}
5 changes: 2 additions & 3 deletions tests/Integration/IntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
use CuyZ\Valinor\MapperBuilder;
use FilesystemIterator;
use PHPUnit\Framework\TestCase;
use Throwable;

use function implode;
use function is_dir;
Expand Down Expand Up @@ -61,8 +60,8 @@ protected function mappingFail(MappingError $error)
$errors = [];

foreach ($node->messages() as $message) {
if ($message instanceof Throwable) {
$errors[] = $message->getMessage();
if ($message->isError()) {
$errors[] = (string)$message;
}
}

Expand Down
17 changes: 4 additions & 13 deletions tests/Integration/Mapping/Object/ArrayValuesMappingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,9 @@
namespace CuyZ\Valinor\Tests\Integration\Mapping\Object;

use CuyZ\Valinor\Mapper\MappingError;
use CuyZ\Valinor\Mapper\Tree\Exception\CannotCastToScalarValue;
use CuyZ\Valinor\Mapper\Tree\Exception\InvalidNodeValue;
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
use CuyZ\Valinor\Tests\Integration\Mapping\Fixture\SimpleObject;
use CuyZ\Valinor\Tests\Integration\Mapping\Fixture\SimpleObject as SimpleObjectAlias;
use Throwable;

final class ArrayValuesMappingTest extends IntegrationTest
{
Expand Down Expand Up @@ -78,11 +75,8 @@ public function test_empty_array_in_non_empty_array_throws_exception(): void
} catch (MappingError $exception) {
$error = $exception->node()->children()['nonEmptyArraysOfStrings']->messages()[0];

assert($error instanceof Throwable);

self::assertInstanceOf(InvalidNodeValue::class, $error);
self::assertSame(1630678334, $error->getCode());
self::assertSame('Empty array is not accepted by `non-empty-array<string>`.', $error->getMessage());
self::assertSame('1630678334', $error->code());
self::assertSame('Empty array is not accepted by `non-empty-array<string>`.', (string)$error);
}
}

Expand All @@ -95,11 +89,8 @@ public function test_value_that_cannot_be_casted_throws_exception(): void
} catch (MappingError $exception) {
$error = $exception->node()->children()['integers']->children()[0]->messages()[0];

assert($error instanceof Throwable);

self::assertInstanceOf(CannotCastToScalarValue::class, $error);
self::assertSame(1618736242, $error->getCode());
self::assertSame('Cannot cast value of type `string` to `int`.', $error->getMessage());
self::assertSame('1618736242', $error->code());
self::assertSame('Cannot cast value of type `string` to `int`.', (string)$error);
}
}
}
Expand Down
Loading

0 comments on commit a805ba0

Please sign in to comment.