Skip to content

Commit

Permalink
feat: introduce utility class to build messages
Browse files Browse the repository at this point in the history
The new class `\CuyZ\Valinor\Mapper\Tree\Message\MessageBuilder` can be
used to easily create an instance of (error) message.

This new straightforward way of creating messages leads to the
depreciation of `\CuyZ\Valinor\Mapper\Tree\Message\ThrowableMessage`.

```php
$message = MessageBuilder::newError('Some message / {some_parameter}.')
    ->withCode('some_code')
    ->withParameter('some_parameter', 'some_value')
    ->build();
```
  • Loading branch information
romm committed Sep 29, 2022
1 parent de8aa9f commit cb87925
Show file tree
Hide file tree
Showing 6 changed files with 307 additions and 12 deletions.
2 changes: 1 addition & 1 deletion docs/pages/other/dealing-with-dates.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Here is an implementation example for the [nesbot/carbon] library:
// Carbon uses its own exceptions, so we need to wrap it for the mapper
->filterExceptions(function (Throwable $exception) {
if ($exception instanceof \Carbon\Exceptions\Exception) {
return \CuyZ\Valinor\Mapper\Tree\Message\ThrowableMessage::from($exception);
return \CuyZ\Valinor\Mapper\Tree\Message\MessageBuilder::from($exception);
}

throw $exception;
Expand Down
19 changes: 10 additions & 9 deletions docs/pages/validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,21 +110,22 @@ try {
}
```

### 2. Use provided message class
### 2. Use provided message builder

The built-in class `\CuyZ\Valinor\Mapper\Tree\Message\ThrowableMessage` can be
thrown.
The utility class `\CuyZ\Valinor\Mapper\Tree\Message\MessageBuilder` can be used
to build a message.

```php
final class SomeClass
{
public function __construct(private string $value)
{
if ($this->value === 'foo') {
throw \CuyZ\Valinor\Mapper\Tree\Message\ThrowableMessage::new(
'Some custom error message.',
'some_code'
);
if (str_starts_with($this->value, 'foo_')) {
throw \CuyZ\Valinor\Mapper\Tree\Message\MessageBuilder::newError(
'Some custom error message: {value}.'
)
->withCode('some_code')
->withParameter('value', $this->value);
}
}
}
Expand Down Expand Up @@ -161,7 +162,7 @@ try {
(new \CuyZ\Valinor\MapperBuilder())
->filterExceptions(function (Throwable $exception) {
if ($exception instanceof \Webmozart\Assert\InvalidArgumentException) {
return \CuyZ\Valinor\Mapper\Tree\Message\ThrowableMessage::from($exception);
return \CuyZ\Valinor\Mapper\Tree\Message\MessageBuilder::from($exception);
}

// If the exception should not be caught by this library, it
Expand Down
204 changes: 204 additions & 0 deletions src/Mapper/Tree/Message/MessageBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
<?php

declare(strict_types=1);

namespace CuyZ\Valinor\Mapper\Tree\Message;

use LanguageServerProtocol\MessageType;
use RuntimeException;
use Throwable;

/**
* Can be used to easily create an instance of (error) message.
*
* ```php
* $message = MessageBuilder::newError('Some message with {some_parameter}.')
* ->withCode('some_code')
* ->withParameter('some_parameter', 'some_value')
* ->build();
* ```
*
* @api
*
* @template MessageType of Message
*/
final class MessageBuilder
{
private bool $isError = false;

private string $body;

private string $code = 'unknown';

/** @var array<string, string> */
private array $parameters = [];

private function __construct(string $body)
{
$this->body = $body;
}

/**
* @return self<Message>
*/
public static function new(string $body): self
{
return new self($body);
}

/**
* @return self<ErrorMessage>
*/
public static function newError(string $body): self
{
$instance = new self($body);
$instance->isError = true;

/** @var self<ErrorMessage> */
return $instance;
}

public static function from(Throwable $error): ErrorMessage
{
if ($error instanceof ErrorMessage) {
return $error;
}

return self::newError($error->getMessage())
->withCode((string)$error->getCode())
->build();
}

/**
* @return self<MessageType>
*/
public function withBody(string $body): self
{
$clone = clone $this;
$clone->body = $body;

return $clone;
}

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

/**
* @return self<MessageType>
*/
public function withCode(string $code): self
{
$clone = clone $this;
$clone->code = $code;

return $clone;
}

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

/**
* @return self<MessageType>
*/
public function withParameter(string $name, string $value): self
{
$clone = clone $this;
$clone->parameters[$name] = $value;

return $clone;
}

/**
* @return array<string, string>
*/
public function parameters(): array
{
return $this->parameters;
}

/**
* @return MessageType&HasCode&HasParameters
*/
public function build(): Message
{
/** @var MessageType&HasCode&HasParameters */
return $this->isError
? $this->buildErrorMessage()
: $this->buildMessage();
}

private function buildMessage(): Message
{
return new class ($this->body, $this->code, $this->parameters) implements Message, HasCode, HasParameters {
private string $body;

private string $code;

/** @var array<string, string> */
private array $parameters;

/**
* @param array<string, string> $parameters
*/
public function __construct(string $body, string $code, array $parameters)
{
$this->body = $body;
$this->code = $code;
$this->parameters = $parameters;
}

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

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

public function parameters(): array
{
return $this->parameters;
}
};
}

private function buildErrorMessage(): ErrorMessage
{
return new class ($this->body, $this->code, $this->parameters) extends RuntimeException implements ErrorMessage, HasCode, HasParameters {
/** @var array<string, string> */
private array $parameters;

/**
* @param array<string, string> $parameters
*/
public function __construct(string $body, string $code, array $parameters)
{
parent::__construct($body);

$this->code = $code;
$this->parameters = $parameters;
}

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

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

public function parameters(): array
{
return $this->parameters;
}
};
}
}
6 changes: 5 additions & 1 deletion src/Mapper/Tree/Message/ThrowableMessage.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
use RuntimeException;
use Throwable;

/** @api */
/**
* @api
*
* @deprecated Use {@see \CuyZ\Valinor\Mapper\Tree\Message\MessageBuilder}
*/
final class ThrowableMessage extends RuntimeException implements ErrorMessage, HasCode
{
public static function new(string $message, string $code): self
Expand Down
2 changes: 1 addition & 1 deletion src/MapperBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ public function flexible(): self
* (new \CuyZ\Valinor\MapperBuilder())
* ->filterExceptions(function (Throwable $exception) {
* if ($exception instanceof \Webmozart\Assert\InvalidArgumentException) {
* return \CuyZ\Valinor\Mapper\Tree\Message\ThrowableMessage::from($exception);
* return \CuyZ\Valinor\Mapper\Tree\Message\MessageBuilder::from($exception);
* }
*
* // If the exception should not be caught by this library, it must
Expand Down
86 changes: 86 additions & 0 deletions tests/Unit/Mapper/Tree/Message/MessageBuilderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?php

declare(strict_types=1);

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

use CuyZ\Valinor\Mapper\Tree\Message\HasCode;
use CuyZ\Valinor\Mapper\Tree\Message\MessageBuilder;
use CuyZ\Valinor\Tests\Fake\Mapper\Tree\Message\FakeErrorMessage;
use Exception;
use PHPUnit\Framework\TestCase;

final class MessageBuilderTest extends TestCase
{
public function test_body_can_be_retrieved(): void
{
$message = MessageBuilder::new('some message body');
$messageError = MessageBuilder::newError('some error message body');

self::assertSame('some message body', $message->body());
self::assertSame('some message body', $message->build()->body());

self::assertSame('some error message body', $messageError->body());
self::assertSame('some error message body', $messageError->build()->body());

$message = $message->withBody('some new message body');
$messageError = $messageError->withBody('some new error message body');

self::assertSame('some new message body', $message->body());
self::assertSame('some new message body', $message->build()->body());

self::assertSame('some new error message body', $messageError->body());
self::assertSame('some new error message body', $messageError->build()->body());
}

public function test_code_can_be_retrieved(): void
{
$message = MessageBuilder::new('some message body');
$message = $message->withCode('some_code');

self::assertSame('some_code', $message->code());
self::assertSame('some_code', $message->build()->code());
}

public function test_parameters_can_be_retrieved(): void
{
$message = MessageBuilder::new('some message body');
$message = $message
->withParameter('parameter_a', 'valueA')
->withParameter('parameter_b', 'valueB');

self::assertSame(['parameter_a' => 'valueA', 'parameter_b' => 'valueB'], $message->parameters());
self::assertSame(['parameter_a' => 'valueA', 'parameter_b' => 'valueB'], $message->build()->parameters());
}

public function test_modifiers_return_clone_instances(): void
{
$messageA = MessageBuilder::new('some message body');
$messageB = $messageA->withBody('some new message body');
$messageC = $messageB->withCode('some_code');
$messageD = $messageC->withParameter('parameter_a', 'valueA');

self::assertNotSame($messageA, $messageB);
self::assertNotSame($messageB, $messageC);
self::assertNotSame($messageC, $messageD);
}

public function test_from_throwable_build_error_message(): void
{
$exception = new Exception('some error message', 1664450422);

$message = MessageBuilder::from($exception);

self::assertSame('some error message', $message->body());
self::assertInstanceOf(HasCode::class, $message);
self::assertSame('1664450422', $message->code());
}

public function test_from_error_message_returns_same_instance(): void
{
$error = new FakeErrorMessage();
$message = MessageBuilder::from($error);

self::assertSame($error, $message);
}
}

0 comments on commit cb87925

Please sign in to comment.