diff --git a/src/Exception/ApiException.php b/src/Exception/ApiException.php index bc7f41fe..0a487d33 100644 --- a/src/Exception/ApiException.php +++ b/src/Exception/ApiException.php @@ -12,17 +12,32 @@ class ApiException extends Exception */ private $responseCode; + /** + * @var string + */ + private $responseId; + /** * @param string $message * @param int $responseCode + * @param string $responseId */ - public function __construct(string $message, int $responseCode) + public function __construct(string $message, int $responseCode, string $responseId) { $this->responseCode = $responseCode; + $this->responseId = $responseId; parent::__construct($message); } + /** + * @return string + */ + public function getResponseId(): string + { + return $this->responseId; + } + /** * @return int */ diff --git a/src/Exception/ExceptionFactory.php b/src/Exception/ExceptionFactory.php index c7cf7d3d..8327bfd6 100644 --- a/src/Exception/ExceptionFactory.php +++ b/src/Exception/ExceptionFactory.php @@ -20,7 +20,13 @@ class ExceptionFactory * Formatting constants */ const FORMAT_RESPONSE_CODE_LINE = 'HTTP Response Code: %s'; - const GLUE_ERROR_MESSAGES = "\n"; + const FORMAT_RESPONSE_ID = 'The response id to help bunq debug: %s'; + const FORMAT_ERROR_MESSAGE_LINE = 'Error message: %s'; + + /** + * String separator constants + */ + const SEPARATOR_ERROR_MESSAGES = "\n"; /** * The first item index in an array. @@ -28,56 +34,68 @@ class ExceptionFactory const INDEX_FIRST = 0; /** - * @param string[] $messages + * @param string[] $allMessage * @param int $responseCode + * @param string $responseId * * @return ApiException */ - public static function createExceptionForResponse(array $messages, int $responseCode): ApiException - { - $errorMessage = static::generateErrorMessage($responseCode, $messages); + public static function createExceptionForResponse( + array $allMessage, + int $responseCode, + string $responseId + ): ApiException { + $errorMessage = static::generateErrorMessage($responseCode, $allMessage, $responseId); - switch ($responseCode) - { + switch ($responseCode) { case self::HTTP_RESPONSE_CODE_BAD_REQUEST: - return new BadRequestException($errorMessage, $responseCode); + return new BadRequestException($errorMessage, $responseCode, $responseId); case self::HTTP_RESPONSE_CODE_UNAUTHORIZED: - return new UnauthorizedException($errorMessage, $responseCode); + return new UnauthorizedException($errorMessage, $responseCode, $responseId); case self::HTTP_RESPONSE_CODE_FORBIDDEN: - return new ForbiddenException($errorMessage, $responseCode); + return new ForbiddenException($errorMessage, $responseCode, $responseId); case self::HTTP_RESPONSE_CODE_NOT_FOUND: - return new NotFoundException($errorMessage, $responseCode); + return new NotFoundException($errorMessage, $responseCode, $responseId); case self::HTTP_RESPONSE_CODE_METHOD_NOT_ALLOWED: - return new MethodNotAllowedException($errorMessage, $responseCode); + return new MethodNotAllowedException($errorMessage, $responseCode, $responseId); case self::HTTP_RESPONSE_CODE_TOO_MANY_REQUESTS: - return new TooManyRequestsException($errorMessage, $responseCode); + return new TooManyRequestsException($errorMessage, $responseCode, $responseId); case self::HTTP_RESPONSE_CODE_INTERNAL_SERVER_ERROR: - return new PleaseContactBunqException($errorMessage, $responseCode); + return new PleaseContactBunqException($errorMessage, $responseCode, $responseId); default: - return new UnknownApiErrorException($errorMessage, $responseCode); + return new UnknownApiErrorException($errorMessage, $responseCode, $responseId); } } /** * @param int $responseCode - * @param string[] $messages + * @param string[] $allMessage + * @param string $responseId * * @return string */ - private static function generateErrorMessage(int $responseCode, array $messages): string - { + private static function generateErrorMessage( + int $responseCode, + array $allMessage, + string $responseId + ): string { $lineResponseCode = sprintf(self::FORMAT_RESPONSE_CODE_LINE, $responseCode); + $lineResponseId = sprintf(self::FORMAT_RESPONSE_ID, $responseId); + $lineErrorMessage = sprintf( + self::FORMAT_ERROR_MESSAGE_LINE, + implode($allMessage) + ); - return static::glueMessages(array_merge([$lineResponseCode], $messages)); + return static::glueAllMessage([$lineResponseCode, $lineResponseId, $lineErrorMessage]); } /** - * @param string[] $messages + * @param string[] $allMessage * * @return string */ - private static function glueMessages(array $messages): string + private static function glueAllMessage(array $allMessage): string { - return implode(self::GLUE_ERROR_MESSAGES, $messages); + return implode(self::SEPARATOR_ERROR_MESSAGES, $allMessage); } } diff --git a/src/Http/ApiClient.php b/src/Http/ApiClient.php index 1060f534..ac4729a7 100644 --- a/src/Http/ApiClient.php +++ b/src/Http/ApiClient.php @@ -13,7 +13,6 @@ use GuzzleHttp\Client; use GuzzleHttp\HandlerStack; use GuzzleHttp\Psr7\Uri; -use phpDocumentor\Reflection\Types\Self_; use Psr\Http\Message\ResponseInterface; /** diff --git a/src/Http/Handler/ResponseHandlerError.php b/src/Http/Handler/ResponseHandlerError.php index 8f9e356a..28ff04d8 100644 --- a/src/Http/Handler/ResponseHandlerError.php +++ b/src/Http/Handler/ResponseHandlerError.php @@ -3,6 +3,7 @@ namespace bunq\Http\Handler; use bunq\Exception\ApiException; +use bunq\Exception\BunqException; use bunq\Exception\ExceptionFactory; use Psr\Http\Message\ResponseInterface; @@ -10,17 +11,34 @@ */ class ResponseHandlerError extends ResponseHandlerBase { + /** + * Error constants. + */ + const ERROR_COULD_NOT_DETERMINE_RESPONSE_ID_HEADER = + 'The response header "X-Bunq-Client-Response-Id" or "x-bunq-client-response-id" could not be found.'; + /** * Field constants. */ const FIELD_ERROR = 'Error'; const FIELD_ERROR_DESCRIPTION = 'error_description'; + /** + * Header constants. + */ + const HEADER_BUNQ_CLIENT_RESPONSE_ID_UPPER_CASED = 'X-Bunq-Client-Response-Id'; + const HEADER_BUNQ_CLIENT_RESPONSE_ID_LOWER_CASED = 'x-bunq-client-response-id'; + /** * Http status code constants. */ const STATUS_CODE_OK = 200; + /** + * The index of the first item in an array. + */ + const INDEX_FIRST = 0; + /** * @param ResponseInterface $response * @@ -29,10 +47,11 @@ class ResponseHandlerError extends ResponseHandlerBase */ public function execute(ResponseInterface $response): ResponseInterface { - if ($response->getStatusCode() !== self::STATUS_CODE_OK){ + if ($response->getStatusCode() !== self::STATUS_CODE_OK) { throw ExceptionFactory::createExceptionForResponse( $this->fetchErrorMessages($response), - $response->getStatusCode() + $response->getStatusCode(), + $this->getResponseId($response) ); } @@ -49,9 +68,9 @@ private function fetchErrorMessages(ResponseInterface $response): array $responseBody = $response->getBody(); $responseBodyInJson = json_decode($responseBody, true); - if ($responseBodyInJson !== false){ - return $this->fetchErrorDescriptions($responseBodyInJson); - }else{ + if ($responseBodyInJson !== false) { + return $this->fetchAllErrorDescription($responseBodyInJson); + } else { return [$responseBody]; } } @@ -61,15 +80,36 @@ private function fetchErrorMessages(ResponseInterface $response): array * * @return string[] */ - private function fetchErrorDescriptions(array $errorArray): array + private function fetchAllErrorDescription(array $errorArray): array { - $errorDescriptions = []; + $allErrorDescription = []; - foreach ($errorArray[self::FIELD_ERROR] as $error){ + foreach ($errorArray[self::FIELD_ERROR] as $error) { $description = $error[self::FIELD_ERROR_DESCRIPTION]; - $errorDescriptions[] = $description; + $allErrorDescription[] = $description; + } + + return $allErrorDescription; + } + + /** + * @param ResponseInterface $response + * + * @return string + * @throws BunqException + */ + private function getResponseId(ResponseInterface $response): string + { + $header = $response->getHeader(self::HEADER_BUNQ_CLIENT_RESPONSE_ID_UPPER_CASED); + + if (empty($header)) { + $header = $response->getHeader(self::HEADER_BUNQ_CLIENT_RESPONSE_ID_UPPER_CASED); + } + + if (empty($header)) { + throw new BunqException(self::ERROR_COULD_NOT_DETERMINE_RESPONSE_ID_HEADER); } - return $errorDescriptions; + return $header[self::INDEX_FIRST]; } } diff --git a/tests/Http/ErrorResponseIdTest.php b/tests/Http/ErrorResponseIdTest.php new file mode 100644 index 00000000..302baa19 --- /dev/null +++ b/tests/Http/ErrorResponseIdTest.php @@ -0,0 +1,33 @@ +getResponseId()); + } +}