8000 [HttpClient] Parse common API error formats for better exception mess… · symfony/symfony@d2477c7 · GitHub
[go: up one dir, main page]

Skip to content

Commit d2477c7

Browse files
committed
[HttpClient] Parse common API error formats for better exception messages
1 parent 50ff35f commit d2477c7

File tree

2 files changed

+94
-1
lines changed

2 files changed

+94
-1
lines changed

src/Symfony/Component/HttpClient/Exception/HttpExceptionTrait.php

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,41 @@ public function __construct(ResponseInterface $response)
2626
$url = $response->getInfo('url');
2727
$message = sprintf('HTTP %d returned for URL "%s".', $code, $url);
2828

29+
$httpCodeFound = false;
30+
$json = false;
2931
foreach (array_reverse($response->getInfo('raw_headers')) as $h) {
3032
if (0 === strpos($h, 'HTTP/')) {
33+
if ($httpCodeFound) {
34+
break;
35+
}
36+
3137
$message = sprintf('%s returned for URL "%s".', $h, $url);
32-
break;
38+
$httpCodeFound = true;
39+
}
40+
41+
if (0 === stripos($h, 'content-type:')) {
42+
if (preg_match('/\bjson\b/i', $h)) {
43+
$json = true;
44+
}
45+
46+
if ($httpCodeFound) {
47+
break;
48+
}
49+
}
50+
}
51+
52+
// Try to guess a better error message using common API error formats
53+
// The MIME type isn't explicitly checked because some formats inherit of others
54+
// Ex: JSON:API follows RFC 7807 semantics, Hydra can be used in any JSON-LD-compatible format
55+
if ($json && $body = json_decode($response->getContent(false), true)) {
56+
if (isset($body['hydra:title']) || isset($body['hydra:description'])) {
57+
// see http://www.hydra-cg.com/spec/latest/core/#description-of-http-status-codes-and-errors
58+
$sep = isset($body['hydra:title'], $body['hydra:description']) ? "\n\n" : '';
59+
$message = ($body['hydra:title'] ?? '').$sep.($body['hydra:description'] ?? '');
60+
} elseif (isset($body['title']) || isset($body['detail'])) {
61+
// see RFC 7807 and https://jsonapi.org/format/#error-objects
62+
$sep = isset($body['title'], $body['detail']) ? "\n\n" : '';
63+
$message = ($body['title'] ?? '').$sep.($body['detail'] ?? '');
3364
}
3465
}
3566

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\HttpClient\Tests\Exception;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\HttpClient\Exception\HttpExceptionTrait;
16+
use Symfony\Contracts\HttpClient\ResponseInterface;
17+
18+
/**
19+
* @author Kévin Dunglas <dunglas@gmail.com>
20+
*/
21+
class HttpExceptionTraitTest extends TestCase
22+
{
23+
public function provideParseError()
24+
{
25+
yield ['application/ld+json', '{"hydra:title": "An error occurred", "hydra:description": "Some details"}'];
26+
yield ['application/problem+json', '{"title": "An error occurred", "detail": "Some details"}'];
27+
yield ['application/vnd.api+json', '{"title": "An error occurred", "detail": "Some details"}'];
28+
}
29+
30+
/**
31+
* @dataProvider provideParseError
32+
*/
33+
public function testParseError(string $mimeType, string $json): void
34+
{
35+
$response = $this->createMock(ResponseInterface::class);
36+
$response
37+
->method('getInfo')
38+
->will($this->returnValueMap([
39+
['http_code', 400],
40+
['url', 'http://example.com'],
41+
['raw_headers', [
42+
'HTTP/1.1 400 Bad Request',
43+
'Content-Type: '.$mimeType,
44+
]],
45+
]));
46+
$response->method('getContent')->willReturn($json);
47+
48+
$e = new TestException($response);
49+
$this->assertSame(400, $e->getCode());
50+
$this->assertSame(<<<ERROR
51+
An error occurred
52+
53+
Some details
54+
ERROR
55+
, $e->getMessage());
56+
}
57+
}
58+
59+
class TestException extends \Exception
60+
{
61+
use HttpExceptionTrait;
< 3707 /code>
62+
}

0 commit comments

Comments
 (0)
0