Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

transfrom projector throwable into an array #456

Merged
merged 1 commit into from
Jan 6, 2024
Merged
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
23 changes: 12 additions & 11 deletions baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,18 @@
<code><![CDATA[ArrayIterator<int, Projection>]]></code>
</InvalidReturnType>
</file>
<file src="src/Projection/Projection/Store/DoctrineStore.php">
<MixedArgument>
<code>$context</code>
</MixedArgument>
<MixedAssignment>
<code>$context</code>
</MixedAssignment>
<PossiblyNullPropertyFetch>
<code><![CDATA[$projectionError->errorContext]]></code>
<code><![CDATA[$projectionError->errorContext]]></code>
</PossiblyNullPropertyFetch>
</file>
<file src="src/Projection/Projector/InMemoryProjectorRepository.php">
<InvalidOperand>
<code><![CDATA[$this->projectors]]></code>
Expand Down Expand Up @@ -135,12 +147,6 @@
<code>$name</code>
</PropertyNotSetInConstructor>
</file>
<file src="tests/Integration/BankAccountSplitStream/Projection/BankAccountProjection.php">
<MixedMethodCall>
<code>toString</code>
<code>toString</code>
</MixedMethodCall>
</file>
<file src="tests/Integration/BasicImplementation/Aggregate/Profile.php">
<PropertyNotSetInConstructor>
<code>$id</code>
Expand All @@ -153,11 +159,6 @@
<code>$name</code>
</PropertyNotSetInConstructor>
</file>
<file src="tests/Integration/Outbox/Projection/ProfileProjection.php">
<MixedMethodCall>
<code>toString</code>
</MixedMethodCall>
</file>
<file src="tests/Integration/Pipeline/Aggregate/Profile.php">
<PropertyNotSetInConstructor>
<code>$id</code>
Expand Down
5 changes: 5 additions & 0 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ parameters:
count: 1
path: src/EventBus/Message.php

-
message: "#^Parameter \\#2 \\$errorContext of class Patchlevel\\\\EventSourcing\\\\Projection\\\\Projection\\\\ProjectionError constructor expects array\\<int, array\\{message\\: string, code\\: int\\|string, file\\: string, line\\: int, trace\\: string\\}\\>\\|null, mixed given\\.$#"
count: 1
path: src/Projection/Projection/Store/DoctrineStore.php

-
message: "#^Method Patchlevel\\\\EventSourcing\\\\Projection\\\\Projector\\\\InMemoryProjectorRepository\\:\\:projectors\\(\\) should return array\\<int, object\\> but returns array\\<int\\|string, object\\>\\.$#"
count: 1
Expand Down
19 changes: 15 additions & 4 deletions src/Console/Command/ProjectionStatusCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@
use Patchlevel\EventSourcing\Console\OutputStyle;
use Patchlevel\EventSourcing\Projection\Projection\Projection;
use Patchlevel\EventSourcing\Projection\Projection\ProjectionId;
use Patchlevel\EventSourcing\Projection\Projection\Store\ErrorContext;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Throwable;

use function array_map;
use function is_array;

/** @psalm-import-type Context from ErrorContext */
#[AsCommand(
'event-sourcing:projection:status',
'View the current status of the projections',
Expand Down Expand Up @@ -89,12 +91,21 @@ protected function execute(InputInterface $input, OutputInterface $output): int
],
);

$errorObject = $projection->projectionError()?->errorObject;
$contexts = $projection->projectionError()?->errorContext;

if ($errorObject instanceof Throwable) {
$io->throwable($errorObject);
if (is_array($contexts)) {
foreach ($contexts as $context) {
$this->displayError($io, $context);
}
}

return 0;
}

/** @param Context $context */
private function displayError(OutputStyle $io, array $context): void
{
$io->error($context['message']);
$io->block($context['trace']);
}
}
7 changes: 5 additions & 2 deletions src/Projection/Projection/ProjectionError.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,21 @@

namespace Patchlevel\EventSourcing\Projection\Projection;

use Patchlevel\EventSourcing\Projection\Projection\Store\ErrorContext;
use Throwable;

/** @psalm-import-type Context from ErrorContext */
final class ProjectionError
{
/** @param list<Context>|null $errorContext */
public function __construct(
public readonly string $errorMessage,
public readonly Throwable|null $errorObject = null,
public readonly array|null $errorContext = null,
) {
}

public static function fromThrowable(Throwable $error): self
{
return new self($error->getMessage(), $error);
return new self($error->getMessage(), ErrorContext::fromThrowable($error));
}
}
23 changes: 15 additions & 8 deletions src/Projection/Projection/Store/DoctrineStore.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,18 @@
use Patchlevel\EventSourcing\Schema\SchemaConfigurator;

use function array_map;
use function json_decode;
use function json_encode;

use const JSON_THROW_ON_ERROR;

