Skip to content

Commit 99367b9

Browse files
committed
Merged PR 58579: Release 2.6.0
### What's new - Responses with a 429 (Too Many Requests) error code now trigger a specific exception with properties exposing additional information (the call scope, rate limit and time to retry). ### Bug fixes - `Contact::setDataFields()` no longer breaks if passed an empty array. - The import summary property is now allowed to be empty. Related work items: #236362, #270151, #272401
2 parents ae874f2 + 3925514 commit 99367b9

9 files changed

Lines changed: 185 additions & 22 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
# 2.6.0
2+
3+
### What's new
4+
- Responses with a 429 (Too Many Requests) error code now trigger a specific exception with properties exposing additional information (the call scope, rate limit and time to retry).
5+
6+
### Bug fixes
7+
- `Contact::setDataFields()` no longer breaks if passed an empty array.
8+
19
# 2.5.0
210

311
### Improvements

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "dotdigital/dotdigital-php",
33
"description": "Dotdigital PHP Library",
4-
"version": "2.5.0",
4+
"version": "2.6.0",
55
"license": "MIT",
66
"autoload": {
77
"psr-4": {

src/Exception/ResponseValidationException.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ private function decodeResponse($responseBody)
6868
if (json_last_error() !== JSON_ERROR_NONE) {
6969
$decoded = [
7070
'description' => sprintf('Error decoding response - %s', json_last_error_msg()),
71-
'errorCode' => 'Error Unknown',
71+
'errorCode' => $this->getCode() ?: 'Error Unknown',
7272
'details' => [],
7373
];
7474
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<?php
2+
3+
namespace Dotdigital\Exception;
4+
5+
use Psr\Http\Message\ResponseInterface;
6+
7+
class TooManyRequestsException extends \ErrorException implements ExceptionInterface
8+
{
9+
/**
10+
* @var ?string
11+
*/
12+
private $scope;
13+
14+
/**
15+
* @var ?int
16+
*/
17+
private $limit;
18+
19+
/**
20+
* @var ?int
21+
*/
22+
private $reset;
23+
24+
/**
25+
* @param ResponseInterface $errorResponse
26+
* @return TooManyRequestsException
27+
*/
28+
public static function fromErrorResponse(
29+
ResponseInterface $errorResponse
30+
): TooManyRequestsException {
31+
$message = $errorResponse->getReasonPhrase();
32+
$status = $errorResponse->getStatusCode();
33+
$exception = new self($message, $status);
34+
$exception->setScope($errorResponse);
35+
$exception->setLimit($errorResponse);
36+
$exception->setReset($errorResponse);
37+
return $exception;
38+
}
39+
40+
/**
41+
* @param ResponseInterface $errorResponse
42+
*
43+
* @return void
44+
*/
45+
public function setScope($errorResponse): void
46+
{
47+
$this->scope = $errorResponse->hasHeader('x-ratelimit-scope') ?
48+
$errorResponse->getHeader('x-ratelimit-scope')[0] :
49+
null;
50+
}
51+
52+
/**
53+
* @return string|null
54+
*/
55+
public function getScope(): ?string
56+
{
57+
return $this->scope;
58+
}
59+
60+
/**
61+
* @param ResponseInterface $errorResponse
62+
*
63+
* @return void
64+
*/
65+
public function setLimit(ResponseInterface $errorResponse)
66+
{
67+
$this->limit = $errorResponse->hasHeader('x-ratelimit-limit') ?
68+
(int) $errorResponse->getHeader('x-ratelimit-limit')[0] :
69+
null;
70+
}
71+
72+
/**
73+
* @return int|null
74+
*/
75+
public function getLimit(): ?int
76+
{
77+
return $this->limit;
78+
}
79+
80+
/**
81+
* Set the time in seconds until the rate limit resets.
82+
*
83+
* @param ResponseInterface $errorResponse
84+
*
85+
* @return void
86+
*/
87+
public function setReset(ResponseInterface $errorResponse)
88+
{
89+
$this->reset = $errorResponse->hasHeader('x-ratelimit-reset') ?
90+
(int) $errorResponse->getHeader('x-ratelimit-reset')[0] :
91+
null;
92+
}
93+
94+
/**
95+
* @return int|null
96+
*/
97+
public function getReset(): ?int
98+
{
99+
return $this->reset;
100+
}
101+
}

src/HttpClient/Message/V3/ResponseMediator.php

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,29 @@
33
namespace Dotdigital\HttpClient\Message\V3;
44

55
use Dotdigital\Exception\ResponseValidationException;
6+
use Dotdigital\Exception\TooManyRequestsException;
67
use Psr\Http\Message\ResponseInterface;
78

89
class ResponseMediator
910
{
10-
/**
11-
* @var int[] $passableStatusCodes
12-
*/
13-
private static $passableStatusCodes = [
14-
200,
15-
201,
16-
202,
17-
204
18-
];
19-
2011
/**
2112
* @param ResponseInterface $response
2213
*
2314
* @return string
24-
* @throws ResponseValidationException
15+
* @throws ResponseValidationException|TooManyRequestsException
2516
*/
2617
public static function getContent(ResponseInterface $response)
2718
{
28-
if (!in_array($response->getStatusCode(), self::$passableStatusCodes)) {
29-
throw ResponseValidationException::fromErrorResponse($response);
19+
switch ($response->getStatusCode()) {
20+
case 200:
21+
case 201:
22+
case 202:
23+
case 204:
24+
return $response->getBody()->getContents();
25+
case 429:
26+
throw TooManyRequestsException::fromErrorResponse($response);
27+
default:
28+
throw ResponseValidationException::fromErrorResponse($response);
3029
}
31-
return $response->getBody()->getContents();
3230
}
3331
}

src/V3/Models/Contact.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,10 @@ public function setMatchIdentifier($matchIdentifier): void
110110
*/
111111
public function setDataFields($data)
112112
{
113-
$dataFieldsCollection = empty($data) ? null : new DataFieldCollection();
113+
if (empty($data)) {
114+
return;
115+
}
116+
$dataFieldsCollection = new DataFieldCollection();
114117
foreach ($data as $key => $value) {
115118
$dataField = new DataField($key, $value);
116119
$dataFieldsCollection->add($dataField);

src/V3/Models/Contact/Import.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ private function createContactCollection(array $contactsData): ContactCollection
3939
}
4040

4141
/**
42-
* @inheirtDoc
42+
* @inheritDoc
4343
*/
4444
public function createFailures(array $failuresData): FailureCollection
4545
{
@@ -52,7 +52,7 @@ public function createFailures(array $failuresData): FailureCollection
5252
}
5353

5454
/**
55-
* @inheirtDoc
55+
* @inheritDoc
5656
*/
5757
public function setSummary(array $summary): void
5858
{
@@ -82,7 +82,7 @@ public function setUpdated(array $updated): void
8282
/**
8383
* @inheritDoc
8484
*/
85-
public function getSummary(): SummaryInterface
85+
public function getSummary(): ?SummaryInterface
8686
{
8787
return $this->summary;
8888
}

src/V3/Models/Import/ImportInterface.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ public function setStatus(string $status): void;
3737
public function setFailures(array $failures): void;
3838

3939
/**
40-
* @return SummaryInterface
40+
* @return SummaryInterface|null
4141
*/
42-
public function getSummary(): SummaryInterface;
42+
public function getSummary(): ?SummaryInterface;
4343

4444
/**
4545
* @return string
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
namespace Dotdigital\Tests\V3\Unit\Models\Contact;
4+
5+
use Dotdigital\V3\Models\Contact;
6+
use Dotdigital\V3\Models\Contact\DataField;
7+
use Dotdigital\V3\Models\DataFieldCollection;
8+
use PHPUnit\Framework\TestCase;
9+
10+
class DataFieldsTest extends TestCase
11+
{
12+
public function testDataFieldsCanBeSet()
13+
{
14+
$dataFields = [
15+
'FIRSTNAME' => 'Chaz',
16+
'LASTNAME' => 'Knox',
17+
];
18+
$contact = new Contact();
19+
$contact->setDataFields($dataFields);
20+
21+
$this->assertInstanceOf(DataFieldCollection::class, $contact->getDataFields());
22+
}
23+
24+
public function testDataFieldsAreNotSetIfNoneSupplied()
25+
{
26+
$dataFields = [];
27+
$contact = new Contact();
28+
$contact->setDataFields($dataFields);
29+
30+
$this->assertEquals(null, $contact->getDataFields());
31+
}
32+
33+
public function testSetDataFieldsCanPassEmptyCollection()
34+
{
35+
$dataFields = new DataFieldCollection();
36+
$contact = new Contact();
37+
$contact->setDataFields($dataFields);
38+
39+
$this->assertInstanceOf(DataFieldCollection::class, $contact->getDataFields());
40+
}
41+
42+
public function testSetDataFieldsCanPassLoadedCollection() {
43+
$dataField = new DataField('FIRSTNAME', 'Chaz');
44+
$dataFields = new DataFieldCollection();
45+
$dataFields->add($dataField);
46+
47+
$contact = new Contact();
48+
$contact->setDataFields($dataFields);
49+
50+
$this->assertInstanceOf(DataFieldCollection::class, $contact->getDataFields());
51+
$this->assertInstanceOf(DataField::class, $contact->getDataFields()->first());
52+
}
53+
}

0 commit comments

Comments
 (0)