feat: add ErrorCategory enum and SocialPublishException base class

This commit is contained in:
Paulo Castellano 2026-03-31 20:34:41 -03:00
parent 5e5a15e796
commit c3c51d4ccd
3 changed files with 138 additions and 0 deletions

View file

@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace App\Exceptions\Social;
enum ErrorCategory: string
{
case MediaFormat = 'media_format';
case RateLimit = 'rate_limit';
case Permission = 'permission';
case ContentPolicy = 'content_policy';
case ServerError = 'server_error';
case Unknown = 'unknown';
}

View file

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace App\Exceptions\Social;
use RuntimeException;
abstract class SocialPublishException extends RuntimeException
{
public function __construct(
public readonly string $userMessage,
public readonly ErrorCategory $category,
public readonly ?string $platformErrorCode = null,
public readonly ?string $rawResponse = null,
) {
parent::__construct($userMessage);
}
/**
* @return array{platform: string, category: string, platform_error_code: ?string, user_message: string, raw_response: ?string}
*/
public function context(): array
{
return [
'platform' => $this->platform(),
'category' => $this->category->value,
'platform_error_code' => $this->platformErrorCode,
'user_message' => $this->userMessage,
'raw_response' => $this->rawResponse,
];
}
abstract public static function fromApiResponse(mixed $response): static;
abstract public function platform(): string;
}

View file

@ -0,0 +1,86 @@
<?php
declare(strict_types=1);
use App\Exceptions\Social\ErrorCategory;
use App\Exceptions\Social\SocialPublishException;
// Concrete implementation for testing the abstract class
class TestPlatformException extends SocialPublishException
{
public static function fromApiResponse(mixed $response): static
{
return new static(
userMessage: 'Test error',
category: ErrorCategory::Unknown,
platformErrorCode: null,
rawResponse: json_encode($response),
);
}
public function platform(): string
{
return 'test_platform';
}
}
test('context returns correct data', function () {
$exception = new TestPlatformException(
userMessage: 'Your image is too large for Instagram.',
category: ErrorCategory::MediaFormat,
platformErrorCode: 'MEDIA_TOO_LARGE',
rawResponse: '{"error": "image too large"}',
);
expect($exception->context())->toBe([
'platform' => 'test_platform',
'category' => 'media_format',
'platform_error_code' => 'MEDIA_TOO_LARGE',
'user_message' => 'Your image is too large for Instagram.',
'raw_response' => '{"error": "image too large"}',
]);
});
test('context returns null for optional fields when not provided', function () {
$exception = new TestPlatformException(
userMessage: 'Something went wrong.',
category: ErrorCategory::Unknown,
);
expect($exception->context())->toBe([
'platform' => 'test_platform',
'category' => 'unknown',
'platform_error_code' => null,
'user_message' => 'Something went wrong.',
'raw_response' => null,
]);
});
test('exception message matches user message', function () {
$exception = new TestPlatformException(
userMessage: 'Rate limit exceeded.',
category: ErrorCategory::RateLimit,
);
expect($exception->getMessage())->toBe('Rate limit exceeded.');
});
test('fromApiResponse creates exception from response', function () {
$exception = TestPlatformException::fromApiResponse(['error' => 'test']);
expect($exception)
->toBeInstanceOf(SocialPublishException::class)
->and($exception->userMessage)->toBe('Test error')
->and($exception->category)->toBe(ErrorCategory::Unknown)
->and($exception->rawResponse)->toBe('{"error":"test"}');
});
test('error category enum has all expected cases', function () {
expect(ErrorCategory::cases())->toHaveCount(6)
->and(ErrorCategory::MediaFormat->value)->toBe('media_format')
->and(ErrorCategory::RateLimit->value)->toBe('rate_limit')
->and(ErrorCategory::Permission->value)->toBe('permission')
->and(ErrorCategory::ContentPolicy->value)->toBe('content_policy')
->and(ErrorCategory::ServerError->value)->toBe('server_error')
->and(ErrorCategory::Unknown->value)->toBe('unknown');
});