feat(provider): add health-probe contract and supporting value objects
Add getName(), ping(), and probe() to ProviderInterface. Introduce ProbeTrait to classify HTTP/transport exceptions into typed error codes, ProviderStatus as the result value object, and ProviderStatusType / ProviderErrorCode as backing enums. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use Symfony\Contracts\HttpClient\Exception\HttpExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
|
||||
trait ProbeTrait
|
||||
{
|
||||
public function probe(): ProviderStatus
|
||||
{
|
||||
if (!$this->isConfigured()) {
|
||||
return new ProviderStatus($this->getName(), ProviderStatusType::NotConfigured);
|
||||
}
|
||||
|
||||
try {
|
||||
$this->ping();
|
||||
|
||||
return new ProviderStatus($this->getName(), ProviderStatusType::Ok);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->statusFromException($e);
|
||||
}
|
||||
}
|
||||
|
||||
private function statusFromException(\Throwable $e): ProviderStatus
|
||||
{
|
||||
[$error, $message] = $this->classifyException($e);
|
||||
|
||||
return new ProviderStatus($this->getName(), ProviderStatusType::Error, $error, $message);
|
||||
}
|
||||
|
||||
/** @return array{ProviderErrorCode, string} */
|
||||
private function classifyException(\Throwable $e): array
|
||||
{
|
||||
if ($e instanceof TransportExceptionInterface) {
|
||||
return [ProviderErrorCode::UrlUnreachable, 'Could not reach the server: ' . $e->getMessage()];
|
||||
}
|
||||
|
||||
if ($e instanceof HttpExceptionInterface) {
|
||||
$code = $e->getResponse()->getStatusCode();
|
||||
|
||||
return match (true) {
|
||||
$code === 401 => [ProviderErrorCode::AuthFailed, 'Invalid or expired token — verify your credentials'],
|
||||
$code === 403 => [ProviderErrorCode::AuthFailed, 'Access denied — token lacks the required scopes'],
|
||||
$code === 404 => [ProviderErrorCode::UrlUnreachable, 'Endpoint not found — check the configured URL'],
|
||||
default => [ProviderErrorCode::Unknown, "HTTP {$code}: " . $e->getMessage()],
|
||||
};
|
||||
}
|
||||
|
||||
$msg = $e->getMessage();
|
||||
$lower = strtolower($msg);
|
||||
$error = match (true) {
|
||||
str_contains($msg, 'not found') || str_contains($msg, 'Could not resolve') => ProviderErrorCode::UserNotFound,
|
||||
str_contains($msg, 'GraphQL error')
|
||||
&& (str_contains($lower, 'unauthorized') || str_contains($lower, 'bad credentials')) => ProviderErrorCode::AuthFailed,
|
||||
default => ProviderErrorCode::Unknown,
|
||||
};
|
||||
|
||||
return [$error, $msg];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
enum ProviderErrorCode: string
|
||||
{
|
||||
case AuthFailed = 'auth_failed';
|
||||
case UrlUnreachable = 'url_unreachable';
|
||||
case UserNotFound = 'user_not_found';
|
||||
case Unknown = 'unknown';
|
||||
}
|
||||
@@ -10,4 +10,10 @@ interface ProviderInterface
|
||||
public function fetch(): array;
|
||||
|
||||
public function isConfigured(): bool;
|
||||
|
||||
public function getName(): string;
|
||||
|
||||
public function probe(): ProviderStatus;
|
||||
|
||||
public function ping(): void;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
final class ProviderStatus
|
||||
{
|
||||
public function __construct(
|
||||
public readonly string $name,
|
||||
public readonly ProviderStatusType $status,
|
||||
public readonly ?ProviderErrorCode $error = null,
|
||||
public readonly ?string $message = null,
|
||||
) {}
|
||||
|
||||
/** @return array<string, string> */
|
||||
public function toArray(): array
|
||||
{
|
||||
$data = ['status' => $this->status->value];
|
||||
if ($this->error !== null) {
|
||||
$data['error'] = $this->error->value;
|
||||
}
|
||||
if ($this->message !== null) {
|
||||
$data['message'] = $this->message;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
enum ProviderStatusType: string
|
||||
{
|
||||
case Ok = 'ok';
|
||||
case Error = 'error';
|
||||
case NotConfigured = 'not_configured';
|
||||
}
|
||||
Reference in New Issue
Block a user