From 34892b325daf92e6c108372a3d20372e6fbd4a61 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 28 Apr 2026 04:35:51 +0000 Subject: [PATCH 01/13] fix: revert enum parsing change that lead to unconditional failure --- src/Core/Conversion/EnumOf.php | 20 ++--------- tests/Core/ModelTest.php | 62 ---------------------------------- 2 files changed, 3 insertions(+), 79 deletions(-) diff --git a/src/Core/Conversion/EnumOf.php b/src/Core/Conversion/EnumOf.php index 55872cd..611a7df 100644 --- a/src/Core/Conversion/EnumOf.php +++ b/src/Core/Conversion/EnumOf.php @@ -19,12 +19,9 @@ final class EnumOf implements Converter /** * @param list $members - * @param class-string<\BackedEnum>|null $class */ - public function __construct( - private readonly array $members, - private readonly ?string $class = null, - ) { + public function __construct(private readonly array $members) + { $type = 'NULL'; foreach ($this->members as $member) { $type = gettype($member); @@ -36,24 +33,13 @@ public function __construct( public static function fromBackedEnum(string $enum): self { // @phpstan-ignore-next-line argument.type - return self::$cache[$enum] ??= new self( - array_column($enum::cases(), column_key: 'value'), - class: $enum, - ); + return self::$cache[$enum] ??= new self(array_column($enum::cases(), column_key: 'value')); } public function coerce(mixed $value, CoerceState $state): mixed { $this->tally($value, state: $state); - if ($value instanceof \BackedEnum) { - return $value; - } - - if (null !== $this->class && (is_int($value) || is_string($value))) { - return ($this->class)::tryFrom($value) ?? $value; - } - return $value; } diff --git a/tests/Core/ModelTest.php b/tests/Core/ModelTest.php index c551866..033b9d1 100644 --- a/tests/Core/ModelTest.php +++ b/tests/Core/ModelTest.php @@ -47,30 +47,6 @@ public function __construct( } } -enum TicketPriority: string -{ - case Low = 'low'; - case High = 'high'; -} - -class Ticket implements BaseModel -{ - /** @use SdkModel> */ - use SdkModel; - - #[Required(enum: TicketPriority::class)] - public TicketPriority $priority; - - /** @var list */ - #[Required(list: TicketPriority::class)] - public array $labels; - - public function __construct() - { - $this->initialize(); - } -} - /** * @internal * @@ -165,42 +141,4 @@ public function testSerializeModelWithExplicitNull(): void json_encode($model) ); } - - #[Test] - public function testScalarEnumCoercesToInstance(): void - { - $model = Ticket::fromArray(['priority' => 'low', 'labels' => []]); - $this->assertSame(TicketPriority::Low, $model->priority); - } - - #[Test] - public function testListOfEnumCoercesElementsToInstances(): void - { - $model = Ticket::fromArray(['priority' => 'low', 'labels' => ['low', 'high']]); - $this->assertCount(2, $model->labels); - $this->assertSame(TicketPriority::Low, $model->labels[0]); - $this->assertSame(TicketPriority::High, $model->labels[1]); - } - - #[Test] - public function testEnumInstancePassesThrough(): void - { - $model = Ticket::fromArray(['priority' => TicketPriority::High, 'labels' => []]); - $this->assertSame(TicketPriority::High, $model->priority); - } - - #[Test] - public function testInvalidEnumScalarFallsBackToData(): void - { - $model = Ticket::fromArray(['priority' => 'urgent', 'labels' => []]); - $this->assertSame('urgent', $model['priority']); - } - - #[Test] - public function testEnumWireFormatStableAcrossConstruction(): void - { - $fromScalar = Ticket::fromArray(['priority' => 'low', 'labels' => ['high']]); - $fromInstance = Ticket::fromArray(['priority' => TicketPriority::Low, 'labels' => [TicketPriority::High]]); - $this->assertSame(json_encode($fromScalar), json_encode($fromInstance)); - } } From 47b835c86b80681f501f029709a8198fb44cdce3 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 12 Jul 2026 21:36:50 +0000 Subject: [PATCH 02/13] feat: support setting headers via env --- src/Client.php | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/Client.php b/src/Client.php index 8267a23..e3ad701 100644 --- a/src/Client.php +++ b/src/Client.php @@ -113,18 +113,31 @@ public function __construct( $requestOptions, ); + /** @var array $headers */ + $headers = [ + 'Content-Type' => 'application/json', + 'Accept' => 'application/json', + 'User-Agent' => sprintf('cas-parser/PHP %s', VERSION), + 'X-Stainless-Lang' => 'php', + 'X-Stainless-Package-Version' => '0.7.0', + 'X-Stainless-Arch' => Util::machtype(), + 'X-Stainless-OS' => Util::ostype(), + 'X-Stainless-Runtime' => php_sapi_name(), + 'X-Stainless-Runtime-Version' => phpversion(), + ]; + + $customHeadersEnv = Util::getenv('CAS_PARSER_CUSTOM_HEADERS'); + if (null !== $customHeadersEnv) { + foreach (explode("\n", $customHeadersEnv) as $line) { + $colon = strpos($line, ':'); + if (false !== $colon) { + $headers[trim(substr($line, 0, $colon))] = trim(substr($line, $colon + 1)); + } + } + } + parent::__construct( - headers: [ - 'Content-Type' => 'application/json', - 'Accept' => 'application/json', - 'User-Agent' => sprintf('cas-parser/PHP %s', VERSION), - 'X-Stainless-Lang' => 'php', - 'X-Stainless-Package-Version' => '0.0.1', - 'X-Stainless-Arch' => Util::machtype(), - 'X-Stainless-OS' => Util::ostype(), - 'X-Stainless-Runtime' => php_sapi_name(), - 'X-Stainless-Runtime-Version' => phpversion(), - ], + headers: $headers, baseUrl: $baseUrl, options: $options ); From 7e1cddaf327a70232a26e3e09eafa55c98b69260 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 30 Apr 2026 04:25:43 +0000 Subject: [PATCH 03/13] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index e26d438..4aee240 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 21 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cas-parser%2Fcas-parser-e5c0c65637cdf3a6c4360b8193973b73a3d35ad1056ef607c3319ef03e591a55.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cas-parser/cas-parser-e5c0c65637cdf3a6c4360b8193973b73a3d35ad1056ef607c3319ef03e591a55.yml openapi_spec_hash: 7515d1e5fe3130b9f5411f7aacbc8a64 config_hash: 5509bb7a961ae2e79114b24c381606d4 From 3584e8fa49d508bb542c49bb782e20e9f3bbc007 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 1 May 2026 03:25:22 +0000 Subject: [PATCH 04/13] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 4aee240..d5b8f44 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 21 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cas-parser/cas-parser-e5c0c65637cdf3a6c4360b8193973b73a3d35ad1056ef607c3319ef03e591a55.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cas-parser/cas-parser-2fd773786951b723a5d7d7342bf1c6ab46f08bd2851e916d188faae379d5aa4c.yml openapi_spec_hash: 7515d1e5fe3130b9f5411f7aacbc8a64 config_hash: 5509bb7a961ae2e79114b24c381606d4 From 673d29896be612e78e60e40283e6ce7bb980f0c3 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 2 May 2026 10:18:11 +0000 Subject: [PATCH 05/13] feat(api): api update --- .stats.yml | 4 ++-- src/InboundEmail/InboundEmailGetResponse.php | 24 +++++++++---------- .../InboundEmailGetResponse/Status.php | 2 +- src/InboundEmail/InboundEmailListParams.php | 4 ++-- .../InboundEmailListResponse/InboundEmail.php | 24 +++++++++---------- .../InboundEmail/Status.php | 2 +- src/InboundEmail/InboundEmailNewResponse.php | 24 +++++++++---------- .../InboundEmailNewResponse/Status.php | 2 +- src/Inbox/InboxListCasFilesResponse/File.php | 10 ++++++-- src/Services/InboundEmailRawService.php | 6 ++--- src/Services/InboundEmailService.php | 6 ++--- 11 files changed, 57 insertions(+), 51 deletions(-) diff --git a/.stats.yml b/.stats.yml index d5b8f44..cd58596 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 21 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cas-parser/cas-parser-2fd773786951b723a5d7d7342bf1c6ab46f08bd2851e916d188faae379d5aa4c.yml -openapi_spec_hash: 7515d1e5fe3130b9f5411f7aacbc8a64 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cas-parser/cas-parser-c7cca9a7a8e15f8a584c22eab142c4af72a329117f63cc3b3f7cabb25410f2ce.yml +openapi_spec_hash: f40d936e433bbf8c98179d0b36f304c8 config_hash: 5509bb7a961ae2e79114b24c381606d4 diff --git a/src/InboundEmail/InboundEmailGetResponse.php b/src/InboundEmail/InboundEmailGetResponse.php index f692457..64fb583 100644 --- a/src/InboundEmail/InboundEmailGetResponse.php +++ b/src/InboundEmail/InboundEmailGetResponse.php @@ -39,14 +39,14 @@ final class InboundEmailGetResponse implements BaseModel public ?array $allowedSources; /** - * Webhook URL for email notifications. `null` means files are only - * retrievable via `GET /v4/inbound-email/{id}/files` (pull delivery). + * Webhook URL for email notifications. Empty string (`""`) means files are + * only retrievable via `GET /v4/inbound-email/{id}/files` (SDK / pull mode). */ - #[Optional('callback_url', nullable: true)] + #[Optional('callback_url')] public ?string $callbackURL; /** - * When the mailbox was created. + * When the inbound email was created. */ #[Optional('created_at')] public ?\DateTimeInterface $createdAt; @@ -78,7 +78,7 @@ final class InboundEmailGetResponse implements BaseModel public ?string $reference; /** - * Current mailbox status. + * Current inbound email lifecycle status. * * @var value-of|null $status */ @@ -86,7 +86,7 @@ final class InboundEmailGetResponse implements BaseModel public ?string $status; /** - * When the mailbox was last updated. + * When the inbound email was last updated. */ #[Optional('updated_at')] public ?\DateTimeInterface $updatedAt; @@ -145,10 +145,10 @@ public function withAllowedSources(array $allowedSources): self } /** - * Webhook URL for email notifications. `null` means files are only - * retrievable via `GET /v4/inbound-email/{id}/files` (pull delivery). + * Webhook URL for email notifications. Empty string (`""`) means files are + * only retrievable via `GET /v4/inbound-email/{id}/files` (SDK / pull mode). */ - public function withCallbackURL(?string $callbackURL): self + public function withCallbackURL(string $callbackURL): self { $self = clone $this; $self['callbackURL'] = $callbackURL; @@ -157,7 +157,7 @@ public function withCallbackURL(?string $callbackURL): self } /** - * When the mailbox was created. + * When the inbound email was created. */ public function withCreatedAt(\DateTimeInterface $createdAt): self { @@ -214,7 +214,7 @@ public function withReference(?string $reference): self } /** - * Current mailbox status. + * Current inbound email lifecycle status. * * @param Status|value-of $status */ @@ -227,7 +227,7 @@ public function withStatus(Status|string $status): self } /** - * When the mailbox was last updated. + * When the inbound email was last updated. */ public function withUpdatedAt(\DateTimeInterface $updatedAt): self { diff --git a/src/InboundEmail/InboundEmailGetResponse/Status.php b/src/InboundEmail/InboundEmailGetResponse/Status.php index 4ba7c1a..a8fae74 100644 --- a/src/InboundEmail/InboundEmailGetResponse/Status.php +++ b/src/InboundEmail/InboundEmailGetResponse/Status.php @@ -5,7 +5,7 @@ namespace CasParser\InboundEmail\InboundEmailGetResponse; /** - * Current mailbox status. + * Current inbound email lifecycle status. */ enum Status: string { diff --git a/src/InboundEmail/InboundEmailListParams.php b/src/InboundEmail/InboundEmailListParams.php index 99dde7f..ceb05f7 100644 --- a/src/InboundEmail/InboundEmailListParams.php +++ b/src/InboundEmail/InboundEmailListParams.php @@ -11,8 +11,8 @@ use CasParser\InboundEmail\InboundEmailListParams\Status; /** - * List all mailboxes associated with your API key. - * Returns active and inactive mailboxes (deleted mailboxes are excluded). + * List all inbound emails associated with your API key. + * Returns active and paused inbound emails (deleted ones are excluded). * * @see CasParser\Services\InboundEmailService::list() * diff --git a/src/InboundEmail/InboundEmailListResponse/InboundEmail.php b/src/InboundEmail/InboundEmailListResponse/InboundEmail.php index 6ee9453..b57b326 100644 --- a/src/InboundEmail/InboundEmailListResponse/InboundEmail.php +++ b/src/InboundEmail/InboundEmailListResponse/InboundEmail.php @@ -39,14 +39,14 @@ final class InboundEmail implements BaseModel public ?array $allowedSources; /** - * Webhook URL for email notifications. `null` means files are only - * retrievable via `GET /v4/inbound-email/{id}/files` (pull delivery). + * Webhook URL for email notifications. Empty string (`""`) means files are + * only retrievable via `GET /v4/inbound-email/{id}/files` (SDK / pull mode). */ - #[Optional('callback_url', nullable: true)] + #[Optional('callback_url')] public ?string $callbackURL; /** - * When the mailbox was created. + * When the inbound email was created. */ #[Optional('created_at')] public ?\DateTimeInterface $createdAt; @@ -78,7 +78,7 @@ final class InboundEmail implements BaseModel public ?string $reference; /** - * Current mailbox status. + * Current inbound email lifecycle status. * * @var value-of|null $status */ @@ -86,7 +86,7 @@ final class InboundEmail implements BaseModel public ?string $status; /** - * When the mailbox was last updated. + * When the inbound email was last updated. */ #[Optional('updated_at')] public ?\DateTimeInterface $updatedAt; @@ -145,10 +145,10 @@ public function withAllowedSources(array $allowedSources): self } /** - * Webhook URL for email notifications. `null` means files are only - * retrievable via `GET /v4/inbound-email/{id}/files` (pull delivery). + * Webhook URL for email notifications. Empty string (`""`) means files are + * only retrievable via `GET /v4/inbound-email/{id}/files` (SDK / pull mode). */ - public function withCallbackURL(?string $callbackURL): self + public function withCallbackURL(string $callbackURL): self { $self = clone $this; $self['callbackURL'] = $callbackURL; @@ -157,7 +157,7 @@ public function withCallbackURL(?string $callbackURL): self } /** - * When the mailbox was created. + * When the inbound email was created. */ public function withCreatedAt(\DateTimeInterface $createdAt): self { @@ -214,7 +214,7 @@ public function withReference(?string $reference): self } /** - * Current mailbox status. + * Current inbound email lifecycle status. * * @param Status|value-of $status */ @@ -227,7 +227,7 @@ public function withStatus(Status|string $status): self } /** - * When the mailbox was last updated. + * When the inbound email was last updated. */ public function withUpdatedAt(\DateTimeInterface $updatedAt): self { diff --git a/src/InboundEmail/InboundEmailListResponse/InboundEmail/Status.php b/src/InboundEmail/InboundEmailListResponse/InboundEmail/Status.php index f884841..c82fed3 100644 --- a/src/InboundEmail/InboundEmailListResponse/InboundEmail/Status.php +++ b/src/InboundEmail/InboundEmailListResponse/InboundEmail/Status.php @@ -5,7 +5,7 @@ namespace CasParser\InboundEmail\InboundEmailListResponse\InboundEmail; /** - * Current mailbox status. + * Current inbound email lifecycle status. */ enum Status: string { diff --git a/src/InboundEmail/InboundEmailNewResponse.php b/src/InboundEmail/InboundEmailNewResponse.php index f5fe80f..ae3f755 100644 --- a/src/InboundEmail/InboundEmailNewResponse.php +++ b/src/InboundEmail/InboundEmailNewResponse.php @@ -39,14 +39,14 @@ final class InboundEmailNewResponse implements BaseModel public ?array $allowedSources; /** - * Webhook URL for email notifications. `null` means files are only - * retrievable via `GET /v4/inbound-email/{id}/files` (pull delivery). + * Webhook URL for email notifications. Empty string (`""`) means files are + * only retrievable via `GET /v4/inbound-email/{id}/files` (SDK / pull mode). */ - #[Optional('callback_url', nullable: true)] + #[Optional('callback_url')] public ?string $callbackURL; /** - * When the mailbox was created. + * When the inbound email was created. */ #[Optional('created_at')] public ?\DateTimeInterface $createdAt; @@ -78,7 +78,7 @@ final class InboundEmailNewResponse implements BaseModel public ?string $reference; /** - * Current mailbox status. + * Current inbound email lifecycle status. * * @var value-of|null $status */ @@ -86,7 +86,7 @@ final class InboundEmailNewResponse implements BaseModel public ?string $status; /** - * When the mailbox was last updated. + * When the inbound email was last updated. */ #[Optional('updated_at')] public ?\DateTimeInterface $updatedAt; @@ -145,10 +145,10 @@ public function withAllowedSources(array $allowedSources): self } /** - * Webhook URL for email notifications. `null` means files are only - * retrievable via `GET /v4/inbound-email/{id}/files` (pull delivery). + * Webhook URL for email notifications. Empty string (`""`) means files are + * only retrievable via `GET /v4/inbound-email/{id}/files` (SDK / pull mode). */ - public function withCallbackURL(?string $callbackURL): self + public function withCallbackURL(string $callbackURL): self { $self = clone $this; $self['callbackURL'] = $callbackURL; @@ -157,7 +157,7 @@ public function withCallbackURL(?string $callbackURL): self } /** - * When the mailbox was created. + * When the inbound email was created. */ public function withCreatedAt(\DateTimeInterface $createdAt): self { @@ -214,7 +214,7 @@ public function withReference(?string $reference): self } /** - * Current mailbox status. + * Current inbound email lifecycle status. * * @param Status|value-of $status */ @@ -227,7 +227,7 @@ public function withStatus(Status|string $status): self } /** - * When the mailbox was last updated. + * When the inbound email was last updated. */ public function withUpdatedAt(\DateTimeInterface $updatedAt): self { diff --git a/src/InboundEmail/InboundEmailNewResponse/Status.php b/src/InboundEmail/InboundEmailNewResponse/Status.php index ee4c67a..2371655 100644 --- a/src/InboundEmail/InboundEmailNewResponse/Status.php +++ b/src/InboundEmail/InboundEmailNewResponse/Status.php @@ -5,7 +5,7 @@ namespace CasParser\InboundEmail\InboundEmailNewResponse; /** - * Current mailbox status. + * Current inbound email lifecycle status. */ enum Status: string { diff --git a/src/Inbox/InboxListCasFilesResponse/File.php b/src/Inbox/InboxListCasFilesResponse/File.php index 99c15a9..7232701 100644 --- a/src/Inbox/InboxListCasFilesResponse/File.php +++ b/src/Inbox/InboxListCasFilesResponse/File.php @@ -38,7 +38,10 @@ final class File implements BaseModel public ?string $casType; /** - * URL expiration time in seconds (default 86400 = 24 hours). + * URL expiration time in seconds. Defaults vary by source: + * - Gmail Inbox Import: 86400 (24h) + * - Inbound Email (webhook mode): 172800 (48h) + * - Inbound Email (SDK mode): aligned with the session TTL (~30 min) */ #[Optional('expires_in')] public ?int $expiresIn; @@ -137,7 +140,10 @@ public function withCasType(CasType|string $casType): self } /** - * URL expiration time in seconds (default 86400 = 24 hours). + * URL expiration time in seconds. Defaults vary by source: + * - Gmail Inbox Import: 86400 (24h) + * - Inbound Email (webhook mode): 172800 (48h) + * - Inbound Email (SDK mode): aligned with the session TTL (~30 min) */ public function withExpiresIn(int $expiresIn): self { diff --git a/src/Services/InboundEmailRawService.php b/src/Services/InboundEmailRawService.php index f42c0ee..561aa34 100644 --- a/src/Services/InboundEmailRawService.php +++ b/src/Services/InboundEmailRawService.php @@ -97,7 +97,7 @@ public function create( /** * @api * - * Retrieve details of a specific mailbox including statistics. + * Retrieve details of a specific inbound email including statistics. * * @param string $inboundEmailID Inbound Email ID * @param RequestOpts|null $requestOptions @@ -122,8 +122,8 @@ public function retrieve( /** * @api * - * List all mailboxes associated with your API key. - * Returns active and inactive mailboxes (deleted mailboxes are excluded). + * List all inbound emails associated with your API key. + * Returns active and paused inbound emails (deleted ones are excluded). * * @param array{ * limit?: int, offset?: int, status?: Status|value-of diff --git a/src/Services/InboundEmailService.php b/src/Services/InboundEmailService.php index 3506afb..4f838b2 100644 --- a/src/Services/InboundEmailService.php +++ b/src/Services/InboundEmailService.php @@ -114,7 +114,7 @@ public function create( /** * @api * - * Retrieve details of a specific mailbox including statistics. + * Retrieve details of a specific inbound email including statistics. * * @param string $inboundEmailID Inbound Email ID * @param RequestOpts|null $requestOptions @@ -134,8 +134,8 @@ public function retrieve( /** * @api * - * List all mailboxes associated with your API key. - * Returns active and inactive mailboxes (deleted mailboxes are excluded). + * List all inbound emails associated with your API key. + * Returns active and paused inbound emails (deleted ones are excluded). * * @param int $limit Maximum number of inbound emails to return * @param int $offset Pagination offset From 61803b218533dc37365058e8d109cc47253c09de Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 2 May 2026 11:18:15 +0000 Subject: [PATCH 06/13] feat(api): api update --- .stats.yml | 4 ++-- src/InboundEmail/InboundEmailGetResponse.php | 10 ++++++---- .../InboundEmailListResponse/InboundEmail.php | 10 ++++++---- src/InboundEmail/InboundEmailNewResponse.php | 10 ++++++---- src/Inbox/InboxListCasFilesResponse/File.php | 8 ++++---- 5 files changed, 24 insertions(+), 18 deletions(-) diff --git a/.stats.yml b/.stats.yml index cd58596..d224bd8 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 21 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cas-parser/cas-parser-c7cca9a7a8e15f8a584c22eab142c4af72a329117f63cc3b3f7cabb25410f2ce.yml -openapi_spec_hash: f40d936e433bbf8c98179d0b36f304c8 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cas-parser/cas-parser-0dce3ce202e44ecf2270f6ca42942cc297bbeeafddb3128f8230ba3ae85c7551.yml +openapi_spec_hash: e9cef5743f686d9f12910c81832accca config_hash: 5509bb7a961ae2e79114b24c381606d4 diff --git a/src/InboundEmail/InboundEmailGetResponse.php b/src/InboundEmail/InboundEmailGetResponse.php index 64fb583..c5ed54c 100644 --- a/src/InboundEmail/InboundEmailGetResponse.php +++ b/src/InboundEmail/InboundEmailGetResponse.php @@ -39,8 +39,9 @@ final class InboundEmailGetResponse implements BaseModel public ?array $allowedSources; /** - * Webhook URL for email notifications. Empty string (`""`) means files are - * only retrievable via `GET /v4/inbound-email/{id}/files` (SDK / pull mode). + * Webhook URL for email notifications. If set, we POST each parsed + * email here. If omitted, files are only retrievable via + * `GET /v4/inbound-email/{id}/files`. */ #[Optional('callback_url')] public ?string $callbackURL; @@ -145,8 +146,9 @@ public function withAllowedSources(array $allowedSources): self } /** - * Webhook URL for email notifications. Empty string (`""`) means files are - * only retrievable via `GET /v4/inbound-email/{id}/files` (SDK / pull mode). + * Webhook URL for email notifications. If set, we POST each parsed + * email here. If omitted, files are only retrievable via + * `GET /v4/inbound-email/{id}/files`. */ public function withCallbackURL(string $callbackURL): self { diff --git a/src/InboundEmail/InboundEmailListResponse/InboundEmail.php b/src/InboundEmail/InboundEmailListResponse/InboundEmail.php index b57b326..d4e32a8 100644 --- a/src/InboundEmail/InboundEmailListResponse/InboundEmail.php +++ b/src/InboundEmail/InboundEmailListResponse/InboundEmail.php @@ -39,8 +39,9 @@ final class InboundEmail implements BaseModel public ?array $allowedSources; /** - * Webhook URL for email notifications. Empty string (`""`) means files are - * only retrievable via `GET /v4/inbound-email/{id}/files` (SDK / pull mode). + * Webhook URL for email notifications. If set, we POST each parsed + * email here. If omitted, files are only retrievable via + * `GET /v4/inbound-email/{id}/files`. */ #[Optional('callback_url')] public ?string $callbackURL; @@ -145,8 +146,9 @@ public function withAllowedSources(array $allowedSources): self } /** - * Webhook URL for email notifications. Empty string (`""`) means files are - * only retrievable via `GET /v4/inbound-email/{id}/files` (SDK / pull mode). + * Webhook URL for email notifications. If set, we POST each parsed + * email here. If omitted, files are only retrievable via + * `GET /v4/inbound-email/{id}/files`. */ public function withCallbackURL(string $callbackURL): self { diff --git a/src/InboundEmail/InboundEmailNewResponse.php b/src/InboundEmail/InboundEmailNewResponse.php index ae3f755..86d25ba 100644 --- a/src/InboundEmail/InboundEmailNewResponse.php +++ b/src/InboundEmail/InboundEmailNewResponse.php @@ -39,8 +39,9 @@ final class InboundEmailNewResponse implements BaseModel public ?array $allowedSources; /** - * Webhook URL for email notifications. Empty string (`""`) means files are - * only retrievable via `GET /v4/inbound-email/{id}/files` (SDK / pull mode). + * Webhook URL for email notifications. If set, we POST each parsed + * email here. If omitted, files are only retrievable via + * `GET /v4/inbound-email/{id}/files`. */ #[Optional('callback_url')] public ?string $callbackURL; @@ -145,8 +146,9 @@ public function withAllowedSources(array $allowedSources): self } /** - * Webhook URL for email notifications. Empty string (`""`) means files are - * only retrievable via `GET /v4/inbound-email/{id}/files` (SDK / pull mode). + * Webhook URL for email notifications. If set, we POST each parsed + * email here. If omitted, files are only retrievable via + * `GET /v4/inbound-email/{id}/files`. */ public function withCallbackURL(string $callbackURL): self { diff --git a/src/Inbox/InboxListCasFilesResponse/File.php b/src/Inbox/InboxListCasFilesResponse/File.php index 7232701..e30938d 100644 --- a/src/Inbox/InboxListCasFilesResponse/File.php +++ b/src/Inbox/InboxListCasFilesResponse/File.php @@ -40,8 +40,8 @@ final class File implements BaseModel /** * URL expiration time in seconds. Defaults vary by source: * - Gmail Inbox Import: 86400 (24h) - * - Inbound Email (webhook mode): 172800 (48h) - * - Inbound Email (SDK mode): aligned with the session TTL (~30 min) + * - Inbound Email with `callback_url` set: 172800 (48h) + * - Inbound Email without `callback_url`: aligned with the session TTL (~30 min) */ #[Optional('expires_in')] public ?int $expiresIn; @@ -142,8 +142,8 @@ public function withCasType(CasType|string $casType): self /** * URL expiration time in seconds. Defaults vary by source: * - Gmail Inbox Import: 86400 (24h) - * - Inbound Email (webhook mode): 172800 (48h) - * - Inbound Email (SDK mode): aligned with the session TTL (~30 min) + * - Inbound Email with `callback_url` set: 172800 (48h) + * - Inbound Email without `callback_url`: aligned with the session TTL (~30 min) */ public function withExpiresIn(int $expiresIn): self { From ca800b582cca93a4898244a27a14f51fda58ae28 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 2 May 2026 23:18:15 +0000 Subject: [PATCH 07/13] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index d224bd8..582a92c 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 21 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cas-parser/cas-parser-0dce3ce202e44ecf2270f6ca42942cc297bbeeafddb3128f8230ba3ae85c7551.yml -openapi_spec_hash: e9cef5743f686d9f12910c81832accca +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cas-parser/cas-parser-e572d88c2af6e4d7bc4f7e119357fd3f68b1e67d612fd1d3a657d916cde0087c.yml +openapi_spec_hash: a9fc7d947111bffa9184f8ca8be4a579 config_hash: 5509bb7a961ae2e79114b24c381606d4 From 7a5def32e6cff5d527ec67949b5882411f367253 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 12 May 2026 03:02:31 +0000 Subject: [PATCH 08/13] fix: guzzle requires special handling to enable streaming --- composer.json | 1 + composer.lock | 374 +++++++++++++++++- src/Client.php | 6 + src/Core/BaseClient.php | 6 +- src/Core/FileParam.php | 2 +- .../Implementation/StreamingHttpClient.php | 29 ++ src/Core/Util.php | 12 + src/RequestOptions.php | 17 + tests/Core/StreamingTransportTest.php | 122 ++++++ 9 files changed, 565 insertions(+), 4 deletions(-) create mode 100644 src/Core/Implementation/StreamingHttpClient.php create mode 100644 tests/Core/StreamingTransportTest.php diff --git a/composer.json b/composer.json index 1fee198..6830a8f 100644 --- a/composer.json +++ b/composer.json @@ -38,6 +38,7 @@ }, "require-dev": { "friendsofphp/php-cs-fixer": "^3", + "guzzlehttp/guzzle": "^7", "nyholm/psr7": "^1", "pestphp/pest": "^3", "php-http/mock-client": "^1", diff --git a/composer.lock b/composer.lock index 7a5e63d..b0e2283 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ffa287ea8babf60e021f37e62c6c207a", + "content-hash": "bbae64cb4d21c987158bc3723eda4226", "packages": [ { "name": "php-http/discovery", @@ -968,6 +968,332 @@ ], "time": "2026-01-08T21:57:37+00:00" }, + { + "name": "guzzlehttp/guzzle", + "version": "7.10.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^2.3", + "guzzlehttp/psr7": "^2.8", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-curl": "*", + "guzzle/client-integration-tests": "3.0.2", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.39 || ^9.6.20", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.10.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2025-08-23T22:36:01+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "481557b130ef3790cf82b713667b43030dc9c957" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957", + "reference": "481557b130ef3790cf82b713667b43030dc9c957", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.44 || ^9.6.25" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/2.3.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2025-08-22T14:34:08+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.9.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/7d0ed42f28e42d61352a7a79de682e5e67fec884", + "reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "0.9.0", + "jshttp/mime-db": "1.54.0.1", + "phpunit/phpunit": "^8.5.44 || ^9.6.25" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.9.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2026-03-10T16:41:02+00:00" + }, { "name": "jean85/pretty-package-versions", "version": "2.1.1", @@ -3250,6 +3576,50 @@ }, "time": "2021-10-29T13:26:27+00:00" }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, { "name": "react/cache", "version": "v1.2.0", @@ -6680,5 +7050,5 @@ "platform-overrides": { "php": "8.3" }, - "plugin-api-version": "2.9.0" + "plugin-api-version": "2.6.0" } diff --git a/src/Client.php b/src/Client.php index e3ad701..5a4eb37 100644 --- a/src/Client.php +++ b/src/Client.php @@ -5,6 +5,7 @@ namespace CasParser; use CasParser\Core\BaseClient; +use CasParser\Core\Implementation\StreamingHttpClient; use CasParser\Core\Util; use CasParser\Services\AccessTokenService; use CasParser\Services\CamsKfintechService; @@ -113,6 +114,11 @@ public function __construct( $requestOptions, ); + if (is_null($options->streamingTransporter)) { + assert(!is_null($options->transporter)); + $options->streamingTransporter = new StreamingHttpClient($options->transporter); + } + /** @var array $headers */ $headers = [ 'Content-Type' => 'application/json', diff --git a/src/Core/BaseClient.php b/src/Core/BaseClient.php index 50534bf..6f115a5 100644 --- a/src/Core/BaseClient.php +++ b/src/Core/BaseClient.php @@ -241,11 +241,15 @@ protected function sendRequest( $req = $req->withHeader('X-Stainless-Retry-Count', strval($retryCount)); $req = Util::withSetBody($opts->streamFactory, req: $req, body: $data); + $transporter = Util::isStreamingRequest($req) + ? ($opts->streamingTransporter ?? $opts->transporter) + : $opts->transporter; + $rsp = null; $err = null; try { - $rsp = $opts->transporter->sendRequest($req); + $rsp = $transporter->sendRequest($req); } catch (ClientExceptionInterface $e) { $err = $e; } diff --git a/src/Core/FileParam.php b/src/Core/FileParam.php index 992b5f0..ba610ff 100644 --- a/src/Core/FileParam.php +++ b/src/Core/FileParam.php @@ -41,7 +41,7 @@ public static function fromResource(mixed $resource, ?string $filename = null, s throw new \InvalidArgumentException('Expected a resource, got '.get_debug_type($resource)); } - if (null === $filename) { + if (is_null($filename)) { $meta = stream_get_meta_data($resource); $filename = basename($meta['uri'] ?? 'upload'); } diff --git a/src/Core/Implementation/StreamingHttpClient.php b/src/Core/Implementation/StreamingHttpClient.php new file mode 100644 index 0000000..23c346a --- /dev/null +++ b/src/Core/Implementation/StreamingHttpClient.php @@ -0,0 +1,29 @@ +inner, '\GuzzleHttp\Client')) { + return $this->inner->send($request, ['stream' => true]); + } + + return $this->inner->sendRequest($request); + } +} diff --git a/src/Core/Util.php b/src/Core/Util.php index d7fbe57..b3db605 100644 --- a/src/Core/Util.php +++ b/src/Core/Util.php @@ -25,6 +25,8 @@ final class Util public const JSONL_CONTENT_TYPE = '/^application\/(:?x-(?:n|l)djson)|(:?(?:x-)?jsonl)/'; + public const STREAMING_CONTENT_TYPE = ['/^text\/event-stream/', self::JSONL_CONTENT_TYPE]; + public static function getenv(string $key): ?string { if (array_key_exists($key, array: $_ENV)) { @@ -217,6 +219,16 @@ public static function joinUri( return $base->withQuery($qs); } + public static function isStreamingRequest(RequestInterface $request): bool + { + $accept = $request->getHeaderLine('Accept'); + + return !empty(array_filter( + self::STREAMING_CONTENT_TYPE, + static fn (string $pattern) => (bool) preg_match($pattern, subject: $accept), + )); + } + /** * @param array|null> $headers */ diff --git a/src/RequestOptions.php b/src/RequestOptions.php index 7ae6885..ab11b39 100644 --- a/src/RequestOptions.php +++ b/src/RequestOptions.php @@ -23,6 +23,7 @@ * extraQueryParams?: array|null, * extraBodyParams?: mixed, * transporter?: ClientInterface|null, + * streamingTransporter?: ClientInterface|null, * uriFactory?: UriFactoryInterface|null, * streamFactory?: StreamFactoryInterface|null, * requestFactory?: RequestFactoryInterface|null, @@ -60,6 +61,9 @@ final class RequestOptions implements BaseModel #[Optional] public ?ClientInterface $transporter; + #[Optional] + public ?ClientInterface $streamingTransporter; + #[Optional] public ?UriFactoryInterface $uriFactory; @@ -98,6 +102,7 @@ public static function with( ?array $extraQueryParams = null, mixed $extraBodyParams = null, ?ClientInterface $transporter = null, + ?ClientInterface $streamingTransporter = null, ?UriFactoryInterface $uriFactory = null, ?StreamFactoryInterface $streamFactory = null, ?RequestFactoryInterface $requestFactory = null, @@ -114,6 +119,9 @@ public static function with( null !== $extraQueryParams && $self->extraQueryParams = $extraQueryParams; null !== $extraBodyParams && $self->extraBodyParams = $extraBodyParams; null !== $transporter && $self->transporter = $transporter; + null !== $streamingTransporter && $self + ->streamingTransporter = $streamingTransporter + ; null !== $uriFactory && $self->uriFactory = $uriFactory; null !== $streamFactory && $self->streamFactory = $streamFactory; null !== $requestFactory && $self->requestFactory = $requestFactory; @@ -191,6 +199,15 @@ public function withTransporter(ClientInterface $transporter): self return $self; } + public function withStreamingTransporter( + ClientInterface $streamingTransporter + ): self { + $self = clone $this; + $self->streamingTransporter = $streamingTransporter; + + return $self; + } + public function withUriFactory(UriFactoryInterface $uriFactory): self { $self = clone $this; diff --git a/tests/Core/StreamingTransportTest.php b/tests/Core/StreamingTransportTest.php new file mode 100644 index 0000000..3ff0d4f --- /dev/null +++ b/tests/Core/StreamingTransportTest.php @@ -0,0 +1,122 @@ + true, + 'application/x-ndjson' => true, + 'application/x-ldjson' => true, + 'application/jsonl' => true, + 'application/x-jsonl' => true, + 'text/event-stream; charset=utf-8' => true, + 'application/json' => false, + 'text/plain' => false, + '' => false, + ]; + + foreach ($cases as $accept => $expected) { + $req = $factory->createRequest('GET', 'http://localhost'); + if ('' !== $accept) { + $req = $req->withHeader('Accept', $accept); + } + $this->assertSame( + $expected, + Util::isStreamingRequest($req), + "Accept: '{$accept}'", + ); + } + } + + #[Test] + public function testRoutesNonStreamingRequestToTransporter(): void + { + [$client, $plain, $streaming] = $this->buildClient(); + + $client->request('GET', '/'); + + $this->assertCount(1, $plain->getRequests()); + $this->assertCount(0, $streaming->getRequests()); + } + + #[Test] + public function testRoutesStreamingRequestToStreamingTransporter(): void + { + [$client, $plain, $streaming] = $this->buildClient(); + + $client->request('GET', '/', headers: ['Accept' => 'text/event-stream']); + + $this->assertCount(0, $plain->getRequests()); + $this->assertCount(1, $streaming->getRequests()); + + $sent = $streaming->getRequests()[0]; + $this->assertStringContainsString('text/event-stream', $sent->getHeaderLine('Accept')); + } + + /** + * @return array{BaseClient, MockClient, MockClient} + */ + private function buildClient(): array + { + $plain = new MockClient; + $plain->setDefaultResponse($this->jsonResponse()); + + $streaming = new MockClient; + $streaming->setDefaultResponse($this->sseResponse()); + + $options = RequestOptions::with( + transporter: $plain, + streamingTransporter: $streaming, + uriFactory: Psr17FactoryDiscovery::findUriFactory(), + requestFactory: Psr17FactoryDiscovery::findRequestFactory(), + streamFactory: Psr17FactoryDiscovery::findStreamFactory(), + ); + + $client = new class(headers: [], baseUrl: 'http://localhost', options: $options) extends BaseClient {}; + + return [$client, $plain, $streaming]; + } + + private function jsonResponse(): ResponseInterface + { + $responseFactory = Psr17FactoryDiscovery::findResponseFactory(); + $streamFactory = Psr17FactoryDiscovery::findStreamFactory(); + + return $responseFactory->createResponse(200) + ->withHeader('Content-Type', 'application/json') + ->withBody($streamFactory->createStream('{}')) + ; + } + + private function sseResponse(): ResponseInterface + { + $responseFactory = Psr17FactoryDiscovery::findResponseFactory(); + $streamFactory = Psr17FactoryDiscovery::findStreamFactory(); + + return $responseFactory->createResponse(200) + ->withHeader('Content-Type', 'text/event-stream') + ->withBody($streamFactory->createStream('')) + ; + } +} From c0687bc8f199fa150c3bf2bc2ef7ab2597c77b1e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 13 May 2026 02:36:53 +0000 Subject: [PATCH 09/13] ci: pin GitHub Actions to commit SHAs Pin all GitHub Actions referenced in generated workflows (both first-party `actions/*` and third-party) to immutable commit SHAs. Updating pinned actions is now a deliberate codegen-side bump rather than implicit on every workflow run. --- .github/workflows/ci.yml | 8 ++++---- .github/workflows/release-doctor.yml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2e73cc1..9b9d786 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,10 +22,10 @@ jobs: if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up PHP - uses: 'shivammathur/setup-php@v2' + uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # 2.37.0 with: php-version: '8.3' @@ -40,10 +40,10 @@ jobs: runs-on: ${{ github.repository == 'stainless-sdks/cas-parser-php' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up PHP - uses: 'shivammathur/setup-php@v2' + uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # 2.37.0 with: php-version: '8.3' diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml index 99587de..f3e0d42 100644 --- a/.github/workflows/release-doctor.yml +++ b/.github/workflows/release-doctor.yml @@ -12,7 +12,7 @@ jobs: if: github.repository == 'CASParser/cas-parser-php' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next') steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Check release environment run: | From c7d2bc411a35e6b162003a7a26f57ba2ac1dc0be Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 10:17:52 +0000 Subject: [PATCH 10/13] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 582a92c..fb087e4 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 21 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cas-parser/cas-parser-e572d88c2af6e4d7bc4f7e119357fd3f68b1e67d612fd1d3a657d916cde0087c.yml -openapi_spec_hash: a9fc7d947111bffa9184f8ca8be4a579 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cas-parser/cas-parser-904e3aa8081755d046016db9d84d13d140a4235c724e18e1cd7f8ebb7712883e.yml +openapi_spec_hash: 453b8e667c364b064e04352ad4deccfa config_hash: 5509bb7a961ae2e79114b24c381606d4 From 503334f2e043c24e149f50821aaac62f14887955 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 12 Jul 2026 14:18:12 +0000 Subject: [PATCH 11/13] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index fb087e4..20f0c84 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 21 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cas-parser/cas-parser-904e3aa8081755d046016db9d84d13d140a4235c724e18e1cd7f8ebb7712883e.yml -openapi_spec_hash: 453b8e667c364b064e04352ad4deccfa +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cas-parser/cas-parser-902b4d5af31c8c6bab33a08d9cea10f3444be221abf735466eb9fd58a14e0a87.yml +openapi_spec_hash: e333f46097f3a3a452cb4d6564a3db67 config_hash: 5509bb7a961ae2e79114b24c381606d4 From 9a46640f85e4f02cf8bacafb197ae0e8f1bc902f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 18 Jul 2026 02:06:46 +0000 Subject: [PATCH 12/13] feat(stlc): configurable CI runner and private-production-repo support in workflow templates --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9b9d786..84d8fb1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: lint: timeout-minutes: 10 name: lint - runs-on: ${{ github.repository == 'stainless-sdks/cas-parser-php' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + runs-on: ${{ startsWith(github.repository, 'stainless-sdks/') && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: @@ -37,7 +37,7 @@ jobs: test: timeout-minutes: 10 name: test - runs-on: ${{ github.repository == 'stainless-sdks/cas-parser-php' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + runs-on: ${{ startsWith(github.repository, 'stainless-sdks/') && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 From 1e8b14594fdb6f8427e414d4ab2fd97521c0b94b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 18 Jul 2026 02:07:09 +0000 Subject: [PATCH 13/13] release: 0.8.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 17 +++++++++++++++++ src/Version.php | 2 +- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 1b77f50..6538ca9 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.7.0" + ".": "0.8.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 841b745..c720566 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Changelog +## 0.8.0 (2026-07-18) + +Full Changelog: [v0.7.0...v0.8.0](https://github.com/CASParser/cas-parser-php/compare/v0.7.0...v0.8.0) + +### Features + +* **api:** api update ([61803b2](https://github.com/CASParser/cas-parser-php/commit/61803b218533dc37365058e8d109cc47253c09de)) +* **api:** api update ([673d298](https://github.com/CASParser/cas-parser-php/commit/673d29896be612e78e60e40283e6ce7bb980f0c3)) +* **stlc:** configurable CI runner and private-production-repo support in workflow templates ([9a46640](https://github.com/CASParser/cas-parser-php/commit/9a46640f85e4f02cf8bacafb197ae0e8f1bc902f)) +* support setting headers via env ([47b835c](https://github.com/CASParser/cas-parser-php/commit/47b835c86b80681f501f029709a8198fb44cdce3)) + + +### Bug Fixes + +* guzzle requires special handling to enable streaming ([7a5def3](https://github.com/CASParser/cas-parser-php/commit/7a5def32e6cff5d527ec67949b5882411f367253)) +* revert enum parsing change that lead to unconditional failure ([34892b3](https://github.com/CASParser/cas-parser-php/commit/34892b325daf92e6c108372a3d20372e6fbd4a61)) + ## 0.7.0 (2026-04-19) Full Changelog: [v0.6.2...v0.7.0](https://github.com/CASParser/cas-parser-php/compare/v0.6.2...v0.7.0) diff --git a/src/Version.php b/src/Version.php index 57860bd..1c12b5e 100644 --- a/src/Version.php +++ b/src/Version.php @@ -5,5 +5,5 @@ namespace CasParser; // x-release-please-start-version -const VERSION = '0.7.0'; +const VERSION = '0.8.0'; // x-release-please-end