Skip to content

Commit

Permalink
Merged PR 58579: Release 2.6.0
Browse files Browse the repository at this point in the history
### 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
  • Loading branch information
pvpcookie committed Oct 7, 2024
2 parents ae874f2 + 3925514 commit 99367b9
Show file tree
Hide file tree
Showing 9 changed files with 185 additions and 22 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
# 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.

# 2.5.0

### Improvements
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "dotdigital/dotdigital-php",
"description": "Dotdigital PHP Library",
"version": "2.5.0",
"version": "2.6.0",
"license": "MIT",
"autoload": {
"psr-4": {
Expand Down
2 changes: 1 addition & 1 deletion src/Exception/ResponseValidationException.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ private function decodeResponse($responseBody)
if (json_last_error() !== JSON_ERROR_NONE) {
$decoded = [
'description' => sprintf('Error decoding response - %s', json_last_error_msg()),
'errorCode' => 'Error Unknown',
'errorCode' => $this->getCode() ?: 'Error Unknown',
'details' => [],
];
}
Expand Down
101 changes: 101 additions & 0 deletions src/Exception/TooManyRequestsException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?php

namespace Dotdigital\Exception;

use Psr\Http\Message\ResponseInterface;

class TooManyRequestsException extends \ErrorException implements ExceptionInterface
{
/**
* @var ?string
*/
private $scope;

/**
* @var ?int
*/
private $limit;

/**
* @var ?int
*/
private $reset;

/**
* @param ResponseInterface $errorResponse
* @return TooManyRequestsException
*/
public static function fromErrorResponse(
ResponseInterface $errorResponse
): TooManyRequestsException {
$message = $errorResponse->getReasonPhrase();
$status = $errorResponse->getStatusCode();
$exception = new self($message, $status);
$exception->setScope($errorResponse);
$exception->setLimit($errorResponse);
$exception->setReset($errorResponse);
return $exception;
}

/**
* @param ResponseInterface $errorResponse
*
* @return void
*/
public function setScope($errorResponse): void
{
$this->scope = $errorResponse->hasHeader('x-ratelimit-scope') ?
$errorResponse->getHeader('x-ratelimit-scope')[0] :
null;
}

/**
* @return string|null
*/
public function getScope(): ?string
{
return $this->scope;
}

/**
* @param ResponseInterface $errorResponse
*
* @return void
*/
public function setLimit(ResponseInterface $errorResponse)
{
$this->limit = $errorResponse->hasHeader('x-ratelimit-limit') ?
(int) $errorResponse->getHeader('x-ratelimit-limit')[0] :
null;
}

/**
* @return int|null
*/
public function getLimit(): ?int
{
return $this->limit;
}

/**
* Set the time in seconds until the rate limit resets.
*
* @param ResponseInterface $errorResponse
*
* @return void
*/
public function setReset(ResponseInterface $errorResponse)
{
$this->reset = $errorResponse->hasHeader('x-ratelimit-reset') ?
(int) $errorResponse->getHeader('x-ratelimit-reset')[0] :
null;
}

/**
* @return int|null
*/
public function getReset(): ?int
{
return $this->reset;
}
}
26 changes: 12 additions & 14 deletions src/HttpClient/Message/V3/ResponseMediator.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,29 @@
namespace Dotdigital\HttpClient\Message\V3;

use Dotdigital\Exception\ResponseValidationException;
use Dotdigital\Exception\TooManyRequestsException;
use Psr\Http\Message\ResponseInterface;

class ResponseMediator
{
/**
* @var int[] $passableStatusCodes
*/
private static $passableStatusCodes = [
200,
201,
202,
204
];

/**
* @param ResponseInterface $response
*
* @return string
* @throws ResponseValidationException
* @throws ResponseValidationException|TooManyRequestsException
*/
public static function getContent(ResponseInterface $response)
{
if (!in_array($response->getStatusCode(), self::$passableStatusCodes)) {
throw ResponseValidationException::fromErrorResponse($response);
switch ($response->getStatusCode()) {
case 200:
case 201:
case 202:
case 204:
return $response->getBody()->getContents();
case 429:
throw TooManyRequestsException::fromErrorResponse($response);
default:
throw ResponseValidationException::fromErrorResponse($response);
}
return $response->getBody()->getContents();
}
}
5 changes: 4 additions & 1 deletion src/V3/Models/Contact.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,10 @@ public function setMatchIdentifier($matchIdentifier): void
*/
public function setDataFields($data)
{
$dataFieldsCollection = empty($data) ? null : new DataFieldCollection();
if (empty($data)) {
return;
}
$dataFieldsCollection = new DataFieldCollection();
foreach ($data as $key => $value) {
$dataField = new DataField($key, $value);
$dataFieldsCollection->add($dataField);
Expand Down
6 changes: 3 additions & 3 deletions src/V3/Models/Contact/Import.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ private function createContactCollection(array $contactsData): ContactCollection
}

/**
* @inheirtDoc
* @inheritDoc
*/
public function createFailures(array $failuresData): FailureCollection
{
Expand All @@ -52,7 +52,7 @@ public function createFailures(array $failuresData): FailureCollection
}

/**
* @inheirtDoc
* @inheritDoc
*/
public function setSummary(array $summary): void
{
Expand Down Expand Up @@ -82,7 +82,7 @@ public function setUpdated(array $updated): void
/**
* @inheritDoc
*/
public function getSummary(): SummaryInterface
public function getSummary(): ?SummaryInterface
{
return $this->summary;
}
Expand Down
4 changes: 2 additions & 2 deletions src/V3/Models/Import/ImportInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ public function setStatus(string $status): void;
public function setFailures(array $failures): void;

/**
* @return SummaryInterface
* @return SummaryInterface|null
*/
public function getSummary(): SummaryInterface;
public function getSummary(): ?SummaryInterface;

/**
* @return string
Expand Down
53 changes: 53 additions & 0 deletions tests/V3/Unit/Models/Contact/DataFieldsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

namespace Dotdigital\Tests\V3\Unit\Models\Contact;

use Dotdigital\V3\Models\Contact;
use Dotdigital\V3\Models\Contact\DataField;
use Dotdigital\V3\Models\DataFieldCollection;
use PHPUnit\Framework\TestCase;

class DataFieldsTest extends TestCase
{
public function testDataFieldsCanBeSet()
{
$dataFields = [
'FIRSTNAME' => 'Chaz',
'LASTNAME' => 'Knox',
];
$contact = new Contact();
$contact->setDataFields($dataFields);

$this->assertInstanceOf(DataFieldCollection::class, $contact->getDataFields());
}

public function testDataFieldsAreNotSetIfNoneSupplied()
{
$dataFields = [];
$contact = new Contact();
$contact->setDataFields($dataFields);

$this->assertEquals(null, $contact->getDataFields());
}

public function testSetDataFieldsCanPassEmptyCollection()
{
$dataFields = new DataFieldCollection();
$contact = new Contact();
$contact->setDataFields($dataFields);

$this->assertInstanceOf(DataFieldCollection::class, $contact->getDataFields());
}

public function testSetDataFieldsCanPassLoadedCollection() {
$dataField = new DataField('FIRSTNAME', 'Chaz');
$dataFields = new DataFieldCollection();
$dataFields->add($dataField);

$contact = new Contact();
$contact->setDataFields($dataFields);

$this->assertInstanceOf(DataFieldCollection::class, $contact->getDataFields());
$this->assertInstanceOf(DataField::class, $contact->getDataFields()->first());
}
}

0 comments on commit 99367b9

Please sign in to comment.