/** @psalm-type Data = array{
* name: string,
* version: int,
* position: int,
* status: string,
* error_message: string|null,
* error_object: string|null,
* error_context: string|null,
* retry: int,
* }
*/
Expand Down Expand Up @@ -77,13 +81,16 @@ public function all(): ProjectionCollection
/** @param Data $row */
private function createProjection(array $row): Projection
{
$context = $row['error_context'] ?
json_decode($row['error_context'], true, 512, JSON_THROW_ON_ERROR) : null;

return new Projection(
new ProjectionId($row['name'], $row['version']),
ProjectionStatus::from($row['status']),
$row['position'],
$row['error_message'] ? new ProjectionError(
$row['error_message'],
ErrorSerializer::unserialize($row['error_object']),
$context,
) : null,
$row['retry'],
);
Expand All @@ -94,16 +101,16 @@ public function save(Projection ...$projections): void
$this->connection->transactional(
function (Connection $connection) use ($projections): void {
foreach ($projections as $projection) {
$errorObject = ErrorSerializer::serialize($projection->projectionError()?->errorObject);
$projectionError = $projection->projectionError();

try {
$effectedRows = (int)$connection->update(
$this->projectionTable,
[
'position' => $projection->position(),
'status' => $projection->status()->value,
'error_message' => $projection->projectionError()?->errorMessage,
'error_object' => $errorObject,
'error_message' => $projectionError?->errorMessage,
'error_context' => $projectionError?->errorContext ? json_encode($projectionError->errorContext, JSON_THROW_ON_ERROR) : null,
'retry' => $projection->retry(),
],
[
Expand All @@ -123,8 +130,8 @@ function (Connection $connection) use ($projections): void {
'version' => $projection->id()->version(),
'position' => $projection->position(),
'status' => $projection->status()->value,
'error_message' => $projection->projectionError()?->errorMessage,
'error_object' => $errorObject,
'error_message' => $projectionError?->errorMessage,
'error_context' => $projectionError?->errorContext ? json_encode($projectionError->errorContext, JSON_THROW_ON_ERROR) : null,
'retry' => $projection->retry(),
],
);
Expand Down Expand Up @@ -162,7 +169,7 @@ public function configureSchema(Schema $schema, Connection $connection): void
->setNotnull(true);
$table->addColumn('error_message', Types::STRING)
->setNotnull(false);
$table->addColumn('error_object', Types::BLOB)
$table->addColumn('error_context', Types::JSON)
->setNotnull(false);
$table->addColumn('retry', Types::INTEGER)
->setNotnull(true)
Expand Down
36 changes: 36 additions & 0 deletions src/Projection/Projection/Store/ErrorContext.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace Patchlevel\EventSourcing\Projection\Projection\Store;

use Throwable;

/** @psalm-type Context = array{message: string, code: int|string, file: string, line: int, trace: string} */
final class ErrorContext
{
/** @return list<Context> */
public static function fromThrowable(Throwable $error): array
{
$errors = [];

do {
$errors[] = self::transform($error);
$error = $error->getPrevious();
} while ($error);

return $errors;
}

/** @return Context */
private static function transform(Throwable $error): array
{
return [
'message' => $error->getMessage(),
'code' => $error->getCode(),
'file' => $error->getFile(),
'line' => $error->getLine(),
'trace' => $error->getTraceAsString(),
];
}
}
41 changes: 0 additions & 41 deletions src/Projection/Projection/Store/ErrorSerializer.php

This file was deleted.

9 changes: 8 additions & 1 deletion tests/Unit/Projection/Projection/ProjectionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Patchlevel\EventSourcing\Projection\Projection\ProjectionError;
use Patchlevel\EventSourcing\Projection\Projection\ProjectionId;
use Patchlevel\EventSourcing\Projection\Projection\ProjectionStatus;
use Patchlevel\EventSourcing\Projection\Projection\Store\ErrorContext;
use PHPUnit\Framework\TestCase;
use RuntimeException;

Expand Down Expand Up @@ -77,7 +78,13 @@ public function testError(): void
self::assertFalse($projection->isActive());
self::assertTrue($projection->isError());
self::assertFalse($projection->isOutdated());
self::assertEquals(new ProjectionError('test', $exception), $projection->projectionError());
self::assertEquals(
new ProjectionError(
'test',
ErrorContext::fromThrowable($exception),
),
$projection->projectionError(),
);
}

public function testOutdated(): void
Expand Down
41 changes: 41 additions & 0 deletions tests/Unit/Projection/Projection/Store/ErrorContextTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace Patchlevel\EventSourcing\Tests\Unit\Projection\Projection\Store;

use Patchlevel\EventSourcing\Projection\Projection\Store\ErrorContext;
use PHPUnit\Framework\TestCase;
use RuntimeException;

/** @covers \Patchlevel\EventSourcing\Projection\Projection\Store\ErrorContext */
final class ErrorContextTest extends TestCase
{
public function testWithoutPrevious(): void
{
$error = new RuntimeException('foo');

$result = ErrorContext::fromThrowable($error);

self::assertCount(1, $result);
self::assertSame('foo', $result[0]['message']);
self::assertSame(0, $result[0]['code']);
self::assertSame(__FILE__, $result[0]['file']);
}

public function testWithPrevious(): void
{
$error = new RuntimeException('foo', 0, new RuntimeException('bar'));

$result = ErrorContext::fromThrowable($error);

self::assertCount(2, $result);
self::assertSame('foo', $result[0]['message']);
self::assertSame(0, $result[0]['code']);
self::assertSame(__FILE__, $result[0]['file']);

self::assertSame('bar', $result[1]['message']);
self::assertSame(0, $result[1]['code']);
self::assertSame(__FILE__, $result[1]['file']);
}
}
46 changes: 0 additions & 46 deletions tests/Unit/Projection/Projection/Store/ErrorSerializerTest.php

This file was deleted.

Loading
Loading