Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .fern/metadata.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
2 changes: 1 addition & 1 deletion src/ApplePay/ApplePayClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class ApplePayClient
* maxRetries?: int,
* timeout?: float,
* headers?: array<string, string>,
* } $options
* } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator
*/
private array $options;

Expand Down
2 changes: 1 addition & 1 deletion src/BankAccounts/BankAccountsClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class BankAccountsClient
* maxRetries?: int,
* timeout?: float,
* headers?: array<string, string>,
* } $options
* } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator
*/
private array $options;

Expand Down
2 changes: 1 addition & 1 deletion src/Bookings/BookingsClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ class BookingsClient
* maxRetries?: int,
* timeout?: float,
* headers?: array<string, string>,
* } $options
* } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator
*/
private array $options;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class CustomAttributeDefinitionsClient
* maxRetries?: int,
* timeout?: float,
* headers?: array<string, string>,
* } $options
* } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator
*/
private array $options;

Expand Down
2 changes: 1 addition & 1 deletion src/Bookings/CustomAttributes/CustomAttributesClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class CustomAttributesClient
* maxRetries?: int,
* timeout?: float,
* headers?: array<string, string>,
* } $options
* } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator
*/
private array $options;

Expand Down
2 changes: 1 addition & 1 deletion src/Bookings/LocationProfiles/LocationProfilesClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class LocationProfilesClient
* maxRetries?: int,
* timeout?: float,
* headers?: array<string, string>,
* } $options
* } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator
*/
private array $options;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class TeamMemberProfilesClient
* maxRetries?: int,
* timeout?: float,
* headers?: array<string, string>,
* } $options
* } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator
*/
private array $options;

Expand Down
2 changes: 1 addition & 1 deletion src/Cards/CardsClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class CardsClient
* maxRetries?: int,
* timeout?: float,
* headers?: array<string, string>,
* } $options
* } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator
*/
private array $options;

Expand Down
2 changes: 1 addition & 1 deletion src/CashDrawers/CashDrawersClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class CashDrawersClient
* maxRetries?: int,
* timeout?: float,
* headers?: array<string, string>,
* } $options
* } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator
*/
private array $options;

Expand Down
2 changes: 1 addition & 1 deletion src/CashDrawers/Shifts/ShiftsClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class ShiftsClient
* maxRetries?: int,
* timeout?: float,
* headers?: array<string, string>,
* } $options
* } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator
*/
private array $options;

Expand Down
2 changes: 1 addition & 1 deletion src/Catalog/CatalogClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class CatalogClient
* maxRetries?: int,
* timeout?: float,
* headers?: array<string, string>,
* } $options
* } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator
*/
private array $options;

Expand Down
2 changes: 1 addition & 1 deletion src/Catalog/Images/ImagesClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class ImagesClient
* maxRetries?: int,
* timeout?: float,
* headers?: array<string, string>,
* } $options
* } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator
*/
private array $options;

Expand Down
2 changes: 1 addition & 1 deletion src/Catalog/Object/ObjectClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class ObjectClient
* maxRetries?: int,
* timeout?: float,
* headers?: array<string, string>,
* } $options
* } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator
*/
private array $options;

Expand Down
2 changes: 1 addition & 1 deletion src/Channels/ChannelsClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class ChannelsClient
* maxRetries?: int,
* timeout?: float,
* headers?: array<string, string>,
* } $options
* } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator
*/
private array $options;

Expand Down
2 changes: 1 addition & 1 deletion src/Checkout/CheckoutClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class CheckoutClient
* maxRetries?: int,
* timeout?: float,
* headers?: array<string, string>,
* } $options
* } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator
*/
private array $options;

Expand Down
2 changes: 1 addition & 1 deletion src/Checkout/PaymentLinks/PaymentLinksClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class PaymentLinksClient
* maxRetries?: int,
* timeout?: float,
* headers?: array<string, string>,
* } $options
* } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator
*/
private array $options;

Expand Down
10 changes: 10 additions & 0 deletions src/Core/Client/RawClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,17 @@ class RawClient
*/
private array $headers;

/**
* @var ?(callable(): array<string, string>) $getAuthHeaders
*/
private $getAuthHeaders;

/**
* @param ?array{
* baseUrl?: string,
* client?: ClientInterface,
* headers?: array<string, string>,
* getAuthHeaders?: callable(): array<string, string>,
* } $options
*/
public function __construct(
Expand All @@ -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;
}

/**
Expand Down Expand Up @@ -128,18 +135,21 @@ private function encodeHeaders(
BaseApiRequest $request,
array $options,
): array {
$authHeaders = $this->getAuthHeaders !== null ? ($this->getAuthHeaders)() : [];
return match (get_class($request)) {
JsonApiRequest::class => array_merge(
[
"Content-Type" => "application/json",
"Accept" => "*/*",
],
$this->headers,
$authHeaders,
$request->headers,
$options['headers'] ?? [],
),
MultipartApiRequest::class => array_merge(
$this->headers,
$authHeaders,
$request->headers,
$options['headers'] ?? [],
),
Expand Down
81 changes: 78 additions & 3 deletions src/Core/Client/RetryMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
};
}

Expand Down Expand Up @@ -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.
*
Expand Down
4 changes: 3 additions & 1 deletion src/Core/Json/JsonDeserializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand Down
7 changes: 6 additions & 1 deletion src/Core/Json/JsonSerializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/Customers/Cards/CardsClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class CardsClient
* maxRetries?: int,
* timeout?: float,
* headers?: array<string, string>,
* } $options
* } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator
*/
private array $options;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class CustomAttributeDefinitionsClient
* maxRetries?: int,
* timeout?: float,
* headers?: array<string, string>,
* } $options
* } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator
*/
private array $options;

Expand Down
2 changes: 1 addition & 1 deletion src/Customers/CustomAttributes/CustomAttributesClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class CustomAttributesClient
* maxRetries?: int,
* timeout?: float,
* headers?: array<string, string>,
* } $options
* } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator
*/
private array $options;

Expand Down
2 changes: 1 addition & 1 deletion src/Customers/CustomersClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ class CustomersClient
* maxRetries?: int,
* timeout?: float,
* headers?: array<string, string>,
* } $options
* } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator
*/
private array $options;

Expand Down
2 changes: 1 addition & 1 deletion src/Customers/Groups/GroupsClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class GroupsClient
* maxRetries?: int,
* timeout?: float,
* headers?: array<string, string>,
* } $options
* } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator
*/
private array $options;

Expand Down
2 changes: 1 addition & 1 deletion src/Customers/Segments/SegmentsClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class SegmentsClient
* maxRetries?: int,
* timeout?: float,
* headers?: array<string, string>,
* } $options
* } $options @phpstan-ignore-next-line Property is used in endpoint methods via HttpEndpointGenerator
*/
private array $options;

Expand Down
Loading
Loading