diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 091cfb1..f7014c3 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.10.0" + ".": "0.11.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index e7b8198..c12f542 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 20 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cas-parser/cas-parser-cd8e042a9746bbe9bd180614fccc7597b85f4e8f6a29da6cd2f4cbf831fb2fbe.yml -openapi_spec_hash: e27c0d9cd8cdeb348c88e6c4e8777e39 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cas-parser/cas-parser-4d179917b01ea51a3325e7b37ecbbb60d0ba8f60381fe715ff3ec31284ca8042.yml +openapi_spec_hash: d027d37bd7051aa8c05fe1820c05b316 config_hash: 5509bb7a961ae2e79114b24c381606d4 diff --git a/CHANGELOG.md b/CHANGELOG.md index b1c925e..61a3dce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## 0.11.0 (2026-08-14) + +Full Changelog: [v0.10.0...v0.11.0](https://github.com/CASParser/cas-parser-php/compare/v0.10.0...v0.11.0) + +### Features + +* **api:** api update ([aaebadc](https://github.com/CASParser/cas-parser-php/commit/aaebadcb40240cbbe19d3c2cebb65dbd9d9f3e39)) +* **api:** api update ([2b43dbc](https://github.com/CASParser/cas-parser-php/commit/2b43dbcf6cfb7b88b74a1ae6315e41919a952457)) + + +### Chores + +* **internal:** codegen related update ([2ab577d](https://github.com/CASParser/cas-parser-php/commit/2ab577dcc8902ab6a68c5066b1ffbc2c7660f6fc)) + ## 0.10.0 (2026-08-02) Full Changelog: [v0.9.0...v0.10.0](https://github.com/CASParser/cas-parser-php/compare/v0.9.0...v0.10.0) diff --git a/src/Core/BaseClient.php b/src/Core/BaseClient.php index 6f115a5..fd84b21 100644 --- a/src/Core/BaseClient.php +++ b/src/Core/BaseClient.php @@ -12,6 +12,7 @@ use CasParser\Core\Exceptions\APIConnectionException; use CasParser\Core\Exceptions\APIStatusException; use CasParser\Core\Implementation\RawResponse; +use CasParser\Core\Implementation\StreamingHttpClient; use CasParser\RequestOptions; use Psr\Http\Client\ClientExceptionInterface; use Psr\Http\Message\RequestInterface; @@ -249,7 +250,13 @@ protected function sendRequest( $err = null; try { - $rsp = $transporter->sendRequest($req); + if ($transporter instanceof StreamingHttpClient) { + $rsp = $transporter->sendRequest($req, timeout: $opts->timeout); + } elseif (is_a($transporter, '\GuzzleHttp\Client')) { + $rsp = $transporter->send($req, ['timeout' => $opts->timeout]); + } else { + $rsp = $transporter->sendRequest($req); + } } catch (ClientExceptionInterface $e) { $err = $e; } diff --git a/src/Core/Implementation/StreamingHttpClient.php b/src/Core/Implementation/StreamingHttpClient.php index 23c346a..d4a028d 100644 --- a/src/Core/Implementation/StreamingHttpClient.php +++ b/src/Core/Implementation/StreamingHttpClient.php @@ -18,10 +18,15 @@ final class StreamingHttpClient implements ClientInterface { public function __construct(private ClientInterface $inner) {} - public function sendRequest(RequestInterface $request): ResponseInterface + public function sendRequest(RequestInterface $request, ?float $timeout = null): ResponseInterface { if (is_a($this->inner, '\GuzzleHttp\Client')) { - return $this->inner->send($request, ['stream' => true]); + $options = ['stream' => true]; + if (null !== $timeout) { + $options['timeout'] = $timeout; + } + + return $this->inner->send($request, $options); } return $this->inner->sendRequest($request); diff --git a/src/Inbox/InboxConnectEmailParams.php b/src/Inbox/InboxConnectEmailParams.php index c871809..43b9353 100644 --- a/src/Inbox/InboxConnectEmailParams.php +++ b/src/Inbox/InboxConnectEmailParams.php @@ -9,6 +9,7 @@ use CasParser\Core\Concerns\SdkModel; use CasParser\Core\Concerns\SdkParams; use CasParser\Core\Contracts\BaseModel; +use CasParser\Inbox\InboxConnectEmailParams\Provider; /** * Initiate OAuth flow to connect user's email inbox. @@ -26,11 +27,17 @@ * - `state` - Your original state parameter * * **Store the `inbox_token` client-side** and use it for all subsequent inbox API calls. + * The token is long-lived (it stores an encrypted refresh token), so a single OAuth + * connect gives ongoing access to both historical and future CAS statements in the + * user's inbox. Reuse the same token until the user revokes access via + * `/v4/inbox/disconnect` or their provider's account settings. * * @see CasParser\Services\InboxService::connectEmail() * * @phpstan-type InboxConnectEmailParamsShape = array{ - * redirectUri: string, state?: string|null + * redirectUri: string, + * provider?: null|Provider|value-of, + * state?: string|null, * } */ final class InboxConnectEmailParams implements BaseModel @@ -45,6 +52,27 @@ final class InboxConnectEmailParams implements BaseModel #[Required('redirect_uri')] public string $redirectUri; + /** + * Mail provider to connect. Defaults to `gmail`. + * + * - `gmail` - Google accounts: `@gmail.com` and Google + * Workspace domains. + * - `outlook` - personal Microsoft accounts: `@outlook.com`, + * `@hotmail.com`, `@live.com`, `@msn.com` and localised + * variants (`@hotmail.co.uk`, `@live.in`, `@hotmail.fr`). + * Any other address registered as a personal Microsoft + * account also works, including custom domains. + * - `zoho` - Zoho Mail accounts, including custom domains + * hosted on Zoho. + * + * Any unrecognised value is treated as `gmail`. The resolved + * provider is returned in the response. + * + * @var value-of|null $provider + */ + #[Optional(enum: Provider::class)] + public ?string $provider; + /** * State parameter for CSRF protection (returned in redirect). */ @@ -74,13 +102,19 @@ public function __construct() * Construct an instance from the required parameters. * * You must use named parameters to construct any parameters with a default value. + * + * @param Provider|value-of|null $provider */ - public static function with(string $redirectUri, ?string $state = null): self - { + public static function with( + string $redirectUri, + Provider|string|null $provider = null, + ?string $state = null + ): self { $self = new self; $self['redirectUri'] = $redirectUri; + null !== $provider && $self['provider'] = $provider; null !== $state && $self['state'] = $state; return $self; @@ -97,6 +131,32 @@ public function withRedirectUri(string $redirectUri): self return $self; } + /** + * Mail provider to connect. Defaults to `gmail`. + * + * - `gmail` - Google accounts: `@gmail.com` and Google + * Workspace domains. + * - `outlook` - personal Microsoft accounts: `@outlook.com`, + * `@hotmail.com`, `@live.com`, `@msn.com` and localised + * variants (`@hotmail.co.uk`, `@live.in`, `@hotmail.fr`). + * Any other address registered as a personal Microsoft + * account also works, including custom domains. + * - `zoho` - Zoho Mail accounts, including custom domains + * hosted on Zoho. + * + * Any unrecognised value is treated as `gmail`. The resolved + * provider is returned in the response. + * + * @param Provider|value-of $provider + */ + public function withProvider(Provider|string $provider): self + { + $self = clone $this; + $self['provider'] = $provider; + + return $self; + } + /** * State parameter for CSRF protection (returned in redirect). */ diff --git a/src/Inbox/InboxConnectEmailParams/Provider.php b/src/Inbox/InboxConnectEmailParams/Provider.php new file mode 100644 index 0000000..02a1322 --- /dev/null +++ b/src/Inbox/InboxConnectEmailParams/Provider.php @@ -0,0 +1,30 @@ +, + * status?: string|null, * } */ final class InboxConnectEmailResponse implements BaseModel @@ -30,6 +34,14 @@ final class InboxConnectEmailResponse implements BaseModel #[Optional('oauth_url')] public ?string $oauthURL; + /** + * The provider this OAuth URL was generated for. + * + * @var value-of|null $provider + */ + #[Optional(enum: Provider::class)] + public ?string $provider; + #[Optional] public ?string $status; @@ -42,16 +54,20 @@ public function __construct() * Construct an instance from the required parameters. * * You must use named parameters to construct any parameters with a default value. + * + * @param Provider|value-of|null $provider */ public static function with( ?int $expiresIn = null, ?string $oauthURL = null, - ?string $status = null + Provider|string|null $provider = null, + ?string $status = null, ): self { $self = new self; null !== $expiresIn && $self['expiresIn'] = $expiresIn; null !== $oauthURL && $self['oauthURL'] = $oauthURL; + null !== $provider && $self['provider'] = $provider; null !== $status && $self['status'] = $status; return $self; @@ -79,6 +95,19 @@ public function withOAuthURL(string $oauthURL): self return $self; } + /** + * The provider this OAuth URL was generated for. + * + * @param Provider|value-of $provider + */ + public function withProvider(Provider|string $provider): self + { + $self = clone $this; + $self['provider'] = $provider; + + return $self; + } + public function withStatus(string $status): self { $self = clone $this; diff --git a/src/Inbox/InboxConnectEmailResponse/Provider.php b/src/Inbox/InboxConnectEmailResponse/Provider.php new file mode 100644 index 0000000..fd125e2 --- /dev/null +++ b/src/Inbox/InboxConnectEmailResponse/Provider.php @@ -0,0 +1,17 @@ + $provider Mail provider to connect. Defaults to `gmail`. + * + * - `gmail` - Google accounts: `@gmail.com` and Google + * Workspace domains. + * - `outlook` - personal Microsoft accounts: `@outlook.com`, + * `@hotmail.com`, `@live.com`, `@msn.com` and localised + * variants (`@hotmail.co.uk`, `@live.in`, `@hotmail.fr`). + * Any other address registered as a personal Microsoft + * account also works, including custom domains. + * - `zoho` - Zoho Mail accounts, including custom domains + * hosted on Zoho. + * + * Any unrecognised value is treated as `gmail`. The resolved + * provider is returned in the response. * @param string $state State parameter for CSRF protection (returned in redirect) * @param RequestOpts|null $requestOptions * @@ -41,6 +56,7 @@ public function checkConnectionStatus( */ public function connectEmail( string $redirectUri, + Provider|string $provider = 'gmail', ?string $state = null, RequestOptions|array|null $requestOptions = null, ): InboxConnectEmailResponse; diff --git a/src/Services/InboxRawService.php b/src/Services/InboxRawService.php index 0f8cd85..a583454 100644 --- a/src/Services/InboxRawService.php +++ b/src/Services/InboxRawService.php @@ -11,6 +11,7 @@ use CasParser\Inbox\InboxCheckConnectionStatusParams; use CasParser\Inbox\InboxCheckConnectionStatusResponse; use CasParser\Inbox\InboxConnectEmailParams; +use CasParser\Inbox\InboxConnectEmailParams\Provider; use CasParser\Inbox\InboxConnectEmailResponse; use CasParser\Inbox\InboxDisconnectEmailParams; use CasParser\Inbox\InboxDisconnectEmailResponse; @@ -23,7 +24,14 @@ /** * Endpoints for importing CAS files directly from user email inboxes. * - * **Supported Providers:** Gmail (more coming soon) + * **Supported Providers:** + * + * - **Gmail** (`gmail`, default) — `@gmail.com` and Google Workspace domains + * - **Microsoft** (`outlook`) — personal Microsoft accounts: `@outlook.com`, + * `@hotmail.com`, `@live.com`, `@msn.com`, and localised variants such as + * `@hotmail.co.uk`, `@live.in`, `@hotmail.fr`. Any other address registered + * as a personal Microsoft account also works, including custom domains. + * - **Zoho Mail** (`zoho`) — Zoho-hosted mailboxes, including custom domains * * **How it works:** * 1. Call `POST /v4/inbox/connect` to get an OAuth URL @@ -102,9 +110,13 @@ public function checkConnectionStatus( * - `state` - Your original state parameter * * **Store the `inbox_token` client-side** and use it for all subsequent inbox API calls. + * The token is long-lived (it stores an encrypted refresh token), so a single OAuth + * connect gives ongoing access to both historical and future CAS statements in the + * user's inbox. Reuse the same token until the user revokes access via + * `/v4/inbox/disconnect` or their provider's account settings. * * @param array{ - * redirectUri: string, state?: string + * redirectUri: string, provider?: Provider|value-of, state?: string * }|InboxConnectEmailParams $params * @param RequestOpts|null $requestOptions * diff --git a/src/Services/InboxService.php b/src/Services/InboxService.php index fefca88..6c93279 100644 --- a/src/Services/InboxService.php +++ b/src/Services/InboxService.php @@ -8,6 +8,7 @@ use CasParser\Core\Exceptions\APIException; use CasParser\Core\Util; use CasParser\Inbox\InboxCheckConnectionStatusResponse; +use CasParser\Inbox\InboxConnectEmailParams\Provider; use CasParser\Inbox\InboxConnectEmailResponse; use CasParser\Inbox\InboxDisconnectEmailResponse; use CasParser\Inbox\InboxListCasFilesParams\CasType; @@ -18,7 +19,14 @@ /** * Endpoints for importing CAS files directly from user email inboxes. * - * **Supported Providers:** Gmail (more coming soon) + * **Supported Providers:** + * + * - **Gmail** (`gmail`, default) — `@gmail.com` and Google Workspace domains + * - **Microsoft** (`outlook`) — personal Microsoft accounts: `@outlook.com`, + * `@hotmail.com`, `@live.com`, `@msn.com`, and localised variants such as + * `@hotmail.co.uk`, `@live.in`, `@hotmail.fr`. Any other address registered + * as a personal Microsoft account also works, including custom domains. + * - **Zoho Mail** (`zoho`) — Zoho-hosted mailboxes, including custom domains * * **How it works:** * 1. Call `POST /v4/inbox/connect` to get an OAuth URL @@ -92,8 +100,26 @@ public function checkConnectionStatus( * - `state` - Your original state parameter * * **Store the `inbox_token` client-side** and use it for all subsequent inbox API calls. + * The token is long-lived (it stores an encrypted refresh token), so a single OAuth + * connect gives ongoing access to both historical and future CAS statements in the + * user's inbox. Reuse the same token until the user revokes access via + * `/v4/inbox/disconnect` or their provider's account settings. * * @param string $redirectUri Your callback URL to receive the inbox_token (must be http or https) + * @param Provider|value-of $provider Mail provider to connect. Defaults to `gmail`. + * + * - `gmail` - Google accounts: `@gmail.com` and Google + * Workspace domains. + * - `outlook` - personal Microsoft accounts: `@outlook.com`, + * `@hotmail.com`, `@live.com`, `@msn.com` and localised + * variants (`@hotmail.co.uk`, `@live.in`, `@hotmail.fr`). + * Any other address registered as a personal Microsoft + * account also works, including custom domains. + * - `zoho` - Zoho Mail accounts, including custom domains + * hosted on Zoho. + * + * Any unrecognised value is treated as `gmail`. The resolved + * provider is returned in the response. * @param string $state State parameter for CSRF protection (returned in redirect) * @param RequestOpts|null $requestOptions * @@ -101,11 +127,16 @@ public function checkConnectionStatus( */ public function connectEmail( string $redirectUri, + Provider|string $provider = 'gmail', ?string $state = null, RequestOptions|array|null $requestOptions = null, ): InboxConnectEmailResponse { $params = Util::removeNulls( - ['redirectUri' => $redirectUri, 'state' => $state] + [ + 'redirectUri' => $redirectUri, + 'provider' => $provider, + 'state' => $state, + ], ); // @phpstan-ignore-next-line argument.type diff --git a/src/Version.php b/src/Version.php index fabeb74..05fd03f 100644 --- a/src/Version.php +++ b/src/Version.php @@ -5,5 +5,5 @@ namespace CasParser; // x-release-please-start-version -const VERSION = '0.10.0'; +const VERSION = '0.11.0'; // x-release-please-end diff --git a/tests/Core/RequestTimeoutTest.php b/tests/Core/RequestTimeoutTest.php new file mode 100644 index 0000000..246ac38 --- /dev/null +++ b/tests/Core/RequestTimeoutTest.php @@ -0,0 +1,81 @@ +buildClient(); + + $client->request('GET', '/', options: ['timeout' => 1.5]); + + $this->assertSame(1.5, $mock->getLastOptions()['timeout']); + } + + #[Test] + public function testPassesDefaultTimeoutToGuzzleTransporter(): void + { + [$client, $mock] = $this->buildClient(); + + $client->request('GET', '/'); + + $this->assertSame((new RequestOptions)->timeout, $mock->getLastOptions()['timeout']); + } + + #[Test] + public function testPassesTimeoutToStreamingTransporter(): void + { + [$client, $mock] = $this->buildClient(streaming: true); + + $client->request('GET', '/', headers: ['Accept' => 'text/event-stream'], options: ['timeout' => 2.5]); + + $options = $mock->getLastOptions(); + $this->assertTrue($options['stream']); + $this->assertSame(2.5, $options['timeout']); + } + + /** + * @return array{BaseClient, MockHandler} + */ + private function buildClient(bool $streaming = false): array + { + $response = $streaming + ? new Response(200, ['Content-Type' => 'text/event-stream'], '') + : new Response(200, ['Content-Type' => 'application/json'], '{}'); + + $mock = new MockHandler([$response]); + $guzzle = new GuzzleClient(['handler' => HandlerStack::create($mock)]); + + $options = RequestOptions::with( + transporter: $guzzle, + streamingTransporter: new StreamingHttpClient($guzzle), + uriFactory: Psr17FactoryDiscovery::findUriFactory(), + requestFactory: Psr17FactoryDiscovery::findRequestFactory(), + streamFactory: Psr17FactoryDiscovery::findStreamFactory(), + ); + + $client = new class(headers: [], baseUrl: 'http://localhost', options: $options) extends BaseClient {}; + + return [$client, $mock]; + } +} diff --git a/tests/Services/InboxTest.php b/tests/Services/InboxTest.php index f7d2e8e..1bc357f 100644 --- a/tests/Services/InboxTest.php +++ b/tests/Services/InboxTest.php @@ -85,7 +85,8 @@ public function testConnectEmailWithOptionalParams(): void $result = $this->client->inbox->connectEmail( redirectUri: 'https://yourapp.com/oauth-callback', - state: 'abc123' + provider: 'outlook', + state: 'abc123', ); // @phpstan-ignore-next-line method.alreadyNarrowedType