diff --git a/.fern/metadata.json b/.fern/metadata.json index 20275927..4fa7d9e6 100644 --- a/.fern/metadata.json +++ b/.fern/metadata.json @@ -1,7 +1,7 @@ { - "cliVersion": "2.8.1", + "cliVersion": "3.20.0", "generatorName": "fernapi/fern-php-sdk", - "generatorVersion": "1.22.3", + "generatorVersion": "1.25.1", "generatorConfig": { "clientName": "SquareClient", "namespace": "Square", diff --git a/src/ApplePay/ApplePayClient.php b/src/ApplePay/ApplePayClient.php index c9baba06..ec987646 100644 --- a/src/ApplePay/ApplePayClient.php +++ b/src/ApplePay/ApplePayClient.php @@ -24,7 +24,7 @@ class ApplePayClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/BankAccounts/BankAccountsClient.php b/src/BankAccounts/BankAccountsClient.php index b050feba..7f710bf4 100644 --- a/src/BankAccounts/BankAccountsClient.php +++ b/src/BankAccounts/BankAccountsClient.php @@ -31,7 +31,7 @@ class BankAccountsClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Bookings/BookingsClient.php b/src/Bookings/BookingsClient.php index 55bd1929..5cb38b2d 100644 --- a/src/Bookings/BookingsClient.php +++ b/src/Bookings/BookingsClient.php @@ -68,7 +68,7 @@ class BookingsClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Bookings/CustomAttributeDefinitions/CustomAttributeDefinitionsClient.php b/src/Bookings/CustomAttributeDefinitions/CustomAttributeDefinitionsClient.php index f2cf45bf..af660c74 100644 --- a/src/Bookings/CustomAttributeDefinitions/CustomAttributeDefinitionsClient.php +++ b/src/Bookings/CustomAttributeDefinitions/CustomAttributeDefinitionsClient.php @@ -35,7 +35,7 @@ class CustomAttributeDefinitionsClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Bookings/CustomAttributes/CustomAttributesClient.php b/src/Bookings/CustomAttributes/CustomAttributesClient.php index 0c5fbacb..1e5e4c45 100644 --- a/src/Bookings/CustomAttributes/CustomAttributesClient.php +++ b/src/Bookings/CustomAttributes/CustomAttributesClient.php @@ -37,7 +37,7 @@ class CustomAttributesClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Bookings/LocationProfiles/LocationProfilesClient.php b/src/Bookings/LocationProfiles/LocationProfilesClient.php index ec6a8366..ef0458de 100644 --- a/src/Bookings/LocationProfiles/LocationProfilesClient.php +++ b/src/Bookings/LocationProfiles/LocationProfilesClient.php @@ -27,7 +27,7 @@ class LocationProfilesClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Bookings/TeamMemberProfiles/TeamMemberProfilesClient.php b/src/Bookings/TeamMemberProfiles/TeamMemberProfilesClient.php index c0d68dae..3fb1dd96 100644 --- a/src/Bookings/TeamMemberProfiles/TeamMemberProfilesClient.php +++ b/src/Bookings/TeamMemberProfiles/TeamMemberProfilesClient.php @@ -29,7 +29,7 @@ class TeamMemberProfilesClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Cards/CardsClient.php b/src/Cards/CardsClient.php index 215833dc..998a4cc2 100644 --- a/src/Cards/CardsClient.php +++ b/src/Cards/CardsClient.php @@ -33,7 +33,7 @@ class CardsClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/CashDrawers/CashDrawersClient.php b/src/CashDrawers/CashDrawersClient.php index f1e06b41..98956c4a 100644 --- a/src/CashDrawers/CashDrawersClient.php +++ b/src/CashDrawers/CashDrawersClient.php @@ -20,7 +20,7 @@ class CashDrawersClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/CashDrawers/Shifts/ShiftsClient.php b/src/CashDrawers/Shifts/ShiftsClient.php index ea978aa6..c41546eb 100644 --- a/src/CashDrawers/Shifts/ShiftsClient.php +++ b/src/CashDrawers/Shifts/ShiftsClient.php @@ -32,7 +32,7 @@ class ShiftsClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Catalog/CatalogClient.php b/src/Catalog/CatalogClient.php index 7d4ed18a..4e7d4cb9 100644 --- a/src/Catalog/CatalogClient.php +++ b/src/Catalog/CatalogClient.php @@ -54,7 +54,7 @@ class CatalogClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Catalog/Images/ImagesClient.php b/src/Catalog/Images/ImagesClient.php index 298fc491..23216a34 100644 --- a/src/Catalog/Images/ImagesClient.php +++ b/src/Catalog/Images/ImagesClient.php @@ -27,7 +27,7 @@ class ImagesClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Catalog/Object/ObjectClient.php b/src/Catalog/Object/ObjectClient.php index 0404225b..96178406 100644 --- a/src/Catalog/Object/ObjectClient.php +++ b/src/Catalog/Object/ObjectClient.php @@ -28,7 +28,7 @@ class ObjectClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Channels/ChannelsClient.php b/src/Channels/ChannelsClient.php index 37a4a0a6..60e2b86f 100644 --- a/src/Channels/ChannelsClient.php +++ b/src/Channels/ChannelsClient.php @@ -31,7 +31,7 @@ class ChannelsClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Checkout/CheckoutClient.php b/src/Checkout/CheckoutClient.php index b99e3576..99c8941f 100644 --- a/src/Checkout/CheckoutClient.php +++ b/src/Checkout/CheckoutClient.php @@ -35,7 +35,7 @@ class CheckoutClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Checkout/PaymentLinks/PaymentLinksClient.php b/src/Checkout/PaymentLinks/PaymentLinksClient.php index e584ab17..b58ca58d 100644 --- a/src/Checkout/PaymentLinks/PaymentLinksClient.php +++ b/src/Checkout/PaymentLinks/PaymentLinksClient.php @@ -35,7 +35,7 @@ class PaymentLinksClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Core/Client/RawClient.php b/src/Core/Client/RawClient.php index 32318c4f..d5672cc2 100644 --- a/src/Core/Client/RawClient.php +++ b/src/Core/Client/RawClient.php @@ -28,11 +28,17 @@ class RawClient */ private array $headers; + /** + * @var ?(callable(): array) $getAuthHeaders + */ + private $getAuthHeaders; + /** * @param ?array{ * baseUrl?: string, * client?: ClientInterface, * headers?: array, + * getAuthHeaders?: callable(): array, * } $options */ public function __construct( @@ -41,6 +47,7 @@ public function __construct( $this->client = $this->options['client'] ?? $this->createDefaultClient(); $this->headers = $this->options['headers'] ?? []; + $this->getAuthHeaders = $this->options['getAuthHeaders'] ?? null; } /** @@ -128,6 +135,7 @@ private function encodeHeaders( BaseApiRequest $request, array $options, ): array { + $authHeaders = $this->getAuthHeaders !== null ? ($this->getAuthHeaders)() : []; return match (get_class($request)) { JsonApiRequest::class => array_merge( [ @@ -135,11 +143,13 @@ private function encodeHeaders( "Accept" => "*/*", ], $this->headers, + $authHeaders, $request->headers, $options['headers'] ?? [], ), MultipartApiRequest::class => array_merge( $this->headers, + $authHeaders, $request->headers, $options['headers'] ?? [], ), diff --git a/src/Core/Client/RetryMiddleware.php b/src/Core/Client/RetryMiddleware.php index 2b3fabd3..adb3dcb2 100644 --- a/src/Core/Client/RetryMiddleware.php +++ b/src/Core/Client/RetryMiddleware.php @@ -16,6 +16,8 @@ class RetryMiddleware 'baseDelay' => 1000 ]; private const RETRY_STATUS_CODES = [408, 429]; + private const MAX_RETRY_DELAY = 60000; // 60 seconds in milliseconds + private const JITTER_FACTOR = 0.2; // 20% random jitter /** * @var callable(RequestInterface, array): PromiseInterface @@ -133,7 +135,7 @@ private function onFulfilled(RequestInterface $request, array $options): callabl return $value; } - return $this->doRetry($request, $options); + return $this->doRetry($request, $options, $value); }; } @@ -171,14 +173,87 @@ private function onRejected(RequestInterface $req, array $options): callable * delay: int, * retryAttempt: int, * } $options + * @param ?ResponseInterface $response * @return PromiseInterface */ - private function doRetry(RequestInterface $request, array $options): PromiseInterface + private function doRetry(RequestInterface $request, array $options, ?ResponseInterface $response = null): PromiseInterface { - $options['delay'] = $this->exponentialDelay(++$options['retryAttempt']); + $options['delay'] = $this->getRetryDelay(++$options['retryAttempt'], $response); return $this($request, $options); } + /** + * Calculate the retry delay based on response headers or exponential backoff. + * + * @param int $retryAttempt + * @param ?ResponseInterface $response + * @return int milliseconds + */ + private function getRetryDelay(int $retryAttempt, ?ResponseInterface $response): int + { + if ($response !== null) { + // Check Retry-After header + $retryAfter = $response->getHeaderLine('Retry-After'); + if ($retryAfter !== '') { + // Try parsing as integer (seconds) + if (is_numeric($retryAfter)) { + $retryAfterSeconds = (int)$retryAfter; + if ($retryAfterSeconds > 0) { + return min($retryAfterSeconds * 1000, self::MAX_RETRY_DELAY); + } + } + + // Try parsing as HTTP date + $retryAfterDate = strtotime($retryAfter); + if ($retryAfterDate !== false) { + $delay = ($retryAfterDate - time()) * 1000; + if ($delay > 0) { + return min(max($delay, 0), self::MAX_RETRY_DELAY); + } + } + } + + // Check X-RateLimit-Reset header + $rateLimitReset = $response->getHeaderLine('X-RateLimit-Reset'); + if ($rateLimitReset !== '' && is_numeric($rateLimitReset)) { + $resetTime = (int)$rateLimitReset; + $delay = ($resetTime * 1000) - (int)(microtime(true) * 1000); + if ($delay > 0) { + return $this->addPositiveJitter(min($delay, self::MAX_RETRY_DELAY)); + } + } + } + + // Fall back to exponential backoff with symmetric jitter + return $this->addSymmetricJitter( + min($this->exponentialDelay($retryAttempt), self::MAX_RETRY_DELAY) + ); + } + + /** + * Add positive jitter (0% to +20%) to the delay. + * + * @param int $delay + * @return int + */ + private function addPositiveJitter(int $delay): int + { + $jitterMultiplier = 1 + (mt_rand() / mt_getrandmax()) * self::JITTER_FACTOR; + return (int)($delay * $jitterMultiplier); + } + + /** + * Add symmetric jitter (-10% to +10%) to the delay. + * + * @param int $delay + * @return int + */ + private function addSymmetricJitter(int $delay): int + { + $jitterMultiplier = 1 + ((mt_rand() / mt_getrandmax()) - 0.5) * self::JITTER_FACTOR; + return (int)($delay * $jitterMultiplier); + } + /** * Default exponential backoff delay function. * diff --git a/src/Core/Json/JsonDeserializer.php b/src/Core/Json/JsonDeserializer.php index 7f659cd3..16b3a87b 100644 --- a/src/Core/Json/JsonDeserializer.php +++ b/src/Core/Json/JsonDeserializer.php @@ -95,7 +95,9 @@ public static function deserializeUnion(mixed $data, Union $type): mixed foreach ($type->types as $unionType) { try { return self::deserializeValue($data, $unionType); - } catch (Exception) { + } catch (\Throwable) { + // Catching Throwable instead of Exception to handle TypeError + // that occurs when assigning null to non-nullable typed properties continue; } } diff --git a/src/Core/Json/JsonSerializer.php b/src/Core/Json/JsonSerializer.php index d3039ce0..0a31ab9e 100644 --- a/src/Core/Json/JsonSerializer.php +++ b/src/Core/Json/JsonSerializer.php @@ -24,13 +24,18 @@ public static function serializeDate(DateTime $date): string /** * Serializes a DateTime object into a string using the date-time format. + * Normalizes UTC times to use 'Z' suffix instead of '+00:00'. * * @param DateTime $date The DateTime object to serialize. * @return string The serialized date-time string. */ public static function serializeDateTime(DateTime $date): string { - return $date->format(Constant::DateTimeFormat); + $formatted = $date->format(Constant::DateTimeFormat); + if (str_ends_with($formatted, '+00:00')) { + return substr($formatted, 0, -6) . 'Z'; + } + return $formatted; } /** diff --git a/src/Customers/Cards/CardsClient.php b/src/Customers/Cards/CardsClient.php index faa3a824..6e9cca3e 100644 --- a/src/Customers/Cards/CardsClient.php +++ b/src/Customers/Cards/CardsClient.php @@ -26,7 +26,7 @@ class CardsClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Customers/CustomAttributeDefinitions/CustomAttributeDefinitionsClient.php b/src/Customers/CustomAttributeDefinitions/CustomAttributeDefinitionsClient.php index 69836d21..fa339b57 100644 --- a/src/Customers/CustomAttributeDefinitions/CustomAttributeDefinitionsClient.php +++ b/src/Customers/CustomAttributeDefinitions/CustomAttributeDefinitionsClient.php @@ -37,7 +37,7 @@ class CustomAttributeDefinitionsClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Customers/CustomAttributes/CustomAttributesClient.php b/src/Customers/CustomAttributes/CustomAttributesClient.php index 782c12d2..fd76884f 100644 --- a/src/Customers/CustomAttributes/CustomAttributesClient.php +++ b/src/Customers/CustomAttributes/CustomAttributesClient.php @@ -33,7 +33,7 @@ class CustomAttributesClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Customers/CustomersClient.php b/src/Customers/CustomersClient.php index ad0c9be4..a984980e 100644 --- a/src/Customers/CustomersClient.php +++ b/src/Customers/CustomersClient.php @@ -75,7 +75,7 @@ class CustomersClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Customers/Groups/GroupsClient.php b/src/Customers/Groups/GroupsClient.php index 9395e72b..a3c4f356 100644 --- a/src/Customers/Groups/GroupsClient.php +++ b/src/Customers/Groups/GroupsClient.php @@ -39,7 +39,7 @@ class GroupsClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Customers/Segments/SegmentsClient.php b/src/Customers/Segments/SegmentsClient.php index f1869330..a332846f 100644 --- a/src/Customers/Segments/SegmentsClient.php +++ b/src/Customers/Segments/SegmentsClient.php @@ -29,7 +29,7 @@ class SegmentsClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Devices/Codes/CodesClient.php b/src/Devices/Codes/CodesClient.php index ae4e670f..d0a42dc3 100644 --- a/src/Devices/Codes/CodesClient.php +++ b/src/Devices/Codes/CodesClient.php @@ -31,7 +31,7 @@ class CodesClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Devices/DevicesClient.php b/src/Devices/DevicesClient.php index 84133f50..3a1880fb 100644 --- a/src/Devices/DevicesClient.php +++ b/src/Devices/DevicesClient.php @@ -35,7 +35,7 @@ class DevicesClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Disputes/DisputesClient.php b/src/Disputes/DisputesClient.php index 06377251..c816ddcb 100644 --- a/src/Disputes/DisputesClient.php +++ b/src/Disputes/DisputesClient.php @@ -45,7 +45,7 @@ class DisputesClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Disputes/Evidence/EvidenceClient.php b/src/Disputes/Evidence/EvidenceClient.php index e5b6fd5b..fe64cc1f 100644 --- a/src/Disputes/Evidence/EvidenceClient.php +++ b/src/Disputes/Evidence/EvidenceClient.php @@ -31,7 +31,7 @@ class EvidenceClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Employees/EmployeesClient.php b/src/Employees/EmployeesClient.php index b4c2e25a..b4ab48bd 100644 --- a/src/Employees/EmployeesClient.php +++ b/src/Employees/EmployeesClient.php @@ -29,7 +29,7 @@ class EmployeesClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Events/EventsClient.php b/src/Events/EventsClient.php index 76425ef6..635a67e5 100644 --- a/src/Events/EventsClient.php +++ b/src/Events/EventsClient.php @@ -28,7 +28,7 @@ class EventsClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/GiftCards/Activities/ActivitiesClient.php b/src/GiftCards/Activities/ActivitiesClient.php index a432453a..556138de 100644 --- a/src/GiftCards/Activities/ActivitiesClient.php +++ b/src/GiftCards/Activities/ActivitiesClient.php @@ -29,7 +29,7 @@ class ActivitiesClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/GiftCards/GiftCardsClient.php b/src/GiftCards/GiftCardsClient.php index 1e20719f..0c4467f5 100644 --- a/src/GiftCards/GiftCardsClient.php +++ b/src/GiftCards/GiftCardsClient.php @@ -45,7 +45,7 @@ class GiftCardsClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Inventory/InventoryClient.php b/src/Inventory/InventoryClient.php index 93804ebc..9b481af5 100644 --- a/src/Inventory/InventoryClient.php +++ b/src/Inventory/InventoryClient.php @@ -44,7 +44,7 @@ class InventoryClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Invoices/InvoicesClient.php b/src/Invoices/InvoicesClient.php index 5e6ba7b0..6f82d10d 100644 --- a/src/Invoices/InvoicesClient.php +++ b/src/Invoices/InvoicesClient.php @@ -47,7 +47,7 @@ class InvoicesClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Labor/BreakTypes/BreakTypesClient.php b/src/Labor/BreakTypes/BreakTypesClient.php index a6205b9d..35a8c5ee 100644 --- a/src/Labor/BreakTypes/BreakTypesClient.php +++ b/src/Labor/BreakTypes/BreakTypesClient.php @@ -35,7 +35,7 @@ class BreakTypesClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Labor/EmployeeWages/EmployeeWagesClient.php b/src/Labor/EmployeeWages/EmployeeWagesClient.php index 4a064c66..2108dd9d 100644 --- a/src/Labor/EmployeeWages/EmployeeWagesClient.php +++ b/src/Labor/EmployeeWages/EmployeeWagesClient.php @@ -29,7 +29,7 @@ class EmployeeWagesClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Labor/LaborClient.php b/src/Labor/LaborClient.php index 168c0486..f95d92bd 100644 --- a/src/Labor/LaborClient.php +++ b/src/Labor/LaborClient.php @@ -74,7 +74,7 @@ class LaborClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Labor/Shifts/ShiftsClient.php b/src/Labor/Shifts/ShiftsClient.php index 654b7b88..b61e429c 100644 --- a/src/Labor/Shifts/ShiftsClient.php +++ b/src/Labor/Shifts/ShiftsClient.php @@ -32,7 +32,7 @@ class ShiftsClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Labor/TeamMemberWages/TeamMemberWagesClient.php b/src/Labor/TeamMemberWages/TeamMemberWagesClient.php index 12ec3250..6f5b4462 100644 --- a/src/Labor/TeamMemberWages/TeamMemberWagesClient.php +++ b/src/Labor/TeamMemberWages/TeamMemberWagesClient.php @@ -29,7 +29,7 @@ class TeamMemberWagesClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Labor/WorkweekConfigs/WorkweekConfigsClient.php b/src/Labor/WorkweekConfigs/WorkweekConfigsClient.php index fc29afcc..cf196c7e 100644 --- a/src/Labor/WorkweekConfigs/WorkweekConfigsClient.php +++ b/src/Labor/WorkweekConfigs/WorkweekConfigsClient.php @@ -29,7 +29,7 @@ class WorkweekConfigsClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Locations/CustomAttributeDefinitions/CustomAttributeDefinitionsClient.php b/src/Locations/CustomAttributeDefinitions/CustomAttributeDefinitionsClient.php index a2e4b604..b939d511 100644 --- a/src/Locations/CustomAttributeDefinitions/CustomAttributeDefinitionsClient.php +++ b/src/Locations/CustomAttributeDefinitions/CustomAttributeDefinitionsClient.php @@ -35,7 +35,7 @@ class CustomAttributeDefinitionsClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Locations/CustomAttributes/CustomAttributesClient.php b/src/Locations/CustomAttributes/CustomAttributesClient.php index 6bcd3f60..8b67edf0 100644 --- a/src/Locations/CustomAttributes/CustomAttributesClient.php +++ b/src/Locations/CustomAttributes/CustomAttributesClient.php @@ -37,7 +37,7 @@ class CustomAttributesClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Locations/LocationsClient.php b/src/Locations/LocationsClient.php index 3d2dbfb2..de293946 100644 --- a/src/Locations/LocationsClient.php +++ b/src/Locations/LocationsClient.php @@ -49,7 +49,7 @@ class LocationsClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Locations/Transactions/TransactionsClient.php b/src/Locations/Transactions/TransactionsClient.php index ec5f2747..ff9e7195 100644 --- a/src/Locations/Transactions/TransactionsClient.php +++ b/src/Locations/Transactions/TransactionsClient.php @@ -30,7 +30,7 @@ class TransactionsClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Loyalty/Accounts/AccountsClient.php b/src/Loyalty/Accounts/AccountsClient.php index 814b6769..4c54fc14 100644 --- a/src/Loyalty/Accounts/AccountsClient.php +++ b/src/Loyalty/Accounts/AccountsClient.php @@ -32,7 +32,7 @@ class AccountsClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Loyalty/LoyaltyClient.php b/src/Loyalty/LoyaltyClient.php index 40949973..3054cf93 100644 --- a/src/Loyalty/LoyaltyClient.php +++ b/src/Loyalty/LoyaltyClient.php @@ -42,7 +42,7 @@ class LoyaltyClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Loyalty/Programs/ProgramsClient.php b/src/Loyalty/Programs/ProgramsClient.php index eb8483f2..bfc4f4e5 100644 --- a/src/Loyalty/Programs/ProgramsClient.php +++ b/src/Loyalty/Programs/ProgramsClient.php @@ -33,7 +33,7 @@ class ProgramsClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Loyalty/Programs/Promotions/PromotionsClient.php b/src/Loyalty/Programs/Promotions/PromotionsClient.php index 2cde943d..5fc28fb0 100644 --- a/src/Loyalty/Programs/Promotions/PromotionsClient.php +++ b/src/Loyalty/Programs/Promotions/PromotionsClient.php @@ -33,7 +33,7 @@ class PromotionsClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Loyalty/Rewards/RewardsClient.php b/src/Loyalty/Rewards/RewardsClient.php index 95925dfd..934c8098 100644 --- a/src/Loyalty/Rewards/RewardsClient.php +++ b/src/Loyalty/Rewards/RewardsClient.php @@ -32,7 +32,7 @@ class RewardsClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Merchants/CustomAttributeDefinitions/CustomAttributeDefinitionsClient.php b/src/Merchants/CustomAttributeDefinitions/CustomAttributeDefinitionsClient.php index d08f4bf7..354f9664 100644 --- a/src/Merchants/CustomAttributeDefinitions/CustomAttributeDefinitionsClient.php +++ b/src/Merchants/CustomAttributeDefinitions/CustomAttributeDefinitionsClient.php @@ -35,7 +35,7 @@ class CustomAttributeDefinitionsClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Merchants/CustomAttributes/CustomAttributesClient.php b/src/Merchants/CustomAttributes/CustomAttributesClient.php index 177e5701..a65b161c 100644 --- a/src/Merchants/CustomAttributes/CustomAttributesClient.php +++ b/src/Merchants/CustomAttributes/CustomAttributesClient.php @@ -37,7 +37,7 @@ class CustomAttributesClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Merchants/MerchantsClient.php b/src/Merchants/MerchantsClient.php index f72b1a6e..bdb76451 100644 --- a/src/Merchants/MerchantsClient.php +++ b/src/Merchants/MerchantsClient.php @@ -41,7 +41,7 @@ class MerchantsClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Mobile/MobileClient.php b/src/Mobile/MobileClient.php index ec5ce928..3f00659e 100644 --- a/src/Mobile/MobileClient.php +++ b/src/Mobile/MobileClient.php @@ -24,7 +24,7 @@ class MobileClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/OAuth/OAuthClient.php b/src/OAuth/OAuthClient.php index f2cd8a9d..bca77c84 100644 --- a/src/OAuth/OAuthClient.php +++ b/src/OAuth/OAuthClient.php @@ -27,7 +27,7 @@ class OAuthClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Orders/CustomAttributeDefinitions/CustomAttributeDefinitionsClient.php b/src/Orders/CustomAttributeDefinitions/CustomAttributeDefinitionsClient.php index b463aee6..7ee2b03e 100644 --- a/src/Orders/CustomAttributeDefinitions/CustomAttributeDefinitionsClient.php +++ b/src/Orders/CustomAttributeDefinitions/CustomAttributeDefinitionsClient.php @@ -35,7 +35,7 @@ class CustomAttributeDefinitionsClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Orders/CustomAttributes/CustomAttributesClient.php b/src/Orders/CustomAttributes/CustomAttributesClient.php index df0e4ca9..2afb30b8 100644 --- a/src/Orders/CustomAttributes/CustomAttributesClient.php +++ b/src/Orders/CustomAttributes/CustomAttributesClient.php @@ -37,7 +37,7 @@ class CustomAttributesClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Orders/OrdersClient.php b/src/Orders/OrdersClient.php index 0f9304cb..98996e72 100644 --- a/src/Orders/OrdersClient.php +++ b/src/Orders/OrdersClient.php @@ -50,7 +50,7 @@ class OrdersClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Payments/PaymentsClient.php b/src/Payments/PaymentsClient.php index f4f6dbeb..45b9f050 100644 --- a/src/Payments/PaymentsClient.php +++ b/src/Payments/PaymentsClient.php @@ -39,7 +39,7 @@ class PaymentsClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Payouts/PayoutsClient.php b/src/Payouts/PayoutsClient.php index d6b28f47..d4aac3aa 100644 --- a/src/Payouts/PayoutsClient.php +++ b/src/Payouts/PayoutsClient.php @@ -32,7 +32,7 @@ class PayoutsClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Refunds/RefundsClient.php b/src/Refunds/RefundsClient.php index 1241f916..57d76e8c 100644 --- a/src/Refunds/RefundsClient.php +++ b/src/Refunds/RefundsClient.php @@ -31,7 +31,7 @@ class RefundsClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Sites/SitesClient.php b/src/Sites/SitesClient.php index 40e466ff..23cc9e4c 100644 --- a/src/Sites/SitesClient.php +++ b/src/Sites/SitesClient.php @@ -23,7 +23,7 @@ class SitesClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Snippets/SnippetsClient.php b/src/Snippets/SnippetsClient.php index 60756494..0080842d 100644 --- a/src/Snippets/SnippetsClient.php +++ b/src/Snippets/SnippetsClient.php @@ -28,7 +28,7 @@ class SnippetsClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/SquareClient.php b/src/SquareClient.php index e8d20c07..cd2e3498 100644 --- a/src/SquareClient.php +++ b/src/SquareClient.php @@ -231,7 +231,7 @@ class SquareClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; @@ -270,12 +270,12 @@ public function __construct( } $this->options = $options ?? []; + $this->options['headers'] = array_merge( $defaultHeaders, $this->options['headers'] ?? [], ); - $this->client = new RawClient( options: $this->options, ); diff --git a/src/Subscriptions/SubscriptionsClient.php b/src/Subscriptions/SubscriptionsClient.php index d9629e5a..84206b30 100644 --- a/src/Subscriptions/SubscriptionsClient.php +++ b/src/Subscriptions/SubscriptionsClient.php @@ -49,7 +49,7 @@ class SubscriptionsClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Team/TeamClient.php b/src/Team/TeamClient.php index 347c5380..1748606c 100644 --- a/src/Team/TeamClient.php +++ b/src/Team/TeamClient.php @@ -30,7 +30,7 @@ class TeamClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/TeamMembers/TeamMembersClient.php b/src/TeamMembers/TeamMembersClient.php index 86b79e44..b3157ec6 100644 --- a/src/TeamMembers/TeamMembersClient.php +++ b/src/TeamMembers/TeamMembersClient.php @@ -40,7 +40,7 @@ class TeamMembersClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/TeamMembers/WageSetting/WageSettingClient.php b/src/TeamMembers/WageSetting/WageSettingClient.php index 43cace58..631173f8 100644 --- a/src/TeamMembers/WageSetting/WageSettingClient.php +++ b/src/TeamMembers/WageSetting/WageSettingClient.php @@ -26,7 +26,7 @@ class WageSettingClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Terminal/Actions/ActionsClient.php b/src/Terminal/Actions/ActionsClient.php index 92cc7cf9..a26e10af 100644 --- a/src/Terminal/Actions/ActionsClient.php +++ b/src/Terminal/Actions/ActionsClient.php @@ -30,7 +30,7 @@ class ActionsClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Terminal/Checkouts/CheckoutsClient.php b/src/Terminal/Checkouts/CheckoutsClient.php index 198a3ae2..bad02ea0 100644 --- a/src/Terminal/Checkouts/CheckoutsClient.php +++ b/src/Terminal/Checkouts/CheckoutsClient.php @@ -30,7 +30,7 @@ class CheckoutsClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Terminal/Refunds/RefundsClient.php b/src/Terminal/Refunds/RefundsClient.php index f8917809..ae556b91 100644 --- a/src/Terminal/Refunds/RefundsClient.php +++ b/src/Terminal/Refunds/RefundsClient.php @@ -30,7 +30,7 @@ class RefundsClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Terminal/TerminalClient.php b/src/Terminal/TerminalClient.php index b85a5315..261928c2 100644 --- a/src/Terminal/TerminalClient.php +++ b/src/Terminal/TerminalClient.php @@ -46,7 +46,7 @@ class TerminalClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/TransferOrders/TransferOrdersClient.php b/src/TransferOrders/TransferOrdersClient.php index a84d5654..7f57c11d 100644 --- a/src/TransferOrders/TransferOrdersClient.php +++ b/src/TransferOrders/TransferOrdersClient.php @@ -41,7 +41,7 @@ class TransferOrdersClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/V1Transactions/V1TransactionsClient.php b/src/V1Transactions/V1TransactionsClient.php index 27c0f4fe..cb7f578b 100644 --- a/src/V1Transactions/V1TransactionsClient.php +++ b/src/V1Transactions/V1TransactionsClient.php @@ -27,7 +27,7 @@ class V1TransactionsClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Vendors/VendorsClient.php b/src/Vendors/VendorsClient.php index f3f427f1..d20bf686 100644 --- a/src/Vendors/VendorsClient.php +++ b/src/Vendors/VendorsClient.php @@ -36,7 +36,7 @@ class VendorsClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Webhooks/EventTypes/EventTypesClient.php b/src/Webhooks/EventTypes/EventTypesClient.php index c0cd4a2b..7e78b6dc 100644 --- a/src/Webhooks/EventTypes/EventTypesClient.php +++ b/src/Webhooks/EventTypes/EventTypesClient.php @@ -24,7 +24,7 @@ class EventTypesClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Webhooks/Subscriptions/SubscriptionsClient.php b/src/Webhooks/Subscriptions/SubscriptionsClient.php index 1738b5a0..66fc51b1 100644 --- a/src/Webhooks/Subscriptions/SubscriptionsClient.php +++ b/src/Webhooks/Subscriptions/SubscriptionsClient.php @@ -39,7 +39,7 @@ class SubscriptionsClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/src/Webhooks/WebhooksClient.php b/src/Webhooks/WebhooksClient.php index 4d8909ae..30ecf2f7 100644 --- a/src/Webhooks/WebhooksClient.php +++ b/src/Webhooks/WebhooksClient.php @@ -26,7 +26,7 @@ class WebhooksClient * maxRetries?: int, * timeout?: float, * headers?: array, - * } $options + * } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator */ private array $options; diff --git a/tests/Core/Client/RawClientTest.php b/tests/Core/Client/RawClientTest.php index 54d136da..a1ec187c 100644 --- a/tests/Core/Client/RawClientTest.php +++ b/tests/Core/Client/RawClientTest.php @@ -5,6 +5,8 @@ use GuzzleHttp\Client; use GuzzleHttp\Handler\MockHandler; use GuzzleHttp\HandlerStack; +use GuzzleHttp\Promise as P; +use GuzzleHttp\Psr7\Request; use GuzzleHttp\Psr7\Response; use PHPUnit\Framework\TestCase; use Psr\Http\Client\ClientExceptionInterface; @@ -441,4 +443,229 @@ public function testShouldFailOn400Response(): void $this->assertEquals(1, $this->mockHandler->count()); } + + public function testRetryAfterSecondsHeaderControlsDelay(): void + { + $responses = [ + new Response(503, ['Retry-After' => '10']), + new Response(200), + ]; + $capturedOptions = []; + + $handler = function (RequestInterface $request, array $options) use (&$responses, &$capturedOptions) { + $capturedOptions[] = $options; + $response = array_shift($responses); + return P\Create::promiseFor($response); + }; + + $middleware = RetryMiddleware::create([ + 'maxRetries' => 2, + 'baseDelay' => 1000, + ]); + + $retryHandler = $middleware($handler); + $request = new Request('GET', $this->baseUrl . '/test'); + + $promise = $retryHandler($request, ['delay' => 0]); + $promise->wait(); + + $this->assertCount(2, $capturedOptions); + $this->assertSame(0, $capturedOptions[0]['delay']); + $delay = $capturedOptions[1]['delay']; + $this->assertGreaterThanOrEqual(10000, $delay); + $this->assertLessThanOrEqual(12000, $delay); + } + + public function testRetryAfterHttpDateHeaderIsHandled(): void + { + $retryAfterDate = gmdate('D, d M Y H:i:s \G\M\T', time() + 5); + + $responses = [ + new Response(503, ['Retry-After' => $retryAfterDate]), + new Response(200), + ]; + $capturedOptions = []; + + $handler = function (RequestInterface $request, array $options) use (&$responses, &$capturedOptions) { + $capturedOptions[] = $options; + $response = array_shift($responses); + return P\Create::promiseFor($response); + }; + + $middleware = RetryMiddleware::create([ + 'maxRetries' => 2, + 'baseDelay' => 1000, + ]); + + $retryHandler = $middleware($handler); + $request = new Request('GET', $this->baseUrl . '/test'); + + $promise = $retryHandler($request, ['delay' => 0]); + $promise->wait(); + + $this->assertCount(2, $capturedOptions); + $delay = $capturedOptions[1]['delay']; + $this->assertGreaterThan(0, $delay); + $this->assertLessThanOrEqual(60000, $delay); + } + + public function testRateLimitResetHeaderControlsDelay(): void + { + $resetTime = (int) floor(microtime(true)) + 5; + $responses = [ + new Response(429, ['X-RateLimit-Reset' => (string) $resetTime]), + new Response(200), + ]; + $capturedOptions = []; + + $handler = function (RequestInterface $request, array $options) use (&$responses, &$capturedOptions) { + $capturedOptions[] = $options; + $response = array_shift($responses); + return P\Create::promiseFor($response); + }; + + $middleware = RetryMiddleware::create([ + 'maxRetries' => 2, + 'baseDelay' => 1000, + ]); + + $retryHandler = $middleware($handler); + $request = new Request('GET', $this->baseUrl . '/test'); + + $promise = $retryHandler($request, ['delay' => 0]); + $promise->wait(); + + $this->assertCount(2, $capturedOptions); + $delay = $capturedOptions[1]['delay']; + $this->assertGreaterThan(0, $delay); + $this->assertLessThanOrEqual(60000, $delay); + } + + public function testRateLimitResetHeaderRespectsMaxDelayAndPositiveJitter(): void + { + $resetTime = (int) floor(microtime(true)) + 1000; + $responses = [ + new Response(429, ['X-RateLimit-Reset' => (string) $resetTime]), + new Response(200), + ]; + $capturedOptions = []; + + $handler = function (RequestInterface $request, array $options) use (&$responses, &$capturedOptions) { + $capturedOptions[] = $options; + $response = array_shift($responses); + return P\Create::promiseFor($response); + }; + + $middleware = RetryMiddleware::create([ + 'maxRetries' => 1, + 'baseDelay' => 1000, + ]); + + $retryHandler = $middleware($handler); + $request = new Request('GET', $this->baseUrl . '/test'); + + $promise = $retryHandler($request, ['delay' => 0]); + $promise->wait(); + + $delay = $capturedOptions[1]['delay']; + $this->assertGreaterThanOrEqual(60000, $delay); + $this->assertLessThanOrEqual(72000, $delay); + } + + public function testExponentialBackoffWithSymmetricJitterWhenNoHeaders(): void + { + $responses = [ + new Response(503), + new Response(200), + ]; + $capturedOptions = []; + + $handler = function (RequestInterface $request, array $options) use (&$responses, &$capturedOptions) { + $capturedOptions[] = $options; + $response = array_shift($responses); + return P\Create::promiseFor($response); + }; + + $middleware = RetryMiddleware::create([ + 'maxRetries' => 1, + 'baseDelay' => 1000, + ]); + + $retryHandler = $middleware($handler); + $request = new Request('GET', $this->baseUrl . '/test'); + + $promise = $retryHandler($request, ['delay' => 0]); + $promise->wait(); + + $this->assertCount(2, $capturedOptions); + $delay = $capturedOptions[1]['delay']; + $this->assertGreaterThanOrEqual(900, $delay); + $this->assertLessThanOrEqual(1100, $delay); + } + + public function testRetryAfterHeaderTakesPrecedenceOverRateLimitReset(): void + { + $resetTime = (int) floor(microtime(true)) + 30; + $responses = [ + new Response(503, [ + 'Retry-After' => '5', + 'X-RateLimit-Reset' => (string) $resetTime, + ]), + new Response(200), + ]; + $capturedOptions = []; + + $handler = function (RequestInterface $request, array $options) use (&$responses, &$capturedOptions) { + $capturedOptions[] = $options; + $response = array_shift($responses); + return P\Create::promiseFor($response); + }; + + $middleware = RetryMiddleware::create([ + 'maxRetries' => 2, + 'baseDelay' => 1000, + ]); + + $retryHandler = $middleware($handler); + $request = new Request('GET', $this->baseUrl . '/test'); + + $promise = $retryHandler($request, ['delay' => 0]); + $promise->wait(); + + $this->assertCount(2, $capturedOptions); + $delay = $capturedOptions[1]['delay']; + $this->assertGreaterThanOrEqual(5000, $delay); + $this->assertLessThanOrEqual(6000, $delay); + } + + public function testMaxDelayCapIsApplied(): void + { + $responses = [ + new Response(503, ['Retry-After' => '120']), + new Response(200), + ]; + $capturedOptions = []; + + $handler = function (RequestInterface $request, array $options) use (&$responses, &$capturedOptions) { + $capturedOptions[] = $options; + $response = array_shift($responses); + return P\Create::promiseFor($response); + }; + + $middleware = RetryMiddleware::create([ + 'maxRetries' => 2, + 'baseDelay' => 1000, + ]); + + $retryHandler = $middleware($handler); + $request = new Request('GET', $this->baseUrl . '/test'); + + $promise = $retryHandler($request, ['delay' => 0]); + $promise->wait(); + + $this->assertCount(2, $capturedOptions); + $delay = $capturedOptions[1]['delay']; + $this->assertGreaterThanOrEqual(60000, $delay); + $this->assertLessThanOrEqual(72000, $delay); + } } diff --git a/tests/Core/Json/ExhaustiveTest.php b/tests/Core/Json/ExhaustiveTest.php index 4384a3a1..38a29912 100644 --- a/tests/Core/Json/ExhaustiveTest.php +++ b/tests/Core/Json/ExhaustiveTest.php @@ -142,7 +142,7 @@ public function testExhaustive(): void 'simple_property' => 'Test String', // Omit 'nullable_property' to test null serialization 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'datetime_property' => '2023-01-01T12:34:56Z', 'string_array' => ['one', 'two', 'three'], 'map_property' => ['key1' => 1, 'key2' => 2], 'object_array' => [ diff --git a/tests/Core/Json/UnionArrayTest.php b/tests/Core/Json/UnionArrayTest.php index fd2691d5..e551c764 100644 --- a/tests/Core/Json/UnionArrayTest.php +++ b/tests/Core/Json/UnionArrayTest.php @@ -37,7 +37,7 @@ public function testUnionArray(): void $expectedJson = json_encode( [ 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', + 1 => '2023-01-01T12:00:00Z', 2 => null, 3 => 'Some String' ]