diff --git a/src/Traits/Plugins/HasApiVersion.php b/src/Traits/Plugins/HasApiVersion.php index 5be6e8e7..2e3e2185 100644 --- a/src/Traits/Plugins/HasApiVersion.php +++ b/src/Traits/Plugins/HasApiVersion.php @@ -4,6 +4,7 @@ namespace Saloon\Traits\Plugins; +use Saloon\Helpers\Helpers; use InvalidArgumentException; use Saloon\Enums\VersionMode; use Saloon\Http\PendingRequest; @@ -48,6 +49,14 @@ public function getApiVersion(): ?string return $this->apiVersion; } + /** + * Get where the API version is applied to the request + */ + public function getVersionMode(): VersionMode + { + return $this->versionMode; + } + /** * Boot HasApiVersion plugin. */ @@ -61,11 +70,32 @@ public function bootHasApiVersion(PendingRequest $pendingRequest): void VersionMode::Header => $pendingRequest->headers()->add($this->versionKey, $this->apiVersion), VersionMode::QueryParam => $pendingRequest->query()->add($this->versionKey, $this->apiVersion), VersionMode::Url => $pendingRequest->setUrl( - str_replace('{version}', $this->ensureVersionIsSafeForUrl($this->apiVersion), $pendingRequest->getUrl()) + str_replace('{version}', $this->ensureVersionIsSafeForUrl($this->resolveUrlApiVersion($pendingRequest)), $pendingRequest->getUrl()) ), }; } + /** + * Resolve which version to substitute into `{version}` for URL mode. + */ + protected function resolveUrlApiVersion(PendingRequest $pendingRequest): string + { + if ($pendingRequest->getConnector() === $this) { + $request = $pendingRequest->getRequest(); + + if (in_array(HasApiVersion::class, Helpers::classUsesRecursive($request), true) + && $request->getVersionMode() === VersionMode::Url) { + $requestVersion = $request->getApiVersion(); + + if (is_string($requestVersion) && $requestVersion !== '') { + return $requestVersion; + } + } + } + + return $this->apiVersion; + } + /** * Ensure the version can only ever form a single subdomain label or path segment. * diff --git a/tests/Unit/Concerns/HasApiVersionTest.php b/tests/Unit/Concerns/HasApiVersionTest.php index b7f614a9..bbd6d9aa 100644 --- a/tests/Unit/Concerns/HasApiVersionTest.php +++ b/tests/Unit/Concerns/HasApiVersionTest.php @@ -138,6 +138,65 @@ return true; }); }); + + test('the request can override the connector api version in the url', function () { + $mockClient = sendVersionedRequest( + makeVersionedConnector('https://api.provider.com/{version}', VersionMode::Url, 'v1'), + makeVersionedRequest('beta', VersionMode::Url), + ); + + $mockClient->assertSent(function (Request $request, $response) { + expect($response->getPendingRequest()->getUrl())->toBe('https://api.provider.com/beta'); + + return true; + }); + }); + + test('a request versioned by header leaves the connector api version in the url', function () { + $mockClient = sendVersionedRequest( + makeVersionedConnector('https://api.provider.com/{version}', VersionMode::Url, 'v1'), + makeVersionedRequest('beta', VersionMode::Header), + ); + + $mockClient->assertSent(function (Request $request, $response) { + expect($response->getPendingRequest()->getUrl())->toBe('https://api.provider.com/v1'); + expect($response->getPsrRequest()->getHeaderLine('api-version'))->toBe('beta'); + + return true; + }); + }); + + test('a request with its own version methods but not the trait leaves the connector api version in the url', function () { + $request = new class extends Request { + protected Method $method = Method::GET; + + public function resolveEndpoint(): string + { + return ''; + } + + public function getApiVersion(): string + { + return 'beta'; + } + + public function getVersionMode(): VersionMode + { + return VersionMode::Url; + } + }; + + $mockClient = sendVersionedRequest( + makeVersionedConnector('https://api.provider.com/{version}', VersionMode::Url, 'v1'), + $request, + ); + + $mockClient->assertSent(function (Request $request, $response) { + expect($response->getPendingRequest()->getUrl())->toBe('https://api.provider.com/v1'); + + return true; + }); + }); }); describe('Without A Version', function () {