From 778e36346f3ea04b46e055b36065a1bc61a583cb Mon Sep 17 00:00:00 2001 From: Mike Kulakovsky Date: Wed, 11 Mar 2026 20:17:33 +0000 Subject: [PATCH] fix error response parsing; throw on errors --- src/Exceptions/RdapResponseException.php | 85 +++++++++++++++++++++ src/Interfaces/RdapRequestInterface.php | 5 +- src/Response/Abstracts/AbstractResponse.php | 32 +++++++- 3 files changed, 117 insertions(+), 5 deletions(-) create mode 100644 src/Exceptions/RdapResponseException.php diff --git a/src/Exceptions/RdapResponseException.php b/src/Exceptions/RdapResponseException.php new file mode 100644 index 0000000..0bd219a --- /dev/null +++ b/src/Exceptions/RdapResponseException.php @@ -0,0 +1,85 @@ + + */ + private array $response; + + /** + * Construct from a previously prepared message. + * + * Use the named factory if possible. + * + * @param string $message human readable description + * @param int $code errorCode from the payload + * @param array $response original decoded response + * @param \Throwable|null $previous previous exception + */ + public function __construct( + string $message = "", + int $code = 0, + array $response = [], + ?\Throwable $previous = null + ) { + parent::__construct($message, $code, $previous); + $this->response = $response; + } + + /** + * Create an exception instance from a decoded RDAP error response. + * + * @param array $response + * @return self + */ + public static function fromResponse(array $response): self + { + $code = isset($response['errorCode']) ? (int) $response['errorCode'] : 0; + $title = is_string($response['title'] ?? null) ? $response['title'] : ''; + $description = ''; + if (isset($response['description'])) { + if (is_string($response['description'])) { + $description = $response['description']; + } elseif (is_array($response['description'])) { + $description = implode(' ', $response['description']); + } + } + + $parts = array_filter([$title, $description]); + $message = $parts ? implode(' - ', $parts) : 'RDAP error response'; + + return new self($message, $code, $response); + } + + /** + * Get the raw response array that triggered the exception. + * + * @return array + */ + public function getResponse(): array + { + return $this->response; + } +} + diff --git a/src/Interfaces/RdapRequestInterface.php b/src/Interfaces/RdapRequestInterface.php index b1064c3..0b26c02 100644 --- a/src/Interfaces/RdapRequestInterface.php +++ b/src/Interfaces/RdapRequestInterface.php @@ -46,8 +46,11 @@ public function getTarget() : string; /** * Get RDAP Response - * + * * @return RdapResponseInterface + * + * @throws \ArrayAccess\RdapClient\Exceptions\RdapRemoteRequestException + * @throws \ArrayAccess\RdapClient\Exceptions\RdapResponseException */ public function getResponse() : RdapResponseInterface; diff --git a/src/Response/Abstracts/AbstractResponse.php b/src/Response/Abstracts/AbstractResponse.php index bada54f..1d8b519 100644 --- a/src/Response/Abstracts/AbstractResponse.php +++ b/src/Response/Abstracts/AbstractResponse.php @@ -5,6 +5,7 @@ use ArrayAccess\RdapClient\Exceptions\InvalidDataTypeException; use ArrayAccess\RdapClient\Exceptions\MismatchProtocolBehaviorException; +use ArrayAccess\RdapClient\Exceptions\RdapResponseException; use ArrayAccess\RdapClient\Interfaces\RdapProtocolInterface; use ArrayAccess\RdapClient\Interfaces\RdapRequestInterface; use ArrayAccess\RdapClient\Interfaces\RdapResponseInterface; @@ -67,19 +68,42 @@ public function __construct( } /** - * Assert response + * Assert that the raw JSON response is valid and populate internal + * array representation. + * * @param string $responseJson * @return void + * + * @throws InvalidDataTypeException when the content cannot be decoded or + * does not contain any of the required fields: objectClassName, + * rdapConformance, or errorCode. + * @throws RdapResponseException when the payload represents an error + * response (``errorCode`` field present). The returned exception + * may be inspected via {@see RdapResponseException::getResponse()} + * for the full payload; its message and code are derived from the + * `title`/`description`/`errorCode` fields. */ private function assertResponse(string $responseJson): void { - $responseJson = json_decode($responseJson, true); - if (!is_array($responseJson) || !is_string($responseJson['objectClassName']??null)) { + $decoded = json_decode($responseJson, true); + + if (!is_array($decoded) || + (!isset($decoded['objectClassName']) && + !isset($decoded['rdapConformance']) && + !isset($decoded['errorCode'])) + ) { throw new InvalidDataTypeException( 'Response is not valid json content' ); } - $this->responseArray = $responseJson; + + if (isset($decoded['errorCode'])) { + // convert immediately into an exception; no need to validate + // required fields for error payloads. + throw RdapResponseException::fromResponse($decoded); + } + + $this->responseArray = $decoded; } /**