Skip to content

Commit 1e58a65

Browse files
authored
Merge pull request #164 from cloudflare/apalaistras/fix-request-error-handling
Adapter/Guzzle: Fix error handling for v4 API
2 parents 658ab3f + d2c4d22 commit 1e58a65

File tree

4 files changed

+131
-74
lines changed

4 files changed

+131
-74
lines changed

src/Adapter/Guzzle.php

+12-28
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11
<?php
2-
/**
3-
* User: junade
4-
* Date: 13/01/2017
5-
* Time: 18:26
6-
*/
72

83
namespace Cloudflare\API\Adapter;
94

105
use Cloudflare\API\Auth\Auth;
116
use GuzzleHttp\Client;
7+
use GuzzleHttp\Exception\RequestException;
128
use Psr\Http\Message\ResponseInterface;
139

1410
class Guzzle implements Adapter
@@ -74,36 +70,24 @@ public function delete(string $uri, array $data = [], array $headers = []): Resp
7470
return $this->request('delete', $uri, $data, $headers);
7571
}
7672

73+
/**
74+
* @SuppressWarnings(PHPMD.StaticAccess)
75+
*/
7776
public function request(string $method, string $uri, array $data = [], array $headers = [])
7877
{
7978
if (!in_array($method, ['get', 'post', 'put', 'patch', 'delete'])) {
8079
throw new \InvalidArgumentException('Request method must be get, post, put, patch, or delete');
8180
}
8281

83-
$response = $this->client->$method($uri, [
84-
'headers' => $headers,
85-
($method === 'get' ? 'query' : 'json') => $data,
86-
]);
87-
88-
$this->checkError($response);
89-
90-
return $response;
91-
}
92-
93-
private function checkError(ResponseInterface $response)
94-
{
95-
$json = json_decode($response->getBody());
96-
97-
if (json_last_error() !== JSON_ERROR_NONE) {
98-
throw new JSONException();
82+
try {
83+
$response = $this->client->$method($uri, [
84+
'headers' => $headers,
85+
($method === 'get' ? 'query' : 'json') => $data,
86+
]);
87+
} catch (RequestException $err) {
88+
throw ResponseException::fromRequestException($err);
9989
}
10090

101-
if (isset($json->errors) && count($json->errors) >= 1) {
102-
throw new ResponseException($json->errors[0]->message, $json->errors[0]->code);
103-
}
104-
105-
if (isset($json->success) && !$json->success) {
106-
throw new ResponseException('Request was unsuccessful.');
107-
}
91+
return $response;
10892
}
10993
}

src/Adapter/ResponseException.php

+31
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,37 @@
88

99
namespace Cloudflare\API\Adapter;
1010

11+
use GuzzleHttp\Exception\RequestException;
12+
1113
class ResponseException extends \Exception
1214
{
15+
/**
16+
* Generates a ResponseException from a Guzzle RequestException.
17+
*
18+
* @param RequestException $err The client request exception (typicall 4xx or 5xx response).
19+
* @return ResponseException
20+
*/
21+
public static function fromRequestException(RequestException $err): self
22+
{
23+
if (!$err->hasResponse()) {
24+
return new ResponseException($err->getMessage(), 0, $err);
25+
}
26+
27+
$response = $err->getResponse();
28+
$contentType = $response->getHeaderLine('Content-Type');
29+
30+
// Attempt to derive detailed error from standard JSON response.
31+
if (strpos($contentType, 'application/json') !== false) {
32+
$json = json_decode($response->getBody());
33+
if (json_last_error() !== JSON_ERROR_NONE) {
34+
return new ResponseException($err->getMessage(), 0, new JSONException(json_last_error_msg(), 0, $err));
35+
}
36+
37+
if (isset($json->errors) && count($json->errors) >= 1) {
38+
return new ResponseException($json->errors[0]->message, $json->errors[0]->code, $err);
39+
}
40+
}
41+
42+
return new ResponseException($err->getMessage(), 0, $err);
43+
}
1344
}

tests/Adapter/GuzzleTest.php

+7-46
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11
<?php
22

3-
/**
4-
* User: junade
5-
* Date: 13/01/2017
6-
* Time: 23:35
7-
*/
8-
9-
use GuzzleHttp\Psr7\Response;
3+
use Cloudflare\API\Adapter\ResponseException;
104

115
class GuzzleTest extends TestCase
126
{
@@ -89,48 +83,15 @@ public function testDelete()
8983
$this->assertEquals('Testing a DELETE request.', $body->json->{'X-Delete-Test'});
9084
}
9185

