diff --git a/omnistack-kotlin-client-okhttp/src/main/kotlin/com/omnistack/api/client/okhttp/OkHttpClient.kt b/omnistack-kotlin-client-okhttp/src/main/kotlin/com/omnistack/api/client/okhttp/OkHttpClient.kt index e58b8e2..a4c4eed 100644 --- a/omnistack-kotlin-client-okhttp/src/main/kotlin/com/omnistack/api/client/okhttp/OkHttpClient.kt +++ b/omnistack-kotlin-client-okhttp/src/main/kotlin/com/omnistack/api/client/okhttp/OkHttpClient.kt @@ -80,7 +80,7 @@ private constructor(private val okHttpClient: okhttp3.OkHttpClient, private val private fun HttpRequest.toRequest(): Request { var body: RequestBody? = body?.toRequestBody() - // OkHttpClient always requires a request body for PUT and POST methods + // OkHttpClient always requires a request body for PUT and POST methods. if (body == null && (method == HttpMethod.PUT || method == HttpMethod.POST)) { body = "".toRequestBody() } @@ -108,21 +108,13 @@ private constructor(private val okHttpClient: okhttp3.OkHttpClient, private val val length = contentLength() return object : RequestBody() { - override fun contentType(): MediaType? { - return mediaType - } + override fun contentType(): MediaType? = mediaType - override fun contentLength(): Long { - return length - } + override fun contentLength(): Long = length - override fun isOneShot(): Boolean { - return !repeatable() - } + override fun isOneShot(): Boolean = !repeatable() - override fun writeTo(sink: BufferedSink) { - writeTo(sink.outputStream()) - } + override fun writeTo(sink: BufferedSink) = writeTo(sink.outputStream()) } } @@ -130,21 +122,13 @@ private constructor(private val okHttpClient: okhttp3.OkHttpClient, private val val headers = headers.toHeaders() return object : HttpResponse { - override fun statusCode(): Int { - return code - } + override fun statusCode(): Int = code - override fun headers(): ListMultimap { - return headers - } + override fun headers(): ListMultimap = headers - override fun body(): InputStream { - return body!!.byteStream() - } + override fun body(): InputStream = body!!.byteStream() - override fun close() { - body!!.close() - } + override fun close() = body!!.close() } } @@ -153,9 +137,7 @@ private constructor(private val okHttpClient: okhttp3.OkHttpClient, private val MultimapBuilder.treeKeys(String.CASE_INSENSITIVE_ORDER) .arrayListValues() .build() - forEach { pair -> headers.put(pair.first, pair.second) } - return headers } @@ -166,7 +148,7 @@ private constructor(private val okHttpClient: okhttp3.OkHttpClient, private val class Builder { private var baseUrl: HttpUrl? = null - // default timeout is 1 minute + // The default timeout is 1 minute. private var timeout: Duration = Duration.ofSeconds(60) private var proxy: Proxy? = null @@ -176,8 +158,8 @@ private constructor(private val okHttpClient: okhttp3.OkHttpClient, private val fun proxy(proxy: Proxy?) = apply { this.proxy = proxy } - fun build(): OkHttpClient { - return OkHttpClient( + fun build(): OkHttpClient = + OkHttpClient( okhttp3.OkHttpClient.Builder() .connectTimeout(timeout) .readTimeout(timeout) @@ -187,7 +169,6 @@ private constructor(private val okHttpClient: okhttp3.OkHttpClient, private val .build(), checkNotNull(baseUrl) { "`baseUrl` is required but was not set" }, ) - } } private suspend fun Call.executeAsync(): Response = diff --git a/omnistack-kotlin-client-okhttp/src/main/kotlin/com/omnistack/api/client/okhttp/OmnistackOkHttpClient.kt b/omnistack-kotlin-client-okhttp/src/main/kotlin/com/omnistack/api/client/okhttp/OmnistackOkHttpClient.kt index c686a0e..edbc614 100644 --- a/omnistack-kotlin-client-okhttp/src/main/kotlin/com/omnistack/api/client/okhttp/OmnistackOkHttpClient.kt +++ b/omnistack-kotlin-client-okhttp/src/main/kotlin/com/omnistack/api/client/okhttp/OmnistackOkHttpClient.kt @@ -23,7 +23,7 @@ class OmnistackOkHttpClient private constructor() { private var clientOptions: ClientOptions.Builder = ClientOptions.builder() private var baseUrl: String = ClientOptions.PRODUCTION_URL - // default timeout for client is 1 minute + // The default timeout for the client is 1 minute. private var timeout: Duration = Duration.ofSeconds(60) private var proxy: Proxy? = null @@ -52,6 +52,24 @@ class OmnistackOkHttpClient private constructor() { fun removeHeader(name: String) = apply { clientOptions.removeHeader(name) } + fun queryParams(queryParams: Map>) = apply { + clientOptions.queryParams(queryParams) + } + + fun putQueryParam(key: String, value: String) = apply { + clientOptions.putQueryParam(key, value) + } + + fun putQueryParams(key: String, values: Iterable) = apply { + clientOptions.putQueryParams(key, values) + } + + fun putAllQueryParams(queryParams: Map>) = apply { + clientOptions.putAllQueryParams(queryParams) + } + + fun removeQueryParam(key: String) = apply { clientOptions.removeQueryParam(key) } + fun timeout(timeout: Duration) = apply { this.timeout = timeout } fun maxRetries(maxRetries: Int) = apply { clientOptions.maxRetries(maxRetries) } @@ -66,8 +84,8 @@ class OmnistackOkHttpClient private constructor() { fun fromEnv() = apply { clientOptions.fromEnv() } - fun build(): OmnistackClient { - return OmnistackClientImpl( + fun build(): OmnistackClient = + OmnistackClientImpl( clientOptions .httpClient( OkHttpClient.builder() @@ -78,6 +96,5 @@ class OmnistackOkHttpClient private constructor() { ) .build() ) - } } } diff --git a/omnistack-kotlin-client-okhttp/src/main/kotlin/com/omnistack/api/client/okhttp/OmnistackOkHttpClientAsync.kt b/omnistack-kotlin-client-okhttp/src/main/kotlin/com/omnistack/api/client/okhttp/OmnistackOkHttpClientAsync.kt index 9f17683..74af000 100644 --- a/omnistack-kotlin-client-okhttp/src/main/kotlin/com/omnistack/api/client/okhttp/OmnistackOkHttpClientAsync.kt +++ b/omnistack-kotlin-client-okhttp/src/main/kotlin/com/omnistack/api/client/okhttp/OmnistackOkHttpClientAsync.kt @@ -23,7 +23,7 @@ class OmnistackOkHttpClientAsync private constructor() { private var clientOptions: ClientOptions.Builder = ClientOptions.builder() private var baseUrl: String = ClientOptions.PRODUCTION_URL - // default timeout for client is 1 minute + // The default timeout for the client is 1 minute. private var timeout: Duration = Duration.ofSeconds(60) private var proxy: Proxy? = null @@ -52,6 +52,24 @@ class OmnistackOkHttpClientAsync private constructor() { fun removeHeader(name: String) = apply { clientOptions.removeHeader(name) } + fun queryParams(queryParams: Map>) = apply { + clientOptions.queryParams(queryParams) + } + + fun putQueryParam(key: String, value: String) = apply { + clientOptions.putQueryParam(key, value) + } + + fun putQueryParams(key: String, values: Iterable) = apply { + clientOptions.putQueryParams(key, values) + } + + fun putAllQueryParams(queryParams: Map>) = apply { + clientOptions.putAllQueryParams(queryParams) + } + + fun removeQueryParam(key: String) = apply { clientOptions.removeQueryParam(key) } + fun timeout(timeout: Duration) = apply { this.timeout = timeout } fun maxRetries(maxRetries: Int) = apply { clientOptions.maxRetries(maxRetries) } @@ -66,8 +84,8 @@ class OmnistackOkHttpClientAsync private constructor() { fun fromEnv() = apply { clientOptions.fromEnv() } - fun build(): OmnistackClientAsync { - return OmnistackClientAsyncImpl( + fun build(): OmnistackClientAsync = + OmnistackClientAsyncImpl( clientOptions .httpClient( OkHttpClient.builder() @@ -78,6 +96,5 @@ class OmnistackOkHttpClientAsync private constructor() { ) .build() ) - } } } diff --git a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/client/OmnistackClient.kt b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/client/OmnistackClient.kt index 7e0e1e8..8f1caca 100644 --- a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/client/OmnistackClient.kt +++ b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/client/OmnistackClient.kt @@ -1,7 +1,5 @@ // File generated from our OpenAPI spec by Stainless. -@file:Suppress("OVERLOADS_INTERFACE") // See https://youtrack.jetbrains.com/issue/KT-36102 - package com.omnistack.api.client import com.omnistack.api.models.* diff --git a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/client/OmnistackClientAsync.kt b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/client/OmnistackClientAsync.kt index 7be4583..40a86dc 100644 --- a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/client/OmnistackClientAsync.kt +++ b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/client/OmnistackClientAsync.kt @@ -1,7 +1,5 @@ // File generated from our OpenAPI spec by Stainless. -@file:Suppress("OVERLOADS_INTERFACE") // See https://youtrack.jetbrains.com/issue/KT-36102 - package com.omnistack.api.client import com.omnistack.api.models.* diff --git a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/client/OmnistackClientAsyncImpl.kt b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/client/OmnistackClientAsyncImpl.kt index 769a258..da7ac3f 100644 --- a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/client/OmnistackClientAsyncImpl.kt +++ b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/client/OmnistackClientAsyncImpl.kt @@ -3,6 +3,7 @@ package com.omnistack.api.client import com.omnistack.api.core.ClientOptions +import com.omnistack.api.core.getPackageVersion import com.omnistack.api.models.* import com.omnistack.api.services.async.* @@ -11,12 +12,21 @@ constructor( private val clientOptions: ClientOptions, ) : OmnistackClientAsync { + private val clientOptionsWithUserAgent = + if (clientOptions.headers.containsKey("User-Agent")) clientOptions + else + clientOptions + .toBuilder() + .putHeader("User-Agent", "${javaClass.simpleName}/Kotlin ${getPackageVersion()}") + .build() + + // Pass the original clientOptions so that this client sets its own User-Agent. private val sync: OmnistackClient by lazy { OmnistackClientImpl(clientOptions) } - private val chats: ChatServiceAsync by lazy { ChatServiceAsyncImpl(clientOptions) } + private val chats: ChatServiceAsync by lazy { ChatServiceAsyncImpl(clientOptionsWithUserAgent) } private val completions: CompletionServiceAsync by lazy { - CompletionServiceAsyncImpl(clientOptions) + CompletionServiceAsyncImpl(clientOptionsWithUserAgent) } override fun sync(): OmnistackClient = sync diff --git a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/client/OmnistackClientImpl.kt b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/client/OmnistackClientImpl.kt index 32366df..f01afba 100644 --- a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/client/OmnistackClientImpl.kt +++ b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/client/OmnistackClientImpl.kt @@ -3,6 +3,7 @@ package com.omnistack.api.client import com.omnistack.api.core.ClientOptions +import com.omnistack.api.core.getPackageVersion import com.omnistack.api.models.* import com.omnistack.api.services.blocking.* @@ -11,11 +12,22 @@ constructor( private val clientOptions: ClientOptions, ) : OmnistackClient { + private val clientOptionsWithUserAgent = + if (clientOptions.headers.containsKey("User-Agent")) clientOptions + else + clientOptions + .toBuilder() + .putHeader("User-Agent", "${javaClass.simpleName}/Kotlin ${getPackageVersion()}") + .build() + + // Pass the original clientOptions so that this client sets its own User-Agent. private val async: OmnistackClientAsync by lazy { OmnistackClientAsyncImpl(clientOptions) } - private val chats: ChatService by lazy { ChatServiceImpl(clientOptions) } + private val chats: ChatService by lazy { ChatServiceImpl(clientOptionsWithUserAgent) } - private val completions: CompletionService by lazy { CompletionServiceImpl(clientOptions) } + private val completions: CompletionService by lazy { + CompletionServiceImpl(clientOptionsWithUserAgent) + } override fun async(): OmnistackClientAsync = async diff --git a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/core/ClientOptions.kt b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/core/ClientOptions.kt index 7605cfc..da1abc8 100644 --- a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/core/ClientOptions.kt +++ b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/core/ClientOptions.kt @@ -6,21 +6,26 @@ import com.fasterxml.jackson.databind.json.JsonMapper import com.google.common.collect.ArrayListMultimap import com.google.common.collect.ListMultimap import com.omnistack.api.core.http.HttpClient +import com.omnistack.api.core.http.PhantomReachableClosingHttpClient import com.omnistack.api.core.http.RetryingHttpClient import java.time.Clock class ClientOptions private constructor( + private val originalHttpClient: HttpClient, val httpClient: HttpClient, val jsonMapper: JsonMapper, val clock: Clock, val baseUrl: String, - val apiKey: String, val headers: ListMultimap, val queryParams: ListMultimap, val responseValidation: Boolean, + val maxRetries: Int, + val apiKey: String, ) { + fun toBuilder() = Builder().from(this) + companion object { const val PRODUCTION_URL = "https://api.omnistack.sh/openai/v1" @@ -33,60 +38,68 @@ private constructor( class Builder { private var httpClient: HttpClient? = null - private var jsonMapper: JsonMapper? = null + private var jsonMapper: JsonMapper = jsonMapper() private var clock: Clock = Clock.systemUTC() private var baseUrl: String = PRODUCTION_URL - private var headers: MutableMap> = mutableMapOf() - private var queryParams: MutableMap> = mutableMapOf() + private var headers: ListMultimap = ArrayListMultimap.create() + private var queryParams: ListMultimap = ArrayListMultimap.create() private var responseValidation: Boolean = false private var maxRetries: Int = 2 private var apiKey: String? = null + internal fun from(clientOptions: ClientOptions) = apply { + httpClient = clientOptions.originalHttpClient + jsonMapper = clientOptions.jsonMapper + clock = clientOptions.clock + baseUrl = clientOptions.baseUrl + headers = ArrayListMultimap.create(clientOptions.headers) + queryParams = ArrayListMultimap.create(clientOptions.queryParams) + responseValidation = clientOptions.responseValidation + maxRetries = clientOptions.maxRetries + apiKey = clientOptions.apiKey + } + fun httpClient(httpClient: HttpClient) = apply { this.httpClient = httpClient } fun jsonMapper(jsonMapper: JsonMapper) = apply { this.jsonMapper = jsonMapper } - fun baseUrl(baseUrl: String) = apply { this.baseUrl = baseUrl } - fun clock(clock: Clock) = apply { this.clock = clock } + fun baseUrl(baseUrl: String) = apply { this.baseUrl = baseUrl } + fun headers(headers: Map>) = apply { this.headers.clear() putAllHeaders(headers) } - fun putHeader(name: String, value: String) = apply { - this.headers.getOrPut(name) { mutableListOf() }.add(value) - } + fun putHeader(name: String, value: String) = apply { headers.put(name, value) } fun putHeaders(name: String, values: Iterable) = apply { - this.headers.getOrPut(name) { mutableListOf() }.addAll(values) + headers.putAll(name, values) } fun putAllHeaders(headers: Map>) = apply { - headers.forEach(this::putHeaders) + headers.forEach(::putHeaders) } - fun removeHeader(name: String) = apply { this.headers.put(name, mutableListOf()) } + fun removeHeader(name: String) = apply { headers.removeAll(name) } fun queryParams(queryParams: Map>) = apply { this.queryParams.clear() putAllQueryParams(queryParams) } - fun putQueryParam(name: String, value: String) = apply { - this.queryParams.getOrPut(name) { mutableListOf() }.add(value) - } + fun putQueryParam(key: String, value: String) = apply { queryParams.put(key, value) } - fun putQueryParams(name: String, values: Iterable) = apply { - this.queryParams.getOrPut(name) { mutableListOf() }.addAll(values) + fun putQueryParams(key: String, values: Iterable) = apply { + queryParams.putAll(key, values) } fun putAllQueryParams(queryParams: Map>) = apply { - queryParams.forEach(this::putQueryParams) + queryParams.forEach(::putQueryParams) } - fun removeQueryParam(name: String) = apply { this.queryParams.put(name, mutableListOf()) } + fun removeQueryParam(key: String) = apply { queryParams.removeAll(key) } fun responseValidation(responseValidation: Boolean) = apply { this.responseValidation = responseValidation @@ -109,26 +122,31 @@ private constructor( headers.put("X-Stainless-OS", getOsName()) headers.put("X-Stainless-OS-Version", getOsVersion()) headers.put("X-Stainless-Package-Version", getPackageVersion()) + headers.put("X-Stainless-Runtime", "JRE") headers.put("X-Stainless-Runtime-Version", getJavaVersion()) if (!apiKey.isNullOrEmpty()) { headers.put("Authorization", "Bearer ${apiKey}") } - this.headers.forEach(headers::replaceValues) - this.queryParams.forEach(queryParams::replaceValues) + this.headers.asMap().forEach(headers::replaceValues) + this.queryParams.asMap().forEach(queryParams::replaceValues) return ClientOptions( - RetryingHttpClient.builder() - .httpClient(httpClient!!) - .clock(clock) - .maxRetries(maxRetries) - .build(), - jsonMapper ?: jsonMapper(), + httpClient!!, + PhantomReachableClosingHttpClient( + RetryingHttpClient.builder() + .httpClient(httpClient!!) + .clock(clock) + .maxRetries(maxRetries) + .build() + ), + jsonMapper, clock, baseUrl, - apiKey!!, - headers.toUnmodifiable(), - queryParams.toUnmodifiable(), + headers.toImmutable(), + queryParams.toImmutable(), responseValidation, + maxRetries, + apiKey!!, ) } } diff --git a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/core/PhantomReachable.kt b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/core/PhantomReachable.kt new file mode 100644 index 0000000..db2978d --- /dev/null +++ b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/core/PhantomReachable.kt @@ -0,0 +1,45 @@ +@file:JvmName("PhantomReachable") + +package com.omnistack.api.core + +import com.omnistack.api.errors.OmnistackException +import java.lang.reflect.InvocationTargetException + +/** + * Closes [closeable] when [observed] becomes only phantom reachable. + * + * This is a wrapper around a Java 9+ [java.lang.ref.Cleaner], or a no-op in older Java versions. + */ +internal fun closeWhenPhantomReachable(observed: Any, closeable: AutoCloseable) { + check(observed !== closeable) { + "`observed` cannot be the same object as `closeable` because it would never become phantom reachable" + } + closeWhenPhantomReachable?.let { it(observed, closeable::close) } +} + +private val closeWhenPhantomReachable: ((Any, AutoCloseable) -> Unit)? by lazy { + try { + val cleanerClass = Class.forName("java.lang.ref.Cleaner") + val cleanerCreate = cleanerClass.getMethod("create") + val cleanerRegister = + cleanerClass.getMethod("register", Any::class.java, Runnable::class.java) + val cleanerObject = cleanerCreate.invoke(null); + + { observed, closeable -> + try { + cleanerRegister.invoke(cleanerObject, observed, Runnable { closeable.close() }) + } catch (e: ReflectiveOperationException) { + if (e is InvocationTargetException) { + when (val cause = e.cause) { + is RuntimeException, + is Error -> throw cause + } + } + throw OmnistackException("Unexpected reflective invocation failure", e) + } + } + } catch (e: ReflectiveOperationException) { + // We're running Java 8, which has no Cleaner. + null + } +} diff --git a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/core/Utils.kt b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/core/Utils.kt index b236402..312ec0b 100644 --- a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/core/Utils.kt +++ b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/core/Utils.kt @@ -4,53 +4,24 @@ package com.omnistack.api.core import com.google.common.collect.ImmutableListMultimap import com.google.common.collect.ListMultimap -import com.google.common.collect.Multimaps import com.omnistack.api.errors.OmnistackInvalidDataException import java.util.Collections +import java.util.SortedMap -internal fun T?.getOrThrow(name: String): T { - if (this == null) { - throw OmnistackInvalidDataException("'${name}' is not present") - } - - return this -} - -internal fun List.toUnmodifiable(): List { - if (isEmpty()) { - return Collections.emptyList() - } - - return Collections.unmodifiableList(this) -} - -internal fun Map.toUnmodifiable(): Map { - if (isEmpty()) { - return Collections.emptyMap() - } - - return Collections.unmodifiableMap(this) -} - -internal fun ListMultimap.toUnmodifiable(): ListMultimap { - if (isEmpty()) { - return ImmutableListMultimap.of() - } - - return Multimaps.unmodifiableListMultimap(this) -} - -internal fun ListMultimap.getRequiredHeader(header: String): String { - val value = - entries() - .stream() - .filter { entry -> entry.key.equals(header, ignoreCase = true) } - .map { entry -> entry.value } - .findFirst() - if (!value.isPresent) { - throw OmnistackInvalidDataException("Could not find $header header") - } - return value.get() -} +internal fun T?.getOrThrow(name: String): T = + this ?: throw OmnistackInvalidDataException("`${name}` is not present") + +internal fun List.toImmutable(): List = + if (isEmpty()) Collections.emptyList() else Collections.unmodifiableList(toList()) + +internal fun Map.toImmutable(): Map = + if (isEmpty()) Collections.emptyMap() else Collections.unmodifiableMap(toMap()) + +internal fun , V> SortedMap.toImmutable(): SortedMap = + if (isEmpty()) Collections.emptySortedMap() + else Collections.unmodifiableSortedMap(toSortedMap(comparator())) + +internal fun ListMultimap.toImmutable(): ListMultimap = + ImmutableListMultimap.copyOf(this) internal interface Enum diff --git a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/core/Values.kt b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/core/Values.kt index 32ef01a..ad76968 100644 --- a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/core/Values.kt +++ b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/core/Values.kt @@ -138,6 +138,8 @@ sealed class JsonField { // This filter should not be used directly and should instead use the @ExcludeMissing annotation class IsMissing { override fun equals(other: Any?): Boolean = other is JsonMissing + + override fun hashCode(): Int = Objects.hash() } class Deserializer(private val type: JavaType? = null) : @@ -383,7 +385,7 @@ private constructor( override fun toString() = values.toString() companion object { - @JsonCreator fun of(values: List) = JsonArray(values.toUnmodifiable()) + @JsonCreator fun of(values: List) = JsonArray(values.toImmutable()) } } @@ -407,7 +409,7 @@ private constructor( override fun toString() = values.toString() companion object { - @JsonCreator fun of(values: Map) = JsonObject(values.toUnmodifiable()) + @JsonCreator fun of(values: Map) = JsonObject(values.toImmutable()) } } @@ -473,9 +475,8 @@ internal constructor( } } - override fun toString(): String { - return "MultipartFormValue(name='$name', contentType=$contentType, filename=$filename, value=${valueToString()})" - } + override fun toString(): String = + "MultipartFormValue{name=$name, contentType=$contentType, filename=$filename, value=${valueToString()}}" private fun valueToString(): String = when (value) { diff --git a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/core/http/Headers.kt b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/core/http/Headers.kt new file mode 100644 index 0000000..79b172e --- /dev/null +++ b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/core/http/Headers.kt @@ -0,0 +1,88 @@ +package com.omnistack.api.core.http + +import com.omnistack.api.core.toImmutable +import java.util.TreeMap + +class Headers private constructor(private val map: Map>, val size: Int) { + + fun isEmpty(): Boolean = map.isEmpty() + + fun names(): Set = map.keys + + fun values(name: String): List = map[name].orEmpty() + + fun toBuilder(): Builder = Builder().putAll(map) + + companion object { + + fun builder() = Builder() + } + + class Builder { + + private val map: MutableMap> = + TreeMap(String.CASE_INSENSITIVE_ORDER) + private var size: Int = 0 + + fun put(name: String, value: String) = apply { + map.getOrPut(name) { mutableListOf() }.add(value) + size++ + } + + fun put(name: String, values: Iterable) = apply { values.forEach { put(name, it) } } + + fun putAll(headers: Map>) = apply { headers.forEach(::put) } + + fun putAll(headers: Headers) = apply { + headers.names().forEach { put(it, headers.values(it)) } + } + + fun replace(name: String, value: String) = apply { + remove(name) + put(name, value) + } + + fun replace(name: String, values: Iterable) = apply { + remove(name) + put(name, values) + } + + fun replaceAll(headers: Map>) = apply { + headers.forEach(::replace) + } + + fun replaceAll(headers: Headers) = apply { + headers.names().forEach { replace(it, headers.values(it)) } + } + + fun remove(name: String) = apply { size -= map.remove(name).orEmpty().size } + + fun removeAll(names: Set) = apply { names.forEach(::remove) } + + fun clear() = apply { + map.clear() + size = 0 + } + + fun build() = + Headers( + map.mapValuesTo(TreeMap(String.CASE_INSENSITIVE_ORDER)) { (_, values) -> + values.toImmutable() + } + .toImmutable(), + size + ) + } + + override fun hashCode(): Int = map.hashCode() + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is Headers && map == other.map + } + + override fun toString(): String = "Headers{map=$map}" +} diff --git a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/core/http/HttpRequest.kt b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/core/http/HttpRequest.kt index 694ae1b..94a2a2c 100644 --- a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/core/http/HttpRequest.kt +++ b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/core/http/HttpRequest.kt @@ -4,7 +4,7 @@ import com.google.common.collect.ArrayListMultimap import com.google.common.collect.ListMultimap import com.google.common.collect.Multimap import com.google.common.collect.MultimapBuilder -import com.omnistack.api.core.toUnmodifiable +import com.omnistack.api.core.toImmutable class HttpRequest private constructor( @@ -17,7 +17,7 @@ private constructor( ) { override fun toString(): String = - "HttpRequest {method=$method, pathSegments=$pathSegments, queryParams=$queryParams, headers=$headers, body=$body}" + "HttpRequest{method=$method, pathSegments=$pathSegments, queryParams=$queryParams, headers=$headers, body=$body}" companion object { fun builder() = Builder() @@ -83,8 +83,8 @@ private constructor( HttpRequest( checkNotNull(method) { "`method` is required but was not set" }, url, - pathSegments.toUnmodifiable(), - queryParams.toUnmodifiable(), + pathSegments.toImmutable(), + queryParams.toImmutable(), headers, body, ) diff --git a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/core/http/PhantomReachableClosingHttpClient.kt b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/core/http/PhantomReachableClosingHttpClient.kt new file mode 100644 index 0000000..e525da5 --- /dev/null +++ b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/core/http/PhantomReachableClosingHttpClient.kt @@ -0,0 +1,20 @@ +package com.omnistack.api.core.http + +import com.omnistack.api.core.RequestOptions +import com.omnistack.api.core.closeWhenPhantomReachable + +internal class PhantomReachableClosingHttpClient(private val httpClient: HttpClient) : HttpClient { + init { + closeWhenPhantomReachable(this, httpClient) + } + + override fun execute(request: HttpRequest, requestOptions: RequestOptions): HttpResponse = + httpClient.execute(request, requestOptions) + + override suspend fun executeAsync( + request: HttpRequest, + requestOptions: RequestOptions + ): HttpResponse = httpClient.executeAsync(request, requestOptions) + + override fun close() = httpClient.close() +} diff --git a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/core/http/QueryParams.kt b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/core/http/QueryParams.kt new file mode 100644 index 0000000..9ed8488 --- /dev/null +++ b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/core/http/QueryParams.kt @@ -0,0 +1,82 @@ +package com.omnistack.api.core.http + +import com.omnistack.api.core.toImmutable + +class QueryParams private constructor(private val map: Map>, val size: Int) { + + fun isEmpty(): Boolean = map.isEmpty() + + fun keys(): Set = map.keys + + fun values(key: String): List = map[key].orEmpty() + + fun toBuilder(): Builder = Builder().putAll(map) + + companion object { + + fun builder() = Builder() + } + + class Builder { + + private val map: MutableMap> = mutableMapOf() + private var size: Int = 0 + + fun put(key: String, value: String) = apply { + map.getOrPut(key) { mutableListOf() }.add(value) + size++ + } + + fun put(key: String, values: Iterable) = apply { values.forEach { put(key, it) } } + + fun putAll(queryParams: Map>) = apply { + queryParams.forEach(::put) + } + + fun putAll(queryParams: QueryParams) = apply { + queryParams.keys().forEach { put(it, queryParams.values(it)) } + } + + fun replace(key: String, value: String) = apply { + remove(key) + put(key, value) + } + + fun replace(key: String, values: Iterable) = apply { + remove(key) + put(key, values) + } + + fun replaceAll(queryParams: Map>) = apply { + queryParams.forEach(::replace) + } + + fun replaceAll(queryParams: QueryParams) = apply { + queryParams.keys().forEach { replace(it, queryParams.values(it)) } + } + + fun remove(key: String) = apply { size -= map.remove(key).orEmpty().size } + + fun removeAll(keys: Set) = apply { keys.forEach(::remove) } + + fun clear() = apply { + map.clear() + size = 0 + } + + fun build() = + QueryParams(map.mapValues { (_, values) -> values.toImmutable() }.toImmutable(), size) + } + + override fun hashCode(): Int = map.hashCode() + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is QueryParams && map == other.map + } + + override fun toString(): String = "QueryParams{map=$map}" +} diff --git a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/core/http/RetryingHttpClient.kt b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/core/http/RetryingHttpClient.kt index 3ae47f9..95b6809 100644 --- a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/core/http/RetryingHttpClient.kt +++ b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/core/http/RetryingHttpClient.kt @@ -1,5 +1,3 @@ -@file:JvmSynthetic - package com.omnistack.api.core.http import com.omnistack.api.core.RequestOptions @@ -109,15 +107,12 @@ private constructor( } } - override fun close() { - httpClient.close() - } + override fun close() = httpClient.close() - private fun isRetryable(request: HttpRequest): Boolean { + private fun isRetryable(request: HttpRequest): Boolean = // Some requests, such as when a request body is being streamed, cannot be retried because // the body data aren't available on subsequent attempts. - return request.body?.repeatable() ?: true - } + request.body?.repeatable() ?: true private fun setRetryCountHeader(request: HttpRequest, retries: Int) { request.headers.removeAll("x-stainless-retry-count") @@ -155,11 +150,10 @@ private constructor( } } - private fun shouldRetry(throwable: Throwable): Boolean { + private fun shouldRetry(throwable: Throwable): Boolean = // Only retry IOException and OmnistackIoException, other exceptions are not intended to be // retried. - return throwable is IOException || throwable is OmnistackIoException - } + throwable is IOException || throwable is OmnistackIoException private fun getRetryBackoffMillis(retries: Int, response: HttpResponse?): Duration { // About the Retry-After header: diff --git a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/errors/OmnistackError.kt b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/errors/OmnistackError.kt index 0835fba..b468a6c 100644 --- a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/errors/OmnistackError.kt +++ b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/errors/OmnistackError.kt @@ -7,7 +7,7 @@ import com.fasterxml.jackson.annotation.JsonAnySetter import com.fasterxml.jackson.databind.annotation.JsonDeserialize import com.omnistack.api.core.JsonValue import com.omnistack.api.core.NoAutoDetect -import com.omnistack.api.core.toUnmodifiable +import com.omnistack.api.core.toImmutable import java.util.Objects @JsonDeserialize(builder = OmnistackError.Builder::class) @@ -37,7 +37,7 @@ constructor( companion object { - @JvmStatic fun builder() = Builder() + fun builder() = Builder() } class Builder { @@ -60,6 +60,6 @@ constructor( this.additionalProperties.putAll(additionalProperties) } - fun build(): OmnistackError = OmnistackError(additionalProperties.toUnmodifiable()) + fun build(): OmnistackError = OmnistackError(additionalProperties.toImmutable()) } } diff --git a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/models/ChatCompletionCreateParams.kt b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/models/ChatCompletionCreateParams.kt index e76e34f..46c9a20 100644 --- a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/models/ChatCompletionCreateParams.kt +++ b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/models/ChatCompletionCreateParams.kt @@ -22,7 +22,7 @@ import com.omnistack.api.core.JsonMissing import com.omnistack.api.core.JsonValue import com.omnistack.api.core.NoAutoDetect import com.omnistack.api.core.getOrThrow -import com.omnistack.api.core.toUnmodifiable +import com.omnistack.api.core.toImmutable import com.omnistack.api.errors.OmnistackInvalidDataException import com.omnistack.api.models.* import java.util.Objects @@ -709,11 +709,11 @@ constructor( fun build(): ChatCompletionCreateBody = ChatCompletionCreateBody( checkNotNull(messages) { "`messages` is required but was not set" } - .toUnmodifiable(), + .toImmutable(), checkNotNull(model) { "`model` is required but was not set" }, frequencyPenalty, functionCall, - functions?.toUnmodifiable(), + functions?.toImmutable(), logitBias, logprobs, maxCompletionTokens, @@ -729,11 +729,11 @@ constructor( streamOptions, temperature, toolChoice, - tools?.toUnmodifiable(), + tools?.toImmutable(), topLogprobs, topP, user, - additionalProperties.toUnmodifiable(), + additionalProperties.toImmutable(), ) } @@ -1315,12 +1315,11 @@ constructor( fun build(): ChatCompletionCreateParams = ChatCompletionCreateParams( - checkNotNull(messages) { "`messages` is required but was not set" } - .toUnmodifiable(), + checkNotNull(messages) { "`messages` is required but was not set" }.toImmutable(), checkNotNull(model) { "`model` is required but was not set" }, frequencyPenalty, functionCall, - if (functions.size == 0) null else functions.toUnmodifiable(), + if (functions.size == 0) null else functions.toImmutable(), logitBias, logprobs, maxCompletionTokens, @@ -1336,13 +1335,13 @@ constructor( streamOptions, temperature, toolChoice, - if (tools.size == 0) null else tools.toUnmodifiable(), + if (tools.size == 0) null else tools.toImmutable(), topLogprobs, topP, user, - additionalQueryParams.mapValues { it.value.toUnmodifiable() }.toUnmodifiable(), - additionalHeaders.mapValues { it.value.toUnmodifiable() }.toUnmodifiable(), - additionalBodyProperties.toUnmodifiable(), + additionalQueryParams.mapValues { it.value.toImmutable() }.toImmutable(), + additionalHeaders.mapValues { it.value.toImmutable() }.toImmutable(), + additionalBodyProperties.toImmutable(), ) } @@ -1537,6 +1536,7 @@ constructor( override fun ObjectCodec.deserialize(node: JsonNode): Message { val json = JsonValue.fromJsonNode(node) + tryDeserialize(node, jacksonTypeRef()) { it.validate() } @@ -1718,7 +1718,7 @@ constructor( content, role, name, - additionalProperties.toUnmodifiable(), + additionalProperties.toImmutable(), ) } @@ -1822,6 +1822,7 @@ constructor( override fun ObjectCodec.deserialize(node: JsonNode): Content { val json = JsonValue.fromJsonNode(node) + tryDeserialize(node, jacksonTypeRef())?.let { return Content(textContent = it, _json = json) } @@ -1953,7 +1954,7 @@ constructor( ChatCompletionRequestMessageContentPartText( type, text, - additionalProperties.toUnmodifiable(), + additionalProperties.toImmutable(), ) } @@ -2225,7 +2226,7 @@ constructor( content, role, name, - additionalProperties.toUnmodifiable(), + additionalProperties.toImmutable(), ) } @@ -2330,6 +2331,7 @@ constructor( override fun ObjectCodec.deserialize(node: JsonNode): Content { val json = JsonValue.fromJsonNode(node) + tryDeserialize(node, jacksonTypeRef())?.let { return Content(textContent = it, _json = json) } @@ -2515,6 +2517,7 @@ constructor( node: JsonNode ): ChatCompletionRequestUserMessageContentPart { val json = JsonValue.fromJsonNode(node) + tryDeserialize( node, jacksonTypeRef() @@ -2669,7 +2672,7 @@ constructor( ChatCompletionRequestMessageContentPartText( type, text, - additionalProperties.toUnmodifiable(), + additionalProperties.toImmutable(), ) } @@ -2844,7 +2847,7 @@ constructor( ChatCompletionRequestMessageContentPartImage( type, imageUrl, - additionalProperties.toUnmodifiable(), + additionalProperties.toImmutable(), ) } @@ -2954,7 +2957,7 @@ constructor( ImageUrl( url, detail, - additionalProperties.toUnmodifiable(), + additionalProperties.toImmutable(), ) } @@ -3400,9 +3403,9 @@ constructor( refusal, role, name, - toolCalls.map { it.toUnmodifiable() }, + toolCalls.map { it.toImmutable() }, functionCall, - additionalProperties.toUnmodifiable(), + additionalProperties.toImmutable(), ) } @@ -3557,6 +3560,7 @@ constructor( override fun ObjectCodec.deserialize(node: JsonNode): Content { val json = JsonValue.fromJsonNode(node) + tryDeserialize(node, jacksonTypeRef())?.let { return Content(textContent = it, _json = json) } @@ -3744,6 +3748,7 @@ constructor( node: JsonNode ): ChatCompletionRequestAssistantMessageContentPart { val json = JsonValue.fromJsonNode(node) + tryDeserialize( node, jacksonTypeRef() @@ -3898,7 +3903,7 @@ constructor( ChatCompletionRequestMessageContentPartText( type, text, - additionalProperties.toUnmodifiable(), + additionalProperties.toImmutable(), ) } @@ -4077,7 +4082,7 @@ constructor( ChatCompletionRequestMessageContentPartRefusal( type, refusal, - additionalProperties.toUnmodifiable(), + additionalProperties.toImmutable(), ) } @@ -4272,7 +4277,7 @@ constructor( FunctionCall( arguments, name, - additionalProperties.toUnmodifiable(), + additionalProperties.toImmutable(), ) } @@ -4405,7 +4410,7 @@ constructor( id, type, function, - additionalProperties.toUnmodifiable(), + additionalProperties.toImmutable(), ) } @@ -4522,7 +4527,7 @@ constructor( Function( name, arguments, - additionalProperties.toUnmodifiable(), + additionalProperties.toImmutable(), ) } @@ -4753,7 +4758,7 @@ constructor( role, content, toolCallId, - additionalProperties.toUnmodifiable(), + additionalProperties.toImmutable(), ) } @@ -4857,6 +4862,7 @@ constructor( override fun ObjectCodec.deserialize(node: JsonNode): Content { val json = JsonValue.fromJsonNode(node) + tryDeserialize(node, jacksonTypeRef())?.let { return Content(textContent = it, _json = json) } @@ -4988,7 +4994,7 @@ constructor( ChatCompletionRequestMessageContentPartText( type, text, - additionalProperties.toUnmodifiable(), + additionalProperties.toImmutable(), ) } @@ -5248,7 +5254,7 @@ constructor( role, content, name, - additionalProperties.toUnmodifiable(), + additionalProperties.toImmutable(), ) } @@ -5410,6 +5416,7 @@ constructor( override fun ObjectCodec.deserialize(node: JsonNode): Model { val json = JsonValue.fromJsonNode(node) + tryDeserialize(node, jacksonTypeRef())?.let { return Model(string = it, _json = json) } @@ -5759,6 +5766,7 @@ constructor( override fun ObjectCodec.deserialize(node: JsonNode): FunctionCall { val json = JsonValue.fromJsonNode(node) + tryDeserialize(node, jacksonTypeRef())?.let { return FunctionCall(unionMember0 = it, _json = json) } @@ -5921,7 +5929,7 @@ constructor( } fun build(): ChatCompletionFunctionCallOption = - ChatCompletionFunctionCallOption(name, additionalProperties.toUnmodifiable()) + ChatCompletionFunctionCallOption(name, additionalProperties.toImmutable()) } override fun equals(other: Any?): Boolean { @@ -6046,7 +6054,7 @@ constructor( description, checkNotNull(name) { "`name` is required but was not set" }, parameters, - additionalProperties.toUnmodifiable(), + additionalProperties.toImmutable(), ) } @@ -6099,7 +6107,7 @@ constructor( this.additionalProperties.putAll(additionalProperties) } - fun build(): Parameters = Parameters(additionalProperties.toUnmodifiable()) + fun build(): Parameters = Parameters(additionalProperties.toImmutable()) } override fun equals(other: Any?): Boolean { @@ -6192,7 +6200,7 @@ constructor( this.additionalProperties.putAll(additionalProperties) } - fun build(): LogitBias = LogitBias(additionalProperties.toUnmodifiable()) + fun build(): LogitBias = LogitBias(additionalProperties.toImmutable()) } override fun equals(other: Any?): Boolean { @@ -6331,6 +6339,7 @@ constructor( override fun ObjectCodec.deserialize(node: JsonNode): ResponseFormat { val json = JsonValue.fromJsonNode(node) + tryDeserialize(node, jacksonTypeRef()) { it.validate() } ?.let { return ResponseFormat(responseFormatText = it, _json = json) @@ -6436,7 +6445,7 @@ constructor( } fun build(): ResponseFormatText = - ResponseFormatText(type, additionalProperties.toUnmodifiable()) + ResponseFormatText(type, additionalProperties.toImmutable()) } class Type @@ -6579,7 +6588,7 @@ constructor( } fun build(): ResponseFormatJsonObject = - ResponseFormatJsonObject(type, additionalProperties.toUnmodifiable()) + ResponseFormatJsonObject(type, additionalProperties.toImmutable()) } class Type @@ -6741,7 +6750,7 @@ constructor( ResponseFormatJsonSchema( type, jsonSchema, - additionalProperties.toUnmodifiable(), + additionalProperties.toImmutable(), ) } @@ -6920,7 +6929,7 @@ constructor( name, schema, strict, - additionalProperties.toUnmodifiable(), + additionalProperties.toImmutable(), ) } @@ -6975,7 +6984,7 @@ constructor( additionalProperties: Map ) = apply { this.additionalProperties.putAll(additionalProperties) } - fun build(): Schema = Schema(additionalProperties.toUnmodifiable()) + fun build(): Schema = Schema(additionalProperties.toImmutable()) } override fun equals(other: Any?): Boolean { @@ -7234,6 +7243,7 @@ constructor( override fun ObjectCodec.deserialize(node: JsonNode): Stop { val json = JsonValue.fromJsonNode(node) + tryDeserialize(node, jacksonTypeRef())?.let { return Stop(string = it, _json = json) } @@ -7324,7 +7334,7 @@ constructor( } fun build(): StreamOptions = - StreamOptions(includeUsage, additionalProperties.toUnmodifiable()) + StreamOptions(includeUsage, additionalProperties.toImmutable()) } override fun equals(other: Any?): Boolean { @@ -7450,6 +7460,7 @@ constructor( override fun ObjectCodec.deserialize(node: JsonNode): ToolChoice { val json = JsonValue.fromJsonNode(node) + tryDeserialize(node, jacksonTypeRef())?.let { return ToolChoice(unionMember0 = it, _json = json) } @@ -7634,7 +7645,7 @@ constructor( ChatCompletionNamedToolChoice( type, function, - additionalProperties.toUnmodifiable(), + additionalProperties.toImmutable(), ) } @@ -7705,7 +7716,7 @@ constructor( this.additionalProperties.putAll(additionalProperties) } - fun build(): Function = Function(name, additionalProperties.toUnmodifiable()) + fun build(): Function = Function(name, additionalProperties.toImmutable()) } override fun equals(other: Any?): Boolean { @@ -7863,7 +7874,7 @@ constructor( Tool( checkNotNull(type) { "`type` is required but was not set" }, checkNotNull(function) { "`function` is required but was not set" }, - additionalProperties.toUnmodifiable(), + additionalProperties.toImmutable(), ) } @@ -7989,7 +8000,7 @@ constructor( checkNotNull(name) { "`name` is required but was not set" }, parameters, strict, - additionalProperties.toUnmodifiable(), + additionalProperties.toImmutable(), ) } @@ -8042,7 +8053,7 @@ constructor( this.additionalProperties.putAll(additionalProperties) } - fun build(): Parameters = Parameters(additionalProperties.toUnmodifiable()) + fun build(): Parameters = Parameters(additionalProperties.toImmutable()) } override fun equals(other: Any?): Boolean { diff --git a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/models/ChatCompletionCreateResponse.kt b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/models/ChatCompletionCreateResponse.kt index 869e9e4..d9acc67 100644 --- a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/models/ChatCompletionCreateResponse.kt +++ b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/models/ChatCompletionCreateResponse.kt @@ -13,7 +13,7 @@ import com.omnistack.api.core.JsonField import com.omnistack.api.core.JsonMissing import com.omnistack.api.core.JsonValue import com.omnistack.api.core.NoAutoDetect -import com.omnistack.api.core.toUnmodifiable +import com.omnistack.api.core.toImmutable import com.omnistack.api.errors.OmnistackInvalidDataException import java.util.Objects @@ -248,14 +248,14 @@ private constructor( fun build(): ChatCompletionCreateResponse = ChatCompletionCreateResponse( id, - choices.map { it.toUnmodifiable() }, + choices.map { it.toImmutable() }, created, model, serviceTier, systemFingerprint, object_, usage, - additionalProperties.toUnmodifiable(), + additionalProperties.toImmutable(), ) } @@ -411,7 +411,7 @@ private constructor( index, message, logprobs, - additionalProperties.toUnmodifiable(), + additionalProperties.toImmutable(), ) } @@ -578,9 +578,9 @@ private constructor( fun build(): Logprobs = Logprobs( - content.map { it.toUnmodifiable() }, - refusal.map { it.toUnmodifiable() }, - additionalProperties.toUnmodifiable(), + content.map { it.toImmutable() }, + refusal.map { it.toImmutable() }, + additionalProperties.toImmutable(), ) } @@ -766,9 +766,9 @@ private constructor( Content( token, logprob, - bytes.map { it.toUnmodifiable() }, - topLogprobs.map { it.toUnmodifiable() }, - additionalProperties.toUnmodifiable(), + bytes.map { it.toImmutable() }, + topLogprobs.map { it.toImmutable() }, + additionalProperties.toImmutable(), ) } @@ -920,8 +920,8 @@ private constructor( TopLogprob( token, logprob, - bytes.map { it.toUnmodifiable() }, - additionalProperties.toUnmodifiable(), + bytes.map { it.toImmutable() }, + additionalProperties.toImmutable(), ) } @@ -1149,9 +1149,9 @@ private constructor( Refusal( token, logprob, - bytes.map { it.toUnmodifiable() }, - topLogprobs.map { it.toUnmodifiable() }, - additionalProperties.toUnmodifiable(), + bytes.map { it.toImmutable() }, + topLogprobs.map { it.toImmutable() }, + additionalProperties.toImmutable(), ) } @@ -1303,8 +1303,8 @@ private constructor( TopLogprob( token, logprob, - bytes.map { it.toUnmodifiable() }, - additionalProperties.toUnmodifiable(), + bytes.map { it.toImmutable() }, + additionalProperties.toImmutable(), ) } @@ -1532,10 +1532,10 @@ private constructor( Message( content, refusal, - toolCalls.map { it.toUnmodifiable() }, + toolCalls.map { it.toImmutable() }, role, functionCall, - additionalProperties.toUnmodifiable(), + additionalProperties.toImmutable(), ) } @@ -1705,7 +1705,7 @@ private constructor( FunctionCall( arguments, name, - additionalProperties.toUnmodifiable(), + additionalProperties.toImmutable(), ) } @@ -1838,7 +1838,7 @@ private constructor( id, type, function, - additionalProperties.toUnmodifiable(), + additionalProperties.toImmutable(), ) } @@ -1955,7 +1955,7 @@ private constructor( Function( name, arguments, - additionalProperties.toUnmodifiable(), + additionalProperties.toImmutable(), ) } @@ -2344,7 +2344,7 @@ private constructor( promptTokens, totalTokens, completionTokensDetails, - additionalProperties.toUnmodifiable(), + additionalProperties.toImmutable(), ) } @@ -2422,7 +2422,7 @@ private constructor( } fun build(): CompletionTokensDetails = - CompletionTokensDetails(reasoningTokens, additionalProperties.toUnmodifiable()) + CompletionTokensDetails(reasoningTokens, additionalProperties.toImmutable()) } override fun equals(other: Any?): Boolean { diff --git a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/models/CompletionCreateParams.kt b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/models/CompletionCreateParams.kt index 143c6fb..8117a42 100644 --- a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/models/CompletionCreateParams.kt +++ b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/models/CompletionCreateParams.kt @@ -21,7 +21,7 @@ import com.omnistack.api.core.JsonField import com.omnistack.api.core.JsonValue import com.omnistack.api.core.NoAutoDetect import com.omnistack.api.core.getOrThrow -import com.omnistack.api.core.toUnmodifiable +import com.omnistack.api.core.toImmutable import com.omnistack.api.errors.OmnistackInvalidDataException import com.omnistack.api.models.* import java.util.Objects @@ -552,7 +552,7 @@ constructor( temperature, topP, user, - additionalProperties.toUnmodifiable(), + additionalProperties.toImmutable(), ) } @@ -958,9 +958,9 @@ constructor( temperature, topP, user, - additionalQueryParams.mapValues { it.value.toUnmodifiable() }.toUnmodifiable(), - additionalHeaders.mapValues { it.value.toUnmodifiable() }.toUnmodifiable(), - additionalBodyProperties.toUnmodifiable(), + additionalQueryParams.mapValues { it.value.toImmutable() }.toImmutable(), + additionalHeaders.mapValues { it.value.toImmutable() }.toImmutable(), + additionalBodyProperties.toImmutable(), ) } @@ -1049,6 +1049,7 @@ constructor( override fun ObjectCodec.deserialize(node: JsonNode): Model { val json = JsonValue.fromJsonNode(node) + tryDeserialize(node, jacksonTypeRef())?.let { return Model(string = it, _json = json) } @@ -1251,6 +1252,7 @@ constructor( override fun ObjectCodec.deserialize(node: JsonNode): Prompt { val json = JsonValue.fromJsonNode(node) + tryDeserialize(node, jacksonTypeRef())?.let { return Prompt(string = it, _json = json) } @@ -1340,7 +1342,7 @@ constructor( this.additionalProperties.putAll(additionalProperties) } - fun build(): LogitBias = LogitBias(additionalProperties.toUnmodifiable()) + fun build(): LogitBias = LogitBias(additionalProperties.toImmutable()) } override fun equals(other: Any?): Boolean { @@ -1448,6 +1450,7 @@ constructor( override fun ObjectCodec.deserialize(node: JsonNode): Stop { val json = JsonValue.fromJsonNode(node) + tryDeserialize(node, jacksonTypeRef())?.let { return Stop(string = it, _json = json) } @@ -1538,7 +1541,7 @@ constructor( } fun build(): StreamOptions = - StreamOptions(includeUsage, additionalProperties.toUnmodifiable()) + StreamOptions(includeUsage, additionalProperties.toImmutable()) } override fun equals(other: Any?): Boolean { diff --git a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/models/CompletionCreateResponse.kt b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/models/CompletionCreateResponse.kt index eedc362..3bc8592 100644 --- a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/models/CompletionCreateResponse.kt +++ b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/models/CompletionCreateResponse.kt @@ -13,7 +13,7 @@ import com.omnistack.api.core.JsonField import com.omnistack.api.core.JsonMissing import com.omnistack.api.core.JsonValue import com.omnistack.api.core.NoAutoDetect -import com.omnistack.api.core.toUnmodifiable +import com.omnistack.api.core.toImmutable import com.omnistack.api.errors.OmnistackInvalidDataException import java.util.Objects @@ -219,13 +219,13 @@ private constructor( fun build(): CompletionCreateResponse = CompletionCreateResponse( id, - choices.map { it.toUnmodifiable() }, + choices.map { it.toImmutable() }, created, model, systemFingerprint, object_, usage, - additionalProperties.toUnmodifiable(), + additionalProperties.toImmutable(), ) } @@ -365,7 +365,7 @@ private constructor( index, logprobs, text, - additionalProperties.toUnmodifiable(), + additionalProperties.toImmutable(), ) } @@ -547,11 +547,11 @@ private constructor( fun build(): Logprobs = Logprobs( - textOffset.map { it.toUnmodifiable() }, - tokenLogprobs.map { it.toUnmodifiable() }, - tokens.map { it.toUnmodifiable() }, - topLogprobs.map { it.toUnmodifiable() }, - additionalProperties.toUnmodifiable(), + textOffset.map { it.toImmutable() }, + tokenLogprobs.map { it.toImmutable() }, + tokens.map { it.toImmutable() }, + topLogprobs.map { it.toImmutable() }, + additionalProperties.toImmutable(), ) } @@ -604,7 +604,7 @@ private constructor( this.additionalProperties.putAll(additionalProperties) } - fun build(): TopLogprob = TopLogprob(additionalProperties.toUnmodifiable()) + fun build(): TopLogprob = TopLogprob(additionalProperties.toImmutable()) } override fun equals(other: Any?): Boolean { @@ -861,7 +861,7 @@ private constructor( promptTokens, totalTokens, completionTokensDetails, - additionalProperties.toUnmodifiable(), + additionalProperties.toImmutable(), ) } @@ -939,7 +939,7 @@ private constructor( } fun build(): CompletionTokensDetails = - CompletionTokensDetails(reasoningTokens, additionalProperties.toUnmodifiable()) + CompletionTokensDetails(reasoningTokens, additionalProperties.toImmutable()) } override fun equals(other: Any?): Boolean { diff --git a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/models/ProjectApiKey.kt b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/models/ProjectApiKey.kt index c98eb85..6bb5b5c 100644 --- a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/models/ProjectApiKey.kt +++ b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/models/ProjectApiKey.kt @@ -13,7 +13,7 @@ import com.omnistack.api.core.JsonField import com.omnistack.api.core.JsonMissing import com.omnistack.api.core.JsonValue import com.omnistack.api.core.NoAutoDetect -import com.omnistack.api.core.toUnmodifiable +import com.omnistack.api.core.toImmutable import com.omnistack.api.errors.OmnistackInvalidDataException import java.util.Objects @@ -178,7 +178,7 @@ private constructor( createdAt, id, owner, - additionalProperties.toUnmodifiable(), + additionalProperties.toImmutable(), ) } @@ -343,7 +343,7 @@ private constructor( type, user, serviceAccount, - additionalProperties.toUnmodifiable(), + additionalProperties.toImmutable(), ) } diff --git a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/models/ProjectServiceAccount.kt b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/models/ProjectServiceAccount.kt index 435adb4..bfa8eaa 100644 --- a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/models/ProjectServiceAccount.kt +++ b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/models/ProjectServiceAccount.kt @@ -13,7 +13,7 @@ import com.omnistack.api.core.JsonField import com.omnistack.api.core.JsonMissing import com.omnistack.api.core.JsonValue import com.omnistack.api.core.NoAutoDetect -import com.omnistack.api.core.toUnmodifiable +import com.omnistack.api.core.toImmutable import com.omnistack.api.errors.OmnistackInvalidDataException import java.util.Objects @@ -161,7 +161,7 @@ private constructor( name, role, createdAt, - additionalProperties.toUnmodifiable(), + additionalProperties.toImmutable(), ) } diff --git a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/models/ProjectUser.kt b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/models/ProjectUser.kt index 8f437eb..fee5e9d 100644 --- a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/models/ProjectUser.kt +++ b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/models/ProjectUser.kt @@ -13,7 +13,7 @@ import com.omnistack.api.core.JsonField import com.omnistack.api.core.JsonMissing import com.omnistack.api.core.JsonValue import com.omnistack.api.core.NoAutoDetect -import com.omnistack.api.core.toUnmodifiable +import com.omnistack.api.core.toImmutable import com.omnistack.api.errors.OmnistackInvalidDataException import java.util.Objects @@ -180,7 +180,7 @@ private constructor( email, role, addedAt, - additionalProperties.toUnmodifiable(), + additionalProperties.toImmutable(), ) } diff --git a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/services/async/ChatServiceAsync.kt b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/services/async/ChatServiceAsync.kt index bf43aa0..a838633 100644 --- a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/services/async/ChatServiceAsync.kt +++ b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/services/async/ChatServiceAsync.kt @@ -1,7 +1,5 @@ // File generated from our OpenAPI spec by Stainless. -@file:Suppress("OVERLOADS_INTERFACE") // See https://youtrack.jetbrains.com/issue/KT-36102 - package com.omnistack.api.services.async import com.omnistack.api.services.async.chats.CompletionServiceAsync diff --git a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/services/async/CompletionServiceAsync.kt b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/services/async/CompletionServiceAsync.kt index 3d9b691..0860331 100644 --- a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/services/async/CompletionServiceAsync.kt +++ b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/services/async/CompletionServiceAsync.kt @@ -1,7 +1,5 @@ // File generated from our OpenAPI spec by Stainless. -@file:Suppress("OVERLOADS_INTERFACE") // See https://youtrack.jetbrains.com/issue/KT-36102 - package com.omnistack.api.services.async import com.omnistack.api.core.RequestOptions diff --git a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/services/async/chats/CompletionServiceAsync.kt b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/services/async/chats/CompletionServiceAsync.kt index 8c73ac5..549848a 100644 --- a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/services/async/chats/CompletionServiceAsync.kt +++ b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/services/async/chats/CompletionServiceAsync.kt @@ -1,7 +1,5 @@ // File generated from our OpenAPI spec by Stainless. -@file:Suppress("OVERLOADS_INTERFACE") // See https://youtrack.jetbrains.com/issue/KT-36102 - package com.omnistack.api.services.async.chats import com.omnistack.api.core.RequestOptions diff --git a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/services/blocking/ChatService.kt b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/services/blocking/ChatService.kt index 0e27c52..76833bd 100644 --- a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/services/blocking/ChatService.kt +++ b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/services/blocking/ChatService.kt @@ -1,7 +1,5 @@ // File generated from our OpenAPI spec by Stainless. -@file:Suppress("OVERLOADS_INTERFACE") // See https://youtrack.jetbrains.com/issue/KT-36102 - package com.omnistack.api.services.blocking import com.omnistack.api.services.blocking.chats.CompletionService diff --git a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/services/blocking/CompletionService.kt b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/services/blocking/CompletionService.kt index 66db2c0..58f07cb 100644 --- a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/services/blocking/CompletionService.kt +++ b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/services/blocking/CompletionService.kt @@ -1,7 +1,5 @@ // File generated from our OpenAPI spec by Stainless. -@file:Suppress("OVERLOADS_INTERFACE") // See https://youtrack.jetbrains.com/issue/KT-36102 - package com.omnistack.api.services.blocking import com.omnistack.api.core.RequestOptions diff --git a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/services/blocking/chats/CompletionService.kt b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/services/blocking/chats/CompletionService.kt index 43728d2..1dfe901 100644 --- a/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/services/blocking/chats/CompletionService.kt +++ b/omnistack-kotlin-core/src/main/kotlin/com/omnistack/api/services/blocking/chats/CompletionService.kt @@ -1,7 +1,5 @@ // File generated from our OpenAPI spec by Stainless. -@file:Suppress("OVERLOADS_INTERFACE") // See https://youtrack.jetbrains.com/issue/KT-36102 - package com.omnistack.api.services.blocking.chats import com.omnistack.api.core.RequestOptions diff --git a/omnistack-kotlin-core/src/test/kotlin/com/omnistack/api/core/PhantomReachableTest.kt b/omnistack-kotlin-core/src/test/kotlin/com/omnistack/api/core/PhantomReachableTest.kt new file mode 100644 index 0000000..8205cdf --- /dev/null +++ b/omnistack-kotlin-core/src/test/kotlin/com/omnistack/api/core/PhantomReachableTest.kt @@ -0,0 +1,27 @@ +package com.omnistack.api.core + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class PhantomReachableTest { + + @Test + fun closeWhenPhantomReachable_whenObservedIsGarbageCollected_closesCloseable() { + var closed = false + val closeable = AutoCloseable { closed = true } + + closeWhenPhantomReachable( + // Pass an inline object for the object to observe so that it becomes immediately + // unreachable. + Any(), + closeable + ) + + assertThat(closed).isFalse() + + System.gc() + Thread.sleep(3000) + + assertThat(closed).isTrue() + } +} diff --git a/omnistack-kotlin-core/src/test/kotlin/com/omnistack/api/core/http/HeadersTest.kt b/omnistack-kotlin-core/src/test/kotlin/com/omnistack/api/core/http/HeadersTest.kt new file mode 100644 index 0000000..71b7df2 --- /dev/null +++ b/omnistack-kotlin-core/src/test/kotlin/com/omnistack/api/core/http/HeadersTest.kt @@ -0,0 +1,274 @@ +package com.omnistack.api.core.http + +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.catchThrowable +import org.assertj.core.api.Assumptions.assumeThat +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.EnumSource + +internal class HeadersTest { + + enum class TestCase( + val headers: Headers, + val expectedMap: Map>, + val expectedSize: Int + ) { + EMPTY(Headers.builder().build(), expectedMap = mapOf(), expectedSize = 0), + PUT_ONE( + Headers.builder().put("name", "value").build(), + expectedMap = mapOf("name" to listOf("value")), + expectedSize = 1 + ), + PUT_MULTIPLE( + Headers.builder().put("name", listOf("value1", "value2")).build(), + expectedMap = mapOf("name" to listOf("value1", "value2")), + expectedSize = 2 + ), + MULTIPLE_PUT( + Headers.builder().put("name1", "value").put("name2", "value").build(), + expectedMap = mapOf("name1" to listOf("value"), "name2" to listOf("value")), + expectedSize = 2 + ), + MULTIPLE_PUT_SAME_NAME( + Headers.builder().put("name", "value1").put("name", "value2").build(), + expectedMap = mapOf("name" to listOf("value1", "value2")), + expectedSize = 2 + ), + MULTIPLE_PUT_MULTIPLE( + Headers.builder() + .put("name", listOf("value1", "value2")) + .put("name", listOf("value1", "value2")) + .build(), + expectedMap = mapOf("name" to listOf("value1", "value2", "value1", "value2")), + expectedSize = 4 + ), + PUT_CASE_INSENSITIVE( + Headers.builder() + .put("name", "value1") + .put("NAME", "value2") + .put("nAmE", "value3") + .build(), + expectedMap = mapOf("name" to listOf("value1", "value2", "value3")), + expectedSize = 3 + ), + PUT_ALL_MAP( + Headers.builder() + .putAll( + mapOf( + "name1" to listOf("value1", "value2"), + "name2" to listOf("value1", "value2") + ) + ) + .build(), + expectedMap = + mapOf("name1" to listOf("value1", "value2"), "name2" to listOf("value1", "value2")), + expectedSize = 4 + ), + PUT_ALL_HEADERS( + Headers.builder().putAll(Headers.builder().put("name", "value").build()).build(), + expectedMap = mapOf("name" to listOf("value")), + expectedSize = 1 + ), + PUT_ALL_CASE_INSENSITIVE( + Headers.builder() + .putAll( + mapOf( + "name" to listOf("value1"), + "NAME" to listOf("value2"), + "nAmE" to listOf("value3") + ) + ) + .build(), + expectedMap = mapOf("name" to listOf("value1", "value2", "value3")), + expectedSize = 3 + ), + REMOVE_ABSENT( + Headers.builder().remove("name").build(), + expectedMap = mapOf(), + expectedSize = 0 + ), + REMOVE_PRESENT_ONE( + Headers.builder().put("name", "value").remove("name").build(), + expectedMap = mapOf(), + expectedSize = 0 + ), + REMOVE_PRESENT_MULTIPLE( + Headers.builder().put("name", listOf("value1", "value2")).remove("name").build(), + expectedMap = mapOf(), + expectedSize = 0 + ), + REMOVE_CASE_INSENSITIVE( + Headers.builder().put("name", listOf("value1", "value2")).remove("NAME").build(), + expectedMap = mapOf(), + expectedSize = 0 + ), + REMOVE_ALL( + Headers.builder() + .put("name1", "value") + .put("name3", "value") + .removeAll(setOf("name1", "name2", "name3")) + .build(), + expectedMap = mapOf(), + expectedSize = 0 + ), + REMOVE_ALL_CASE_INSENSITIVE( + Headers.builder() + .put("name1", "value") + .put("name3", "value") + .removeAll(setOf("NAME1", "nAmE3")) + .build(), + expectedMap = mapOf(), + expectedSize = 0 + ), + CLEAR( + Headers.builder().put("name1", "value").put("name2", "value").clear().build(), + expectedMap = mapOf(), + expectedSize = 0 + ), + REPLACE_ONE_ABSENT( + Headers.builder().replace("name", "value").build(), + expectedMap = mapOf("name" to listOf("value")), + expectedSize = 1 + ), + REPLACE_ONE_PRESENT_ONE( + Headers.builder().put("name", "value1").replace("name", "value2").build(), + expectedMap = mapOf("name" to listOf("value2")), + expectedSize = 1 + ), + REPLACE_ONE_PRESENT_MULTIPLE( + Headers.builder() + .put("name", listOf("value1", "value2")) + .replace("name", "value3") + .build(), + expectedMap = mapOf("name" to listOf("value3")), + expectedSize = 1 + ), + REPLACE_MULTIPLE_ABSENT( + Headers.builder().replace("name", listOf("value1", "value2")).build(), + expectedMap = mapOf("name" to listOf("value1", "value2")), + expectedSize = 2 + ), + REPLACE_MULTIPLE_PRESENT_ONE( + Headers.builder() + .put("name", "value1") + .replace("name", listOf("value2", "value3")) + .build(), + expectedMap = mapOf("name" to listOf("value2", "value3")), + expectedSize = 2 + ), + REPLACE_MULTIPLE_PRESENT_MULTIPLE( + Headers.builder() + .put("name", listOf("value1", "value2")) + .replace("name", listOf("value3", "value4")) + .build(), + expectedMap = mapOf("name" to listOf("value3", "value4")), + expectedSize = 2 + ), + REPLACE_CASE_INSENSITIVE( + Headers.builder() + .put("name", "value1") + .replace("NAME", listOf("value2", "value3")) + .build(), + expectedMap = mapOf("NAME" to listOf("value2", "value3")), + expectedSize = 2 + ), + REPLACE_ALL_MAP( + Headers.builder() + .put("name1", "value1") + .put("name2", "value1") + .put("name3", "value1") + .replaceAll(mapOf("name1" to listOf("value2"), "name3" to listOf("value2"))) + .build(), + expectedMap = + mapOf( + "name1" to listOf("value2"), + "name2" to listOf("value1"), + "name3" to listOf("value2") + ), + expectedSize = 3 + ), + REPLACE_ALL_HEADERS( + Headers.builder() + .put("name1", "value1") + .put("name2", "value1") + .put("name3", "value1") + .replaceAll(Headers.builder().put("name1", "value2").put("name3", "value2").build()) + .build(), + expectedMap = + mapOf( + "name1" to listOf("value2"), + "name2" to listOf("value1"), + "name3" to listOf("value2") + ), + expectedSize = 3 + ), + REPLACE_ALL_CASE_INSENSITIVE( + Headers.builder() + .put("name1", "value1") + .put("name2", "value1") + .replaceAll(mapOf("NAME1" to listOf("value2"), "nAmE2" to listOf("value2"))) + .build(), + expectedMap = mapOf("NAME1" to listOf("value2"), "nAmE2" to listOf("value2")), + expectedSize = 2 + ) + } + + @ParameterizedTest + @EnumSource + fun namesAndValues(testCase: TestCase) { + val map = mutableMapOf>() + val headers = testCase.headers + headers.names().forEach { name -> map[name] = headers.values(name) } + + assertThat(map).isEqualTo(testCase.expectedMap) + } + + @ParameterizedTest + @EnumSource + fun caseInsensitiveNames(testCase: TestCase) { + val headers = testCase.headers + + for (name in headers.names()) { + assertThat(headers.values(name)).isEqualTo(headers.values(name.lowercase())) + assertThat(headers.values(name)).isEqualTo(headers.values(name.uppercase())) + } + } + + @ParameterizedTest + @EnumSource + fun size(testCase: TestCase) { + val size = testCase.headers.size + + assertThat(size).isEqualTo(testCase.expectedSize) + } + + @ParameterizedTest + @EnumSource + fun namesAreImmutable(testCase: TestCase) { + val headers = testCase.headers + val headerNamesCopy = headers.names().toSet() + + val throwable = catchThrowable { + (headers.names() as MutableSet).add("another name") + } + + assertThat(throwable).isInstanceOf(UnsupportedOperationException::class.java) + assertThat(headers.names()).isEqualTo(headerNamesCopy) + } + + @ParameterizedTest + @EnumSource + fun valuesAreImmutable(testCase: TestCase) { + val headers = testCase.headers + assumeThat(headers.size).isNotEqualTo(0) + val name = headers.names().first() + val headerValuesCopy = headers.values(name).toList() + + val throwable = catchThrowable { + (headers.values(name) as MutableList).add("another value") + } + + assertThat(throwable).isInstanceOf(UnsupportedOperationException::class.java) + assertThat(headers.values(name)).isEqualTo(headerValuesCopy) + } +} diff --git a/omnistack-kotlin-core/src/test/kotlin/com/omnistack/api/core/http/QueryParamsTest.kt b/omnistack-kotlin-core/src/test/kotlin/com/omnistack/api/core/http/QueryParamsTest.kt new file mode 100644 index 0000000..7c0a8a6 --- /dev/null +++ b/omnistack-kotlin-core/src/test/kotlin/com/omnistack/api/core/http/QueryParamsTest.kt @@ -0,0 +1,212 @@ +package com.omnistack.api.core.http + +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.catchThrowable +import org.assertj.core.api.Assumptions.assumeThat +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.EnumSource + +internal class QueryParamsTest { + + enum class TestCase( + val queryParams: QueryParams, + val expectedMap: Map>, + val expectedSize: Int + ) { + EMPTY(QueryParams.builder().build(), expectedMap = mapOf(), expectedSize = 0), + PUT_ONE( + QueryParams.builder().put("key", "value").build(), + expectedMap = mapOf("key" to listOf("value")), + expectedSize = 1 + ), + PUT_MULTIPLE( + QueryParams.builder().put("key", listOf("value1", "value2")).build(), + expectedMap = mapOf("key" to listOf("value1", "value2")), + expectedSize = 2 + ), + MULTIPLE_PUT( + QueryParams.builder().put("key1", "value").put("key2", "value").build(), + expectedMap = mapOf("key1" to listOf("value"), "key2" to listOf("value")), + expectedSize = 2 + ), + MULTIPLE_PUT_SAME_NAME( + QueryParams.builder().put("key", "value1").put("key", "value2").build(), + expectedMap = mapOf("key" to listOf("value1", "value2")), + expectedSize = 2 + ), + MULTIPLE_PUT_MULTIPLE( + QueryParams.builder() + .put("key", listOf("value1", "value2")) + .put("key", listOf("value1", "value2")) + .build(), + expectedMap = mapOf("key" to listOf("value1", "value2", "value1", "value2")), + expectedSize = 4 + ), + PUT_ALL_MAP( + QueryParams.builder() + .putAll( + mapOf( + "key1" to listOf("value1", "value2"), + "key2" to listOf("value1", "value2") + ) + ) + .build(), + expectedMap = + mapOf("key1" to listOf("value1", "value2"), "key2" to listOf("value1", "value2")), + expectedSize = 4 + ), + PUT_ALL_HEADERS( + QueryParams.builder().putAll(QueryParams.builder().put("key", "value").build()).build(), + expectedMap = mapOf("key" to listOf("value")), + expectedSize = 1 + ), + REMOVE_ABSENT( + QueryParams.builder().remove("key").build(), + expectedMap = mapOf(), + expectedSize = 0 + ), + REMOVE_PRESENT_ONE( + QueryParams.builder().put("key", "value").remove("key").build(), + expectedMap = mapOf(), + expectedSize = 0 + ), + REMOVE_PRESENT_MULTIPLE( + QueryParams.builder().put("key", listOf("value1", "value2")).remove("key").build(), + expectedMap = mapOf(), + expectedSize = 0 + ), + REMOVE_ALL( + QueryParams.builder() + .put("key1", "value") + .put("key3", "value") + .removeAll(setOf("key1", "key2", "key3")) + .build(), + expectedMap = mapOf(), + expectedSize = 0 + ), + CLEAR( + QueryParams.builder().put("key1", "value").put("key2", "value").clear().build(), + expectedMap = mapOf(), + expectedSize = 0 + ), + REPLACE_ONE_ABSENT( + QueryParams.builder().replace("key", "value").build(), + expectedMap = mapOf("key" to listOf("value")), + expectedSize = 1 + ), + REPLACE_ONE_PRESENT_ONE( + QueryParams.builder().put("key", "value1").replace("key", "value2").build(), + expectedMap = mapOf("key" to listOf("value2")), + expectedSize = 1 + ), + REPLACE_ONE_PRESENT_MULTIPLE( + QueryParams.builder() + .put("key", listOf("value1", "value2")) + .replace("key", "value3") + .build(), + expectedMap = mapOf("key" to listOf("value3")), + expectedSize = 1 + ), + REPLACE_MULTIPLE_ABSENT( + QueryParams.builder().replace("key", listOf("value1", "value2")).build(), + expectedMap = mapOf("key" to listOf("value1", "value2")), + expectedSize = 2 + ), + REPLACE_MULTIPLE_PRESENT_ONE( + QueryParams.builder() + .put("key", "value1") + .replace("key", listOf("value2", "value3")) + .build(), + expectedMap = mapOf("key" to listOf("value2", "value3")), + expectedSize = 2 + ), + REPLACE_MULTIPLE_PRESENT_MULTIPLE( + QueryParams.builder() + .put("key", listOf("value1", "value2")) + .replace("key", listOf("value3", "value4")) + .build(), + expectedMap = mapOf("key" to listOf("value3", "value4")), + expectedSize = 2 + ), + REPLACE_ALL_MAP( + QueryParams.builder() + .put("key1", "value1") + .put("key2", "value1") + .put("key3", "value1") + .replaceAll(mapOf("key1" to listOf("value2"), "key3" to listOf("value2"))) + .build(), + expectedMap = + mapOf( + "key1" to listOf("value2"), + "key2" to listOf("value1"), + "key3" to listOf("value2") + ), + expectedSize = 3 + ), + REPLACE_ALL_HEADERS( + QueryParams.builder() + .put("key1", "value1") + .put("key2", "value1") + .put("key3", "value1") + .replaceAll( + QueryParams.builder().put("key1", "value2").put("key3", "value2").build() + ) + .build(), + expectedMap = + mapOf( + "key1" to listOf("value2"), + "key2" to listOf("value1"), + "key3" to listOf("value2") + ), + expectedSize = 3 + ) + } + + @ParameterizedTest + @EnumSource + fun keysAndValues(testCase: TestCase) { + val map = mutableMapOf>() + val queryParams = testCase.queryParams + queryParams.keys().forEach { key -> map[key] = queryParams.values(key) } + + assertThat(map).isEqualTo(testCase.expectedMap) + } + + @ParameterizedTest + @EnumSource + fun size(testCase: TestCase) { + val size = testCase.queryParams.size + + assertThat(size).isEqualTo(testCase.expectedSize) + } + + @ParameterizedTest + @EnumSource + fun keysAreImmutable(testCase: TestCase) { + val queryParams = testCase.queryParams + val queryParamKeysCopy = queryParams.keys().toSet() + + val throwable = catchThrowable { + (queryParams.keys() as MutableSet).add("another key") + } + + assertThat(throwable).isInstanceOf(UnsupportedOperationException::class.java) + assertThat(queryParams.keys()).isEqualTo(queryParamKeysCopy) + } + + @ParameterizedTest + @EnumSource + fun valuesAreImmutable(testCase: TestCase) { + val queryParams = testCase.queryParams + assumeThat(queryParams.size).isNotEqualTo(0) + val key = queryParams.keys().first() + val queryParamValuesCopy = queryParams.values(key).toList() + + val throwable = catchThrowable { + (queryParams.values(key) as MutableList).add("another value") + } + + assertThat(throwable).isInstanceOf(UnsupportedOperationException::class.java) + assertThat(queryParams.values(key)).isEqualTo(queryParamValuesCopy) + } +} diff --git a/omnistack-kotlin-core/src/test/kotlin/com/omnistack/api/core/http/SerializerTest.kt b/omnistack-kotlin-core/src/test/kotlin/com/omnistack/api/core/http/SerializerTest.kt index c1653f5..588c244 100644 --- a/omnistack-kotlin-core/src/test/kotlin/com/omnistack/api/core/http/SerializerTest.kt +++ b/omnistack-kotlin-core/src/test/kotlin/com/omnistack/api/core/http/SerializerTest.kt @@ -92,7 +92,7 @@ internal class SerializerTest { fun build(): ClassWithBooleanFieldPrefixedWithIs = ClassWithBooleanFieldPrefixedWithIs( isActive, - additionalProperties.toUnmodifiable(), + additionalProperties.toImmutable(), ) } } diff --git a/omnistack-kotlin-core/src/test/kotlin/com/omnistack/api/services/blocking/ChatServiceTest.kt b/omnistack-kotlin-core/src/test/kotlin/com/omnistack/api/services/blocking/ChatServiceTest.kt deleted file mode 100644 index 7fffa08..0000000 --- a/omnistack-kotlin-core/src/test/kotlin/com/omnistack/api/services/blocking/ChatServiceTest.kt +++ /dev/null @@ -1,9 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. - -package com.omnistack.api.services.blocking - -import com.omnistack.api.TestServerExtension -import com.omnistack.api.models.* -import org.junit.jupiter.api.extension.ExtendWith - -@ExtendWith(TestServerExtension::class) class ChatServiceTest