From 2c65a8d117e63a28fb29382a7ef67c18e62bb135 Mon Sep 17 00:00:00 2001 From: Thomas Raffray Date: Fri, 6 Sep 2024 10:41:14 +0200 Subject: [PATCH] feat(clients): helper to switch API key in use (#3616) Co-authored-by: Pierre Millot --- .../algoliasearch/Clients/AlgoliaConfig.cs | 13 ++++- .../algoliasearch/Transport/HttpTransport.cs | 2 +- .../client_core/lib/src/api_client.dart | 9 +-- .../src/transport/dio/auth_interceptor.dart | 2 +- .../lib/src/transport/dio/dio_requester.dart | 53 ++++++++++-------- .../lib/src/transport/requester.dart | 3 + .../src/main/java/com/algolia/ApiClient.java | 14 ++++- .../interceptors/AuthInterceptor.java | 6 +- .../configuration/internal/HttpClient.kt | 2 +- .../com/algolia/client/transport/Requester.kt | 2 + .../transport/internal/KtorRequester.kt | 10 ++++ .../lib/Configuration/Configuration.php | 33 ++++++++--- .../algoliasearch/http/base_config.py | 5 ++ .../lib/algolia/api_client.rb | 4 ++ .../lib/algolia/configuration.rb | 5 ++ .../main/scala/algoliasearch/ApiClient.scala | 8 ++- .../interceptor/AuthInterceptor.scala | 8 ++- .../Core/Networking/BaseConfiguration.swift | 4 +- .../Sources/Core/Networking/Transporter.swift | 6 +- config/generation.config.mjs | 7 ++- .../codegen/cts/tests/SnippetsGenerator.java | 4 +- .../codegen/cts/tests/TestsClient.java | 32 ++++++++++- .../codegen/cts/tests/TestsRequest.java | 4 +- scripts/cts/testServer/apiKey.ts | 35 ++++++++++++ scripts/cts/testServer/index.ts | 4 +- scripts/cts/testServer/waitFor.ts | 4 +- scripts/specs/snippets.ts | 10 ++-- specs/abtesting/spec.yml | 6 ++ specs/analytics/spec.yml | 6 ++ specs/common/helpers/setClientApiKey.yml | 20 +++++++ specs/ingestion/spec.yml | 6 ++ specs/insights/spec.yml | 6 ++ specs/monitoring/spec.yml | 6 ++ specs/personalization/spec.yml | 6 ++ specs/query-suggestions/spec.yml | 6 ++ specs/recommend/spec.yml | 6 ++ specs/search/spec.yml | 3 + specs/usage/spec.yml | 6 ++ templates/csharp/api.mustache | 13 ++++- templates/csharp/snippets/method.mustache | 5 +- .../csharp/tests/client/benchmark.mustache | 2 +- templates/csharp/tests/client/client.mustache | 2 +- templates/csharp/tests/client/method.mustache | 2 +- templates/csharp/tests/client/tests.mustache | 6 ++ templates/dart/api.mustache | 18 +++--- templates/dart/snippets/method.mustache | 5 +- templates/dart/tests/client/client.mustache | 8 ++- templates/dart/tests/client/method.mustache | 2 +- templates/go/client.mustache | 13 ++++- templates/go/snippets/method.mustache | 9 +-- templates/go/tests/client/method.mustache | 2 +- templates/go/tests/client/tests.mustache | 6 ++ templates/go/tests/requests/requests.mustache | 2 +- templates/java/snippets/method.mustache | 1 + .../java/tests/client/benchmark.mustache | 2 +- templates/java/tests/client/method.mustache | 2 +- templates/java/tests/client/tests.mustache | 10 +++- .../javascript/clients/api-single.mustache | 10 ++++ templates/javascript/snippets/method.mustache | 5 +- .../tests/client/benchmark.mustache | 4 +- .../javascript/tests/client/client.mustache | 2 +- .../javascript/tests/client/method.mustache | 2 +- .../javascript/tests/client/tests.mustache | 6 ++ templates/kotlin/ApiClient.kt.mustache | 18 ++++-- templates/kotlin/api.mustache | 2 +- templates/kotlin/snippets/method.mustache | 7 ++- templates/php/api.mustache | 12 ++++ templates/php/snippets/method.mustache | 5 +- templates/php/tests/client/method.mustache | 2 +- templates/php/tests/client/tests.mustache | 6 ++ templates/php/tests/generateParams.mustache | 2 +- templates/python/api.mustache | 6 +- templates/python/snippets/method.mustache | 7 ++- templates/python/tests/client/method.mustache | 2 +- templates/ruby/api.mustache | 10 +++- templates/ruby/snippets/method.mustache | 9 ++- templates/ruby/tests/client/method.mustache | 2 +- templates/scala/snippets/method.mustache | 7 ++- templates/scala/tests/client/client.mustache | 6 ++ .../scala/tests/client/createClient.mustache | 4 +- templates/scala/tests/client/method.mustache | 6 +- templates/swift/api.mustache | 5 ++ templates/swift/client_configuration.mustache | 2 +- templates/swift/snippets/method.mustache | 3 +- templates/swift/tests/client/method.mustache | 2 +- templates/swift/tests/client/tests.mustache | 6 ++ tests/CTS/client/common/setClientApiKey.json | 55 +++++++++++++++++++ tests/output/dart/lib/src/run.dart | 7 +++ 88 files changed, 567 insertions(+), 131 deletions(-) create mode 100644 scripts/cts/testServer/apiKey.ts create mode 100644 specs/common/helpers/setClientApiKey.yml create mode 100644 tests/CTS/client/common/setClientApiKey.json diff --git a/clients/algoliasearch-client-csharp/algoliasearch/Clients/AlgoliaConfig.cs b/clients/algoliasearch-client-csharp/algoliasearch/Clients/AlgoliaConfig.cs index e5c9efee81..15085fca5d 100644 --- a/clients/algoliasearch-client-csharp/algoliasearch/Clients/AlgoliaConfig.cs +++ b/clients/algoliasearch-client-csharp/algoliasearch/Clients/AlgoliaConfig.cs @@ -45,7 +45,7 @@ protected AlgoliaConfig(string appId, string apiKey, string clientName, string c /// The admin API Key /// /// - public string ApiKey { get; } + public string ApiKey { get; set; } /// /// Configurations hosts @@ -97,5 +97,16 @@ internal Dictionary BuildHeaders() DefaultHeaders[Defaults.UserAgentHeader.ToLowerInvariant()] = UserAgent.ToString(); return DefaultHeaders; } + + /// + /// Helper to switch the API key sent with each request + /// + /// Your API Key + /// + public void SetClientApiKey(string apiKey) + { + ApiKey = apiKey; + DefaultHeaders[Defaults.AlgoliaApiKeyHeader.ToLowerInvariant()] = apiKey; + } } } diff --git a/clients/algoliasearch-client-csharp/algoliasearch/Transport/HttpTransport.cs b/clients/algoliasearch-client-csharp/algoliasearch/Transport/HttpTransport.cs index 0b8d8167c1..213720dd92 100644 --- a/clients/algoliasearch-client-csharp/algoliasearch/Transport/HttpTransport.cs +++ b/clients/algoliasearch-client-csharp/algoliasearch/Transport/HttpTransport.cs @@ -24,7 +24,7 @@ internal class HttpTransport private readonly IHttpRequester _httpClient; private readonly ISerializer _serializer; private readonly RetryStrategy _retryStrategy; - private readonly AlgoliaConfig _algoliaConfig; + internal AlgoliaConfig _algoliaConfig; private string _errorMessage; private readonly ILogger _logger; diff --git a/clients/algoliasearch-client-dart/packages/client_core/lib/src/api_client.dart b/clients/algoliasearch-client-dart/packages/client_core/lib/src/api_client.dart index f49e8750e6..3609434688 100644 --- a/clients/algoliasearch-client-dart/packages/client_core/lib/src/api_client.dart +++ b/clients/algoliasearch-client-dart/packages/client_core/lib/src/api_client.dart @@ -2,15 +2,12 @@ import 'package:algolia_client_core/src/config/client_options.dart'; /// An abstract class representing an API client with specific properties and options. abstract interface class ApiClient { - /// The unique identifier for the application using the API client. - String get appId; - - /// The API key used for authentication. - String get apiKey; - /// A set of custom client options to configure the behavior of the API client. ClientOptions get options; + /// Allow switching the API key used to authenticate requests. + void setClientApiKey({required String apiKey}); + /// Dispose of underlying resources. void dispose(); } diff --git a/clients/algoliasearch-client-dart/packages/client_core/lib/src/transport/dio/auth_interceptor.dart b/clients/algoliasearch-client-dart/packages/client_core/lib/src/transport/dio/auth_interceptor.dart index c1fa50e5f0..ca35266c4b 100644 --- a/clients/algoliasearch-client-dart/packages/client_core/lib/src/transport/dio/auth_interceptor.dart +++ b/clients/algoliasearch-client-dart/packages/client_core/lib/src/transport/dio/auth_interceptor.dart @@ -9,7 +9,7 @@ class AuthInterceptor extends Interceptor { final String appId; /// The API key used for Algolia authentication. - final String apiKey; + String apiKey; /// Constructs an [AuthInterceptor] with the provided application id and API key. AuthInterceptor({ diff --git a/clients/algoliasearch-client-dart/packages/client_core/lib/src/transport/dio/dio_requester.dart b/clients/algoliasearch-client-dart/packages/client_core/lib/src/transport/dio/dio_requester.dart index a39e6018de..cbe05a122e 100644 --- a/clients/algoliasearch-client-dart/packages/client_core/lib/src/transport/dio/dio_requester.dart +++ b/clients/algoliasearch-client-dart/packages/client_core/lib/src/transport/dio/dio_requester.dart @@ -16,7 +16,8 @@ import 'package:dio/dio.dart'; /// response conversion and error handling. class DioRequester implements Requester { /// The underlying Dio client. - final Dio _client; + final AuthInterceptor _authInterceptor; + late final Dio _client; /// Constructs a [DioRequester] with the given [appId], [apiKey], and [options]. DioRequester({ @@ -28,29 +29,30 @@ class DioRequester implements Requester { Function(Object?)? logger, Iterable? interceptors, HttpClientAdapter? httpClientAdapter, - }) : _client = Dio( - BaseOptions( - headers: headers, - connectTimeout: connectTimeout, + }) : _authInterceptor = AuthInterceptor( + appId: appId, + apiKey: apiKey, + ) { + _client = Dio( + BaseOptions( + headers: headers, + connectTimeout: connectTimeout, + ), + )..interceptors.addAll([ + _authInterceptor, + AgentInterceptor( + agent: AlgoliaAgent(packageVersion) + ..addAll(clientSegments ?? const []) + ..addAll(Platform.agentSegments()), + ), + if (logger != null) + LogInterceptor( + requestBody: true, + responseBody: true, + logPrint: logger, ), - )..interceptors.addAll([ - AuthInterceptor( - appId: appId, - apiKey: apiKey, - ), - AgentInterceptor( - agent: AlgoliaAgent(packageVersion) - ..addAll(clientSegments ?? const []) - ..addAll(Platform.agentSegments()), - ), - if (logger != null) - LogInterceptor( - requestBody: true, - responseBody: true, - logPrint: logger, - ), - if (interceptors != null) ...interceptors, - ]) { + if (interceptors != null) ...interceptors, + ]); if (httpClientAdapter != null) { _client.httpClientAdapter = httpClientAdapter; } @@ -114,4 +116,9 @@ class DioRequester implements Requester { @override void close() => _client.close(); + + @override + void setClientApiKey(String apiKey) { + _authInterceptor.apiKey = apiKey; + } } diff --git a/clients/algoliasearch-client-dart/packages/client_core/lib/src/transport/requester.dart b/clients/algoliasearch-client-dart/packages/client_core/lib/src/transport/requester.dart index e87a708a08..d37f9d9db5 100644 --- a/clients/algoliasearch-client-dart/packages/client_core/lib/src/transport/requester.dart +++ b/clients/algoliasearch-client-dart/packages/client_core/lib/src/transport/requester.dart @@ -11,6 +11,9 @@ abstract class Requester { /// The method returns a Future that resolves to an [HttpResponse]. Future perform(HttpRequest request); + /// Allows to switch the API key used to authenticate requests. + void setClientApiKey(String apiKey); + /// Closes any underlying resources that the Requester might be using. /// /// By default, it does nothing (no-op), but it can be implemented to handle resource cleanup diff --git a/clients/algoliasearch-client-java/algoliasearch/src/main/java/com/algolia/ApiClient.java b/clients/algoliasearch-client-java/algoliasearch/src/main/java/com/algolia/ApiClient.java index 2052c3cae4..fff2ae1991 100644 --- a/clients/algoliasearch-client-java/algoliasearch/src/main/java/com/algolia/ApiClient.java +++ b/clients/algoliasearch-client-java/algoliasearch/src/main/java/com/algolia/ApiClient.java @@ -16,6 +16,7 @@ import java.util.concurrent.ExecutorService; import java.util.stream.Collectors; import org.jetbrains.annotations.Nullable; +import javax.annotation.Nonnull; /** * Represents a base client for making API requests. The client uses a {@link Requester} for @@ -26,6 +27,7 @@ public abstract class ApiClient implements Closeable { private final Requester requester; private final ExecutorService executor; + private AuthInterceptor authInterceptor; /** Constructs a new instance of the {@link ApiClient}. */ protected ApiClient(String appId, String apiKey, String clientName, @Nullable ClientOptions options, List defaultHosts) { @@ -52,8 +54,9 @@ private Requester defaultRequester(String appId, String apiKey, String clientNam List statefulHosts = hosts.stream().map(StatefulHost::new).collect(Collectors.toList()); JsonSerializer serializer = JsonSerializer.builder().setCustomConfig(options.getMapperConfig()).build(); + this.authInterceptor = new AuthInterceptor(appId, apiKey); HttpRequester.Builder builder = new HttpRequester.Builder(serializer) - .addInterceptor(new AuthInterceptor(appId, apiKey)) + .addInterceptor(authInterceptor) .addInterceptor(new UserAgentInterceptor(algoliaAgent)) .addInterceptor(new RetryStrategy(statefulHosts)); if (options.getRequesterConfig() != null) { @@ -62,6 +65,15 @@ private Requester defaultRequester(String appId, String apiKey, String clientNam return builder.build(options); } + /** + * Helper method to switch the API key used to authenticate the requests. + * + * @param apiKey The new API key to be used from now on. + */ + public void setClientApiKey(@Nonnull String apiKey) { + this.authInterceptor.setApiKey(apiKey); + } + /** * Executes an HTTP request asynchronously and returns a {@link CompletableFuture} of the response * deserialized into a specified type. diff --git a/clients/algoliasearch-client-java/algoliasearch/src/main/java/com/algolia/internal/interceptors/AuthInterceptor.java b/clients/algoliasearch-client-java/algoliasearch/src/main/java/com/algolia/internal/interceptors/AuthInterceptor.java index 14c25d6a01..f7940c91d1 100644 --- a/clients/algoliasearch-client-java/algoliasearch/src/main/java/com/algolia/internal/interceptors/AuthInterceptor.java +++ b/clients/algoliasearch-client-java/algoliasearch/src/main/java/com/algolia/internal/interceptors/AuthInterceptor.java @@ -12,13 +12,17 @@ public final class AuthInterceptor implements Interceptor { private static final String HEADER_API_KEY = "x-algolia-api-key"; private final String applicationId; - private final String apiKey; + private String apiKey; public AuthInterceptor(String applicationId, String apiKey) { this.applicationId = applicationId; this.apiKey = apiKey; } + public void setApiKey(String apiKey) { + this.apiKey = apiKey; + } + @Nonnull @Override public Response intercept(Chain chain) throws IOException { diff --git a/clients/algoliasearch-client-kotlin/client/src/commonMain/kotlin/com/algolia/client/configuration/internal/HttpClient.kt b/clients/algoliasearch-client-kotlin/client/src/commonMain/kotlin/com/algolia/client/configuration/internal/HttpClient.kt index 5f380cc8ee..9ceea49118 100644 --- a/clients/algoliasearch-client-kotlin/client/src/commonMain/kotlin/com/algolia/client/configuration/internal/HttpClient.kt +++ b/clients/algoliasearch-client-kotlin/client/src/commonMain/kotlin/com/algolia/client/configuration/internal/HttpClient.kt @@ -9,7 +9,7 @@ import io.ktor.client.request.* import io.ktor.serialization.kotlinx.json.* private const val HEADER_APPLICATION_ID = "x-algolia-application-id" -private const val HEADER_APIKEY = "x-algolia-api-key" +public const val HEADER_APIKEY: String = "x-algolia-api-key" internal fun algoliaHttpClient( appId: String, diff --git a/clients/algoliasearch-client-kotlin/client/src/commonMain/kotlin/com/algolia/client/transport/Requester.kt b/clients/algoliasearch-client-kotlin/client/src/commonMain/kotlin/com/algolia/client/transport/Requester.kt index f694d88acb..94f772139a 100644 --- a/clients/algoliasearch-client-kotlin/client/src/commonMain/kotlin/com/algolia/client/transport/Requester.kt +++ b/clients/algoliasearch-client-kotlin/client/src/commonMain/kotlin/com/algolia/client/transport/Requester.kt @@ -30,4 +30,6 @@ public interface Requester { requestOptions: RequestOptions? = null, returnType: TypeInfo, ): T + + public fun setClientApiKey(apiKey: String); } diff --git a/clients/algoliasearch-client-kotlin/client/src/commonMain/kotlin/com/algolia/client/transport/internal/KtorRequester.kt b/clients/algoliasearch-client-kotlin/client/src/commonMain/kotlin/com/algolia/client/transport/internal/KtorRequester.kt index ddd3982350..27b170b651 100644 --- a/clients/algoliasearch-client-kotlin/client/src/commonMain/kotlin/com/algolia/client/transport/internal/KtorRequester.kt +++ b/clients/algoliasearch-client-kotlin/client/src/commonMain/kotlin/com/algolia/client/transport/internal/KtorRequester.kt @@ -2,6 +2,7 @@ package com.algolia.client.transport.internal import com.algolia.client.configuration.CallType import com.algolia.client.configuration.Host +import com.algolia.client.configuration.internal.HEADER_APIKEY import com.algolia.client.exception.AlgoliaRetryException import com.algolia.client.exception.internal.asApiException import com.algolia.client.exception.internal.asClientException @@ -38,6 +39,15 @@ public class KtorRequester( private val mutex: Mutex = Mutex() private val retryableHosts = hosts.map { RetryableHost(it) } + public override fun setClientApiKey(apiKey: String) { + headers { + if (contains(HEADER_APIKEY)) { + remove(HEADER_APIKEY) + } + append(HEADER_APIKEY, apiKey) + } + } + override suspend fun execute( requestConfig: RequestConfig, requestOptions: RequestOptions?, diff --git a/clients/algoliasearch-client-php/lib/Configuration/Configuration.php b/clients/algoliasearch-client-php/lib/Configuration/Configuration.php index 10f11f8e34..72b9de5b48 100644 --- a/clients/algoliasearch-client-php/lib/Configuration/Configuration.php +++ b/clients/algoliasearch-client-php/lib/Configuration/Configuration.php @@ -67,7 +67,7 @@ public function __construct(array $config = []) throw new AlgoliaException('`apiKey` is missing.'); } - $this->setAlgoliaApiKey($config['apiKey']); + $this->setClientApiKey($config['apiKey']); $this->setAuth('x-algolia-api-key', $config['apiKey']); $this->setAppId($config['appId']); @@ -193,13 +193,6 @@ public function getAlgoliaApiKey() return $this->config['apiKey']; } - public function setAlgoliaApiKey($apiKey) - { - $this->config['apiKey'] = $apiKey; - - return $this; - } - public function getHosts() { return $this->config['hosts']; @@ -272,6 +265,30 @@ public function setDefaultHeaders(array $defaultHeaders) return $this; } + /** + * Switch the API key used to authenticate requessts. + * + * @param string $apiKey The new API key to be used + * + * @return void + */ + public function setClientApiKey($apiKey) + { + $this->config['apiKey'] = $apiKey; + + return $this; + } + + /** + * @deprecated This method is deprecated. Use setClientApiKey() instead. + * + * @param mixed $apiKey + */ + public function setAlgoliaApiKey($apiKey) + { + return $this->setClientApiKey($apiKey); + } + /** * Sets the user agent of the api client. * diff --git a/clients/algoliasearch-client-python/algoliasearch/http/base_config.py b/clients/algoliasearch-client-python/algoliasearch/http/base_config.py index e10b02efb2..572bb27c21 100644 --- a/clients/algoliasearch-client-python/algoliasearch/http/base_config.py +++ b/clients/algoliasearch-client-python/algoliasearch/http/base_config.py @@ -40,3 +40,8 @@ def __init__(self, app_id: Optional[str] = None, api_key: Optional[str] = None): self.headers = None self.proxies = None self.hosts = None + + def set_client_api_key(self, api_key: str) -> None: + """Sets a new API key to authenticate requests.""" + self.api_key = api_key + self.headers["x-algolia-api-key"] = api_key diff --git a/clients/algoliasearch-client-ruby/lib/algolia/api_client.rb b/clients/algoliasearch-client-ruby/lib/algolia/api_client.rb index 4438762206..25ebf8358a 100644 --- a/clients/algoliasearch-client-ruby/lib/algolia/api_client.rb +++ b/clients/algoliasearch-client-ruby/lib/algolia/api_client.rb @@ -20,6 +20,10 @@ def self.default @@default ||= ApiClient.new end + def set_client_api_key(api_key) + @config.set_client_api_key(api_key) + end + # Call an API with given options. # # @return [Http::Response] the response. diff --git a/clients/algoliasearch-client-ruby/lib/algolia/configuration.rb b/clients/algoliasearch-client-ruby/lib/algolia/configuration.rb index f60a629f15..03dc9bd52f 100644 --- a/clients/algoliasearch-client-ruby/lib/algolia/configuration.rb +++ b/clients/algoliasearch-client-ruby/lib/algolia/configuration.rb @@ -44,6 +44,11 @@ def initialize(app_id, api_key, hosts, client_name, opts = {}) yield(self) if block_given? end + def set_client_api_key(api_key) + @api_key = api_key + @header_params["X-Algolia-API-Key"] = api_key + end + # The default Configuration object. def self.default @@default ||= Configuration.new diff --git a/clients/algoliasearch-client-scala/src/main/scala/algoliasearch/ApiClient.scala b/clients/algoliasearch-client-scala/src/main/scala/algoliasearch/ApiClient.scala index 6d10169c75..79bee85922 100644 --- a/clients/algoliasearch-client-scala/src/main/scala/algoliasearch/ApiClient.scala +++ b/clients/algoliasearch-client-scala/src/main/scala/algoliasearch/ApiClient.scala @@ -40,6 +40,8 @@ abstract class ApiClient( throw AlgoliaClientException("`apiKey` is missing.") } + private val authInterceptor = new AuthInterceptor(appId, apiKey) + private val requester = options.customRequester match { case Some(customRequester) => customRequester case None => @@ -62,7 +64,7 @@ abstract class ApiClient( val builder = HttpRequester .builder(options.customFormats.getOrElse(formats)) - .withInterceptor(new AuthInterceptor(appId, apiKey)) + .withInterceptor(authInterceptor) .withInterceptor(new UserAgentInterceptor(algoliaAgent)) .withInterceptor(new RetryStrategy(statefulHosts)) @@ -89,4 +91,8 @@ abstract class ApiClient( override def close(): Unit = { Try(requester.close()) } + + def setClientApiKey(apiKey: String): Unit = { + authInterceptor.setApiKey(apiKey) + } } diff --git a/clients/algoliasearch-client-scala/src/main/scala/algoliasearch/internal/interceptor/AuthInterceptor.scala b/clients/algoliasearch-client-scala/src/main/scala/algoliasearch/internal/interceptor/AuthInterceptor.scala index 2d32bb8ba9..59b9d667a3 100644 --- a/clients/algoliasearch-client-scala/src/main/scala/algoliasearch/internal/interceptor/AuthInterceptor.scala +++ b/clients/algoliasearch-client-scala/src/main/scala/algoliasearch/internal/interceptor/AuthInterceptor.scala @@ -10,13 +10,17 @@ import okhttp3.{Interceptor, Response} * API key */ private[algoliasearch] class AuthInterceptor( - applicationId: String, - apiKey: String + applicationId: String, + private var apiKey: String ) extends Interceptor { private val HeaderApplicationId = "x-algolia-application-id" private val HeaderApiKey = "x-algolia-api-key" + def setApiKey(newApiKey: String): Unit = { + apiKey = newApiKey + } + override def intercept(chain: Interceptor.Chain): Response = { val originalRequest = chain.request() val builder = originalRequest.newBuilder() diff --git a/clients/algoliasearch-client-swift/Sources/Core/Networking/BaseConfiguration.swift b/clients/algoliasearch-client-swift/Sources/Core/Networking/BaseConfiguration.swift index 86784e30c9..8ba836333c 100644 --- a/clients/algoliasearch-client-swift/Sources/Core/Networking/BaseConfiguration.swift +++ b/clients/algoliasearch-client-swift/Sources/Core/Networking/BaseConfiguration.swift @@ -23,7 +23,7 @@ public protocol BaseConfiguration { var hosts: [RetryableHost] { get } /// Default headers that should be applied to every request. - var defaultHeaders: [String: String]? { get } + var defaultHeaders: [String: String]? { get set } /// Compression type var compression: CompressionAlgorithm { get } @@ -48,7 +48,7 @@ public struct DefaultConfiguration: BaseConfiguration { public let writeTimeout: TimeInterval = 30 public let readTimeout: TimeInterval = 5 public let logLevel: LogLevel = .info - public let defaultHeaders: [String: String]? = [:] + public var defaultHeaders: [String: String]? = [:] public var hosts: [RetryableHost] = [] public let compression: CompressionAlgorithm = .none } diff --git a/clients/algoliasearch-client-swift/Sources/Core/Networking/Transporter.swift b/clients/algoliasearch-client-swift/Sources/Core/Networking/Transporter.swift index 0937fc02d1..558a8df70b 100644 --- a/clients/algoliasearch-client-swift/Sources/Core/Networking/Transporter.swift +++ b/clients/algoliasearch-client-swift/Sources/Core/Networking/Transporter.swift @@ -12,7 +12,7 @@ import Foundation // MARK: - Transporter open class Transporter { - let configuration: BaseConfiguration + var configuration: BaseConfiguration let retryStrategy: RetryStrategy let requestBuilder: RequestBuilder let exposeIntermediateErrors: Bool @@ -43,6 +43,10 @@ open class Transporter { self.requestBuilder = requestBuilder } + public func setClientApiKey(apiKey: String) { + self.configuration.defaultHeaders?.updateValue(apiKey, forKey: "X-Algolia-API-Key") + } + public func send( method: String, path: String, data: Codable?, requestOptions: RequestOptions? = nil, useReadTransporter: Bool = false diff --git a/config/generation.config.mjs b/config/generation.config.mjs index f87c4052ce..ae2359d91e 100644 --- a/config/generation.config.mjs +++ b/config/generation.config.mjs @@ -71,7 +71,9 @@ export const patterns = [ '!clients/algoliasearch-client-javascript/scripts/**', '!clients/algoliasearch-client-javascript/tests/**', // the release process is allowed to push changes to this file, but in general we don't because those files are generated - process.env.RELEASE ? '!clients/algoliasearch-client-javascript/packages/**/package.json' : 'clients/algoliasearch-client-javascript/packages/**/package.json', + process.env.RELEASE + ? '!clients/algoliasearch-client-javascript/packages/**/package.json' + : 'clients/algoliasearch-client-javascript/packages/**/package.json', '!clients/algoliasearch-client-javascript/packages/requester-*/**', '!clients/algoliasearch-client-javascript/packages/client-common/**', '!clients/algoliasearch-client-javascript/packages/algoliasearch/__tests__/**', @@ -142,6 +144,7 @@ export const patterns = [ '!clients/algoliasearch-client-scala/**', 'clients/algoliasearch-client-scala/version.sbt', 'clients/algoliasearch-client-scala/src/main/scala/algoliasearch/**', + '!clients/algoliasearch-client-scala/src/main/scala/algoliasearch/ApiClient.scala', '!clients/algoliasearch-client-scala/src/main/scala/algoliasearch/exception/**', '!clients/algoliasearch-client-scala/src/main/scala/algoliasearch/internal/**', '!clients/algoliasearch-client-scala/src/main/scala/algoliasearch/config/**', @@ -166,5 +169,5 @@ export const patterns = [ '!tests/output/swift/handwritten/**', '!tests/output/swift/Utils/**', - 'clients/**/LICENSE' + 'clients/**/LICENSE', ]; diff --git a/generators/src/main/java/com/algolia/codegen/cts/tests/SnippetsGenerator.java b/generators/src/main/java/com/algolia/codegen/cts/tests/SnippetsGenerator.java index f351180993..e1a3f27918 100644 --- a/generators/src/main/java/com/algolia/codegen/cts/tests/SnippetsGenerator.java +++ b/generators/src/main/java/com/algolia/codegen/cts/tests/SnippetsGenerator.java @@ -147,11 +147,11 @@ public void run(Map models, Map // Determines whether the endpoint is expected to return a response payload deserialized // and therefore a variable to store it into. - test.put("hasResponsePayload", true); + test.put("hasResponse", true); for (CodegenResponse response : ope.responses) { if (response.code.equals("204")) { - test.put("hasResponsePayload", false); + test.put("hasResponse", false); } } diff --git a/generators/src/main/java/com/algolia/codegen/cts/tests/TestsClient.java b/generators/src/main/java/com/algolia/codegen/cts/tests/TestsClient.java index 727c80ae5c..4e3a417f90 100644 --- a/generators/src/main/java/com/algolia/codegen/cts/tests/TestsClient.java +++ b/generators/src/main/java/com/algolia/codegen/cts/tests/TestsClient.java @@ -10,6 +10,7 @@ import java.util.Map; import org.openapitools.codegen.CodegenModel; import org.openapitools.codegen.CodegenOperation; +import org.openapitools.codegen.CodegenResponse; import org.openapitools.codegen.SupportingFile; public class TestsClient extends TestsGenerator { @@ -69,7 +70,8 @@ public void run(Map models, Map skipTest:for (ClientTestData test : blockEntry.getValue()) { try { Map testOut = new HashMap<>(); - List steps = new ArrayList<>(); + List> steps = new ArrayList<>(); + int methodCount = 0; testOut.put("inClientTest", true); testOut.put("testName", test.testName); testOut.put("testIndex", testIndex++); @@ -114,9 +116,27 @@ public void run(Map models, Map stepOut.put("returnsBoolean", ope.returnType.equals("Boolean")); // ruby requires a ? for boolean functions. } + boolean isHelper = (boolean) ope.vendorExtensions.getOrDefault("x-helper", false); + stepOut.put("isHelper", isHelper); + // default to true because most api calls are asynchronous + stepOut.put("isAsync", (boolean) ope.vendorExtensions.getOrDefault("x-asynchronous-helper", true)); + + // Determines whether the endpoint is expected to return a response payload + // deserialized and therefore a variable to store it into. + stepOut.put("hasResponse", true); + for (CodegenResponse response : ope.responses) { + if (response.code.equals("204")) { + stepOut.put("hasResponse", false); + } + } + // set on testOut because we need to wrap everything for java. - testOut.put("isHelper", (boolean) ope.vendorExtensions.getOrDefault("x-helper", false)); - testOut.put("isAsync", (boolean) ope.vendorExtensions.getOrDefault("x-asynchronous-helper", true)); // default to true because most api calls are asynchronous + testOut.put("isHelper", isHelper); + + // default to true because most api calls are asynchronous + testOut.put("isAsync", (boolean) ope.vendorExtensions.getOrDefault("x-asynchronous-helper", true)); + + methodCount++; } stepOut.put("method", step.method); @@ -180,6 +200,12 @@ public void run(Map models, Map } steps.add(stepOut); } + for (Map step : steps) { + step.put( + "shouldScope", + (boolean) step.getOrDefault("isMethod", false) && (methodCount > 1 || (boolean) step.getOrDefault("isHelper", false)) + ); + } testOut.put("steps", steps); tests.add(testOut); } catch (CTSException e) { diff --git a/generators/src/main/java/com/algolia/codegen/cts/tests/TestsRequest.java b/generators/src/main/java/com/algolia/codegen/cts/tests/TestsRequest.java index 2265152f0a..0c55defd19 100644 --- a/generators/src/main/java/com/algolia/codegen/cts/tests/TestsRequest.java +++ b/generators/src/main/java/com/algolia/codegen/cts/tests/TestsRequest.java @@ -168,11 +168,11 @@ public void run(Map models, Map // Determines whether the endpoint is expected to return a response payload deserialized // and therefore a variable to store it into. - test.put("hasResponsePayload", true); + test.put("hasResponse", true); for (CodegenResponse response : ope.responses) { if (response.code.equals("204")) { - test.put("hasResponsePayload", false); + test.put("hasResponse", false); } } diff --git a/scripts/cts/testServer/apiKey.ts b/scripts/cts/testServer/apiKey.ts new file mode 100644 index 0000000000..76149c9b94 --- /dev/null +++ b/scripts/cts/testServer/apiKey.ts @@ -0,0 +1,35 @@ +import type { Server } from 'http'; + +import { expect } from 'chai'; +import type { Express, Request, Response } from 'express'; + +import { setupServer } from '.'; + +function addRoutes(app: Express): void { + app.get('/check-api-key/1', (req: Request, res: Response) => { + const headerName = 'x-algolia-api-key'; + + // eslint-disable-next-line no-unused-expressions + expect(headerName in req.headers).to.be.true; + + const headerAPIKeyValue = req.headers[headerName]; + expect(headerAPIKeyValue).to.equal('test-api-key'); + + res.status(200).json({ headerAPIKeyValue }); + }); + app.get('/check-api-key/2', (req: Request, res: Response) => { + const headerName = 'x-algolia-api-key'; + + // eslint-disable-next-line no-unused-expressions + expect(headerName in req.headers).to.be.true; + + const headerAPIKeyValue = req.headers[headerName]; + expect(headerAPIKeyValue).to.equal('updated-api-key'); + + res.status(200).json({ headerAPIKeyValue }); + }); +} + +export function apiKeyServer(): Promise { + return setupServer('apiKey', 6683, addRoutes); +} diff --git a/scripts/cts/testServer/index.ts b/scripts/cts/testServer/index.ts index c44dfb0adb..27b1477b0c 100644 --- a/scripts/cts/testServer/index.ts +++ b/scripts/cts/testServer/index.ts @@ -6,6 +6,7 @@ import type { Express } from 'express'; import { createSpinner } from '../../spinners'; import type { CTSType } from '../runCts'; +import { apiKeyServer } from './apiKey'; import { benchmarkServer } from './benchmark'; import { chunkWrapperServer } from './chunkWrapper'; import { gzipServer } from './gzip'; @@ -24,6 +25,7 @@ export async function startTestServer(suites: Record): Promise replaceAllObjectsServer(), chunkWrapperServer(), waitForApiKeyServer(), + apiKeyServer(), ); } if (suites.benchmark) { @@ -61,7 +63,7 @@ export async function setupServer(name: string, port: number, addRoutes: (app: E }); // catch all error handler - app.use((err, req, res, _) => { + app.use((err, _req, res, _) => { // eslint-disable-next-line no-console console.error(err.message); res.status(500).send({ message: err.message }); diff --git a/scripts/cts/testServer/waitFor.ts b/scripts/cts/testServer/waitFor.ts index 9e3eb7d949..68f68e4b6e 100644 --- a/scripts/cts/testServer/waitFor.ts +++ b/scripts/cts/testServer/waitFor.ts @@ -114,13 +114,13 @@ function addRoutes(app: Express): void { } }); - app.get('/1/indexes/:indexName/task/:taskID', (req, res) => { + app.get('/1/indexes/:indexName/task/:taskID', (_req, res) => { res.status(200).json({ status: 'published', }); }); - app.get('/1/task/:taskID', (req, res) => { + app.get('/1/task/:taskID', (_req, res) => { res.status(200).json({ status: 'published', }); diff --git a/scripts/specs/snippets.ts b/scripts/specs/snippets.ts index 74f0c9532e..15f8464b22 100644 --- a/scripts/specs/snippets.ts +++ b/scripts/specs/snippets.ts @@ -27,7 +27,9 @@ export function transformCodeSamplesToGuideMethods(snippetSamples: SnippetSample } for (const [sampleName, sample] of Object.entries(samples)) { - const sampleMatch = sample.match(/.*Initialize the client\n(.*)((.|\n)*)(.*Call the API\n)((.|\n)*)/); + const sampleMatch = sample.match( + /.*Initialize the client.*\n(.*)((.|\n)*)(.*Call the API\n)((.|\n)*)(#|\/\/) >LOG/, + ); if (!sampleMatch) { continue; } @@ -37,11 +39,11 @@ export function transformCodeSamplesToGuideMethods(snippetSamples: SnippetSample if (!('init' in snippetSamples[language])) { snippetSamples[language].init = { - default: initLine.replace(/\n$/, ''), + default: initLine.trim(), }; } - snippetSamples[language][operation][sampleName] = callLine.replace(/\n$/, ''); + snippetSamples[language][operation][sampleName] = callLine.trim(); } } } @@ -72,7 +74,7 @@ export async function transformSnippetsToCodeSamples(clientName: string): Promis const importMatch = snippetFileContent.match(/>IMPORT\n([\s\S]*?)\n.*IMPORT {{> visibility}} partial class {{classname}} : {{interfacePrefix}}{{classname}} { - private readonly HttpTransport _transport; + internal HttpTransport _transport; private readonly ILogger<{{apiPackageName}}Client> _logger; /// @@ -140,6 +140,17 @@ namespace Algolia.Search.Clients; } } + /// + /// Helper to switch the API key sent with each request + /// + /// Your new API Key + /// + public void SetClientApiKey(string apiKey) + { + _transport._algoliaConfig.SetClientApiKey(apiKey); + } + + {{#operation}} {{#supportsAsync}} diff --git a/templates/csharp/snippets/method.mustache b/templates/csharp/snippets/method.mustache index a302a36559..69f4196d4b 100644 --- a/templates/csharp/snippets/method.mustache +++ b/templates/csharp/snippets/method.mustache @@ -28,10 +28,11 @@ public class Snippet{{client}} var client = new {{client}}(new {{clientPrefix}}Config("YOUR_APP_ID", "YOUR_API_KEY"{{#hasRegionalHost}},"YOUR_APP_ID_REGION"{{/hasRegionalHost}})); // Call the API - {{#hasResponsePayload}}var response = {{/hasResponsePayload}}{{> tests/method}}; + {{#hasResponse}}var response = {{/hasResponse}}{{> tests/method}}; + // >LOG // SEPARATOR< } {{/snippets}} {{/blocksRequests}} -} \ No newline at end of file +} diff --git a/templates/csharp/tests/client/benchmark.mustache b/templates/csharp/tests/client/benchmark.mustache index 1a20b96d44..fc91682737 100644 --- a/templates/csharp/tests/client/benchmark.mustache +++ b/templates/csharp/tests/client/benchmark.mustache @@ -30,4 +30,4 @@ public void Dispose() {{#blocksBenchmark}} {{> tests/client/tests}} {{/blocksBenchmark}} -} +} \ No newline at end of file diff --git a/templates/csharp/tests/client/client.mustache b/templates/csharp/tests/client/client.mustache index 52594f67fb..d1093dd73c 100644 --- a/templates/csharp/tests/client/client.mustache +++ b/templates/csharp/tests/client/client.mustache @@ -32,4 +32,4 @@ public void Dispose() {{#blocksClient}} {{> tests/client/tests}} {{/blocksClient}} -} +} \ No newline at end of file diff --git a/templates/csharp/tests/client/method.mustache b/templates/csharp/tests/client/method.mustache index 2436bd70e6..a477d20953 100644 --- a/templates/csharp/tests/client/method.mustache +++ b/templates/csharp/tests/client/method.mustache @@ -1,2 +1,2 @@ -{{^useEchoRequester}}var res = {{/useEchoRequester}}{{> tests/method}}; +{{^useEchoRequester}}{{#hasResponse}}var res = {{/hasResponse}}{{/useEchoRequester}}{{> tests/method}}; {{#useEchoRequester}}EchoResponse result = _echo.LastResponse;{{/useEchoRequester}} \ No newline at end of file diff --git a/templates/csharp/tests/client/tests.mustache b/templates/csharp/tests/client/tests.mustache index 232c779a44..031f6f1917 100644 --- a/templates/csharp/tests/client/tests.mustache +++ b/templates/csharp/tests/client/tests.mustache @@ -15,6 +15,9 @@ {{/isError}} {{^isError}} + {{#shouldScope}} + { + {{/shouldScope}} {{#dynamicTemplate}}{{/dynamicTemplate}} {{#testUserAgent}} { var regexp = new Regex({{#match}}{{> tests/generateParams}}{{/match}}); @@ -35,6 +38,9 @@ JsonAssert.EqualOverrideDefault({{#match}}{{> tests/generateParams}}{{/match}}, JsonSerializer.Serialize(res, JsonConfig.Options), new JsonDiffConfig(false)); {{/match.isPrimitive}} {{/testResponse}} + {{#shouldScope}} + } + {{/shouldScope}} {{/isError}} {{#times}} } diff --git a/templates/dart/api.mustache b/templates/dart/api.mustache index d7b2f74b15..8455765461 100644 --- a/templates/dart/api.mustache +++ b/templates/dart/api.mustache @@ -13,12 +13,6 @@ import 'package:{{pubName}}/src/version.dart'; {{/description}} final class {{classname}} implements ApiClient { - @override - final String apiKey; - - @override - final String appId; - @override final ClientOptions options; @@ -29,8 +23,8 @@ final class {{classname}} implements ApiClient { final RetryStrategy _retryStrategy; {{classname}}({ - required this.appId, - required this.apiKey, + required String appId, + required String apiKey, this.options = const ClientOptions(), {{#hasRegionalHost}} {{^fallbackToAliasHost}}required{{/fallbackToAliasHost}} this.region, @@ -79,6 +73,12 @@ final class {{classname}} implements ApiClient { assert(appId.isNotEmpty, '`appId` is missing.'); assert(apiKey.isNotEmpty, '`apiKey` is missing.'); } + + /// Allows to switch the API key used to authenticate requests. + @override + void setClientApiKey({required String apiKey}) { + this._retryStrategy.requester.setClientApiKey(apiKey); + } {{#operation}} /// {{{notes}}}{{#vendorExtensions}}{{#x-acl.0}} @@ -214,4 +214,4 @@ final class {{classname}} implements ApiClient { @override void dispose() => _retryStrategy.dispose(); } -{{/operations}} \ No newline at end of file +{{/operations}} diff --git a/templates/dart/snippets/method.mustache b/templates/dart/snippets/method.mustache index eb647f506f..7f8c0fa0e8 100644 --- a/templates/dart/snippets/method.mustache +++ b/templates/dart/snippets/method.mustache @@ -12,9 +12,9 @@ void snippetFor{{method}}{{testIndex}}() async { // >SEPARATOR {{method}} {{testName}} // Initialize the client final client = {{client}}(appId: 'YOUR_APP_ID', apiKey: 'YOUR_API_KEY'{{#hasRegionalHost}}, region: 'YOUR_APP_ID_REGION'{{/hasRegionalHost}}); - + // Call the API - final response = await client.{{method}}( + {{#hasResponse}}final response = {{/hasResponse}}{{#isAsync}}await {{/isAsync}}client.{{method}}( {{#parametersWithDataType}} {{> tests/request_param}} {{/parametersWithDataType}} @@ -37,6 +37,7 @@ void snippetFor{{method}}{{testIndex}}() async { ), {{/hasRequestOptions}} ); + // >LOG // SEPARATOR< } diff --git a/templates/dart/tests/client/client.mustache b/templates/dart/tests/client/client.mustache index c333e0f5e3..41a52cad1c 100644 --- a/templates/dart/tests/client/client.mustache +++ b/templates/dart/tests/client/client.mustache @@ -20,7 +20,7 @@ void main() { options: ClientOptions(requester: requester), ); {{/autoCreateClient}} - {{#steps}} + {{#steps}} {{#isError}} await expectError( '{{{expectedError}}}', @@ -30,6 +30,9 @@ void main() { ); {{/isError}} {{^isError}} + {{#shouldScope}} + { + {{/shouldScope}} {{#match}} requester.setOnRequest((request) { {{#testUserAgent}} @@ -44,6 +47,9 @@ void main() { }); {{/match}} {{#dynamicTemplate}}{{/dynamicTemplate}} + {{#shouldScope}} + } + {{/shouldScope}} {{/isError}} {{/steps}} } diff --git a/templates/dart/tests/client/method.mustache b/templates/dart/tests/client/method.mustache index 15baf9e21e..23e87357c9 100644 --- a/templates/dart/tests/client/method.mustache +++ b/templates/dart/tests/client/method.mustache @@ -1,5 +1,5 @@ try { - final res = await client.{{method}}( + {{#hasResponse}}final res = {{/hasResponse}}await client.{{method}}( {{#parametersWithDataType}} {{> tests/request_param}} {{/parametersWithDataType}} diff --git a/templates/go/client.mustache b/templates/go/client.mustache index 74658a269e..577fc7491d 100644 --- a/templates/go/client.mustache +++ b/templates/go/client.mustache @@ -140,6 +140,17 @@ func (c *APIClient) GetConfiguration() *{{#lambda.titlecase}}{{#lambda.camelcase return c.cfg } +// Allow update of stored API key used to authenticate requests. +func (c *APIClient) SetClientApiKey(apiKey string) error { + if c.cfg == nil { + return errors.New("client config is not set") + } + + c.cfg.ApiKey = apiKey + + return nil +} + // prepareRequest build the request func (c *APIClient) prepareRequest( ctx context.Context, @@ -300,4 +311,4 @@ type APIError struct { func (e APIError) Error() string { return fmt.Sprintf("API error [%d] %s", e.Status, e.Message) -} \ No newline at end of file +} diff --git a/templates/go/snippets/method.mustache b/templates/go/snippets/method.mustache index ed22a3c42b..efac1fe619 100644 --- a/templates/go/snippets/method.mustache +++ b/templates/go/snippets/method.mustache @@ -24,16 +24,17 @@ func SnippetFor{{#lambda.titlecase}}{{method}}{{/lambda.titlecase}}Of{{#lambda.p } // Call the API - {{#hasResponsePayload}}response, err :={{/hasResponsePayload}}{{^hasResponsePayload}}err ={{/hasResponsePayload}} {{> tests/method}} + {{#hasResponse}}response, err :={{/hasResponse}}{{^hasResponse}}err ={{/hasResponse}} {{> tests/method}} if err != nil { // handle the eventual error panic(err) } - - {{#hasResponsePayload}} + + // >LOG + {{#hasResponse}} // use the model directly print(response) - {{/hasResponsePayload}} + {{/hasResponse}} // SEPARATOR< } {{/snippets}} diff --git a/templates/go/tests/client/method.mustache b/templates/go/tests/client/method.mustache index 58b2d3316e..8ab329a4c3 100644 --- a/templates/go/tests/client/method.mustache +++ b/templates/go/tests/client/method.mustache @@ -1 +1 @@ -res, err = {{> tests/method}} \ No newline at end of file +{{#hasResponse}}res, {{/hasResponse}}err = {{> tests/method}} \ No newline at end of file diff --git a/templates/go/tests/client/tests.mustache b/templates/go/tests/client/tests.mustache index 3834085ea7..f63b9bc850 100644 --- a/templates/go/tests/client/tests.mustache +++ b/templates/go/tests/client/tests.mustache @@ -23,6 +23,9 @@ func Test{{#lambda.titlecase}}{{clientPrefix}}{{testType}}{{/lambda.titlecase}}{ require.EqualError(t, err, "{{{expectedError}}}") {{/isError}} {{^isError}} + {{#shouldScope}} + { + {{/shouldScope}} {{#dynamicTemplate}}{{/dynamicTemplate}} require.NoError(t, err) {{#testUserAgent}} @@ -50,6 +53,9 @@ func Test{{#lambda.titlecase}}{{clientPrefix}}{{testType}}{{/lambda.titlecase}}{ require.JSONEq(t, `{{{match.value}}}`, string(rawBody)) {{/match.isPrimitive}} {{/testResponse}} + {{#shouldScope}} + } + {{/shouldScope}} {{/isError}} {{#times}} } diff --git a/templates/go/tests/requests/requests.mustache b/templates/go/tests/requests/requests.mustache index aa94fbc390..7d71dd4290 100644 --- a/templates/go/tests/requests/requests.mustache +++ b/templates/go/tests/requests/requests.mustache @@ -40,7 +40,7 @@ func Test{{#lambda.titlecase}}{{clientPrefix}}{{/lambda.titlecase}}_{{#lambda.ti {{#tests}} t.Run("{{{testName}}}", func(t *testing.T) { - {{#hasResponsePayload}}_, {{/hasResponsePayload}}err := {{> tests/method}} + {{#hasResponse}}_, {{/hasResponse}}err := {{> tests/method}} require.NoError(t, err) {{#request}} diff --git a/templates/java/snippets/method.mustache b/templates/java/snippets/method.mustache index 01664996c5..72210eba85 100644 --- a/templates/java/snippets/method.mustache +++ b/templates/java/snippets/method.mustache @@ -19,6 +19,7 @@ class Snippet{{client}} { // Call the API {{> tests/method}}; + // >LOG // SEPARATOR< } diff --git a/templates/java/tests/client/benchmark.mustache b/templates/java/tests/client/benchmark.mustache index 7e0378fc29..7718d26ced 100644 --- a/templates/java/tests/client/benchmark.mustache +++ b/templates/java/tests/client/benchmark.mustache @@ -43,4 +43,4 @@ class {{client}}Benchmark { {{#blocksBenchmark}} {{> tests/client/tests}} {{/blocksBenchmark}} -} +} \ No newline at end of file diff --git a/templates/java/tests/client/method.mustache b/templates/java/tests/client/method.mustache index 535638be3b..d9122ed393 100644 --- a/templates/java/tests/client/method.mustache +++ b/templates/java/tests/client/method.mustache @@ -1,2 +1,2 @@ -{{^useEchoRequester}}{{{returnType}}} res = {{/useEchoRequester}}{{> tests/method}}; +{{^useEchoRequester}}{{#hasResponse}}{{{returnType}}} res = {{/hasResponse}}{{/useEchoRequester}}{{> tests/method}}; {{#useEchoRequester}}EchoResponse result = echo.getLastResponse();{{/useEchoRequester}} \ No newline at end of file diff --git a/templates/java/tests/client/tests.mustache b/templates/java/tests/client/tests.mustache index 1a1abece5a..b25991d038 100644 --- a/templates/java/tests/client/tests.mustache +++ b/templates/java/tests/client/tests.mustache @@ -6,7 +6,6 @@ void {{testType}}Test{{testIndex}}() { {{client}} client = createClient(); {{/autoCreateClient}} - {{#isHelper}}assertDoesNotThrow(() -> { {{/isHelper}} {{#steps}} {{#times}} for (int i = 0; i < {{.}}; i++) { @@ -20,8 +19,11 @@ void {{testType}}Test{{testIndex}}() { } {{/isError}} {{^isError}} + {{#shouldScope}} + assertDoesNotThrow(() -> { + {{/shouldScope}} {{#dynamicTemplate}}{{/dynamicTemplate}} - {{#testUserAgent}} + {{#testUserAgent}} { String regexp = {{#match}}{{> tests/generateParams}}{{/match}}; assertTrue(result.headers.get("user-agent").matches(regexp), "Expected " + result.headers.get("user-agent") + " to match the following regex: " + regexp); @@ -42,11 +44,13 @@ void {{testType}}Test{{testIndex}}() { assertDoesNotThrow(() -> JSONAssert.assertEquals({{#match}}{{> tests/generateParams}}{{/match}}, json.writeValueAsString(res), JSONCompareMode.STRICT)); {{/match.isPrimitive}} {{/testResponse}} + {{#shouldScope}} + }); + {{/shouldScope}} {{/isError}} {{#times}} } {{/times}} {{/steps}} - {{#isHelper}} });{{/isHelper}} } {{/tests}} \ No newline at end of file diff --git a/templates/javascript/clients/api-single.mustache b/templates/javascript/clients/api-single.mustache index 6ba8c22e8c..d79003e3b0 100644 --- a/templates/javascript/clients/api-single.mustache +++ b/templates/javascript/clients/api-single.mustache @@ -75,6 +75,16 @@ export function create{{#lambda.titlecase}}{{apiName}}{{/lambda.titlecase}}({ transporter.algoliaAgent.add({ segment, version }); }, + /** + * Helper method to switch the API key used to authenticate the requests. + * + * @param params - Method params. + * @param params.apiKey - The new API Key to use. + */ + setClientApiKey({ apiKey }: { apiKey: string }): void { + transporter.baseHeaders['x-algolia-api-key'] = apiKey; + }, + {{#isSearchClient}} {{> client/api/helpers}} {{/isSearchClient}} diff --git a/templates/javascript/snippets/method.mustache b/templates/javascript/snippets/method.mustache index 5914ded7d9..9acc3fd2ef 100644 --- a/templates/javascript/snippets/method.mustache +++ b/templates/javascript/snippets/method.mustache @@ -17,10 +17,13 @@ export {{#isAsync}}async{{/isAsync}} function snippetFor{{#lambda.pascalcase}}{{ const client = {{client}}("YOUR_APP_ID", "YOUR_API_KEY", {{#hasRegionalHost}}'YOUR_APP_ID_REGION', {{/hasRegionalHost}}); // Call the API - const response = {{> tests/method}}; + {{#hasResponse}}const response = {{/hasResponse}}{{> tests/method}}; + // >LOG + {{#hasResponse}} // use typed response console.log(response); + {{/hasResponse}} // SEPARATOR< } diff --git a/templates/javascript/tests/client/benchmark.mustache b/templates/javascript/tests/client/benchmark.mustache index 7376e7ca22..21893a540b 100644 --- a/templates/javascript/tests/client/benchmark.mustache +++ b/templates/javascript/tests/client/benchmark.mustache @@ -1,5 +1,5 @@ // {{generationBanner}} -/* eslint-disable @typescript-eslint/no-unused-vars, require-await */ +/* eslint-disable @typescript-eslint/no-unused-vars, require-await, no-lone-blocks */ // @ts-nocheck Failing tests will have type errors, but we cannot suppress them even with @ts-expect-error because it doesn't work for a block of lines. import { {{client}}, {{#lambda.titlecase}}{{client}}{{/lambda.titlecase}} } from '{{{import}}}'; @@ -12,4 +12,4 @@ function createClient(): {{#lambda.titlecase}}{{client}}{{/lambda.titlecase}} { {{#blocksBenchmark}} {{> tests/client/tests}} -{{/blocksBenchmark}} +{{/blocksBenchmark}} \ No newline at end of file diff --git a/templates/javascript/tests/client/client.mustache b/templates/javascript/tests/client/client.mustache index 83cbf6e946..80c6342fb0 100644 --- a/templates/javascript/tests/client/client.mustache +++ b/templates/javascript/tests/client/client.mustache @@ -1,5 +1,5 @@ // {{generationBanner}} -/* eslint-disable @typescript-eslint/no-unused-vars, require-await */ +/* eslint-disable @typescript-eslint/no-unused-vars, require-await, no-lone-blocks */ // @ts-nocheck Failing tests will have type errors, but we cannot suppress them even with @ts-expect-error because it doesn't work for a block of lines. import { {{client}}, {{#lambda.titlecase}}{{client}}{{/lambda.titlecase}} } from '{{{import}}}'; import { echoRequester } from '@algolia/requester-node-http'; diff --git a/templates/javascript/tests/client/method.mustache b/templates/javascript/tests/client/method.mustache index ba7d0c9ef2..4d8ce1a162 100644 --- a/templates/javascript/tests/client/method.mustache +++ b/templates/javascript/tests/client/method.mustache @@ -1 +1 @@ -const result = {{> tests/method}}{{#useEchoRequester}} as unknown as EchoResponse{{/useEchoRequester}}; \ No newline at end of file +{{#hasResponse}}const result = {{/hasResponse}}{{> tests/method}}{{#useEchoRequester}} as unknown as EchoResponse{{/useEchoRequester}}; \ No newline at end of file diff --git a/templates/javascript/tests/client/tests.mustache b/templates/javascript/tests/client/tests.mustache index 905c51a4a7..73148c84da 100644 --- a/templates/javascript/tests/client/tests.mustache +++ b/templates/javascript/tests/client/tests.mustache @@ -19,6 +19,9 @@ describe('{{testType}}', () => { } {{/isError}} {{^isError}} + {{#shouldScope}} + { + {{/shouldScope}} {{#dynamicTemplate}}{{/dynamicTemplate}} {{#testUserAgent}} @@ -43,6 +46,9 @@ describe('{{testType}}', () => { expect(result).toEqual({{{match.value}}}); {{/match.isPrimitive}} {{/testResponse}} + {{#shouldScope}} + } + {{/shouldScope}} {{/isError}} {{#times}} } diff --git a/templates/kotlin/ApiClient.kt.mustache b/templates/kotlin/ApiClient.kt.mustache index f75a5dd974..01ef0d9dc0 100644 --- a/templates/kotlin/ApiClient.kt.mustache +++ b/templates/kotlin/ApiClient.kt.mustache @@ -11,8 +11,16 @@ import {{packageName}}.transport.Requester * @property options A set of custom client options to configure the behavior of the API client. */ public sealed interface ApiClient { - public val appId: String - public val apiKey: String - public val options: ClientOptions - public val requester: Requester -} + public val appId: String + public var apiKey: String + public val options: ClientOptions + public val requester: Requester + + /** + * Helper method to switch the API key used to authenticate requests. + */ + public fun setClientApiKey(apiKey: String) { + this.apiKey = apiKey + this.requester.setClientApiKey(apiKey) + } +} \ No newline at end of file diff --git a/templates/kotlin/api.mustache b/templates/kotlin/api.mustache index 65c86310af..b814afdc48 100644 --- a/templates/kotlin/api.mustache +++ b/templates/kotlin/api.mustache @@ -12,7 +12,7 @@ import kotlinx.serialization.json.* {{#operations}} public class {{classname}}( override val appId: String, - override val apiKey: String, + override var apiKey: String, {{#hasRegionalHost}} public val region: String{{#fallbackToAliasHost}}? = null{{/fallbackToAliasHost}}, {{/hasRegionalHost}} diff --git a/templates/kotlin/snippets/method.mustache b/templates/kotlin/snippets/method.mustache index 3cf5c7d38a..e09b79e7b8 100644 --- a/templates/kotlin/snippets/method.mustache +++ b/templates/kotlin/snippets/method.mustache @@ -20,10 +20,13 @@ class Snippet{{client}} { val client = {{client}}(appId = "YOUR_APP_ID", apiKey = "YOUR_API_KEY"{{#hasRegionalHost}}, region = "YOUR_APP_ID_REGION"{{/hasRegionalHost}}) // Call the API - var response = client.{{> tests/method}} - + {{#hasResponse}}var response = {{/hasResponse}}client.{{> tests/method}} + + // >LOG + {{#hasResponse}} // Use the response println(response) + {{/hasResponse}} // SEPARATOR< exitProcess(0) diff --git a/templates/php/api.mustache b/templates/php/api.mustache index 7dfdfb30e4..0bd978c8e2 100644 --- a/templates/php/api.mustache +++ b/templates/php/api.mustache @@ -155,6 +155,18 @@ use Algolia\AlgoliaSearch\Exceptions\NotFoundException; return $this->config; } + /** + * Stub method setting a new API key to authenticate requests. + * + * @param string $apiKey + * + * @return void + * + */ + public function setClientApiKey($apiKey) { + $this->config->setClientApiKey($apiKey); + } + {{#operation}} /** {{#notes}} diff --git a/templates/php/snippets/method.mustache b/templates/php/snippets/method.mustache index aba2278a51..f9cbf686ff 100644 --- a/templates/php/snippets/method.mustache +++ b/templates/php/snippets/method.mustache @@ -24,10 +24,13 @@ class Snippet{{client}} $client = {{client}}::create('', ''{{#hasRegionalHost}}, 'YOUR_APP_ID_REGION'{{/hasRegionalHost}}); // Call the API - $response = {{> tests/method}}; + {{#hasResponse}}$response = {{/hasResponse}}{{> tests/method}}; + // >LOG + {{#hasResponse}} // play with the response var_dump($response); + {{/hasResponse}} // SEPARATOR< } diff --git a/templates/php/tests/client/method.mustache b/templates/php/tests/client/method.mustache index dcb5b3e70e..75ea8fa1a2 100644 --- a/templates/php/tests/client/method.mustache +++ b/templates/php/tests/client/method.mustache @@ -1 +1 @@ -{{^useEchoRequester}}$res = {{/useEchoRequester}}{{> tests/method}}; \ No newline at end of file +{{^useEchoRequester}}{{#hasResponse}}$res = {{/hasResponse}}{{/useEchoRequester}}{{> tests/method}}; \ No newline at end of file diff --git a/templates/php/tests/client/tests.mustache b/templates/php/tests/client/tests.mustache index 646b6b9013..2a45a5dd16 100644 --- a/templates/php/tests/client/tests.mustache +++ b/templates/php/tests/client/tests.mustache @@ -18,6 +18,9 @@ public function test{{testIndex}}{{testType}}(): void } {{/isError}} {{^isError}} + {{#shouldScope}} + { + {{/shouldScope}} {{#dynamicTemplate}}{{/dynamicTemplate}} {{#testUserAgent}} $this->assertTrue( @@ -58,6 +61,9 @@ public function test{{testIndex}}{{testType}}(): void ); {{/match.isPrimitive}} {{/testResponse}} + {{#shouldScope}} + } + {{/shouldScope}} {{/isError}} {{#times}} } diff --git a/templates/php/tests/generateParams.mustache b/templates/php/tests/generateParams.mustache index 2265d6e214..02174c21ea 100644 --- a/templates/php/tests/generateParams.mustache +++ b/templates/php/tests/generateParams.mustache @@ -33,4 +33,4 @@ {{^isAnyType}} [{{#value}}{{> tests/generateParams}}{{/value}}], {{/isAnyType}} -{{/isFreeFormObject}} +{{/isFreeFormObject}} \ No newline at end of file diff --git a/templates/python/api.mustache b/templates/python/api.mustache index b5446f39a7..dcae03c4d4 100644 --- a/templates/python/api.mustache +++ b/templates/python/api.mustache @@ -80,6 +80,10 @@ class {{classname}}: async def close(self) -> None: """Closes the underlying `transporter` of the API client.""" return await self._transporter.close() + + def set_client_api_key(self, api_key: str) -> None: + """Sets a new API key to authenticate requests.""" + self._transporter._config.set_client_api_key(api_key) {{#isSearchClient}} {{> search_helpers}} @@ -186,4 +190,4 @@ class {{classname}}: return (await self.{{operationId}}_with_http_info({{#allParams}}{{paramName}},{{/allParams}}request_options)).deserialize({{{returnType}}}) {{/operation}} -{{/operations}} \ No newline at end of file +{{/operations}} diff --git a/templates/python/snippets/method.mustache b/templates/python/snippets/method.mustache index 9640deb9b1..87556ec673 100644 --- a/templates/python/snippets/method.mustache +++ b/templates/python/snippets/method.mustache @@ -16,13 +16,16 @@ async def snippet_for_{{#lambda.snakecase}}{{method}}{{/lambda.snakecase}}{{test _client = {{#lambda.pascalcase}}{{{client}}}{{/lambda.pascalcase}}("YOUR_APP_ID", "YOUR_API_KEY"{{#hasRegionalHost}}, "YOUR_APP_ID_REGION"{{/hasRegionalHost}}) # Call the API - response = await _client.{{#lambda.snakecase}}{{method}}{{/lambda.snakecase}}({{#parametersWithDataType}}{{> tests/generateParams}}{{/parametersWithDataType}}{{#hasRequestOptions}} request_options={ {{#requestOptions.headers.parameters}}"headers":loads("""{{{.}}}"""),{{/requestOptions.headers.parameters}}{{#requestOptions.queryParameters.parameters}}"query_parameters":loads("""{{{.}}}"""),{{/requestOptions.queryParameters.parameters}} }{{/hasRequestOptions}}) - + {{#hasResponse}}response = {{/hasResponse}}{{#isAsync}}await {{/isAsync}}_client.{{#lambda.snakecase}}{{method}}{{/lambda.snakecase}}({{#parametersWithDataType}}{{> tests/generateParams}}{{/parametersWithDataType}}{{#hasRequestOptions}} request_options={ {{#requestOptions.headers.parameters}}"headers":loads("""{{{.}}}"""),{{/requestOptions.headers.parameters}}{{#requestOptions.queryParameters.parameters}}"query_parameters":loads("""{{{.}}}"""),{{/requestOptions.queryParameters.parameters}} }{{/hasRequestOptions}}) + + # >LOG + {{#hasResponse}} # use the class directly print(response) # print the JSON response print(response.to_json()) + {{/hasResponse}} # SEPARATOR< {{/snippets}} diff --git a/templates/python/tests/client/method.mustache b/templates/python/tests/client/method.mustache index 13983a8a67..f3421cfa02 100644 --- a/templates/python/tests/client/method.mustache +++ b/templates/python/tests/client/method.mustache @@ -1 +1 @@ -{{^isError}}_req = {{/isError}}{{#isAsync}}await {{/isAsync}}self._client.{{#lambda.snakecase}}{{{method}}}{{/lambda.snakecase}}{{#useEchoRequester}}_with_http_info{{/useEchoRequester}}({{#parametersWithDataType}}{{> tests/generateParams}}{{/parametersWithDataType}}{{#hasRequestOptions}} request_options={ {{#requestOptions.headers.parameters}}"headers":loads("""{{{.}}}"""),{{/requestOptions.headers.parameters}}{{#requestOptions.queryParameters.parameters}}"query_parameters":loads("""{{{.}}}"""),{{/requestOptions.queryParameters.parameters}} }{{/hasRequestOptions}}) \ No newline at end of file +{{^isError}}{{#hasResponse}}_req = {{/hasResponse}}{{/isError}}{{#isAsync}}await {{/isAsync}}self._client.{{#lambda.snakecase}}{{{method}}}{{/lambda.snakecase}}{{#useEchoRequester}}_with_http_info{{/useEchoRequester}}({{#parametersWithDataType}}{{> tests/generateParams}}{{/parametersWithDataType}}{{#hasRequestOptions}} request_options={ {{#requestOptions.headers.parameters}}"headers":loads("""{{{.}}}"""),{{/requestOptions.headers.parameters}}{{#requestOptions.queryParameters.parameters}}"query_parameters":loads("""{{{.}}}"""),{{/requestOptions.queryParameters.parameters}} }{{/hasRequestOptions}}) \ No newline at end of file diff --git a/templates/ruby/api.mustache b/templates/ruby/api.mustache index bcd7e707fc..61d8a9f8fa 100644 --- a/templates/ruby/api.mustache +++ b/templates/ruby/api.mustache @@ -59,6 +59,14 @@ module {{moduleName}} new(config) end + # Helper method to switch the API key used to authenticate the requests. + # + # @param api_key [String] the new API key to use. + # @return [void] + def set_client_api_key(api_key) + @api_client.set_client_api_key(api_key) + end + {{#operation}} {{#notes}} # {{.}} @@ -229,4 +237,4 @@ module {{moduleName}} {{/isSearchClient}} end {{/operations}} -end \ No newline at end of file +end diff --git a/templates/ruby/snippets/method.mustache b/templates/ruby/snippets/method.mustache index a9ad81a68c..221ece2b2b 100644 --- a/templates/ruby/snippets/method.mustache +++ b/templates/ruby/snippets/method.mustache @@ -14,15 +14,18 @@ def snippet_for_{{#lambda.snakecase}}{{method}}{{/lambda.snakecase}}{{testIndex} client = Algolia::{{#lambda.pascalcase}}{{{client}}}{{/lambda.pascalcase}}.create('YOUR_APP_ID','YOUR_API_KEY'{{#hasRegionalHost}},'YOUR_APP_ID_REGION'{{/hasRegionalHost}}) # Call the API - response = client.{{#lambda.snakecase}}{{method}}{{/lambda.snakecase}}({{#parametersWithDataType}}{{> tests/generateParams}}{{/parametersWithDataType}}{{#hasRequestOptions}}{ {{#requestOptions.headers.parameters}}:header_params => JSON.parse('{{{.}}}', :symbolize_names => true),{{/requestOptions.headers.parameters}}{{#requestOptions.queryParameters.parameters}}:query_params => JSON.parse('{{{.}}}', :symbolize_names => true){{/requestOptions.queryParameters.parameters}} }{{/hasRequestOptions}}) - + {{#hasResponse}}response = {{/hasResponse}}client.{{#lambda.snakecase}}{{method}}{{/lambda.snakecase}}({{#parametersWithDataType}}{{> tests/generateParams}}{{/parametersWithDataType}}{{#hasRequestOptions}}{ {{#requestOptions.headers.parameters}}:header_params => JSON.parse('{{{.}}}', :symbolize_names => true),{{/requestOptions.headers.parameters}}{{#requestOptions.queryParameters.parameters}}:query_params => JSON.parse('{{{.}}}', :symbolize_names => true){{/requestOptions.queryParameters.parameters}} }{{/hasRequestOptions}}) + + # >LOG + {{#hasResponse}} # use the class directly puts response # print the JSON response puts response.to_json + {{/hasResponse}} # SEPARATOR< end {{/snippets}} -{{/blocksRequests}} \ No newline at end of file +{{/blocksRequests}} diff --git a/templates/ruby/tests/client/method.mustache b/templates/ruby/tests/client/method.mustache index 93128e5fb8..c402dadee1 100644 --- a/templates/ruby/tests/client/method.mustache +++ b/templates/ruby/tests/client/method.mustache @@ -1 +1 @@ -{{^isError}}req = {{/isError}}client.{{#lambda.snakecase}}{{method}}{{/lambda.snakecase}}{{#useEchoRequester}}_with_http_info{{/useEchoRequester}}{{#returnsBoolean}}?{{/returnsBoolean}}({{#parametersWithDataType}}{{> tests/generateParams}}{{/parametersWithDataType}}{{#hasRequestOptions}}{ {{#requestOptions.headers.parameters}}:header_params => JSON.parse('{{{.}}}', :symbolize_names => true),{{/requestOptions.headers.parameters}}{{#requestOptions.queryParameters.parameters}}:query_params => JSON.parse('{{{.}}}', :symbolize_names => true){{/requestOptions.queryParameters.parameters}} }{{/hasRequestOptions}}) \ No newline at end of file +{{^isError}}{{#hasResponse}}req = {{/hasResponse}}{{/isError}}client.{{#lambda.snakecase}}{{method}}{{/lambda.snakecase}}{{#useEchoRequester}}_with_http_info{{/useEchoRequester}}{{#returnsBoolean}}?{{/returnsBoolean}}({{#parametersWithDataType}}{{> tests/generateParams}}{{/parametersWithDataType}}{{#hasRequestOptions}}{ {{#requestOptions.headers.parameters}}:header_params => JSON.parse('{{{.}}}', :symbolize_names => true),{{/requestOptions.headers.parameters}}{{#requestOptions.queryParameters.parameters}}:query_params => JSON.parse('{{{.}}}', :symbolize_names => true){{/requestOptions.queryParameters.parameters}} }{{/hasRequestOptions}}) \ No newline at end of file diff --git a/templates/scala/snippets/method.mustache b/templates/scala/snippets/method.mustache index 9d0c93c9d9..4736fe7a95 100644 --- a/templates/scala/snippets/method.mustache +++ b/templates/scala/snippets/method.mustache @@ -32,10 +32,13 @@ class Snippet{{client}} { val client = {{client}}(appId = "YOUR_APP_ID", apiKey = "YOUR_API_KEY"{{#hasRegionalHost}}, region = {{#fallbackToAliasHost}}Option({{/fallbackToAliasHost}}"YOUR_APP_ID_REGION"{{#fallbackToAliasHost}}){{/fallbackToAliasHost}}{{/hasRegionalHost}}) // Call the API - val response = {{> tests/method}} - + {{#hasResponse}}val response = {{/hasResponse}}{{> tests/method}} + + // >LOG + {{#hasResponse}} // Use the response val value = Await.result(response, Duration(100, "sec")) + {{/hasResponse}} // SEPARATOR< } diff --git a/templates/scala/tests/client/client.mustache b/templates/scala/tests/client/client.mustache index 8394c489a8..7a9d73d8ec 100644 --- a/templates/scala/tests/client/client.mustache +++ b/templates/scala/tests/client/client.mustache @@ -54,6 +54,9 @@ class {{clientPrefix}}Test extends AnyFunSuite { } {{/isError}} {{^isError}} + {{#shouldScope}} + { + {{/shouldScope}} {{#dynamicTemplate}}{{/dynamicTemplate}} {{#testUserAgent}} val regexp = """{{{match.value}}}""".r @@ -75,6 +78,9 @@ class {{clientPrefix}}Test extends AnyFunSuite { assert(res == {{#match}}{{> tests/param_value}}{{/match}}) {{/match.isPrimitive}} {{/testResponse}} + {{#shouldScope}} + } + {{/shouldScope}} {{/isError}} {{/steps}} } diff --git a/templates/scala/tests/client/createClient.mustache b/templates/scala/tests/client/createClient.mustache index 21cc4f1160..4ce8353df1 100644 --- a/templates/scala/tests/client/createClient.mustache +++ b/templates/scala/tests/client/createClient.mustache @@ -1,9 +1,9 @@ {{#useEchoRequester}} -val (client, echo) = testClient(appId = "{{parametersWithDataTypeMap.appId.value}}", apiKey = "{{parametersWithDataTypeMap.apiKey.value}}"{{#hasRegionalHost}}{{#parametersWithDataTypeMap.region}}, region = "{{parametersWithDataTypeMap.region.value}}"{{/parametersWithDataTypeMap.region}}{{/hasRegionalHost}}){{/useEchoRequester}}{{#hasCustomHosts}} +val (client, echo) = testClient(appId = "{{parametersWithDataTypeMap.appId.value}}", apiKey = "{{parametersWithDataTypeMap.apiKey.value}}"{{#hasRegionalHost}}{{#parametersWithDataTypeMap.region}}, region = "{{value}}"{{/parametersWithDataTypeMap.region}}{{/hasRegionalHost}}){{/useEchoRequester}}{{#hasCustomHosts}} val client = {{client}}( appId = "{{parametersWithDataTypeMap.appId.value}}", apiKey = "{{parametersWithDataTypeMap.apiKey.value}}", - {{#hasRegionalHost}}{{#parametersWithDataTypeMap.region}}, region = "{{parametersWithDataTypeMap.region.value}}"{{/parametersWithDataTypeMap.region}}{{/hasRegionalHost}} + {{#hasRegionalHost}}{{#parametersWithDataTypeMap.region}} region = {{#fallbackToAliasHost}}Option({{/fallbackToAliasHost}}"{{value}}"{{#fallbackToAliasHost}}){{/fallbackToAliasHost}},{{/parametersWithDataTypeMap.region}}{{/hasRegionalHost}} clientOptions = ClientOptions .builder() .withHosts(List({{#customHosts}}Host("{{host}}", Set(CallType.Read, CallType.Write), "http", Option({{port}})){{^-last}},{{/-last}}{{/customHosts}})) diff --git a/templates/scala/tests/client/method.mustache b/templates/scala/tests/client/method.mustache index a987c7917b..539cffe3d3 100644 --- a/templates/scala/tests/client/method.mustache +++ b/templates/scala/tests/client/method.mustache @@ -1,4 +1,4 @@ -{{^useEchoRequester}}var res = Await.result{{/useEchoRequester}}{{#useEchoRequester}}Await.ready{{/useEchoRequester}}( - {{> tests/method}}, +{{^useEchoRequester}}{{#hasResponse}}var res = {{/hasResponse}}{{#isAsync}}Await.result{{/isAsync}}{{/useEchoRequester}}{{#isAsync}}{{#useEchoRequester}}Await.ready{{/useEchoRequester}}({{/isAsync}} + {{> tests/method}}{{#isAsync}}, Duration.Inf -) \ No newline at end of file +){{/isAsync}} \ No newline at end of file diff --git a/templates/swift/api.mustache b/templates/swift/api.mustache index f957e8cf72..aec1bfff96 100644 --- a/templates/swift/api.mustache +++ b/templates/swift/api.mustache @@ -31,6 +31,11 @@ import Foundation self.init(configuration: try {{classname}}Configuration(appID: appID, apiKey: apiKey{{#hasRegionalHost}}, region: region{{/hasRegionalHost}})) } + open func setClientApiKey(apiKey: String) { + self.configuration.apiKey = apiKey + self.transporter.setClientApiKey(apiKey: apiKey) + } + {{#operation}} {{#allParams}} {{#isEnum}} diff --git a/templates/swift/client_configuration.mustache b/templates/swift/client_configuration.mustache index a1d359fdd0..585d219d32 100644 --- a/templates/swift/client_configuration.mustache +++ b/templates/swift/client_configuration.mustache @@ -14,7 +14,7 @@ public struct {{#lambda.client-to-name}}{{{client}}}{{/lambda.client-to-name}}Cl {{/hasRegionalHost}} public let appID: String - public let apiKey: String + public var apiKey: String public var writeTimeout: TimeInterval public var readTimeout: TimeInterval public var logLevel: LogLevel diff --git a/templates/swift/snippets/method.mustache b/templates/swift/snippets/method.mustache index 36c52fca59..c563251854 100644 --- a/templates/swift/snippets/method.mustache +++ b/templates/swift/snippets/method.mustache @@ -17,11 +17,12 @@ final class {{client}}Snippet { let client = try {{client}}(appID: "YOUR_APP_ID", apiKey: "YOUR_API_KEY"{{#hasRegionalHost}}, region: .{{defaultRegion}}{{/hasRegionalHost}}) // Call the API - let response{{#isGeneric}}: {{{returnType}}}{{/isGeneric}} = try {{#isAsync}}await {{/isAsync}}client.{{method}}({{#hasParams}}{{#parametersWithDataType}}{{> tests/generateParams }}{{^-last}}, {{/-last}}{{/parametersWithDataType}}{{/hasParams}}{{#hasRequestOptions}}, requestOptions: RequestOptions({{#requestOptions.headers}} + {{#hasResponse}}let response{{#isGeneric}}: {{{returnType}}}{{/isGeneric}} = {{/hasResponse}}try {{#isAsync}}await {{/isAsync}}client.{{method}}({{#hasParams}}{{#parametersWithDataType}}{{> tests/generateParams }}{{^-last}}, {{/-last}}{{/parametersWithDataType}}{{/hasParams}}{{#hasRequestOptions}}, requestOptions: RequestOptions({{#requestOptions.headers}} headers: [{{#parametersWithDataType}}"{{key}}": {{> tests/paramValue }}{{^-last}}, {{/-last}}{{/parametersWithDataType}}]{{/requestOptions.headers}} {{#requestOptions.headers}}{{#requestOptions.queryParameters}},{{/requestOptions.queryParameters}}{{/requestOptions.headers}}{{#requestOptions.queryParameters}} queryParameters: [{{#parametersWithDataType}}"{{key}}": {{> tests/paramValue }}{{^-last}}, {{/-last}}{{/parametersWithDataType}}]{{/requestOptions.queryParameters}} ){{/hasRequestOptions}}) + // >LOG // SEPARATOR< } {{/snippets}} diff --git a/templates/swift/tests/client/method.mustache b/templates/swift/tests/client/method.mustache index eca4157878..414f0ed90f 100644 --- a/templates/swift/tests/client/method.mustache +++ b/templates/swift/tests/client/method.mustache @@ -1,4 +1,4 @@ -let response{{#isGeneric}}: Response<{{{returnType}}}>{{/isGeneric}} = {{> tests/method}} +{{#hasResponse}}let response{{#isGeneric}}: Response<{{{returnType}}}>{{/isGeneric}} = {{/hasResponse}}{{> tests/method}} {{^isHelper}} {{^isBenchmark}} let responseBodyData = try XCTUnwrap(response.bodyData) diff --git a/templates/swift/tests/client/tests.mustache b/templates/swift/tests/client/tests.mustache index e7ea6e855f..9029217759 100644 --- a/templates/swift/tests/client/tests.mustache +++ b/templates/swift/tests/client/tests.mustache @@ -24,6 +24,9 @@ } {{/isError}} {{^isError}} + {{#shouldScope}} + do { + {{/shouldScope}} {{#dynamicTemplate}}{{/dynamicTemplate}} {{#testUserAgent}} @@ -64,6 +67,9 @@ XCTAssertEqual(comparableJSON, responseBodyJSON); {{/isHelper}} {{/testResponse}} + {{#shouldScope}} + } + {{/shouldScope}} {{/isError}} {{#times}} } diff --git a/tests/CTS/client/common/setClientApiKey.json b/tests/CTS/client/common/setClientApiKey.json new file mode 100644 index 0000000000..42c390b8c2 --- /dev/null +++ b/tests/CTS/client/common/setClientApiKey.json @@ -0,0 +1,55 @@ +[ + { + "testName": "switch API key", + "autoCreateClient": false, + "steps": [ + { + "type": "createClient", + "parameters": { + "appId": "test-app-id", + "apiKey": "test-api-key", + "region": "us", + "customHosts": [ + { + "host": "${{localhost}}", + "port": 6683 + } + ] + } + }, + { + "type": "method", + "method": "customGet", + "parameters": { + "path": "check-api-key/1" + }, + "expected": { + "type": "response", + "match": { + "headerAPIKeyValue": "test-api-key" + } + } + }, + { + "type": "method", + "method": "setClientApiKey", + "parameters": { + "apiKey": "updated-api-key" + } + }, + { + "type": "method", + "method": "customGet", + "parameters": { + "path": "check-api-key/2" + }, + "expected": { + "type": "response", + "match": { + "headerAPIKeyValue": "updated-api-key" + } + } + } + ] + } +] diff --git a/tests/output/dart/lib/src/run.dart b/tests/output/dart/lib/src/run.dart index 5f3bc7b412..97aedf9d0f 100644 --- a/tests/output/dart/lib/src/run.dart +++ b/tests/output/dart/lib/src/run.dart @@ -24,6 +24,8 @@ Future runTest({ /// /// Uses provided function to intercept HTTP requests. class RequestInterceptor extends Requester { + String apiKey = "fake"; + Function(HttpRequest) onRequest = (_) {}; RequestInterceptor({Function(HttpRequest)? onRequest}) { @@ -39,6 +41,11 @@ class RequestInterceptor extends Requester { onRequest(request); return Future.error(InterceptionException()); } + + @override + void setClientApiKey(String apiKey) { + this.apiKey = apiKey; + } } /// [InterceptionException] implements [Exception].