92-
public function testErrors()
86+
public function testNotFound()
9387
{
94-
$class = new ReflectionClass(\Cloudflare\API\Adapter\Guzzle::class);
95-
$method = $class->getMethod('checkError');
96-
$method->setAccessible(true);
97-
98-
$body =
99-
'{
100-
"result": null,
101-
"success": false,
102-
"errors": [{"code":1003,"message":"Invalid or missing zone id."}],
103-
"messages": []
104-
}'
105-
;
106-
$response = new Response(200, [], $body);
107-
108-
$this->expectException(\Cloudflare\API\Adapter\ResponseException::class);
109-
$method->invokeArgs($this->client, [$response]);
110-
111-
$body =
112-
'{
113-
"result": null,
114-
"success": false,
115-
"errors": [],
116-
"messages": []
117-
}'
118-
;
119-
$response = new Response(200, [], $body);
120-
121-
$this->expectException(\Cloudflare\API\Adapter\ResponseException::class);
122-
$method->invokeArgs($this->client, [$response]);
123-
124-
$body = 'this isnt json.';
125-
$response = new Response(200, [], $body);
126-
127-
$this->expectException(\Cloudflare\API\Adapter\JSONException::class);
128-
$method->invokeArgs($this->client, [$response]);
88+
$this->expectException(ResponseException::class);
89+
$this->client->get('https://httpbin.org/status/404');
12990
}
13091

131-
public function testNotFound()
92+
public function testServerError()
13293
{
133-
$this->expectException(\GuzzleHttp\Exception\RequestException::class);
134-
$this->client->get('https://httpbin.org/status/404');
94+
$this->expectException(ResponseException::class);
95+
$this->client->get('https://httpbin.org/status/500');
13596
}
13697
}
+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php
2+
3+
use Cloudflare\API\Adapter\ResponseException;
4+
use Cloudflare\API\Adapter\JSONException;
5+
use GuzzleHttp\Exception\RequestException;
6+
use GuzzleHttp\Psr7\Request;
7+
use GuzzleHttp\Psr7\Response;
8+
9+
/**
10+
* @SuppressWarnings(PHPMD.StaticAccess)
11+
*/
12+
class ResponseExceptionTest extends TestCase
13+
{
14+
public function testFromRequestExceptionNoResponse()
15+
{
16+
$reqErr = new RequestException('foo', new Request('GET', '/test'));
17+
$respErr = ResponseException::fromRequestException($reqErr);
18+
19+
$this->assertInstanceOf(ResponseException::class, $respErr);
20+
$this->assertEquals($reqErr->getMessage(), $respErr->getMessage());
21+
$this->assertEquals(0, $respErr->getCode());
22+
$this->assertEquals($reqErr, $respErr->getPrevious());
23+
}
24+
25+
public function testFromRequestExceptionEmptyContentType()
26+
{
27+
$resp = new Response(404);
28+
$reqErr = new RequestException('foo', new Request('GET', '/test'), $resp);
29+
$respErr = ResponseException::fromRequestException($reqErr);
30+
31+
$this->assertInstanceOf(ResponseException::class, $respErr);
32+
$this->assertEquals($reqErr->getMessage(), $respErr->getMessage());
33+
$this->assertEquals(0, $respErr->getCode());
34+
$this->assertEquals($reqErr, $respErr->getPrevious());
35+
}
36+
37+
38+
public function testFromRequestExceptionUnknownContentType()
39+
{
40+
$resp = new Response(404, ['Content-Type' => ['application/octet-stream']]);
41+
$reqErr = new RequestException('foo', new Request('GET', '/test'), $resp);
42+
$respErr = ResponseException::fromRequestException($reqErr);
43+
44+
$this->assertInstanceOf(ResponseException::class, $respErr);
45+
$this->assertEquals($reqErr->getMessage(), $respErr->getMessage());
46+
$this->assertEquals(0, $respErr->getCode());
47+
$this->assertEquals($reqErr, $respErr->getPrevious());
48+
}
49+
50+
public function testFromRequestExceptionJSONDecodeError()
51+
{
52+
$resp = new Response(404, ['Content-Type' => ['application/json; charset=utf-8']], '[what]');
53+
$reqErr = new RequestException('foo', new Request('GET', '/test'), $resp);
54+
$respErr = ResponseException::fromRequestException($reqErr);
55+
56+
$this->assertInstanceOf(ResponseException::class, $respErr);
57+
$this->assertEquals($reqErr->getMessage(), $respErr->getMessage());
58+
$this->assertEquals(0, $respErr->getCode());
59+
$this->assertInstanceOf(JSONException::class, $respErr->getPrevious());
60+
$this->assertEquals($reqErr, $respErr->getPrevious()->getPrevious());
61+
}
62+
63+
public function testFromRequestExceptionJSONWithErrors()
64+
{
65+
$body = '{
66+
"result": null,
67+
"success": false,
68+
"errors": [{"code":1003, "message":"This is an error"}],
69+
"messages": []
70+
}';
71+
72+
$resp = new Response(404, ['Content-Type' => ['application/json; charset=utf-8']], $body);
73+
$reqErr = new RequestException('foo', new Request('GET', '/test'), $resp);
74+
$respErr = ResponseException::fromRequestException($reqErr);
75+
76+
$this->assertInstanceOf(ResponseException::class, $respErr);
77+
$this->assertEquals('This is an error', $respErr->getMessage());
78+
$this->assertEquals(1003, $respErr->getCode());
79+
$this->assertEquals($reqErr, $respErr->getPrevious());
80+
}
81+
}

0 commit comments

Comments
 (0)