}> */ public static function codeToExceptionClassProvider(): iterable { yield 'unauthenticated -> AuthenticationException' => ['unauthenticated', AuthenticationException::class]; yield 'not_found -> NotFoundException' => ['not_found', NotFoundException::class]; yield 'invalid_argument -> InvalidArgumentException' => ['invalid_argument', InvalidArgumentException::class]; yield 'permission_denied -> PermissionDeniedException' => ['permission_denied', PermissionDeniedException::class]; yield 'resource_exhausted -> RateLimitException' => ['resource_exhausted', RateLimitException::class]; yield 'unavailable -> ServiceUnavailableException' => ['unavailable', ServiceUnavailableException::class]; yield 'aborted -> base OpenstatusException' => ['aborted', OpenstatusException::class]; yield 'unknown -> base OpenstatusException' => ['unknown', OpenstatusException::class]; } /** * @param class-string $expectedClass */ #[DataProvider('codeToExceptionClassProvider')] public function testRethrowMapsCodeToExceptionClass(string $code, string $expectedClass): void { $body = json_encode(['code' => $code, 'message' => 'something happened']); self::assertIsString($body); $response = new Response(400, [], $body); try { ConnectErrorMapper::rethrow($response); } catch (OpenstatusException $e) { self::assertSame($expectedClass, $e::class); self::assertSame('something happened', $e->getMessage()); self::assertSame($code, $e->connectCode()); self::assertSame(400, $e->httpStatus()); self::assertSame($body, $e->rawBody()); } } public function testRethrowParsesDetails(): void { $body = json_encode([ 'code' => 'invalid_argument', 'message' => 'validation failed', 'details' => [ ['type' => 'buf.validate.Violations', 'value' => 'aGVsbG8='], ['type' => 'debug.example', 'value' => '{}', 'debug' => 'extra context'], ['type' => 'malformed-missing-value'], 'not-an-object', ], ]); self::assertIsString($body); $response = new Response(400, [], $body); try { ConnectErrorMapper::rethrow($response); } catch (InvalidArgumentException $e) { $details = $e->details(); self::assertCount(2, $details); self::assertSame('buf.validate.Violations', $details[0]->type); self::assertSame('aGVsbG8=', $details[0]->value); self::assertNull($details[0]->debug); self::assertSame('debug.example', $details[1]->type); self::assertSame('{}', $details[1]->value); self::assertSame('extra context', $details[1]->debug); } } public function testRethrowFallsBackToServiceUnavailableForInvalidJson(): void { $response = new Response(502, [], 'Bad Gateway'); try { ConnectErrorMapper::rethrow($response); } catch (ServiceUnavailableException $e) { self::assertSame('unavailable', $e->connectCode()); self::assertSame(502, $e->httpStatus()); self::assertSame('Bad Gateway', $e->rawBody()); self::assertStringContainsString('HTTP 502', $e->getMessage()); } } public function testRethrowFallsBackToServiceUnavailableForJsonMissingCode(): void { $response = new Response(500, [], '{"foo":"bar"}'); try { ConnectErrorMapper::rethrow($response); } catch (ServiceUnavailableException $e) { self::assertSame('unavailable', $e->connectCode()); self::assertSame(500, $e->httpStatus()); } } public function testRethrowFallsBackToServiceUnavailableForEmptyBody(): void { $response = new Response(503, [], ''); try { ConnectErrorMapper::rethrow($response); } catch (ServiceUnavailableException $e) { self::assertSame('unavailable', $e->connectCode()); self::assertSame(503, $e->httpStatus()); self::assertSame('', $e->rawBody()); } } }