From 96df4464a171b7e3605a7d6292cb2c20e50d9020 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Wed, 13 Mar 2019 23:02:02 +0100 Subject: [PATCH] [HttpClient] Parse common API error formats for better exception messages --- .../Exception/HttpExceptionTrait.php | 33 +++++++++- .../Exception/HttpExceptionTraitTest.php | 62 +++++++++++++++++++ 2 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Component/HttpClient/Tests/Exception/HttpExceptionTraitTest.php diff --git a/src/Symfony/Component/HttpClient/Exception/HttpExceptionTrait.php b/src/Symfony/Component/HttpClient/Exception/HttpExceptionTrait.php index 48f7b880eba43..e2293f7b285c5 100644 --- a/src/Symfony/Component/HttpClient/Exception/HttpExceptionTrait.php +++ b/src/Symfony/Component/HttpClient/Exception/HttpExceptionTrait.php @@ -26,10 +26,41 @@ public function __construct(ResponseInterface $response) $url = $response->getInfo('url'); $message = sprintf('HTTP %d returned for URL "%s".', $code, $url); + $httpCodeFound = false; + $isJson = false; foreach (array_reverse($response->getInfo('raw_headers')) as $h) { if (0 === strpos($h, 'HTTP/')) { + if ($httpCodeFound) { + break; + } + $message = sprintf('%s returned for URL "%s".', $h, $url); - break; + $httpCodeFound = true; + } + + if (0 === stripos($h, 'content-type:')) { + if (preg_match('/\bjson\b/i', $h)) { + $isJson = true; + } + + if ($httpCodeFound) { + break; + } + } + } + + // Try to guess a better error message using common API error formats + // The MIME type isn't explicitly checked because some formats inherit from others + // Ex: JSON:API follows RFC 7807 semantics, Hydra can be used in any JSON-LD-compatible format + if ($isJson && $body = json_decode($response->getContent(false), true)) { + if (isset($body['hydra:title']) || isset($body['hydra:description'])) { + // see http://www.hydra-cg.com/spec/latest/core/#description-of-http-status-codes-and-errors + $separator = isset($body['hydra:title'], $body['hydra:description']) ? "\n\n" : ''; + $message = ($body['hydra:title'] ?? '').$separator.($body['hydra:description'] ?? ''); + } elseif (isset($body['title']) || isset($body['detail'])) { + // see RFC 7807 and https://jsonapi.org/format/#error-objects + $separator = isset($body['title'], $body['detail']) ? "\n\n" : ''; + $message = ($body['title'] ?? '').$separator.($body['detail'] ?? ''); } } diff --git a/src/Symfony/Component/HttpClient/Tests/Exception/HttpExceptionTraitTest.php b/src/Symfony/Component/HttpClient/Tests/Exception/HttpExceptionTraitTest.php new file mode 100644 index 0000000000000..4993bae71a278 --- /dev/null +++ b/src/Symfony/Component/HttpClient/Tests/Exception/HttpExceptionTraitTest.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Tests\Exception; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpClient\Exception\HttpExceptionTrait; +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * @author Kévin Dunglas + */ +class HttpExceptionTraitTest extends TestCase +{ + public function provideParseError() + { + yield ['application/ld+json', '{"hydra:title": "An error occurred", "hydra:description": "Some details"}']; + yield ['application/problem+json', '{"title": "An error occurred", "detail": "Some details"}']; + yield ['application/vnd.api+json', '{"title": "An error occurred", "detail": "Some details"}']; + } + + /** + * @dataProvider provideParseError + */ + public function testParseError(string $mimeType, string $json): void + { + $response = $this->createMock(ResponseInterface::class); + $response + ->method('getInfo') + ->will($this->returnValueMap([ + ['http_code', 400], + ['url', 'http://example.com'], + ['raw_headers', [ + 'HTTP/1.1 400 Bad Request', + 'Content-Type: '.$mimeType, + ]], + ])); + $response->method('getContent')->willReturn($json); + + $e = new TestException($response); + $this->assertSame(400, $e->getCode()); + $this->assertSame(<<getMessage()); + } +} + +class TestException extends \Exception +{ + use HttpExceptionTrait; +} pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy