diff --git a/clients/algoliasearch-client-csharp/algoliasearch/Http/InternalRequestOptions.cs b/clients/algoliasearch-client-csharp/algoliasearch/Http/InternalRequestOptions.cs index f457b736cf..8d7fcc33c1 100644 --- a/clients/algoliasearch-client-csharp/algoliasearch/Http/InternalRequestOptions.cs +++ b/clients/algoliasearch-client-csharp/algoliasearch/Http/InternalRequestOptions.cs @@ -25,7 +25,9 @@ public InternalRequestOptions(RequestOptions options = null) CustomPathParameters = new Dictionary(); PathParameters = new Dictionary(); - Timeout = options?.Timeout; + ConnectTimeout = options?.ConnectTimeout; + ReadTimeout = options?.ReadTimeout; + WriteTimeout = options?.WriteTimeout; } public void AddQueryParameter(string key, object value) @@ -76,9 +78,19 @@ public void AddCustomQueryParameters(IDictionary values) public object Data { get; set; } /// - /// Request timeout + /// Request read timeout /// - public TimeSpan? Timeout { get; set; } + public TimeSpan? ReadTimeout { get; set; } + + /// + /// Request write timeout + /// + public TimeSpan? WriteTimeout { get; set; } + + /// + /// Request connect timeout + /// + public TimeSpan? ConnectTimeout { get; set; } /// /// Enforce the Read Transporter diff --git a/clients/algoliasearch-client-csharp/algoliasearch/Http/RequestOptionBuilder.cs b/clients/algoliasearch-client-csharp/algoliasearch/Http/RequestOptionBuilder.cs index b9f7014005..6942e27f3a 100644 --- a/clients/algoliasearch-client-csharp/algoliasearch/Http/RequestOptionBuilder.cs +++ b/clients/algoliasearch-client-csharp/algoliasearch/Http/RequestOptionBuilder.cs @@ -47,13 +47,35 @@ public RequestOptionBuilder AddExtraQueryParameters(string key, object value) } /// - /// Set the request timeout + /// Set the request read timeout /// /// /// - public RequestOptionBuilder SetTimeout(TimeSpan timeout) + public RequestOptionBuilder SetReadTimeout(TimeSpan timeout) { - _options.Timeout = timeout; + _options.ReadTimeout = timeout; + return this; + } + + /// + /// Set the request write timeout + /// + /// + /// + public RequestOptionBuilder SetWriteTimeout(TimeSpan timeout) + { + _options.WriteTimeout = timeout; + return this; + } + + /// + /// Set the request connect timeout + /// + /// + /// + public RequestOptionBuilder SetConnectTimeout(TimeSpan timeout) + { + _options.ConnectTimeout = timeout; return this; } diff --git a/clients/algoliasearch-client-csharp/algoliasearch/Http/RequestOptions.cs b/clients/algoliasearch-client-csharp/algoliasearch/Http/RequestOptions.cs index b539805467..3f74e5dbf5 100644 --- a/clients/algoliasearch-client-csharp/algoliasearch/Http/RequestOptions.cs +++ b/clients/algoliasearch-client-csharp/algoliasearch/Http/RequestOptions.cs @@ -20,9 +20,19 @@ public class RequestOptions public IDictionary QueryParameters { get; set; } /// - /// Request timeout in seconds + /// Request timeout /// - public TimeSpan? Timeout { get; set; } + public TimeSpan? ReadTimeout { get; set; } + + /// + /// Request timeout + /// + public TimeSpan? WriteTimeout { get; set; } + + /// + /// Request timeout + /// + public TimeSpan? ConnectTimeout { get; set; } /// /// Constructs a new instance of diff --git a/clients/algoliasearch-client-csharp/algoliasearch/Transport/HttpTransport.cs b/clients/algoliasearch-client-csharp/algoliasearch/Transport/HttpTransport.cs index 213720dd92..63c0957cf4 100644 --- a/clients/algoliasearch-client-csharp/algoliasearch/Transport/HttpTransport.cs +++ b/clients/algoliasearch-client-csharp/algoliasearch/Transport/HttpTransport.cs @@ -119,7 +119,7 @@ private async Task ExecuteRequestAsync(HttpMethod metho request.Uri = BuildUri(host, uri, requestOptions?.CustomPathParameters, requestOptions?.PathParameters, requestOptions?.QueryParameters); var requestTimeout = - TimeSpan.FromTicks((requestOptions?.Timeout ?? GetTimeOut(callType)).Ticks * (host.RetryCount + 1)); + TimeSpan.FromTicks((GetTimeOut(callType, requestOptions)).Ticks * (host.RetryCount + 1)); if (request.Body == null && (method == HttpMethod.Post || method == HttpMethod.Put)) { @@ -137,7 +137,7 @@ private async Task ExecuteRequestAsync(HttpMethod metho } var response = await _httpClient - .SendRequestAsync(request, requestTimeout, _algoliaConfig.ConnectTimeout ?? Defaults.ConnectTimeout, ct) + .SendRequestAsync(request, requestTimeout, requestOptions?.ConnectTimeout ?? _algoliaConfig.ConnectTimeout ?? Defaults.ConnectTimeout, ct) .ConfigureAwait(false); _errorMessage = response.Error; @@ -280,13 +280,14 @@ private static Uri BuildUri(StatefulHost host, string baseUri, /// Compute the request timeout with the given call type and configuration /// /// + /// /// - private TimeSpan GetTimeOut(CallType callType) + private TimeSpan GetTimeOut(CallType callType, InternalRequestOptions requestOptions = null) { return callType switch { - CallType.Read => _algoliaConfig.ReadTimeout ?? Defaults.ReadTimeout, - CallType.Write => _algoliaConfig.WriteTimeout ?? Defaults.WriteTimeout, + CallType.Read => requestOptions?.ReadTimeout ?? _algoliaConfig.ReadTimeout ?? Defaults.ReadTimeout, + CallType.Write => requestOptions?.WriteTimeout ?? _algoliaConfig.WriteTimeout ?? Defaults.WriteTimeout, _ => Defaults.WriteTimeout }; } 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 cbe05a122e..a64a362b7d 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 @@ -121,4 +121,12 @@ class DioRequester implements Requester { void setClientApiKey(String apiKey) { _authInterceptor.apiKey = apiKey; } + + @override + get connectTimeout => _client.options.connectTimeout; + + @override + void setConnectTimeout(Duration connectTimeout) { + _client.options.connectTimeout = connectTimeout; + } } diff --git a/clients/algoliasearch-client-dart/packages/client_core/lib/src/transport/request_options.dart b/clients/algoliasearch-client-dart/packages/client_core/lib/src/transport/request_options.dart index f978bd1051..b3dd312521 100644 --- a/clients/algoliasearch-client-dart/packages/client_core/lib/src/transport/request_options.dart +++ b/clients/algoliasearch-client-dart/packages/client_core/lib/src/transport/request_options.dart @@ -1,11 +1,14 @@ /// Represents options for configuring a request to an endpoint. final class RequestOptions { - /// The write timeout for the request in milliseconds. + /// The write timeout for the request. final Duration? writeTimeout; - /// The read timeout for the request in milliseconds. + /// The read timeout for the request. final Duration? readTimeout; + /// The connect timeout for the request. + final Duration? connectTimeout; + /// Header names to their respective values to be sent with the request. final Map headers; @@ -18,11 +21,27 @@ final class RequestOptions { const RequestOptions({ this.writeTimeout, this.readTimeout, + this.connectTimeout, this.headers = const {}, this.urlParameters = const {}, this.body, }); + RequestOptions operator +(RequestOptions? other) { + if (other == null) { + return this; + } + + return RequestOptions( + writeTimeout: other.writeTimeout ?? writeTimeout, + readTimeout: other.readTimeout ?? readTimeout, + connectTimeout: other.connectTimeout ?? connectTimeout, + headers: {...headers, ...other.headers}, + urlParameters: {...urlParameters, ...other.urlParameters}, + body: other.body ?? body, + ); + } + @override bool operator ==(Object other) => identical(this, other) || @@ -30,6 +49,7 @@ final class RequestOptions { runtimeType == other.runtimeType && writeTimeout == other.writeTimeout && readTimeout == other.readTimeout && + connectTimeout == other.connectTimeout && headers == other.headers && urlParameters == other.urlParameters && body == other.body; @@ -38,12 +58,13 @@ final class RequestOptions { int get hashCode => writeTimeout.hashCode ^ readTimeout.hashCode ^ + connectTimeout.hashCode ^ headers.hashCode ^ urlParameters.hashCode ^ body.hashCode; @override String toString() { - return 'RequestOptions{writeTimeout: $writeTimeout, readTimeout: $readTimeout, headers: $headers, urlParameters: $urlParameters, body: $body}'; + return 'RequestOptions{writeTimeout: $writeTimeout, readTimeout: $readTimeout, connectTimeout: $connectTimeout, headers: $headers, urlParameters: $urlParameters, body: $body}'; } } 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 d37f9d9db5..e54e53cbec 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 @@ -14,6 +14,10 @@ abstract class Requester { /// Allows to switch the API key used to authenticate requests. void setClientApiKey(String apiKey); + /// Allows to customise the connect timeout for the requester. + get connectTimeout => null; + void setConnectTimeout(Duration connectTimeout); + /// 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-dart/packages/client_core/lib/src/transport/retry_strategy.dart b/clients/algoliasearch-client-dart/packages/client_core/lib/src/transport/retry_strategy.dart index 07c98d73c8..1cad26e19f 100644 --- a/clients/algoliasearch-client-dart/packages/client_core/lib/src/transport/retry_strategy.dart +++ b/clients/algoliasearch-client-dart/packages/client_core/lib/src/transport/retry_strategy.dart @@ -53,8 +53,13 @@ final class RetryStrategy { final List errors = []; for (final host in hosts) { final httpRequest = _buildRequest(host, request, callType, options); + final requesterConnectTimeout = requester.connectTimeout; + if (options?.connectTimeout != null) { + requester.setConnectTimeout(options!.connectTimeout!); + } try { final response = await requester.perform(httpRequest); + requester.setConnectTimeout(requesterConnectTimeout); return response.body ?? const {}; } on AlgoliaTimeoutException catch (e) { host.timedOut(); diff --git a/clients/algoliasearch-client-go/algolia/transport/configuration.go b/clients/algoliasearch-client-go/algolia/transport/configuration.go index 431cad634b..f2c06aa020 100644 --- a/clients/algoliasearch-client-go/algolia/transport/configuration.go +++ b/clients/algoliasearch-client-go/algolia/transport/configuration.go @@ -20,3 +20,9 @@ type Configuration struct { Compression compression.Compression ExposeIntermediateNetworkErrors bool } + +type RequestConfiguration struct { + ReadTimeout *time.Duration + WriteTimeout *time.Duration + ConnectTimeout *time.Duration +} diff --git a/clients/algoliasearch-client-go/algolia/transport/transport.go b/clients/algoliasearch-client-go/algolia/transport/transport.go index 345f98278a..018a9ff252 100644 --- a/clients/algoliasearch-client-go/algolia/transport/transport.go +++ b/clients/algoliasearch-client-go/algolia/transport/transport.go @@ -44,7 +44,7 @@ func New(cfg Configuration) *Transport { return transport } -func (t *Transport) Request(ctx context.Context, req *http.Request, k call.Kind) (*http.Response, []byte, error) { +func (t *Transport) Request(ctx context.Context, req *http.Request, k call.Kind, c RequestConfiguration) (*http.Response, []byte, error) { var intermediateNetworkErrors []error // Add Content-Encoding header, if needed @@ -59,9 +59,29 @@ func (t *Transport) Request(ctx context.Context, req *http.Request, k call.Kind) // before the early returns, but when we do so, we do it **after** // reading the body content of the response. Otherwise, a `context // cancelled` error may happen when the body is read. - perRequestCtx, cancel := context.WithTimeout(ctx, h.timeout) + var ( + ctxTimeout time.Duration + connectTimeout time.Duration + ) + + switch { + case k == call.Read && c.ReadTimeout != nil: + ctxTimeout = *c.ReadTimeout + case k == call.Write && c.WriteTimeout != nil: + ctxTimeout = *c.WriteTimeout + default: + ctxTimeout = h.timeout + } + + if c.ConnectTimeout != nil { + connectTimeout = *c.ConnectTimeout + } else { + connectTimeout = t.connectTimeout + } + + perRequestCtx, cancel := context.WithTimeout(ctx, ctxTimeout) req = req.WithContext(perRequestCtx) - res, err := t.request(req, h, h.timeout, t.connectTimeout) + res, err := t.request(req, h, ctxTimeout, connectTimeout) code := 0 if res != nil { 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 734618ee3e..f35f65c11d 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 @@ -17,7 +17,7 @@ import java.util.concurrent.ExecutorService; import java.util.stream.Collectors; import javax.annotation.Nonnull; -import org.jetbrains.annotations.Nullable; +import javax.annotation.Nullable; /** * Represents a base client for making API requests. The client uses a {@link Requester} for diff --git a/clients/algoliasearch-client-java/algoliasearch/src/main/java/com/algolia/config/RequestOptions.java b/clients/algoliasearch-client-java/algoliasearch/src/main/java/com/algolia/config/RequestOptions.java index 695e3a0618..0e84c540fd 100644 --- a/clients/algoliasearch-client-java/algoliasearch/src/main/java/com/algolia/config/RequestOptions.java +++ b/clients/algoliasearch-client-java/algoliasearch/src/main/java/com/algolia/config/RequestOptions.java @@ -4,6 +4,7 @@ import java.time.Duration; import java.util.HashMap; import java.util.Map; +import javax.annotation.Nullable; /** * Request options are used to pass extra parameters, headers, timeout to the request. Parameters @@ -15,6 +16,7 @@ public final class RequestOptions { private final Map queryParameters = new HashMap<>(); private Duration readTimeout; private Duration writeTimeout; + private Duration connectTimeout; public RequestOptions addExtraHeader(String key, Object value) { if (value == null) return this; @@ -62,6 +64,32 @@ public RequestOptions setWriteTimeout(Duration writeTimeout) { return this; } + public Duration getConnectTimeout() { + return connectTimeout; + } + + public RequestOptions setConnectTimeout(Duration connectTimeout) { + this.connectTimeout = connectTimeout; + return this; + } + + // `this` will be merged in `other`. Values in `other` will take precedence over `this`'s. + public RequestOptions mergeRight(@Nullable RequestOptions other) { + if (other == null) { + return this; + } + + RequestOptions requestOptions = new RequestOptions(); + requestOptions.headers.putAll(this.headers); + requestOptions.headers.putAll(other.headers); + requestOptions.queryParameters.putAll(this.queryParameters); + requestOptions.queryParameters.putAll(other.queryParameters); + requestOptions.readTimeout = other.readTimeout != null ? other.readTimeout : this.readTimeout; + requestOptions.writeTimeout = other.writeTimeout != null ? other.writeTimeout : this.writeTimeout; + requestOptions.connectTimeout = other.connectTimeout != null ? other.connectTimeout : this.connectTimeout; + return requestOptions; + } + @Override public String toString() { return ( @@ -74,6 +102,8 @@ public String toString() { readTimeout + ", writeTimeout=" + writeTimeout + + ", connectTimeout=" + + connectTimeout + '}' ); } diff --git a/clients/algoliasearch-client-java/algoliasearch/src/main/java/com/algolia/internal/HttpRequester.java b/clients/algoliasearch-client-java/algoliasearch/src/main/java/com/algolia/internal/HttpRequester.java index 6c89e41756..d8cca00e36 100644 --- a/clients/algoliasearch-client-java/algoliasearch/src/main/java/com/algolia/internal/HttpRequester.java +++ b/clients/algoliasearch-client-java/algoliasearch/src/main/java/com/algolia/internal/HttpRequester.java @@ -172,6 +172,9 @@ private OkHttpClient getOkHttpClient(RequestOptions requestOptions) { if (requestOptions.getWriteTimeout() != null) { builder.writeTimeout(requestOptions.getWriteTimeout()); } + if (requestOptions.getConnectTimeout() != null) { + builder.connectTimeout(requestOptions.getConnectTimeout()); + } return builder.build(); } diff --git a/clients/algoliasearch-client-kotlin/client/src/commonMain/kotlin/com/algolia/client/extensions/SearchClient.kt b/clients/algoliasearch-client-kotlin/client/src/commonMain/kotlin/com/algolia/client/extensions/SearchClient.kt index 9eaa38cab7..9233419725 100644 --- a/clients/algoliasearch-client-kotlin/client/src/commonMain/kotlin/com/algolia/client/extensions/SearchClient.kt +++ b/clients/algoliasearch-client-kotlin/client/src/commonMain/kotlin/com/algolia/client/extensions/SearchClient.kt @@ -83,8 +83,7 @@ public suspend fun SearchClient.waitForApiKey( * @param indexName The index in which to perform the request. * @param taskID The ID of the task to wait for. * @param timeout If specified, the method will throw a - * [kotlinx.coroutines.TimeoutCancellationException] after the timeout value in milliseconds is - * elapsed. + * [kotlinx.coroutines.TimeoutCancellationException] after the timeout value is elapsed. * @param maxRetries maximum number of retry attempts. * @param requestOptions additional request configuration. */ @@ -136,8 +135,7 @@ public suspend fun SearchClient.waitTask( * * @param taskID The ID of the task to wait for. * @param timeout If specified, the method will throw a - * [kotlinx.coroutines.TimeoutCancellationException] after the timeout value in milliseconds is - * elapsed. + * [kotlinx.coroutines.TimeoutCancellationException] after the timeout value is elapsed. * @param maxRetries maximum number of retry attempts. * @param requestOptions additional request configuration. */ @@ -188,8 +186,7 @@ public suspend fun SearchClient.waitAppTask( * @param apiKey Necessary to know if an `update` operation has been processed, compare fields of * the response with it. * @param timeout If specified, the method will throw a - * [kotlinx.coroutines.TimeoutCancellationException] after the timeout value in milliseconds is - * elapsed. + * [kotlinx.coroutines.TimeoutCancellationException] after the timeout value is elapsed. * @param maxRetries Maximum number of retry attempts. * @param requestOptions Additional request configuration. */ @@ -228,8 +225,7 @@ public suspend fun SearchClient.waitKeyUpdate( * Wait on an API key creation operation. * * @param timeout If specified, the method will throw a - * [kotlinx.coroutines.TimeoutCancellationException] after the timeout value in milliseconds is - * elapsed. + * [kotlinx.coroutines.TimeoutCancellationException] after the timeout value is elapsed. * @param maxRetries Maximum number of retry attempts. * @param requestOptions Additional request configuration. */ @@ -263,8 +259,7 @@ public suspend fun SearchClient.waitKeyCreation( * * @param maxRetries Maximum number of retry attempts. * @param timeout If specified, the method will throw a - * [kotlinx.coroutines.TimeoutCancellationException] after the timeout value in milliseconds is - * elapsed. + * [kotlinx.coroutines.TimeoutCancellationException] after the timeout value is elapsed. * @param requestOptions Additional request configuration. */ public suspend fun SearchClient.waitKeyDelete( diff --git a/clients/algoliasearch-client-kotlin/client/src/commonMain/kotlin/com/algolia/client/transport/RequestOptions.kt b/clients/algoliasearch-client-kotlin/client/src/commonMain/kotlin/com/algolia/client/transport/RequestOptions.kt index c02d6a6079..723ba0f441 100644 --- a/clients/algoliasearch-client-kotlin/client/src/commonMain/kotlin/com/algolia/client/transport/RequestOptions.kt +++ b/clients/algoliasearch-client-kotlin/client/src/commonMain/kotlin/com/algolia/client/transport/RequestOptions.kt @@ -6,8 +6,9 @@ import kotlin.time.Duration /** * Represents options for configuring a request to an endpoint. * - * @property writeTimeout The write timeout for the request in milliseconds. - * @property readTimeout The read timeout for the request in milliseconds. + * @property writeTimeout The write timeout for the request. + * @property readTimeout The read timeout for the request. + * @property connectTimeout The connect timeout for the request. * @property headers A mutable map of header names to their respective values to be sent with the request. * @property urlParameters A mutable map of URL parameter names to their respective values to be appended to the request URL. * @property body A JSON object representing the request body. @@ -15,7 +16,23 @@ import kotlin.time.Duration public data class RequestOptions( public val writeTimeout: Duration? = null, public val readTimeout: Duration? = null, + public val connectTimeout: Duration? = null, public val headers: Map = emptyMap(), public val urlParameters: Map = emptyMap(), public val body: JsonObject? = null, -) +) { + public operator fun plus(other: RequestOptions?): RequestOptions { + if (other == null) { + return this + } + + return RequestOptions( + writeTimeout = other.writeTimeout ?: writeTimeout, + readTimeout = other.readTimeout ?: readTimeout, + connectTimeout = other.connectTimeout ?: connectTimeout, + headers = headers + other.headers, + urlParameters = urlParameters + other.urlParameters, + body = other.body ?: body, + ) + } +} 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 27b170b651..0899df77e5 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 @@ -122,7 +122,8 @@ public class KtorRequester( CallType.Read -> requestOptions?.readTimeout ?: readTimeout CallType.Write -> requestOptions?.writeTimeout ?: writeTimeout } - connectTimeoutMillis = connectTimeout.inWholeMilliseconds + val connectTimeoutDuration = requestOptions?.connectTimeout ?: connectTimeout + connectTimeoutMillis = connectTimeoutDuration.inWholeMilliseconds socketTimeoutMillis = timeout.inWholeMilliseconds * (host.retryCount + 1) } } diff --git a/clients/algoliasearch-client-scala/src/main/scala/algoliasearch/config/RequestOptions.scala b/clients/algoliasearch-client-scala/src/main/scala/algoliasearch/config/RequestOptions.scala index 9717367c44..0711cfc639 100644 --- a/clients/algoliasearch-client-scala/src/main/scala/algoliasearch/config/RequestOptions.scala +++ b/clients/algoliasearch-client-scala/src/main/scala/algoliasearch/config/RequestOptions.scala @@ -16,13 +16,27 @@ import scala.concurrent.duration.Duration * HTTP read timeout * @param writeTimeout * HTTP write timeout + * @param connectTimeout + * HTTP connect timeout */ case class RequestOptions( headers: Map[String, String] = Map.empty, queryParameters: Map[String, String] = Map.empty, readTimeout: Option[Duration] = None, - writeTimeout: Option[Duration] = None -) + writeTimeout: Option[Duration] = None, + connectTimeout: Option[Duration] = None +) { + def +(other: Option[RequestOptions]): RequestOptions = { + val some = other.getOrElse(return this) + new RequestOptions( + headers = this.headers ++ some.headers, + queryParameters = this.queryParameters ++ some.queryParameters, + readTimeout = some.readTimeout.orElse(this.readTimeout), + writeTimeout = some.writeTimeout.orElse(this.writeTimeout), + connectTimeout = some.connectTimeout.orElse(this.connectTimeout) + ) + } +} object RequestOptions { @@ -33,6 +47,7 @@ object RequestOptions { private val queryParameters: mutable.Map[String, String] = mutable.Map() private var readTimeout: Option[Duration] = None private var writeTimeout: Option[Duration] = None + private var connectTimeout: Option[Duration] = None /** Adds a header to the request. */ @@ -62,6 +77,13 @@ object RequestOptions { this } + /** Sets the write timeout for the request. + */ + def withConnectTimeout(connectTimeout: Option[Duration]): Builder = { + this.connectTimeout = connectTimeout + this + } + /** Builds the [[RequestOptions]]. */ def build(): RequestOptions = { @@ -69,7 +91,8 @@ object RequestOptions { headers = headers.toMap, queryParameters = queryParameters.toMap, readTimeout = readTimeout, - writeTimeout = writeTimeout + writeTimeout = writeTimeout, + connectTimeout = connectTimeout ) } } diff --git a/clients/algoliasearch-client-scala/src/main/scala/algoliasearch/internal/HttpRequester.scala b/clients/algoliasearch-client-scala/src/main/scala/algoliasearch/internal/HttpRequester.scala index 4c347488ac..533441b1de 100644 --- a/clients/algoliasearch-client-scala/src/main/scala/algoliasearch/internal/HttpRequester.scala +++ b/clients/algoliasearch-client-scala/src/main/scala/algoliasearch/internal/HttpRequester.scala @@ -115,6 +115,7 @@ private[algoliasearch] class HttpRequester private ( requestOptions.foreach(options => { options.readTimeout.foreach(timeout => builder.readTimeout(timeout.toMillis, TimeUnit.MILLISECONDS)) options.writeTimeout.foreach(timeout => builder.writeTimeout(timeout.toMillis, TimeUnit.MILLISECONDS)) + options.connectTimeout.foreach(timeout => builder.connectTimeout(timeout.toMillis, TimeUnit.MILLISECONDS)) }) builder.build } diff --git a/generators/src/main/java/com/algolia/codegen/AlgoliaCSharpGenerator.java b/generators/src/main/java/com/algolia/codegen/AlgoliaCSharpGenerator.java index 6340757f8e..f0223f142d 100644 --- a/generators/src/main/java/com/algolia/codegen/AlgoliaCSharpGenerator.java +++ b/generators/src/main/java/com/algolia/codegen/AlgoliaCSharpGenerator.java @@ -9,7 +9,6 @@ import io.swagger.v3.oas.models.Operation; import io.swagger.v3.oas.models.servers.Server; import java.io.IOException; -import java.time.temporal.ChronoUnit; import java.util.*; import org.openapitools.codegen.*; import org.openapitools.codegen.languages.CSharpClientCodegen; @@ -135,7 +134,7 @@ private String escapeGenericForDoc(String type) { public void processOpenAPI(OpenAPI openAPI) { super.processOpenAPI(openAPI); Helpers.generateServers(super.fromServers(openAPI.getServers()), additionalProperties); - Timeouts.enrichBundle(openAPI, additionalProperties, ChronoUnit.SECONDS); + Timeouts.enrichBundle(openAPI, additionalProperties); } @Override diff --git a/generators/src/main/java/com/algolia/codegen/AlgoliaDartGenerator.java b/generators/src/main/java/com/algolia/codegen/AlgoliaDartGenerator.java index b20f7f359a..9e49549038 100644 --- a/generators/src/main/java/com/algolia/codegen/AlgoliaDartGenerator.java +++ b/generators/src/main/java/com/algolia/codegen/AlgoliaDartGenerator.java @@ -3,6 +3,8 @@ import static org.apache.commons.lang3.StringUtils.*; import com.algolia.codegen.utils.*; +import com.google.common.collect.ImmutableMap; +import com.samskivert.mustache.Mustache; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.Operation; import io.swagger.v3.oas.models.servers.Server; @@ -109,6 +111,11 @@ public void processOpts() { additionalProperties.put("packageVersion", version); } + @Override + protected ImmutableMap.Builder addMustacheLambdas() { + return super.addMustacheLambdas(); + } + @Override public void processOpenAPI(OpenAPI openAPI) { super.processOpenAPI(openAPI); diff --git a/generators/src/main/java/com/algolia/codegen/AlgoliaGoGenerator.java b/generators/src/main/java/com/algolia/codegen/AlgoliaGoGenerator.java index 18ef2d8baf..80364e7e35 100644 --- a/generators/src/main/java/com/algolia/codegen/AlgoliaGoGenerator.java +++ b/generators/src/main/java/com/algolia/codegen/AlgoliaGoGenerator.java @@ -66,7 +66,11 @@ public void processOpts() { @Override protected ImmutableMap.Builder addMustacheLambdas() { - return super.addMustacheLambdas().put("screamingSnakeCase", new ScreamingSnakeCaseLambda()); + ImmutableMap.Builder lambdas = super.addMustacheLambdas(); + + lambdas.put("screamingSnakeCase", new ScreamingSnakeCaseLambda()); + + return lambdas; } @Override diff --git a/generators/src/main/java/com/algolia/codegen/AlgoliaPhpGenerator.java b/generators/src/main/java/com/algolia/codegen/AlgoliaPhpGenerator.java index 81fb63efdb..4abf6a623c 100644 --- a/generators/src/main/java/com/algolia/codegen/AlgoliaPhpGenerator.java +++ b/generators/src/main/java/com/algolia/codegen/AlgoliaPhpGenerator.java @@ -1,11 +1,13 @@ package com.algolia.codegen; import com.algolia.codegen.exceptions.*; +import com.algolia.codegen.lambda.ToSecondsLambda; import com.algolia.codegen.utils.*; +import com.google.common.collect.ImmutableMap; +import com.samskivert.mustache.Mustache; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.Operation; import io.swagger.v3.oas.models.servers.Server; -import java.time.temporal.ChronoUnit; import java.util.*; import org.openapitools.codegen.CodegenOperation; import org.openapitools.codegen.SupportingFile; @@ -65,11 +67,16 @@ public void processOpts() { } } + @Override + protected ImmutableMap.Builder addMustacheLambdas() { + return super.addMustacheLambdas().put("toSeconds", new ToSecondsLambda()); + } + @Override public void processOpenAPI(OpenAPI openAPI) { super.processOpenAPI(openAPI); Helpers.generateServers(super.fromServers(openAPI.getServers()), additionalProperties); - Timeouts.enrichBundle(openAPI, additionalProperties, ChronoUnit.SECONDS); + Timeouts.enrichBundle(openAPI, additionalProperties); } @Override diff --git a/generators/src/main/java/com/algolia/codegen/AlgoliaScalaGenerator.java b/generators/src/main/java/com/algolia/codegen/AlgoliaScalaGenerator.java index dcd35fa6ab..ec1265231d 100644 --- a/generators/src/main/java/com/algolia/codegen/AlgoliaScalaGenerator.java +++ b/generators/src/main/java/com/algolia/codegen/AlgoliaScalaGenerator.java @@ -2,12 +2,12 @@ import com.algolia.codegen.exceptions.GeneratorException; import com.algolia.codegen.utils.*; +import com.google.common.collect.ImmutableMap; import com.samskivert.mustache.Mustache; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.Operation; import io.swagger.v3.oas.models.servers.Server; import java.io.File; -import java.time.temporal.ChronoUnit; import java.util.List; import java.util.Map; import java.util.logging.Logger; @@ -91,6 +91,11 @@ public void processOpts() { } } + @Override + protected ImmutableMap.Builder addMustacheLambdas() { + return super.addMustacheLambdas(); + } + /** Convert a Seq type to a valid class name. */ private String typeToName(String content) { return content.trim().replace("[", "Of").replace("]", "").replace(".", "").replace(", ", ""); @@ -100,7 +105,7 @@ private String typeToName(String content) { public void processOpenAPI(OpenAPI openAPI) { super.processOpenAPI(openAPI); Helpers.generateServers(super.fromServers(openAPI.getServers()), additionalProperties); - Timeouts.enrichBundle(openAPI, additionalProperties, ChronoUnit.SECONDS); + Timeouts.enrichBundle(openAPI, additionalProperties); } @Override diff --git a/generators/src/main/java/com/algolia/codegen/AlgoliaSwiftGenerator.java b/generators/src/main/java/com/algolia/codegen/AlgoliaSwiftGenerator.java index 50a01cf73b..aac6aebfe2 100644 --- a/generators/src/main/java/com/algolia/codegen/AlgoliaSwiftGenerator.java +++ b/generators/src/main/java/com/algolia/codegen/AlgoliaSwiftGenerator.java @@ -4,7 +4,9 @@ import static org.openapitools.codegen.utils.StringUtils.camelize; import com.algolia.codegen.exceptions.*; +import com.algolia.codegen.lambda.ToSecondsLambda; import com.algolia.codegen.utils.*; +import com.google.common.collect.ImmutableMap; import com.samskivert.mustache.Mustache; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.Operation; @@ -14,7 +16,6 @@ import io.swagger.v3.oas.models.servers.Server; import java.io.File; import java.io.IOException; -import java.time.temporal.ChronoUnit; import java.util.*; import java.util.logging.Logger; import org.apache.commons.lang3.StringUtils; @@ -287,11 +288,16 @@ private String typeToName(String content) { return name; } + @Override + protected ImmutableMap.Builder addMustacheLambdas() { + return super.addMustacheLambdas().put("toSeconds", new ToSecondsLambda()); + } + @Override public void processOpenAPI(OpenAPI openAPI) { super.processOpenAPI(openAPI); Helpers.generateServers(super.fromServers(openAPI.getServers()), additionalProperties); - Timeouts.enrichBundle(openAPI, additionalProperties, ChronoUnit.SECONDS); + Timeouts.enrichBundle(openAPI, additionalProperties); } @Override diff --git a/generators/src/main/java/com/algolia/codegen/cts/tests/RequestOptions.java b/generators/src/main/java/com/algolia/codegen/cts/tests/RequestOptions.java index 26bed53251..dbb314ef43 100644 --- a/generators/src/main/java/com/algolia/codegen/cts/tests/RequestOptions.java +++ b/generators/src/main/java/com/algolia/codegen/cts/tests/RequestOptions.java @@ -6,6 +6,9 @@ public class RequestOptions { public Map queryParameters; public Map headers; + public Long readTimeout; + public Long writeTimeout; + public Long connectTimeout; @Override public String toString() { @@ -13,6 +16,9 @@ public String toString() { sb.append("class RequestOptions {\n"); sb.append(" queryParameters: ").append(queryParameters).append("\n"); sb.append(" headers: ").append(headers).append("\n"); + sb.append(" readTimeout: ").append(readTimeout).append("\n"); + sb.append(" writeTimeout: ").append(writeTimeout).append("\n"); + sb.append(" connectTimeout: ").append(connectTimeout).append("\n"); sb.append("}"); return sb.toString(); } diff --git a/generators/src/main/java/com/algolia/codegen/cts/tests/Step.java b/generators/src/main/java/com/algolia/codegen/cts/tests/Step.java index eb776abe59..a230423287 100644 --- a/generators/src/main/java/com/algolia/codegen/cts/tests/Step.java +++ b/generators/src/main/java/com/algolia/codegen/cts/tests/Step.java @@ -8,6 +8,7 @@ public class Step { public String method; public int times; public Map parameters; + public RequestOptions requestOptions; public Expected expected; public String toString() { @@ -17,6 +18,7 @@ public String toString() { sb.append(" method: ").append(method).append("\n"); sb.append(" times: ").append(times).append("\n"); sb.append(" parameters: ").append(parameters).append("\n"); + sb.append(" requestOptions: ").append(requestOptions).append("\n"); sb.append(" expected: ").append(expected).append("\n"); sb.append("}"); return sb.toString(); 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 0faab379aa..a6e4c68445 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 @@ -138,6 +138,8 @@ public void run(Map models, Map // default to true because most api calls are asynchronous testOut.put("isAsyncMethod", (boolean) ope.vendorExtensions.getOrDefault("x-asynchronous-helper", true)); + addRequestOptions(paramsType, step.requestOptions, stepOut); + methodCount++; } diff --git a/generators/src/main/java/com/algolia/codegen/cts/tests/TestsGenerator.java b/generators/src/main/java/com/algolia/codegen/cts/tests/TestsGenerator.java index d30c2d30b6..de8fb6ce7f 100644 --- a/generators/src/main/java/com/algolia/codegen/cts/tests/TestsGenerator.java +++ b/generators/src/main/java/com/algolia/codegen/cts/tests/TestsGenerator.java @@ -3,6 +3,8 @@ import com.algolia.codegen.cts.manager.CTSManager; import com.algolia.codegen.exceptions.CTSException; import com.algolia.codegen.utils.*; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; import io.swagger.v3.core.util.Json; import java.io.File; import java.nio.file.Files; @@ -98,4 +100,33 @@ private String injectVariables(String json) { .replace("${{clientPascalCase}}", Helpers.capitalize(Helpers.camelize(client))) .replace("\"${{nowRounded}}\"", String.valueOf(Math.round(System.currentTimeMillis() / threeDays) * threeDays)); } + + protected void addRequestOptions(ParametersWithDataType paramsType, RequestOptions req, Map output) + throws JsonMappingException, JsonProcessingException { + if (req != null) { + output.put("hasRequestOptions", true); + Map requestOptions = new HashMap<>(); + if (req.queryParameters != null) { + Map queryParameters = new HashMap<>(); + paramsType.enhanceParameters(req.queryParameters, queryParameters); + requestOptions.put("queryParameters", queryParameters); + } + if (req.headers != null) { + Map headers = new HashMap<>(); + // convert the headers to an acceptable type + paramsType.enhanceParameters(new HashMap(req.headers), headers); + requestOptions.put("headers", headers); + } + Map timeouts = null; + if (req.readTimeout != null || req.writeTimeout != null || req.connectTimeout != null) { + timeouts = new HashMap<>(); + if (req.readTimeout != null) timeouts.put("read", req.readTimeout); + if (req.writeTimeout != null) timeouts.put("write", req.writeTimeout); + if (req.connectTimeout != null) timeouts.put("connect", req.connectTimeout); + paramsType.enhanceParameters(timeouts, timeouts); + } + requestOptions.put("timeouts", timeouts); + output.put("requestOptions", requestOptions); + } + } } 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 62b7869194..ed277a9938 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 @@ -156,22 +156,7 @@ public void run(Map models, Map test.put("hasParams", ope.hasParams); test.put("isHelper", isHelper); - if (req.requestOptions != null) { - test.put("hasRequestOptions", true); - Map requestOptions = new HashMap<>(); - if (req.requestOptions.queryParameters != null) { - Map queryParameters = new HashMap<>(); - paramsType.enhanceParameters(req.requestOptions.queryParameters, queryParameters); - requestOptions.put("queryParameters", queryParameters); - } - if (req.requestOptions.headers != null) { - Map headers = new HashMap<>(); - // convert the headers to an acceptable type - paramsType.enhanceParameters(new HashMap(req.requestOptions.headers), headers); - requestOptions.put("headers", headers); - } - test.put("requestOptions", requestOptions); - } + addRequestOptions(paramsType, req.requestOptions, test); // Determines whether the endpoint is expected to return a response payload deserialized // and therefore a variable to store it into. diff --git a/generators/src/main/java/com/algolia/codegen/lambda/ToSecondsLambda.java b/generators/src/main/java/com/algolia/codegen/lambda/ToSecondsLambda.java new file mode 100644 index 0000000000..7d78f59d32 --- /dev/null +++ b/generators/src/main/java/com/algolia/codegen/lambda/ToSecondsLambda.java @@ -0,0 +1,14 @@ +package com.algolia.codegen.lambda; + +import com.samskivert.mustache.Mustache; +import com.samskivert.mustache.Template; +import java.io.IOException; +import java.io.Writer; + +public class ToSecondsLambda implements Mustache.Lambda { + + @Override + public void execute(Template.Fragment fragment, Writer writer) throws IOException { + writer.write(Integer.toString(Integer.parseInt(fragment.execute()) / 1000)); + } +} diff --git a/generators/src/main/java/com/algolia/codegen/utils/Timeouts.java b/generators/src/main/java/com/algolia/codegen/utils/Timeouts.java index 234646981e..2b09bd2fb1 100644 --- a/generators/src/main/java/com/algolia/codegen/utils/Timeouts.java +++ b/generators/src/main/java/com/algolia/codegen/utils/Timeouts.java @@ -3,23 +3,13 @@ import com.algolia.codegen.exceptions.*; import com.fasterxml.jackson.databind.*; import io.swagger.v3.oas.models.OpenAPI; -import java.time.temporal.ChronoUnit; import java.util.*; class TimeoutsValues { - private ChronoUnit unit = ChronoUnit.MILLIS; public long connect; public long read; public long write; - - void toUnit(ChronoUnit unit) { - if (this.unit == ChronoUnit.MILLIS && unit == ChronoUnit.SECONDS) { - connect /= 1000; - read /= 1000; - write /= 1000; - } - } } class TimeoutsBundle { @@ -30,15 +20,8 @@ class TimeoutsBundle { public class Timeouts { + /** Inject timeouts in miliseconds into the given bundle, under the x-timeouts property * */ public static void enrichBundle(OpenAPI spec, Map bundle) throws ConfigException { - enrichBundle(spec, bundle, ChronoUnit.MILLIS); - } - - /** - * Inject timeouts (in miliseconds / divider) into the given bundle, under the x-timeouts property - * * - */ - public static void enrichBundle(OpenAPI spec, Map bundle, ChronoUnit unit) throws ConfigException { TimeoutsBundle defaults = new TimeoutsBundle(); // the default below are what the search API expect, which was previously used for any client defaults.browser.connect = 1000; @@ -62,9 +45,6 @@ public static void enrichBundle(OpenAPI spec, Map bundle, Chrono specTimeouts.server = defaults.server; } - specTimeouts.browser.toUnit(unit); - specTimeouts.server.toUnit(unit); - bundle.put("x-timeouts", specTimeouts); } } diff --git a/templates/csharp/Configuration.mustache b/templates/csharp/Configuration.mustache index 40eeebf6cb..d5f6cc1da6 100644 --- a/templates/csharp/Configuration.mustache +++ b/templates/csharp/Configuration.mustache @@ -25,9 +25,9 @@ public sealed class {{apiPackageName}}Config : AlgoliaConfig { DefaultHosts = GetDefaultHosts(region); Compression = CompressionType.None; - ReadTimeout = TimeSpan.FromSeconds({{x-timeouts.server.read}}); - WriteTimeout = TimeSpan.FromSeconds({{x-timeouts.server.write}}); - ConnectTimeout = TimeSpan.FromSeconds({{x-timeouts.server.connect}}); + ReadTimeout = TimeSpan.FromMilliseconds({{x-timeouts.server.read}}); + WriteTimeout = TimeSpan.FromMilliseconds({{x-timeouts.server.write}}); + ConnectTimeout = TimeSpan.FromMilliseconds({{x-timeouts.server.connect}}); } {{/hasRegionalHost}} {{^hasRegionalHost}} @@ -41,9 +41,9 @@ public sealed class {{apiPackageName}}Config : AlgoliaConfig { DefaultHosts = GetDefaultHosts({{#hostWithAppID}}appId{{/hostWithAppID}}); Compression = CompressionType.None; - ReadTimeout = TimeSpan.FromSeconds({{x-timeouts.server.read}}); - WriteTimeout = TimeSpan.FromSeconds({{x-timeouts.server.write}}); - ConnectTimeout = TimeSpan.FromSeconds({{x-timeouts.server.connect}}); + ReadTimeout = TimeSpan.FromMilliseconds({{x-timeouts.server.read}}); + WriteTimeout = TimeSpan.FromMilliseconds({{x-timeouts.server.write}}); + ConnectTimeout = TimeSpan.FromMilliseconds({{x-timeouts.server.connect}}); } {{/hasRegionalHost}} {{#hostWithAppID}} diff --git a/templates/csharp/api.mustache b/templates/csharp/api.mustache index 1472cd3cb7..3c881667ce 100644 --- a/templates/csharp/api.mustache +++ b/templates/csharp/api.mustache @@ -226,6 +226,11 @@ namespace Algolia.Search.Clients; {{#x-use-read-transporter}} requestOptions.UseReadTransporter = true; {{/x-use-read-transporter}} + {{#vendorExtensions.x-timeouts}} + requestOptions.ReadTimeout ??= TimeSpan.FromMilliseconds({{read}}); + requestOptions.WriteTimeout ??= TimeSpan.FromMilliseconds({{write}}); + requestOptions.ConnectTimeout ??= TimeSpan.FromMilliseconds({{connect}}); + {{/vendorExtensions.x-timeouts}} {{/vendorExtensions}} {{#returnType}}return {{/returnType}}await _transport.ExecuteRequestAsync{{#returnType}}<{{> return_type}}>{{/returnType}}(new HttpMethod("{{httpMethod}}"),"{{{path}}}", requestOptions, cancellationToken).ConfigureAwait(false); } diff --git a/templates/csharp/tests/method.mustache b/templates/csharp/tests/method.mustache index 212269f210..f2f3b4a8de 100644 --- a/templates/csharp/tests/method.mustache +++ b/templates/csharp/tests/method.mustache @@ -5,5 +5,14 @@ {{#requestOptions.headers.parametersWithDataType}} .AddExtraHeader("{{{key}}}", "{{{value}}}") {{/requestOptions.headers.parametersWithDataType}} +{{#requestOptions.timeouts.connect}} + .SetConnectTimeout(TimeSpan.FromMilliseconds({{.}})) +{{/requestOptions.timeouts.connect}} +{{#requestOptions.timeouts.read}} + .SetReadTimeout(TimeSpan.FromMilliseconds({{.}})) +{{/requestOptions.timeouts.read}} +{{#requestOptions.timeouts.write}} + .SetWriteTimeout(TimeSpan.FromMilliseconds({{.}})) +{{/requestOptions.timeouts.write}} .Build() {{/hasRequestOptions}}) \ No newline at end of file diff --git a/templates/dart/api.mustache b/templates/dart/api.mustache index 8455765461..bc07f28dcf 100644 --- a/templates/dart/api.mustache +++ b/templates/dart/api.mustache @@ -165,7 +165,11 @@ final class {{classname}} implements ApiClient { ); {{#returnType}}final response = {{/returnType}}await _retryStrategy.execute( request: request, - options: requestOptions, + options: {{#vendorExtensions.x-timeouts}}new RequestOptions( + writeTimeout: Duration(milliseconds: {{write}}), + readTimeout: Duration(milliseconds: {{read}}), + connectTimeout: Duration(milliseconds: {{connect}}), + ) + {{/vendorExtensions.x-timeouts}}requestOptions, ); {{#returnType}} return deserialize<{{{.}}}, {{{returnBaseType}}}>(response, '{{{.}}}', growable: true,); diff --git a/templates/go/api.mustache b/templates/go/api.mustache index 1d7816869d..168ab83d0f 100644 --- a/templates/go/api.mustache +++ b/templates/go/api.mustache @@ -19,8 +19,8 @@ import ( "errors" "slices" "sort" - "time" {{/isSearchClient}} + "time" "github.com/algolia/algoliasearch-client-go/v4/algolia/utils" ) @@ -30,6 +30,7 @@ type config struct { context context.Context queryParams url.Values headerParams map[string]string + timeouts transport.RequestConfiguration {{#isSearchClient}} // -- ChunkedBatch options @@ -80,6 +81,24 @@ func WithQueryParam(key string, value any) requestOption { }) } +func WithReadTimeout(timeout time.Duration) requestOption { + return requestOption(func(c *config) { + c.timeouts.ReadTimeout = &timeout + }) +} + +func WithWriteTimeout(timeout time.Duration) requestOption { + return requestOption(func(c *config) { + c.timeouts.WriteTimeout = &timeout + }) +} + +func WithConnectTimeout(timeout time.Duration) requestOption { + return requestOption(func(c *config) { + c.timeouts.ConnectTimeout = &timeout + }) +} + {{#isSearchClient}} // --------- ChunkedBatch options --------- @@ -453,6 +472,13 @@ func (c *APIClient) {{nickname}}WithHTTPInfo({{#hasParams}}r {{#structPrefix}}{{ context: context.Background(), queryParams: url.Values{}, headerParams: map[string]string{}, + {{#vendorExtensions.x-timeouts}} + timeouts: transport.RequestConfiguration{ + ReadTimeout: utils.ToPtr({{read}} * time.Millisecond), + WriteTimeout: utils.ToPtr({{write}} * time.Millisecond), + ConnectTimeout: utils.ToPtr({{connect}} * time.Millisecond), + }, + {{/vendorExtensions.x-timeouts}} } {{#vendorExtensions.x-is-custom-request}} @@ -507,7 +533,7 @@ func (c *APIClient) {{nickname}}WithHTTPInfo({{#hasParams}}r {{#structPrefix}}{{ return nil, nil, err } - return c.callAPI(req, {{#vendorExtensions}}{{#x-use-read-transporter}}true{{/x-use-read-transporter}}{{^x-use-read-transporter}}false{{/x-use-read-transporter}}{{/vendorExtensions}}) + return c.callAPI(req, {{#vendorExtensions}}{{#x-use-read-transporter}}true{{/x-use-read-transporter}}{{^x-use-read-transporter}}false{{/x-use-read-transporter}},{{/vendorExtensions}}conf.timeouts) } /* diff --git a/templates/go/client.mustache b/templates/go/client.mustache index 4600d1d9c3..4f0cd044b4 100644 --- a/templates/go/client.mustache +++ b/templates/go/client.mustache @@ -129,13 +129,13 @@ func (c *APIClient) AddDefaultHeader(key string, value string) { } // callAPI do the request. -func (c *APIClient) callAPI(request *http.Request, useReadTransporter bool) (*http.Response, []byte, error) { +func (c *APIClient) callAPI(request *http.Request, useReadTransporter bool, requestConfiguration transport.RequestConfiguration) (*http.Response, []byte, error) { callKind := call.Write if useReadTransporter || request.Method == http.MethodGet { callKind = call.Read } - resp, body, err := c.transport.Request(request.Context(), request, callKind) + resp, body, err := c.transport.Request(request.Context(), request, callKind, requestConfiguration) if err != nil { return nil, nil, fmt.Errorf("failed to do request: %w", err) } diff --git a/templates/go/tests/method.mustache b/templates/go/tests/method.mustache index 91b6558d44..6edb2abdcd 100644 --- a/templates/go/tests/method.mustache +++ b/templates/go/tests/method.mustache @@ -2,4 +2,13 @@ client.{{#lambda.titlecase}}{{method}}{{/lambda.titlecase}}({{#hasParams}}{{^isH {{#parametersWithDataType}}{{#required}}{{> tests/generateParams}},{{/required}}{{/parametersWithDataType}} {{^isHelper}}){{#parametersWithDataType}}{{^required}}.With{{#lambda.pascalcase}}{{{key}}}{{/lambda.pascalcase}}({{> tests/generateParams}}){{/required}}{{/parametersWithDataType}}{{/isHelper}}{{#isHelper}}{{#parametersWithDataType}}{{^required}}{{> tests/generateParams}},{{/required}}{{/parametersWithDataType}}{{/isHelper}}{{/hasParams}}{{#requestOptions}}{{#hasParams}},{{/hasParams}} {{#queryParameters.parametersWithDataType}}{{clientPrefix}}.WithQueryParam("{{{key}}}", {{> tests/generateInnerParams}}),{{/queryParameters.parametersWithDataType}}{{#headers.parametersWithDataType}}{{clientPrefix}}.WithHeaderParam("{{{key}}}", {{> tests/generateInnerParams}}),{{/headers.parametersWithDataType}} +{{#timeouts.read}} +{{clientPrefix}}.WithReadTimeout({{.}} * time.Millisecond), +{{/timeouts.read}} +{{#timeouts.write}} +{{clientPrefix}}.WithWriteTimeout({{.}} * time.Millisecond), +{{/timeouts.write}} +{{#timeouts.connect}} +{{clientPrefix}}.WithConnectTimeout({{.}} * time.Millisecond), +{{/timeouts.connect}} {{/requestOptions}}) \ No newline at end of file diff --git a/templates/java/api.mustache b/templates/java/api.mustache index b3da496b69..5d7079600f 100644 --- a/templates/java/api.mustache +++ b/templates/java/api.mustache @@ -6,6 +6,7 @@ import {{invokerPackage}}.ApiClient; import {{invokerPackage}}.config.ClientOptions; import com.fasterxml.jackson.core.type.TypeReference; +import javax.annotation.Nullable; import okhttp3.Call; import okhttp3.Request; @@ -138,7 +139,7 @@ public class {{classname}} extends ApiClient { {{#vendorExtensions}}{{#x-is-generic}}* @param innerType The class held by the index, could be your custom class or {@link Object}.{{/x-is-generic}}{{/vendorExtensions}} * @param requestOptions The requestOptions to send along with the query, they will be merged with the transporter requestOptions. {{> api_javadoc}} - public {{> return_type}} {{operationId}}({{#requiredParams}}@Nonnull {{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/requiredParams}}{{#hasRequiredParams}}{{#hasOptionalParams}},{{/hasOptionalParams}}{{/hasRequiredParams}}{{#optionalParams}} {{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/optionalParams}}{{#vendorExtensions}}{{#x-is-generic}}, Class innerType{{/x-is-generic}}{{/vendorExtensions}}{{#hasParams}}, {{/hasParams}}RequestOptions requestOptions) throws AlgoliaRuntimeException { + public {{> return_type}} {{operationId}}({{#requiredParams}}@Nonnull {{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/requiredParams}}{{#hasRequiredParams}}{{#hasOptionalParams}},{{/hasOptionalParams}}{{/hasRequiredParams}}{{#optionalParams}} {{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/optionalParams}}{{#vendorExtensions}}{{#x-is-generic}}, Class innerType{{/x-is-generic}}{{/vendorExtensions}}{{#hasParams}}, {{/hasParams}}@Nullable RequestOptions requestOptions) throws AlgoliaRuntimeException { {{#returnType}}return {{/returnType}}LaunderThrowable.await({{operationId}}Async({{#allParams}}{{paramName}}, {{/allParams}}{{#vendorExtensions}}{{#x-is-generic}}innerType, {{/x-is-generic}}{{/vendorExtensions}}requestOptions)); {{^returnType}}return ;{{/returnType}} } @@ -161,7 +162,7 @@ public class {{classname}} extends ApiClient { {{#vendorExtensions}}{{#x-is-generic}}* @param innerType The class held by the index, could be your custom class or {@link Object}.{{/x-is-generic}}{{/vendorExtensions}} * @param requestOptions The requestOptions to send along with the query, they will be merged with the transporter requestOptions. {{> api_javadoc}} - public {{> return_type}} {{operationId}}({{#requiredParams}}@Nonnull {{{dataType}}} {{paramName}}, {{/requiredParams}}{{#vendorExtensions}}{{#x-is-generic}}Class innerType, {{/x-is-generic}}{{/vendorExtensions}}RequestOptions requestOptions) throws AlgoliaRuntimeException { + public {{> return_type}} {{operationId}}({{#requiredParams}}@Nonnull {{{dataType}}} {{paramName}}, {{/requiredParams}}{{#vendorExtensions}}{{#x-is-generic}}Class innerType, {{/x-is-generic}}{{/vendorExtensions}} @Nullable RequestOptions requestOptions) throws AlgoliaRuntimeException { {{#returnType}}return {{/returnType}}this.{{operationId}}({{#requiredParams}}{{paramName}}{{^-last}}, {{/-last}}{{/requiredParams}}{{#requiredParams.0}},{{/requiredParams.0}}{{#optionalParams}}null{{^-last}},{{/-last}}{{/optionalParams}}, {{#vendorExtensions}}{{#x-is-generic}}innerType, {{/x-is-generic}}{{/vendorExtensions}}requestOptions); } {{/optionalParams.0}} @@ -185,7 +186,7 @@ public class {{classname}} extends ApiClient { * @param innerType The class held by the index, could be your custom class or {@link Object}.{{/x-is-generic}}{{/vendorExtensions}} * @param requestOptions The requestOptions to send along with the query, they will be merged with the transporter requestOptions. {{> api_javadoc}} - public {{> return_type_async}} {{operationId}}Async({{#requiredParams}}@Nonnull {{{dataType}}} {{paramName}},{{/requiredParams}}{{#optionalParams}}{{{dataType}}} {{paramName}}, {{/optionalParams}}{{#vendorExtensions}}{{#x-is-generic}}Class innerType, {{/x-is-generic}}{{/vendorExtensions}}RequestOptions requestOptions) throws AlgoliaRuntimeException { + public {{> return_type_async}} {{operationId}}Async({{#requiredParams}}@Nonnull {{{dataType}}} {{paramName}},{{/requiredParams}}{{#optionalParams}}{{{dataType}}} {{paramName}}, {{/optionalParams}}{{#vendorExtensions}}{{#x-is-generic}}Class innerType, {{/x-is-generic}}{{/vendorExtensions}}@Nullable RequestOptions requestOptions) throws AlgoliaRuntimeException { {{#allParams}}{{#required}} Parameters.requireNonNull({{paramName}}, "Parameter `{{paramName}}` is required when calling `{{operationId}}`."); {{/required}}{{/allParams}} @@ -198,7 +199,7 @@ public class {{classname}} extends ApiClient { {{#headerParams}}.addHeader("{{baseName}}", {{paramName}}){{/headerParams}} {{#vendorExtensions}}{{#queryParams}}{{^x-is-custom-request}}.addQueryParameter("{{baseName}}", {{paramName}}){{/x-is-custom-request}}{{#x-is-custom-request}}.addQueryParameters(parameters){{/x-is-custom-request}}{{/queryParams}}{{/vendorExtensions}} .build(); - return executeAsync(request, requestOptions, {{#vendorExtensions}}{{#x-is-generic}}{{{returnType}}}.class, innerType{{/x-is-generic}}{{/vendorExtensions}}{{^vendorExtensions.x-is-generic}}{{^returnType}}null{{/returnType}}{{#returnType}}new TypeReference<{{{.}}}>(){}{{/returnType}}{{/vendorExtensions.x-is-generic}}); + return executeAsync(request, {{#vendorExtensions.x-timeouts}}new RequestOptions().setReadTimeout(Duration.ofMillis({{{read}}}L)).setWriteTimeout(Duration.ofMillis({{{write}}}L)).setConnectTimeout(Duration.ofMillis({{{connect}}}L)).mergeRight({{/vendorExtensions.x-timeouts}}requestOptions{{#vendorExtensions.x-timeouts}}){{/vendorExtensions.x-timeouts}}, {{#vendorExtensions}}{{#x-is-generic}}{{{returnType}}}.class, innerType{{/x-is-generic}}{{/vendorExtensions}}{{^vendorExtensions.x-is-generic}}{{^returnType}}null{{/returnType}}{{#returnType}}new TypeReference<{{{.}}}>(){}{{/returnType}}{{/vendorExtensions.x-is-generic}}); } {{! This case only sets `requestOptions` as optional }} @@ -221,7 +222,7 @@ public class {{classname}} extends ApiClient { * @param innerType The class held by the index, could be your custom class or {@link Object}.{{/x-is-generic}}{{/vendorExtensions}} * @param requestOptions The requestOptions to send along with the query, they will be merged with the transporter requestOptions. {{> api_javadoc}} - public {{> return_type_async}} {{operationId}}Async({{#requiredParams}}@Nonnull {{{dataType}}} {{paramName}}, {{/requiredParams}}{{#vendorExtensions}}{{#x-is-generic}}Class innerType, {{/x-is-generic}}{{/vendorExtensions}}RequestOptions requestOptions) throws AlgoliaRuntimeException { + public {{> return_type_async}} {{operationId}}Async({{#requiredParams}}@Nonnull {{{dataType}}} {{paramName}}, {{/requiredParams}}{{#vendorExtensions}}{{#x-is-generic}}Class innerType, {{/x-is-generic}}{{/vendorExtensions}}@Nullable RequestOptions requestOptions) throws AlgoliaRuntimeException { return this.{{operationId}}Async({{#requiredParams}}{{paramName}}{{^-last}}, {{/-last}}{{/requiredParams}}{{#requiredParams.0}},{{/requiredParams.0}}{{#optionalParams}}null{{^-last}},{{/-last}}{{/optionalParams}}, {{#vendorExtensions}}{{#x-is-generic}}innerType, {{/x-is-generic}}{{/vendorExtensions}}requestOptions); } {{/optionalParams.0}} diff --git a/templates/java/tests/client/client.mustache b/templates/java/tests/client/client.mustache index ba781fcc5c..0b202a209a 100644 --- a/templates/java/tests/client/client.mustache +++ b/templates/java/tests/client/client.mustache @@ -12,6 +12,7 @@ import com.algolia.api.{{client}}; import com.algolia.model.{{import}}.*; import com.algolia.config.*; import java.util.*; +import java.time.Duration; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/templates/java/tests/e2e/e2e.mustache b/templates/java/tests/e2e/e2e.mustache index 2dc150c0cc..3ef00c0501 100644 --- a/templates/java/tests/e2e/e2e.mustache +++ b/templates/java/tests/e2e/e2e.mustache @@ -10,6 +10,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.json.JsonMapper; import io.github.cdimascio.dotenv.Dotenv; import java.util.*; +import java.time.Duration; import org.junit.jupiter.api.*; import org.skyscreamer.jsonassert.JSONAssert; import org.skyscreamer.jsonassert.JSONCompareMode; diff --git a/templates/java/tests/method.mustache b/templates/java/tests/method.mustache index 4d324d3ecd..fcb1a03ca1 100644 --- a/templates/java/tests/method.mustache +++ b/templates/java/tests/method.mustache @@ -5,4 +5,13 @@ client.{{method}}({{#parametersWithDataType}}{{> tests/generateParams}}{{^-last} {{#requestOptions.headers.parametersWithDataType}} .addExtraHeader("{{{key}}}", "{{{value}}}") {{/requestOptions.headers.parametersWithDataType}} + {{#requestOptions.timeouts.read}} + .setReadTimeout(Duration.ofMillis({{.}}L)) + {{/requestOptions.timeouts.read}} + {{#requestOptions.timeouts.connect}} + .setConnectTimeout(Duration.ofMillis({{.}}L)) + {{/requestOptions.timeouts.connect}} + {{#requestOptions.timeouts.write}} + .setWriteTimeout(Duration.ofMillis({{.}}L)) + {{/requestOptions.timeouts.write}} {{/hasRequestOptions}}) \ No newline at end of file diff --git a/templates/java/tests/requests/requests.mustache b/templates/java/tests/requests/requests.mustache index ffe5da51de..90e6d265ad 100644 --- a/templates/java/tests/requests/requests.mustache +++ b/templates/java/tests/requests/requests.mustache @@ -16,6 +16,7 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.json.JsonMapper; import java.util.*; +import java.time.Duration; import org.junit.jupiter.api.*; import org.skyscreamer.jsonassert.JSONAssert; import org.skyscreamer.jsonassert.JSONCompareMode; diff --git a/templates/javascript/tests/method.mustache b/templates/javascript/tests/method.mustache index 6a90f1bd55..2880ac3fc9 100644 --- a/templates/javascript/tests/method.mustache +++ b/templates/javascript/tests/method.mustache @@ -1,3 +1,13 @@ {{#isAsyncMethod}}await {{/isAsyncMethod}}client.{{method}}({{#hasParams}}{{#parametersWithDataType.size}}{{{parameters}}}{{/parametersWithDataType.size}}{{/hasParams}}{{#hasRequestOptions}}, { - {{#requestOptions.queryParameters.parameters}}queryParameters: {{{.}}},{{/requestOptions.queryParameters.parameters}} - {{#requestOptions.headers.parameters}}headers: {{{.}}}{{/requestOptions.headers.parameters}}}{{/hasRequestOptions}}) \ No newline at end of file + {{#requestOptions.queryParameters.parameters}} + queryParameters: {{{.}}}, + {{/requestOptions.queryParameters.parameters}} + {{#requestOptions.headers.parameters}} + headers: {{{.}}}, + {{/requestOptions.headers.parameters}} + {{#requestOptions.timeouts.parameters}} + timeouts: {{{.}}} + {{/requestOptions.timeouts.parameters}} + } + {{/hasRequestOptions}} +) \ No newline at end of file diff --git a/templates/kotlin/api.mustache b/templates/kotlin/api.mustache index 139d792497..165cba8d6f 100644 --- a/templates/kotlin/api.mustache +++ b/templates/kotlin/api.mustache @@ -109,7 +109,11 @@ public class {{classname}}( ) return requester.execute( requestConfig = requestConfig, - requestOptions = requestOptions, + requestOptions = {{#vendorExtensions.x-timeouts}}RequestOptions( + readTimeout = {{{read}}}.milliseconds, + writeTimeout = {{{write}}}.milliseconds, + connectTimeout = {{{connect}}}.milliseconds, + ) + {{/vendorExtensions.x-timeouts}}requestOptions, ) } {{/operation}} diff --git a/templates/kotlin/tests/client/benchmark.mustache b/templates/kotlin/tests/client/benchmark.mustache index 3ba32d1e69..816a603090 100644 --- a/templates/kotlin/tests/client/benchmark.mustache +++ b/templates/kotlin/tests/client/benchmark.mustache @@ -12,6 +12,7 @@ import kotlinx.coroutines.test.* import kotlinx.serialization.json.* import kotlinx.serialization.encodeToString import kotlin.test.* +import kotlin.time.Duration.Companion.milliseconds class {{clientPrefix}}Test { diff --git a/templates/kotlin/tests/client/client.mustache b/templates/kotlin/tests/client/client.mustache index 968f92f58a..3a2160df35 100644 --- a/templates/kotlin/tests/client/client.mustache +++ b/templates/kotlin/tests/client/client.mustache @@ -14,6 +14,7 @@ import kotlinx.coroutines.test.* import kotlinx.serialization.json.* import kotlinx.serialization.encodeToString import kotlin.test.* +import kotlin.time.Duration.Companion.milliseconds class {{clientPrefix}}Test { diff --git a/templates/kotlin/tests/e2e/e2e.mustache b/templates/kotlin/tests/e2e/e2e.mustache index 12a66463b4..25d4c67f8e 100644 --- a/templates/kotlin/tests/e2e/e2e.mustache +++ b/templates/kotlin/tests/e2e/e2e.mustache @@ -14,6 +14,7 @@ import kotlinx.coroutines.test.* import kotlinx.serialization.json.* import kotlinx.serialization.encodeToString import kotlin.test.* +import kotlin.time.Duration.Companion.milliseconds class {{clientPrefix}}Test { diff --git a/templates/kotlin/tests/method.mustache b/templates/kotlin/tests/method.mustache index 8104a289bf..3a86f1c315 100644 --- a/templates/kotlin/tests/method.mustache +++ b/templates/kotlin/tests/method.mustache @@ -18,6 +18,15 @@ {{/parametersWithDataType}} }, {{/requestOptions.headers}} + {{#requestOptions.timeouts.connect}} + connectTimeout = {{.}}.milliseconds, + {{/requestOptions.timeouts.connect}} + {{#requestOptions.timeouts.read}} + readTimeout = {{.}}.milliseconds, + {{/requestOptions.timeouts.read}} + {{#requestOptions.timeouts.write}} + writeTimeout = {{.}}.milliseconds, + {{/requestOptions.timeouts.write}} ) {{/hasRequestOptions}} ) \ No newline at end of file diff --git a/templates/kotlin/tests/requests/requests.mustache b/templates/kotlin/tests/requests/requests.mustache index fb6b47a945..dd528b8f79 100644 --- a/templates/kotlin/tests/requests/requests.mustache +++ b/templates/kotlin/tests/requests/requests.mustache @@ -10,6 +10,7 @@ import io.ktor.http.* import kotlinx.coroutines.test.* import kotlinx.serialization.json.* import kotlin.test.* +import kotlin.time.Duration.Companion.milliseconds class {{clientPrefix}}Test { diff --git a/templates/php/api.mustache b/templates/php/api.mustache index 36f67fb78b..f0cb6f94bb 100644 --- a/templates/php/api.mustache +++ b/templates/php/api.mustache @@ -269,6 +269,18 @@ use Algolia\AlgoliaSearch\Exceptions\NotFoundException; {{/servers.0}} + {{#vendorExtensions.x-timeouts}} + if (!isset($requestOptions['readTimeout'])) { + $requestOptions['readTimeout'] = {{#lambda.toSeconds}}{{{read}}}{{/lambda.toSeconds}}; + } + if (!isset($requestOptions['writeTimeout'])) { + $requestOptions['writeTimeout'] = {{#lambda.toSeconds}}{{{write}}}{{/lambda.toSeconds}}; + } + if (!isset($requestOptions['connectTimeout'])) { + $requestOptions['connectTimeout'] = {{#lambda.toSeconds}}{{{connect}}}{{/lambda.toSeconds}}; + } + {{/vendorExtensions.x-timeouts}} + return $this->sendRequest('{{httpMethod}}', $resourcePath, $headers, $queryParameters, $httpBody, $requestOptions, {{#vendorExtensions.x-use-read-transporter}}true{{/vendorExtensions.x-use-read-transporter}}); } diff --git a/templates/php/client_config.mustache b/templates/php/client_config.mustache index 3bf07d9465..e1b326b492 100644 --- a/templates/php/client_config.mustache +++ b/templates/php/client_config.mustache @@ -61,9 +61,9 @@ class {{configClassname}} extends {{#hasRegionalHost}}ConfigWithRegion{{/hasRegi 'apiKey' => '', 'hosts' => null, 'hasFullHosts' => false, - 'readTimeout' => {{x-timeouts.server.read}}, - 'writeTimeout' => {{x-timeouts.server.write}}, - 'connectTimeout' => {{x-timeouts.server.connect}}, + 'readTimeout' => {{#lambda.toSeconds}}{{x-timeouts.server.read}}{{/lambda.toSeconds}}, + 'writeTimeout' => {{#lambda.toSeconds}}{{x-timeouts.server.write}}{{/lambda.toSeconds}}, + 'connectTimeout' => {{#lambda.toSeconds}}{{x-timeouts.server.connect}}{{/lambda.toSeconds}}, 'defaultHeaders' => [], {{#isSearchClient}} 'waitTaskTimeBeforeRetry' => $this->defaultWaitTaskTimeBeforeRetry, diff --git a/templates/php/tests/method.mustache b/templates/php/tests/method.mustache index e1ba5cd23f..df981e5dec 100644 --- a/templates/php/tests/method.mustache +++ b/templates/php/tests/method.mustache @@ -9,5 +9,15 @@ $client->{{^hasParams}}{{{method}}}(){{/hasParams}}{{#hasParams}}{{{method}}}({{ {{#requestOptions.headers.parametersWithDataType}} '{{{key}}}' => '{{{value}}}', {{/requestOptions.headers.parametersWithDataType}} - ]{{/requestOptions.headers.parameters}} + ], + {{/requestOptions.headers.parameters}} + {{#requestOptions.timeouts.connect}} + 'connectTimeout' => {{.}} / 1000, + {{/requestOptions.timeouts.connect}} + {{#requestOptions.timeouts.read}} + 'readTimeout' => {{.}} / 1000, + {{/requestOptions.timeouts.read}} + {{#requestOptions.timeouts.write}} + 'writeTimeout' => {{.}} / 1000, + {{/requestOptions.timeouts.write}} ]{{/hasRequestOptions}}){{/hasParams}} \ No newline at end of file diff --git a/templates/python/tests/method.mustache b/templates/python/tests/method.mustache index baa4401e8e..e776b9f111 100644 --- a/templates/python/tests/method.mustache +++ b/templates/python/tests/method.mustache @@ -1 +1 @@ -{{^isSyncClient}}await {{/isSyncClient}}_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 +{{^isSyncClient}}await {{/isSyncClient}}_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}}{{#requestOptions.timeouts.parameters}}"timeouts":loads("""{{{.}}}"""),{{/requestOptions.timeouts.parameters}} }{{/hasRequestOptions}}) \ No newline at end of file diff --git a/templates/ruby/api.mustache b/templates/ruby/api.mustache index 971b44e499..ec857f0e72 100644 --- a/templates/ruby/api.mustache +++ b/templates/ruby/api.mustache @@ -139,7 +139,8 @@ module {{moduleName}} header_params = header_params.merge(request_options[:header_params]) unless request_options[:header_params].nil? {{/vendorExtensions}} {{#vendorExtensions.x-timeouts}} - request_options[:timeout] ||= {{#vendorExtensions.x-use-read-transporter}}{{vendorExtensions.x-timeouts.read}}{{/vendorExtensions.x-use-read-transporter}} {{^vendorExtensions.x-use-read-transporter}}{{vendorExtensions.x-timeouts.write}}{{/vendorExtensions.x-use-read-transporter}} + request_options[:timeout] ||= {{#vendorExtensions.x-use-read-transporter}}{{read}}{{/vendorExtensions.x-use-read-transporter}} {{^vendorExtensions.x-use-read-transporter}}{{write}}{{/vendorExtensions.x-use-read-transporter}} + request_options[:connect_timeout] ||= {{connect}} {{/vendorExtensions.x-timeouts}} post_body = request_options[:debug_body]{{#bodyParam}} || @api_client.object_to_http_body({{{paramName}}}){{/bodyParam}} diff --git a/templates/ruby/tests/method.mustache b/templates/ruby/tests/method.mustache index 91dc191dbc..d400d9f3d7 100644 --- a/templates/ruby/tests/method.mustache +++ b/templates/ruby/tests/method.mustache @@ -1 +1 @@ -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 +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}}{{#requestOptions.timeouts.connect}}:connect_timeout => {{{.}}},{{/requestOptions.timeouts.connect}}{{#requestOptions.timeouts.write}}:timeout => {{{.}}},{{/requestOptions.timeouts.write}} }{{/hasRequestOptions}}) \ No newline at end of file diff --git a/templates/scala/api.mustache b/templates/scala/api.mustache index 76a49e8387..912ff511de 100644 --- a/templates/scala/api.mustache +++ b/templates/scala/api.mustache @@ -49,15 +49,15 @@ object {{classname}} { ) private def readTimeout(): Duration = { - Duration({{#x-timeouts.server.read}}{{{.}}}{{/x-timeouts.server.read}}, TimeUnit.SECONDS) + Duration({{x-timeouts.server.read}}, TimeUnit.MILLISECONDS) } private def connectTimeout(): Duration = { - Duration({{#x-timeouts.server.connect}}{{{.}}}{{/x-timeouts.server.connect}}, TimeUnit.SECONDS) + Duration({{x-timeouts.server.connect}}, TimeUnit.MILLISECONDS) } private def writeTimeout(): Duration = { - Duration({{#x-timeouts.server.write}}{{{.}}}{{/x-timeouts.server.write}}, TimeUnit.SECONDS) + Duration({{x-timeouts.server.write}}, TimeUnit.MILLISECONDS) } {{#hasRegionalHost}} @@ -151,7 +151,7 @@ class {{classname}}( {{#vendorExtensions.x-use-read-transporter}}.withRead(true){{/vendorExtensions.x-use-read-transporter}} {{#vendorExtensions}}{{#queryParams}}{{^x-is-custom-request}}.withQueryParameter("{{baseName}}", {{paramName}}){{/x-is-custom-request}}{{#x-is-custom-request}}.withQueryParameters(parameters){{/x-is-custom-request}}{{/queryParams}}{{/vendorExtensions}} .build() - execute[{{#vendorExtensions}}{{#x-is-custom-request}}T{{/x-is-custom-request}}{{^x-is-custom-request}}{{{returnType}}}{{^returnType}}Unit{{/returnType}}{{/x-is-custom-request}}{{/vendorExtensions}}](request, requestOptions) + execute[{{#vendorExtensions}}{{#x-is-custom-request}}T{{/x-is-custom-request}}{{^x-is-custom-request}}{{{returnType}}}{{^returnType}}Unit{{/returnType}}{{/x-is-custom-request}}{{/vendorExtensions}}](request, {{#vendorExtensions.x-timeouts}}Some(RequestOptions(writeTimeout = Some(Duration({{write}}, TimeUnit.MILLISECONDS)), readTimeout = Some(Duration({{read}}, TimeUnit.MILLISECONDS)), connectTimeout = Some(Duration({{connect}}, TimeUnit.MILLISECONDS))) + {{/vendorExtensions.x-timeouts}}requestOptions{{#vendorExtensions.x-timeouts}}){{/vendorExtensions.x-timeouts}}) } {{/operation}} diff --git a/templates/scala/tests/client/client.mustache b/templates/scala/tests/client/client.mustache index 7a9d73d8ec..2a3a78ae3e 100644 --- a/templates/scala/tests/client/client.mustache +++ b/templates/scala/tests/client/client.mustache @@ -12,6 +12,7 @@ import org.json4s.native.Serialization import org.json4s.native.Serialization.write import org.scalatest.funsuite.AnyFunSuite +import java.util.concurrent.TimeUnit import scala.concurrent.duration.Duration import scala.concurrent.{Await, ExecutionContextExecutor} diff --git a/templates/scala/tests/e2e/e2e.mustache b/templates/scala/tests/e2e/e2e.mustache index 6bbbbf5e61..05bebc51f5 100644 --- a/templates/scala/tests/e2e/e2e.mustache +++ b/templates/scala/tests/e2e/e2e.mustache @@ -13,6 +13,7 @@ import org.skyscreamer.jsonassert.JSONCompareMode import org.json4s.native.Serialization import org.json4s.native.Serialization.write +import java.util.concurrent.TimeUnit import scala.concurrent.duration.Duration import scala.concurrent.{Await, ExecutionContextExecutor} diff --git a/templates/scala/tests/method.mustache b/templates/scala/tests/method.mustache index f479e7f571..75d5a27d1d 100644 --- a/templates/scala/tests/method.mustache +++ b/templates/scala/tests/method.mustache @@ -10,6 +10,15 @@ client.{{method}}{{#isCustomRequest}}[JObject]{{/isCustomRequest}}( {{#requestOptions.headers.parametersWithDataType}} .withHeader("{{{key}}}", "{{{value}}}") {{/requestOptions.headers.parametersWithDataType}} +{{#requestOptions.timeouts.connect}} + .withConnectTimeout(Some(Duration({{.}}, TimeUnit.MILLISECONDS))) +{{/requestOptions.timeouts.connect}} +{{#requestOptions.timeouts.read}} + .withReadTimeout(Some(Duration({{.}}, TimeUnit.MILLISECONDS))) +{{/requestOptions.timeouts.read}} +{{#requestOptions.timeouts.write}} + .withWriteTimeout(Some(Duration({{.}}, TimeUnit.MILLISECONDS))) +{{/requestOptions.timeouts.write}} .build()) {{/hasRequestOptions}} ) \ No newline at end of file diff --git a/templates/scala/tests/requests/requests.mustache b/templates/scala/tests/requests/requests.mustache index baafbc1cd2..9cf6795473 100644 --- a/templates/scala/tests/requests/requests.mustache +++ b/templates/scala/tests/requests/requests.mustache @@ -9,6 +9,7 @@ import org.json4s.* import org.json4s.native.JsonParser.* import org.scalatest.funsuite.AnyFunSuite +import java.util.concurrent.TimeUnit import scala.concurrent.duration.Duration import scala.concurrent.{Await, ExecutionContextExecutor} diff --git a/templates/swift/api.mustache b/templates/swift/api.mustache index aec1bfff96..1686fe47c4 100644 --- a/templates/swift/api.mustache +++ b/templates/swift/api.mustache @@ -143,7 +143,12 @@ import Foundation method: "{{httpMethod}}", path: resourcePath, data: body{{#bodyParam}}{{^required}} ?? AnyCodable(){{/required}}{{/bodyParam}}, - requestOptions: RequestOptions(headers: headers, queryParameters: queryParameters) + userRequestOptions{{#vendorExtensions.x-use-read-transporter}}, + requestOptions: RequestOptions( + headers: headers, + queryParameters: queryParameters{{#vendorExtensions.x-timeouts}}, + readTimeout: {{#lambda.toSeconds}}{{{read}}}{{/lambda.toSeconds}}, + writeTimeout: {{#lambda.toSeconds}}{{{write}}}{{/lambda.toSeconds}}{{/vendorExtensions.x-timeouts}} + ) + userRequestOptions{{#vendorExtensions.x-use-read-transporter}}, useReadTransporter: true{{/vendorExtensions.x-use-read-transporter}} ) }{{/vendorExtensions}} diff --git a/templates/swift/client_configuration.mustache b/templates/swift/client_configuration.mustache index 6e2a742eb2..cffd1509a5 100644 --- a/templates/swift/client_configuration.mustache +++ b/templates/swift/client_configuration.mustache @@ -25,8 +25,8 @@ public struct {{#lambda.client-to-name}}{{{client}}}{{/lambda.client-to-name}}Cl public init(appID: String, apiKey: String,{{#hasRegionalHost}} region: Region{{#fallbackToAliasHost}}? = nil{{/fallbackToAliasHost}},{{/hasRegionalHost}} - writeTimeout: TimeInterval = {{x-timeouts.server.write}}, - readTimeout: TimeInterval = {{x-timeouts.server.read}}, + writeTimeout: TimeInterval = {{#lambda.toSeconds}}{{x-timeouts.server.write}}{{/lambda.toSeconds}}, + readTimeout: TimeInterval = {{#lambda.toSeconds}}{{x-timeouts.server.read}}{{/lambda.toSeconds}}, logLevel: LogLevel = DefaultConfiguration.default.logLevel, defaultHeaders: [String: String]? = DefaultConfiguration.default.defaultHeaders, hosts: [RetryableHost]? = nil{{#isSearchClient}}, diff --git a/templates/swift/tests/method.mustache b/templates/swift/tests/method.mustache index 4034c5d3f5..2236b17fcc 100644 --- a/templates/swift/tests/method.mustache +++ b/templates/swift/tests/method.mustache @@ -1,5 +1,14 @@ -try {{#isAsyncMethod}}await {{/isAsyncMethod}}client.{{method}}{{^isHelper}}WithHTTPInfo{{/isHelper}}({{#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}}) \ No newline at end of file +try {{#isAsyncMethod}}await {{/isAsyncMethod}}client.{{method}}{{^isHelper}}WithHTTPInfo{{/isHelper}}({{#hasParams}}{{#parametersWithDataType}}{{> tests/generateParams }}{{^-last}}, {{/-last}}{{/parametersWithDataType}}{{/hasParams}}{{#hasRequestOptions}}{{#requestOptions}}, requestOptions: RequestOptions( + {{#headers}} + headers: [{{#parametersWithDataType}}"{{key}}": {{> tests/paramValue }}{{^-last}}, {{/-last}}{{/parametersWithDataType}}]{{#queryParameters}},{{/queryParameters}} + {{/headers}} + {{#queryParameters}} + queryParameters: [{{#parametersWithDataType}}"{{key}}": {{> tests/paramValue }}{{^-last}}, {{/-last}}{{/parametersWithDataType}}] + {{/queryParameters}} + {{#timeouts.read}} + readTimeout: {{.}} / 1000 + {{/timeouts.read}} + {{#timeouts.write}} + writeTimeout: {{.}} / 1000 + {{/timeouts.write}} +){{/requestOptions}}{{/hasRequestOptions}}) \ No newline at end of file diff --git a/tests/CTS/client/ingestion/api.json b/tests/CTS/client/ingestion/api.json index 474b906fca..cb250eb5b9 100644 --- a/tests/CTS/client/ingestion/api.json +++ b/tests/CTS/client/ingestion/api.json @@ -76,5 +76,52 @@ } } ] + }, + { + "testName": "endpoint level timeout", + "steps": [ + { + "type": "method", + "method": "validateSourceBeforeUpdate", + "parameters": { + "sourceID": "6c02aeb1-775e-418e-870b-1faccd4b2c0f", + "sourceUpdate": { + "name": "newName" + } + }, + "expected": { + "type": "timeouts", + "match": { + "connectTimeout": 180000, + "responseTimeout": 180000 + } + } + } + ] + }, + { + "testName": "can override endpoint level timeout", + "steps": [ + { + "type": "method", + "method": "validateSourceBeforeUpdate", + "parameters": { + "sourceID": "6c02aeb1-775e-418e-870b-1faccd4b2c0f", + "sourceUpdate": { + "name": "newName" + } + }, + "requestOptions": { + "writeTimeout": 3456 + }, + "expected": { + "type": "timeouts", + "match": { + "connectTimeout": 180000, + "responseTimeout": 3456 + } + } + } + ] } ] diff --git a/tests/output/dart/lib/src/run.dart b/tests/output/dart/lib/src/run.dart index 97aedf9d0f..2f433a4ace 100644 --- a/tests/output/dart/lib/src/run.dart +++ b/tests/output/dart/lib/src/run.dart @@ -25,6 +25,7 @@ Future runTest({ /// Uses provided function to intercept HTTP requests. class RequestInterceptor extends Requester { String apiKey = "fake"; + Duration connectTimeout = Duration(seconds: 30); Function(HttpRequest) onRequest = (_) {}; @@ -46,6 +47,11 @@ class RequestInterceptor extends Requester { void setClientApiKey(String apiKey) { this.apiKey = apiKey; } + + @override + void setConnectTimeout(Duration connectTimeout) { + this.connectTimeout = connectTimeout; + } } /// [InterceptionException] implements [Exception].