Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 041f60f

Browse files
committed
feature #30559 [HttpClient] Parse common API error formats for better exception messages (dunglas)
This PR was squashed before being merged into the 4.3-dev branch (closes #30559). Discussion ---------- [HttpClient] Parse common API error formats for better exception messages | Q | A | ------------- | --- | Branch? | master <!-- see below --> | Bug fix? | no | New feature? | yes <!-- don't forget to update src/**/CHANGELOG.md files --> | BC breaks? | no <!-- see https://symfony.com/bc --> | Deprecations? | no <!-- don't forget to update UPGRADE-*.md and src/**/CHANGELOG.md files --> | Tests pass? | yes <!-- please add some, will be required by reviewers --> | Fixed tickets | n/a <!-- #-prefixed issue number(s), if any --> | License | MIT | Doc PR | todo? Use extra details provided by popular error formats following to improve HTTP exception messages. The following formats are supported: * Hydra (default in API Platform) * RFC 7807 (followed by Symfony's [ConstraintViolationListNormalizer](https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Serializer/Normalizer/ConstraintViolationListNormalizer.php) and supported by API Platform and Apigility) * JSON:API (because it respects the semantic of the RFC 7807) It allows to write code like the following (here in a test context): ```php public function testBadRequest() { $this->expectException(ClientExceptionInterface::class); $this->expectExceptionCode(400); // HTTP status code $this->expectExceptionMessage(<<<ERROR Validation Failed users: This collection should contain 1 element or more. users: The current logged in user must be part of the users owning this resource. ERROR ); $response = (HttpClient::create())->request('POST', 'http://example.com/api/projects', [ 'json' => [ 'name' => 'My project', ], ]); $response->getContent(); } ``` Port of api-platform/core#2608 (comment). Commits ------- 96df446 [HttpClient] Parse common API error formats for better exception messages
2 parents c52af28 + 96df446 commit 041f60f

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
@@ -29,10 +29,41 @@ public function __construct(ResponseInterface $response)
2929
$url = $response->getInfo('url');
3030
$message = sprintf('HTTP %d returned for URL "%s".', $code, $url);
3131

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

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 <[email protected]>
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 <[email protected]>
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;
62+
}

0 commit comments

Comments
 (0)