From 34315ddb9b1b1663efbaf95d9ac9b594039dcd60 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 16 Jun 2026 16:37:02 +0000 Subject: [PATCH 1/3] feat(openapi): add DELETE /users/{id}/preferences/{topicId} --- .stats.yml | 8 +- .../PreferenceDeleteTopicParams.kt | 284 ++++++++++++++++++ .../async/users/PreferenceServiceAsync.kt | 57 ++++ .../async/users/PreferenceServiceAsyncImpl.kt | 41 +++ .../blocking/users/PreferenceService.kt | 55 ++++ .../blocking/users/PreferenceServiceImpl.kt | 36 +++ .../PreferenceDeleteTopicParamsTest.kt | 55 ++++ .../async/users/PreferenceServiceAsyncTest.kt | 19 ++ .../blocking/users/PreferenceServiceTest.kt | 16 + 9 files changed, 567 insertions(+), 4 deletions(-) create mode 100644 courier-java-core/src/main/kotlin/com/courier/models/users/preferences/PreferenceDeleteTopicParams.kt create mode 100644 courier-java-core/src/test/kotlin/com/courier/models/users/preferences/PreferenceDeleteTopicParamsTest.kt diff --git a/.stats.yml b/.stats.yml index 6ee7bef5..0243e76f 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 119 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/courier/courier-a64bb97c3455b0689de7f6a297ba1dc1e747561ce310ddb18b9c4a5f4d3d510a.yml -openapi_spec_hash: 6a3b89f3ea7600e784902f61680f8f1a -config_hash: 822a92efc80e63cdb2d496dbd6176620 +configured_endpoints: 120 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/courier/courier-c19b2724476238e0f927330774db54376f38a992e72b96b7f41d8d3427aa2330.yml +openapi_spec_hash: df7abb5a984379086e62873773b85a03 +config_hash: 86b472590f1c27b5a74499744b30c2ee diff --git a/courier-java-core/src/main/kotlin/com/courier/models/users/preferences/PreferenceDeleteTopicParams.kt b/courier-java-core/src/main/kotlin/com/courier/models/users/preferences/PreferenceDeleteTopicParams.kt new file mode 100644 index 00000000..914d8a82 --- /dev/null +++ b/courier-java-core/src/main/kotlin/com/courier/models/users/preferences/PreferenceDeleteTopicParams.kt @@ -0,0 +1,284 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.courier.models.users.preferences + +import com.courier.core.JsonValue +import com.courier.core.Params +import com.courier.core.checkRequired +import com.courier.core.http.Headers +import com.courier.core.http.QueryParams +import com.courier.core.toImmutable +import java.util.Objects +import java.util.Optional +import kotlin.jvm.optionals.getOrNull + +/** + * Remove a user's preferences for a specific subscription topic, resetting the topic to its + * effective default. This operation is idempotent: deleting a preference that does not exist + * succeeds with no error. + */ +class PreferenceDeleteTopicParams +private constructor( + private val userId: String, + private val topicId: String?, + private val tenantId: String?, + private val additionalHeaders: Headers, + private val additionalQueryParams: QueryParams, + private val additionalBodyProperties: Map, +) : Params { + + fun userId(): String = userId + + fun topicId(): Optional = Optional.ofNullable(topicId) + + /** Delete the preferences of a user for this specific tenant context. */ + fun tenantId(): Optional = Optional.ofNullable(tenantId) + + /** Additional body properties to send with the request. */ + fun _additionalBodyProperties(): Map = additionalBodyProperties + + /** Additional headers to send with the request. */ + fun _additionalHeaders(): Headers = additionalHeaders + + /** Additional query param to send with the request. */ + fun _additionalQueryParams(): QueryParams = additionalQueryParams + + fun toBuilder() = Builder().from(this) + + companion object { + + /** + * Returns a mutable builder for constructing an instance of [PreferenceDeleteTopicParams]. + * + * The following fields are required: + * ```java + * .userId() + * ``` + */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [PreferenceDeleteTopicParams]. */ + class Builder internal constructor() { + + private var userId: String? = null + private var topicId: String? = null + private var tenantId: String? = null + private var additionalHeaders: Headers.Builder = Headers.builder() + private var additionalQueryParams: QueryParams.Builder = QueryParams.builder() + private var additionalBodyProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(preferenceDeleteTopicParams: PreferenceDeleteTopicParams) = apply { + userId = preferenceDeleteTopicParams.userId + topicId = preferenceDeleteTopicParams.topicId + tenantId = preferenceDeleteTopicParams.tenantId + additionalHeaders = preferenceDeleteTopicParams.additionalHeaders.toBuilder() + additionalQueryParams = preferenceDeleteTopicParams.additionalQueryParams.toBuilder() + additionalBodyProperties = + preferenceDeleteTopicParams.additionalBodyProperties.toMutableMap() + } + + fun userId(userId: String) = apply { this.userId = userId } + + fun topicId(topicId: String?) = apply { this.topicId = topicId } + + /** Alias for calling [Builder.topicId] with `topicId.orElse(null)`. */ + fun topicId(topicId: Optional) = topicId(topicId.getOrNull()) + + /** Delete the preferences of a user for this specific tenant context. */ + fun tenantId(tenantId: String?) = apply { this.tenantId = tenantId } + + /** Alias for calling [Builder.tenantId] with `tenantId.orElse(null)`. */ + fun tenantId(tenantId: Optional) = tenantId(tenantId.getOrNull()) + + fun additionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.clear() + putAllAdditionalHeaders(additionalHeaders) + } + + fun additionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.clear() + putAllAdditionalHeaders(additionalHeaders) + } + + fun putAdditionalHeader(name: String, value: String) = apply { + additionalHeaders.put(name, value) + } + + fun putAdditionalHeaders(name: String, values: Iterable) = apply { + additionalHeaders.put(name, values) + } + + fun putAllAdditionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.putAll(additionalHeaders) + } + + fun putAllAdditionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.putAll(additionalHeaders) + } + + fun replaceAdditionalHeaders(name: String, value: String) = apply { + additionalHeaders.replace(name, value) + } + + fun replaceAdditionalHeaders(name: String, values: Iterable) = apply { + additionalHeaders.replace(name, values) + } + + fun replaceAllAdditionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.replaceAll(additionalHeaders) + } + + fun replaceAllAdditionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.replaceAll(additionalHeaders) + } + + fun removeAdditionalHeaders(name: String) = apply { additionalHeaders.remove(name) } + + fun removeAllAdditionalHeaders(names: Set) = apply { + additionalHeaders.removeAll(names) + } + + fun additionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.clear() + putAllAdditionalQueryParams(additionalQueryParams) + } + + fun additionalQueryParams(additionalQueryParams: Map>) = apply { + this.additionalQueryParams.clear() + putAllAdditionalQueryParams(additionalQueryParams) + } + + fun putAdditionalQueryParam(key: String, value: String) = apply { + additionalQueryParams.put(key, value) + } + + fun putAdditionalQueryParams(key: String, values: Iterable) = apply { + additionalQueryParams.put(key, values) + } + + fun putAllAdditionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.putAll(additionalQueryParams) + } + + fun putAllAdditionalQueryParams(additionalQueryParams: Map>) = + apply { + this.additionalQueryParams.putAll(additionalQueryParams) + } + + fun replaceAdditionalQueryParams(key: String, value: String) = apply { + additionalQueryParams.replace(key, value) + } + + fun replaceAdditionalQueryParams(key: String, values: Iterable) = apply { + additionalQueryParams.replace(key, values) + } + + fun replaceAllAdditionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.replaceAll(additionalQueryParams) + } + + fun replaceAllAdditionalQueryParams(additionalQueryParams: Map>) = + apply { + this.additionalQueryParams.replaceAll(additionalQueryParams) + } + + fun removeAdditionalQueryParams(key: String) = apply { additionalQueryParams.remove(key) } + + fun removeAllAdditionalQueryParams(keys: Set) = apply { + additionalQueryParams.removeAll(keys) + } + + fun additionalBodyProperties(additionalBodyProperties: Map) = apply { + this.additionalBodyProperties.clear() + putAllAdditionalBodyProperties(additionalBodyProperties) + } + + fun putAdditionalBodyProperty(key: String, value: JsonValue) = apply { + additionalBodyProperties.put(key, value) + } + + fun putAllAdditionalBodyProperties(additionalBodyProperties: Map) = + apply { + this.additionalBodyProperties.putAll(additionalBodyProperties) + } + + fun removeAdditionalBodyProperty(key: String) = apply { + additionalBodyProperties.remove(key) + } + + fun removeAllAdditionalBodyProperties(keys: Set) = apply { + keys.forEach(::removeAdditionalBodyProperty) + } + + /** + * Returns an immutable instance of [PreferenceDeleteTopicParams]. + * + * Further updates to this [Builder] will not mutate the returned instance. + * + * The following fields are required: + * ```java + * .userId() + * ``` + * + * @throws IllegalStateException if any required field is unset. + */ + fun build(): PreferenceDeleteTopicParams = + PreferenceDeleteTopicParams( + checkRequired("userId", userId), + topicId, + tenantId, + additionalHeaders.build(), + additionalQueryParams.build(), + additionalBodyProperties.toImmutable(), + ) + } + + fun _body(): Optional> = + Optional.ofNullable(additionalBodyProperties.ifEmpty { null }) + + fun _pathParam(index: Int): String = + when (index) { + 0 -> userId + 1 -> topicId ?: "" + else -> "" + } + + override fun _headers(): Headers = additionalHeaders + + override fun _queryParams(): QueryParams = + QueryParams.builder() + .apply { + tenantId?.let { put("tenant_id", it) } + putAll(additionalQueryParams) + } + .build() + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is PreferenceDeleteTopicParams && + userId == other.userId && + topicId == other.topicId && + tenantId == other.tenantId && + additionalHeaders == other.additionalHeaders && + additionalQueryParams == other.additionalQueryParams && + additionalBodyProperties == other.additionalBodyProperties + } + + override fun hashCode(): Int = + Objects.hash( + userId, + topicId, + tenantId, + additionalHeaders, + additionalQueryParams, + additionalBodyProperties, + ) + + override fun toString() = + "PreferenceDeleteTopicParams{userId=$userId, topicId=$topicId, tenantId=$tenantId, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams, additionalBodyProperties=$additionalBodyProperties}" +} diff --git a/courier-java-core/src/main/kotlin/com/courier/services/async/users/PreferenceServiceAsync.kt b/courier-java-core/src/main/kotlin/com/courier/services/async/users/PreferenceServiceAsync.kt index 84c3380b..5fea9e13 100644 --- a/courier-java-core/src/main/kotlin/com/courier/services/async/users/PreferenceServiceAsync.kt +++ b/courier-java-core/src/main/kotlin/com/courier/services/async/users/PreferenceServiceAsync.kt @@ -4,7 +4,9 @@ package com.courier.services.async.users import com.courier.core.ClientOptions import com.courier.core.RequestOptions +import com.courier.core.http.HttpResponse import com.courier.core.http.HttpResponseFor +import com.courier.models.users.preferences.PreferenceDeleteTopicParams import com.courier.models.users.preferences.PreferenceRetrieveParams import com.courier.models.users.preferences.PreferenceRetrieveResponse import com.courier.models.users.preferences.PreferenceRetrieveTopicParams @@ -64,6 +66,34 @@ interface PreferenceServiceAsync { ): CompletableFuture = retrieve(userId, PreferenceRetrieveParams.none(), requestOptions) + /** + * Remove a user's preferences for a specific subscription topic, resetting the topic to its + * effective default. This operation is idempotent: deleting a preference that does not exist + * succeeds with no error. + */ + fun deleteTopic( + topicId: String, + params: PreferenceDeleteTopicParams, + ): CompletableFuture = deleteTopic(topicId, params, RequestOptions.none()) + + /** @see deleteTopic */ + fun deleteTopic( + topicId: String, + params: PreferenceDeleteTopicParams, + requestOptions: RequestOptions = RequestOptions.none(), + ): CompletableFuture = + deleteTopic(params.toBuilder().topicId(topicId).build(), requestOptions) + + /** @see deleteTopic */ + fun deleteTopic(params: PreferenceDeleteTopicParams): CompletableFuture = + deleteTopic(params, RequestOptions.none()) + + /** @see deleteTopic */ + fun deleteTopic( + params: PreferenceDeleteTopicParams, + requestOptions: RequestOptions = RequestOptions.none(), + ): CompletableFuture + /** Fetch user preferences for a specific subscription topic. */ fun retrieveTopic( topicId: String, @@ -176,6 +206,33 @@ interface PreferenceServiceAsync { ): CompletableFuture> = retrieve(userId, PreferenceRetrieveParams.none(), requestOptions) + /** + * Returns a raw HTTP response for `delete /users/{user_id}/preferences/{topic_id}`, but is + * otherwise the same as [PreferenceServiceAsync.deleteTopic]. + */ + fun deleteTopic( + topicId: String, + params: PreferenceDeleteTopicParams, + ): CompletableFuture = deleteTopic(topicId, params, RequestOptions.none()) + + /** @see deleteTopic */ + fun deleteTopic( + topicId: String, + params: PreferenceDeleteTopicParams, + requestOptions: RequestOptions = RequestOptions.none(), + ): CompletableFuture = + deleteTopic(params.toBuilder().topicId(topicId).build(), requestOptions) + + /** @see deleteTopic */ + fun deleteTopic(params: PreferenceDeleteTopicParams): CompletableFuture = + deleteTopic(params, RequestOptions.none()) + + /** @see deleteTopic */ + fun deleteTopic( + params: PreferenceDeleteTopicParams, + requestOptions: RequestOptions = RequestOptions.none(), + ): CompletableFuture + /** * Returns a raw HTTP response for `get /users/{user_id}/preferences/{topic_id}`, but is * otherwise the same as [PreferenceServiceAsync.retrieveTopic]. diff --git a/courier-java-core/src/main/kotlin/com/courier/services/async/users/PreferenceServiceAsyncImpl.kt b/courier-java-core/src/main/kotlin/com/courier/services/async/users/PreferenceServiceAsyncImpl.kt index b6d279db..95835d6e 100644 --- a/courier-java-core/src/main/kotlin/com/courier/services/async/users/PreferenceServiceAsyncImpl.kt +++ b/courier-java-core/src/main/kotlin/com/courier/services/async/users/PreferenceServiceAsyncImpl.kt @@ -5,6 +5,7 @@ package com.courier.services.async.users import com.courier.core.ClientOptions import com.courier.core.RequestOptions import com.courier.core.checkRequired +import com.courier.core.handlers.emptyHandler import com.courier.core.handlers.errorBodyHandler import com.courier.core.handlers.errorHandler import com.courier.core.handlers.jsonHandler @@ -16,6 +17,7 @@ import com.courier.core.http.HttpResponseFor import com.courier.core.http.json import com.courier.core.http.parseable import com.courier.core.prepareAsync +import com.courier.models.users.preferences.PreferenceDeleteTopicParams import com.courier.models.users.preferences.PreferenceRetrieveParams import com.courier.models.users.preferences.PreferenceRetrieveResponse import com.courier.models.users.preferences.PreferenceRetrieveTopicParams @@ -45,6 +47,13 @@ class PreferenceServiceAsyncImpl internal constructor(private val clientOptions: // get /users/{user_id}/preferences withRawResponse().retrieve(params, requestOptions).thenApply { it.parse() } + override fun deleteTopic( + params: PreferenceDeleteTopicParams, + requestOptions: RequestOptions, + ): CompletableFuture = + // delete /users/{user_id}/preferences/{topic_id} + withRawResponse().deleteTopic(params, requestOptions).thenAccept {} + override fun retrieveTopic( params: PreferenceRetrieveTopicParams, requestOptions: RequestOptions, @@ -105,6 +114,38 @@ class PreferenceServiceAsyncImpl internal constructor(private val clientOptions: } } + private val deleteTopicHandler: Handler = emptyHandler() + + override fun deleteTopic( + params: PreferenceDeleteTopicParams, + requestOptions: RequestOptions, + ): CompletableFuture { + // We check here instead of in the params builder because this can be specified + // positionally or in the params class. + checkRequired("topicId", params.topicId().getOrNull()) + val request = + HttpRequest.builder() + .method(HttpMethod.DELETE) + .baseUrl(clientOptions.baseUrl()) + .addPathSegments( + "users", + params._pathParam(0), + "preferences", + params._pathParam(1), + ) + .apply { params._body().ifPresent { body(json(clientOptions.jsonMapper, it)) } } + .build() + .prepareAsync(clientOptions, params) + val requestOptions = requestOptions.applyDefaults(RequestOptions.from(clientOptions)) + return request + .thenComposeAsync { clientOptions.httpClient.executeAsync(it, requestOptions) } + .thenApply { response -> + errorHandler.handle(response).parseable { + response.use { deleteTopicHandler.handle(it) } + } + } + } + private val retrieveTopicHandler: Handler = jsonHandler(clientOptions.jsonMapper) diff --git a/courier-java-core/src/main/kotlin/com/courier/services/blocking/users/PreferenceService.kt b/courier-java-core/src/main/kotlin/com/courier/services/blocking/users/PreferenceService.kt index 352dcba1..c0f45b07 100644 --- a/courier-java-core/src/main/kotlin/com/courier/services/blocking/users/PreferenceService.kt +++ b/courier-java-core/src/main/kotlin/com/courier/services/blocking/users/PreferenceService.kt @@ -4,7 +4,9 @@ package com.courier.services.blocking.users import com.courier.core.ClientOptions import com.courier.core.RequestOptions +import com.courier.core.http.HttpResponse import com.courier.core.http.HttpResponseFor +import com.courier.models.users.preferences.PreferenceDeleteTopicParams import com.courier.models.users.preferences.PreferenceRetrieveParams import com.courier.models.users.preferences.PreferenceRetrieveResponse import com.courier.models.users.preferences.PreferenceRetrieveTopicParams @@ -60,6 +62,31 @@ interface PreferenceService { fun retrieve(userId: String, requestOptions: RequestOptions): PreferenceRetrieveResponse = retrieve(userId, PreferenceRetrieveParams.none(), requestOptions) + /** + * Remove a user's preferences for a specific subscription topic, resetting the topic to its + * effective default. This operation is idempotent: deleting a preference that does not exist + * succeeds with no error. + */ + fun deleteTopic(topicId: String, params: PreferenceDeleteTopicParams) = + deleteTopic(topicId, params, RequestOptions.none()) + + /** @see deleteTopic */ + fun deleteTopic( + topicId: String, + params: PreferenceDeleteTopicParams, + requestOptions: RequestOptions = RequestOptions.none(), + ) = deleteTopic(params.toBuilder().topicId(topicId).build(), requestOptions) + + /** @see deleteTopic */ + fun deleteTopic(params: PreferenceDeleteTopicParams) = + deleteTopic(params, RequestOptions.none()) + + /** @see deleteTopic */ + fun deleteTopic( + params: PreferenceDeleteTopicParams, + requestOptions: RequestOptions = RequestOptions.none(), + ) + /** Fetch user preferences for a specific subscription topic. */ fun retrieveTopic( topicId: String, @@ -168,6 +195,34 @@ interface PreferenceService { ): HttpResponseFor = retrieve(userId, PreferenceRetrieveParams.none(), requestOptions) + /** + * Returns a raw HTTP response for `delete /users/{user_id}/preferences/{topic_id}`, but is + * otherwise the same as [PreferenceService.deleteTopic]. + */ + @MustBeClosed + fun deleteTopic(topicId: String, params: PreferenceDeleteTopicParams): HttpResponse = + deleteTopic(topicId, params, RequestOptions.none()) + + /** @see deleteTopic */ + @MustBeClosed + fun deleteTopic( + topicId: String, + params: PreferenceDeleteTopicParams, + requestOptions: RequestOptions = RequestOptions.none(), + ): HttpResponse = deleteTopic(params.toBuilder().topicId(topicId).build(), requestOptions) + + /** @see deleteTopic */ + @MustBeClosed + fun deleteTopic(params: PreferenceDeleteTopicParams): HttpResponse = + deleteTopic(params, RequestOptions.none()) + + /** @see deleteTopic */ + @MustBeClosed + fun deleteTopic( + params: PreferenceDeleteTopicParams, + requestOptions: RequestOptions = RequestOptions.none(), + ): HttpResponse + /** * Returns a raw HTTP response for `get /users/{user_id}/preferences/{topic_id}`, but is * otherwise the same as [PreferenceService.retrieveTopic]. diff --git a/courier-java-core/src/main/kotlin/com/courier/services/blocking/users/PreferenceServiceImpl.kt b/courier-java-core/src/main/kotlin/com/courier/services/blocking/users/PreferenceServiceImpl.kt index 9fd813ba..d072bb6c 100644 --- a/courier-java-core/src/main/kotlin/com/courier/services/blocking/users/PreferenceServiceImpl.kt +++ b/courier-java-core/src/main/kotlin/com/courier/services/blocking/users/PreferenceServiceImpl.kt @@ -5,6 +5,7 @@ package com.courier.services.blocking.users import com.courier.core.ClientOptions import com.courier.core.RequestOptions import com.courier.core.checkRequired +import com.courier.core.handlers.emptyHandler import com.courier.core.handlers.errorBodyHandler import com.courier.core.handlers.errorHandler import com.courier.core.handlers.jsonHandler @@ -16,6 +17,7 @@ import com.courier.core.http.HttpResponseFor import com.courier.core.http.json import com.courier.core.http.parseable import com.courier.core.prepare +import com.courier.models.users.preferences.PreferenceDeleteTopicParams import com.courier.models.users.preferences.PreferenceRetrieveParams import com.courier.models.users.preferences.PreferenceRetrieveResponse import com.courier.models.users.preferences.PreferenceRetrieveTopicParams @@ -44,6 +46,11 @@ class PreferenceServiceImpl internal constructor(private val clientOptions: Clie // get /users/{user_id}/preferences withRawResponse().retrieve(params, requestOptions).parse() + override fun deleteTopic(params: PreferenceDeleteTopicParams, requestOptions: RequestOptions) { + // delete /users/{user_id}/preferences/{topic_id} + withRawResponse().deleteTopic(params, requestOptions) + } + override fun retrieveTopic( params: PreferenceRetrieveTopicParams, requestOptions: RequestOptions, @@ -101,6 +108,35 @@ class PreferenceServiceImpl internal constructor(private val clientOptions: Clie } } + private val deleteTopicHandler: Handler = emptyHandler() + + override fun deleteTopic( + params: PreferenceDeleteTopicParams, + requestOptions: RequestOptions, + ): HttpResponse { + // We check here instead of in the params builder because this can be specified + // positionally or in the params class. + checkRequired("topicId", params.topicId().getOrNull()) + val request = + HttpRequest.builder() + .method(HttpMethod.DELETE) + .baseUrl(clientOptions.baseUrl()) + .addPathSegments( + "users", + params._pathParam(0), + "preferences", + params._pathParam(1), + ) + .apply { params._body().ifPresent { body(json(clientOptions.jsonMapper, it)) } } + .build() + .prepare(clientOptions, params) + val requestOptions = requestOptions.applyDefaults(RequestOptions.from(clientOptions)) + val response = clientOptions.httpClient.execute(request, requestOptions) + return errorHandler.handle(response).parseable { + response.use { deleteTopicHandler.handle(it) } + } + } + private val retrieveTopicHandler: Handler = jsonHandler(clientOptions.jsonMapper) diff --git a/courier-java-core/src/test/kotlin/com/courier/models/users/preferences/PreferenceDeleteTopicParamsTest.kt b/courier-java-core/src/test/kotlin/com/courier/models/users/preferences/PreferenceDeleteTopicParamsTest.kt new file mode 100644 index 00000000..6b3f4e9b --- /dev/null +++ b/courier-java-core/src/test/kotlin/com/courier/models/users/preferences/PreferenceDeleteTopicParamsTest.kt @@ -0,0 +1,55 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.courier.models.users.preferences + +import com.courier.core.http.QueryParams +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class PreferenceDeleteTopicParamsTest { + + @Test + fun create() { + PreferenceDeleteTopicParams.builder() + .userId("user_id") + .topicId("topic_id") + .tenantId("tenant_id") + .build() + } + + @Test + fun pathParams() { + val params = + PreferenceDeleteTopicParams.builder().userId("user_id").topicId("topic_id").build() + + assertThat(params._pathParam(0)).isEqualTo("user_id") + assertThat(params._pathParam(1)).isEqualTo("topic_id") + // out-of-bound path param + assertThat(params._pathParam(2)).isEqualTo("") + } + + @Test + fun queryParams() { + val params = + PreferenceDeleteTopicParams.builder() + .userId("user_id") + .topicId("topic_id") + .tenantId("tenant_id") + .build() + + val queryParams = params._queryParams() + + assertThat(queryParams) + .isEqualTo(QueryParams.builder().put("tenant_id", "tenant_id").build()) + } + + @Test + fun queryParamsWithoutOptionalFields() { + val params = + PreferenceDeleteTopicParams.builder().userId("user_id").topicId("topic_id").build() + + val queryParams = params._queryParams() + + assertThat(queryParams).isEqualTo(QueryParams.builder().build()) + } +} diff --git a/courier-java-core/src/test/kotlin/com/courier/services/async/users/PreferenceServiceAsyncTest.kt b/courier-java-core/src/test/kotlin/com/courier/services/async/users/PreferenceServiceAsyncTest.kt index f881d079..c4207499 100644 --- a/courier-java-core/src/test/kotlin/com/courier/services/async/users/PreferenceServiceAsyncTest.kt +++ b/courier-java-core/src/test/kotlin/com/courier/services/async/users/PreferenceServiceAsyncTest.kt @@ -5,6 +5,7 @@ package com.courier.services.async.users import com.courier.client.okhttp.CourierOkHttpClientAsync import com.courier.models.ChannelClassification import com.courier.models.PreferenceStatus +import com.courier.models.users.preferences.PreferenceDeleteTopicParams import com.courier.models.users.preferences.PreferenceRetrieveParams import com.courier.models.users.preferences.PreferenceRetrieveTopicParams import com.courier.models.users.preferences.PreferenceUpdateOrCreateTopicParams @@ -28,6 +29,24 @@ internal class PreferenceServiceAsyncTest { preference.validate() } + @Disabled("Mock server tests are disabled") + @Test + fun deleteTopic() { + val client = CourierOkHttpClientAsync.builder().apiKey("My API Key").build() + val preferenceServiceAsync = client.users().preferences() + + val future = + preferenceServiceAsync.deleteTopic( + PreferenceDeleteTopicParams.builder() + .userId("user_id") + .topicId("topic_id") + .tenantId("tenant_id") + .build() + ) + + val response = future.get() + } + @Disabled("Mock server tests are disabled") @Test fun retrieveTopic() { diff --git a/courier-java-core/src/test/kotlin/com/courier/services/blocking/users/PreferenceServiceTest.kt b/courier-java-core/src/test/kotlin/com/courier/services/blocking/users/PreferenceServiceTest.kt index 3e3b56a0..ad2bff4b 100644 --- a/courier-java-core/src/test/kotlin/com/courier/services/blocking/users/PreferenceServiceTest.kt +++ b/courier-java-core/src/test/kotlin/com/courier/services/blocking/users/PreferenceServiceTest.kt @@ -5,6 +5,7 @@ package com.courier.services.blocking.users import com.courier.client.okhttp.CourierOkHttpClient import com.courier.models.ChannelClassification import com.courier.models.PreferenceStatus +import com.courier.models.users.preferences.PreferenceDeleteTopicParams import com.courier.models.users.preferences.PreferenceRetrieveParams import com.courier.models.users.preferences.PreferenceRetrieveTopicParams import com.courier.models.users.preferences.PreferenceUpdateOrCreateTopicParams @@ -27,6 +28,21 @@ internal class PreferenceServiceTest { preference.validate() } + @Disabled("Mock server tests are disabled") + @Test + fun deleteTopic() { + val client = CourierOkHttpClient.builder().apiKey("My API Key").build() + val preferenceService = client.users().preferences() + + preferenceService.deleteTopic( + PreferenceDeleteTopicParams.builder() + .userId("user_id") + .topicId("topic_id") + .tenantId("tenant_id") + .build() + ) + } + @Disabled("Mock server tests are disabled") @Test fun retrieveTopic() { From 01ef61c19aef27fb6df26735cf5e1d9ee94cc6b6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 17 Jun 2026 20:13:05 +0000 Subject: [PATCH 2/3] feat(openapi): add add-to-digest JourneyNode variant --- .stats.yml | 4 +- .../models/journeys/CreateJourneyRequest.kt | 4 + .../courier/models/journeys/JourneyNode.kt | 502 ++++++++++++++++++ .../models/journeys/JourneyResponse.kt | 4 + .../models/journeys/JourneyNodeTest.kt | 63 +++ 5 files changed, 575 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 0243e76f..55e2ce66 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 120 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/courier/courier-c19b2724476238e0f927330774db54376f38a992e72b96b7f41d8d3427aa2330.yml -openapi_spec_hash: df7abb5a984379086e62873773b85a03 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/courier/courier-cf10f3428ad9b9f636939a9305e80dd49118fea2c3f86cf61bf2b95367469e0f.yml +openapi_spec_hash: a61356bb015cec773770d2b61cabaa58 config_hash: 86b472590f1c27b5a74499744b30c2ee diff --git a/courier-java-core/src/main/kotlin/com/courier/models/journeys/CreateJourneyRequest.kt b/courier-java-core/src/main/kotlin/com/courier/models/journeys/CreateJourneyRequest.kt index c77f1e8c..8badf43a 100644 --- a/courier-java-core/src/main/kotlin/com/courier/models/journeys/CreateJourneyRequest.kt +++ b/courier-java-core/src/main/kotlin/com/courier/models/journeys/CreateJourneyRequest.kt @@ -212,6 +212,10 @@ private constructor( /** Alias for calling [addNode] with `JourneyNode.ofBatch(batch)`. */ fun addNode(batch: JourneyNode.JourneyBatchNode) = addNode(JourneyNode.ofBatch(batch)) + /** Alias for calling [addNode] with `JourneyNode.ofAddToDigest(addToDigest)`. */ + fun addNode(addToDigest: JourneyNode.JourneyAddToDigestNode) = + addNode(JourneyNode.ofAddToDigest(addToDigest)) + /** Alias for calling [addNode] with `JourneyNode.ofExit(exit)`. */ fun addNode(exit: JourneyExitNode) = addNode(JourneyNode.ofExit(exit)) diff --git a/courier-java-core/src/main/kotlin/com/courier/models/journeys/JourneyNode.kt b/courier-java-core/src/main/kotlin/com/courier/models/journeys/JourneyNode.kt index 0b8bc3c5..c7c163fc 100644 --- a/courier-java-core/src/main/kotlin/com/courier/models/journeys/JourneyNode.kt +++ b/courier-java-core/src/main/kotlin/com/courier/models/journeys/JourneyNode.kt @@ -51,6 +51,7 @@ private constructor( private val throttleStatic: JourneyThrottleStaticNode? = null, private val throttleDynamic: JourneyThrottleDynamicNode? = null, private val batch: JourneyBatchNode? = null, + private val addToDigest: JourneyAddToDigestNode? = null, private val exit: JourneyExitNode? = null, private val branch: JourneyBranchNode? = null, private val _json: JsonValue? = null, @@ -117,6 +118,12 @@ private constructor( */ fun batch(): Optional = Optional.ofNullable(batch) + /** + * Add the current event to a digest keyed by the given subscription topic. The digest + * accumulates events and releases them on the schedule configured for the topic. + */ + fun addToDigest(): Optional = Optional.ofNullable(addToDigest) + /** Terminate the journey run. */ fun exit(): Optional = Optional.ofNullable(exit) @@ -148,6 +155,8 @@ private constructor( fun isBatch(): Boolean = batch != null + fun isAddToDigest(): Boolean = addToDigest != null + fun isExit(): Boolean = exit != null fun isBranch(): Boolean = branch != null @@ -213,6 +222,12 @@ private constructor( */ fun asBatch(): JourneyBatchNode = batch.getOrThrow("batch") + /** + * Add the current event to a digest keyed by the given subscription topic. The digest + * accumulates events and releases them on the schedule configured for the topic. + */ + fun asAddToDigest(): JourneyAddToDigestNode = addToDigest.getOrThrow("addToDigest") + /** Terminate the journey run. */ fun asExit(): JourneyExitNode = exit.getOrThrow("exit") @@ -266,6 +281,7 @@ private constructor( throttleStatic != null -> visitor.visitThrottleStatic(throttleStatic) throttleDynamic != null -> visitor.visitThrottleDynamic(throttleDynamic) batch != null -> visitor.visitBatch(batch) + addToDigest != null -> visitor.visitAddToDigest(addToDigest) exit != null -> visitor.visitExit(exit) branch != null -> visitor.visitBranch(branch) else -> visitor.unknown(_json) @@ -332,6 +348,10 @@ private constructor( batch.validate() } + override fun visitAddToDigest(addToDigest: JourneyAddToDigestNode) { + addToDigest.validate() + } + override fun visitExit(exit: JourneyExitNode) { exit.validate() } @@ -391,6 +411,9 @@ private constructor( override fun visitBatch(batch: JourneyBatchNode) = batch.validity() + override fun visitAddToDigest(addToDigest: JourneyAddToDigestNode) = + addToDigest.validity() + override fun visitExit(exit: JourneyExitNode) = exit.validity() override fun visitBranch(branch: JourneyBranchNode) = branch.validity() @@ -416,6 +439,7 @@ private constructor( throttleStatic == other.throttleStatic && throttleDynamic == other.throttleDynamic && batch == other.batch && + addToDigest == other.addToDigest && exit == other.exit && branch == other.branch } @@ -433,6 +457,7 @@ private constructor( throttleStatic, throttleDynamic, batch, + addToDigest, exit, branch, ) @@ -450,6 +475,7 @@ private constructor( throttleStatic != null -> "JourneyNode{throttleStatic=$throttleStatic}" throttleDynamic != null -> "JourneyNode{throttleDynamic=$throttleDynamic}" batch != null -> "JourneyNode{batch=$batch}" + addToDigest != null -> "JourneyNode{addToDigest=$addToDigest}" exit != null -> "JourneyNode{exit=$exit}" branch != null -> "JourneyNode{branch=$branch}" _json != null -> "JourneyNode{_unknown=$_json}" @@ -532,6 +558,14 @@ private constructor( */ @JvmStatic fun ofBatch(batch: JourneyBatchNode) = JourneyNode(batch = batch) + /** + * Add the current event to a digest keyed by the given subscription topic. The digest + * accumulates events and releases them on the schedule configured for the topic. + */ + @JvmStatic + fun ofAddToDigest(addToDigest: JourneyAddToDigestNode) = + JourneyNode(addToDigest = addToDigest) + /** Terminate the journey run. */ @JvmStatic fun ofExit(exit: JourneyExitNode) = JourneyNode(exit = exit) @@ -606,6 +640,12 @@ private constructor( */ fun visitBatch(batch: JourneyBatchNode): T + /** + * Add the current event to a digest keyed by the given subscription topic. The digest + * accumulates events and releases them on the schedule configured for the topic. + */ + fun visitAddToDigest(addToDigest: JourneyAddToDigestNode): T + /** Terminate the journey run. */ fun visitExit(exit: JourneyExitNode): T @@ -669,6 +709,9 @@ private constructor( tryDeserialize(node, jacksonTypeRef())?.let { JourneyNode(batch = it, _json = json) }, + tryDeserialize(node, jacksonTypeRef())?.let { + JourneyNode(addToDigest = it, _json = json) + }, tryDeserialize(node, jacksonTypeRef())?.let { JourneyNode(exit = it, _json = json) }, @@ -710,6 +753,7 @@ private constructor( value.throttleStatic != null -> generator.writeObject(value.throttleStatic) value.throttleDynamic != null -> generator.writeObject(value.throttleDynamic) value.batch != null -> generator.writeObject(value.batch) + value.addToDigest != null -> generator.writeObject(value.addToDigest) value.exit != null -> generator.writeObject(value.exit) value.branch != null -> generator.writeObject(value.branch) value._json != null -> generator.writeObject(value._json) @@ -1934,6 +1978,456 @@ private constructor( "JourneyBatchNode{maxWaitPeriod=$maxWaitPeriod, retain=$retain, scope=$scope, type=$type, waitPeriod=$waitPeriod, id=$id, categoryKey=$categoryKey, conditions=$conditions, maxItems=$maxItems, additionalProperties=$additionalProperties}" } + /** + * Add the current event to a digest keyed by the given subscription topic. The digest + * accumulates events and releases them on the schedule configured for the topic. + */ + class JourneyAddToDigestNode + @JsonCreator(mode = JsonCreator.Mode.DISABLED) + private constructor( + private val subscriptionTopicId: JsonField, + private val type: JsonField, + private val id: JsonField, + private val conditions: JsonField, + private val additionalProperties: MutableMap, + ) { + + @JsonCreator + private constructor( + @JsonProperty("subscription_topic_id") + @ExcludeMissing + subscriptionTopicId: JsonField = JsonMissing.of(), + @JsonProperty("type") @ExcludeMissing type: JsonField = JsonMissing.of(), + @JsonProperty("id") @ExcludeMissing id: JsonField = JsonMissing.of(), + @JsonProperty("conditions") + @ExcludeMissing + conditions: JsonField = JsonMissing.of(), + ) : this(subscriptionTopicId, type, id, conditions, mutableMapOf()) + + /** + * The subscription topic that owns the digest the event is added to. + * + * @throws CourierInvalidDataException if the JSON field has an unexpected type or is + * unexpectedly missing or null (e.g. if the server responded with an unexpected value). + */ + fun subscriptionTopicId(): String = subscriptionTopicId.getRequired("subscription_topic_id") + + /** + * @throws CourierInvalidDataException if the JSON field has an unexpected type or is + * unexpectedly missing or null (e.g. if the server responded with an unexpected value). + */ + fun type(): Type = type.getRequired("type") + + /** + * @throws CourierInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun id(): Optional = id.getOptional("id") + + /** + * Condition spec for a journey node. Accepts a single condition atom, an AND/OR group, or + * an AND/OR nested group. Omit the `conditions` property entirely to express "no + * conditions". + * + * @throws CourierInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun conditions(): Optional = conditions.getOptional("conditions") + + /** + * Returns the raw JSON value of [subscriptionTopicId]. + * + * Unlike [subscriptionTopicId], this method doesn't throw if the JSON field has an + * unexpected type. + */ + @JsonProperty("subscription_topic_id") + @ExcludeMissing + fun _subscriptionTopicId(): JsonField = subscriptionTopicId + + /** + * Returns the raw JSON value of [type]. + * + * Unlike [type], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("type") @ExcludeMissing fun _type(): JsonField = type + + /** + * Returns the raw JSON value of [id]. + * + * Unlike [id], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("id") @ExcludeMissing fun _id(): JsonField = id + + /** + * Returns the raw JSON value of [conditions]. + * + * Unlike [conditions], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("conditions") + @ExcludeMissing + fun _conditions(): JsonField = conditions + + @JsonAnySetter + private fun putAdditionalProperty(key: String, value: JsonValue) { + additionalProperties.put(key, value) + } + + @JsonAnyGetter + @ExcludeMissing + fun _additionalProperties(): Map = + Collections.unmodifiableMap(additionalProperties) + + fun toBuilder() = Builder().from(this) + + companion object { + + /** + * Returns a mutable builder for constructing an instance of [JourneyAddToDigestNode]. + * + * The following fields are required: + * ```java + * .subscriptionTopicId() + * .type() + * ``` + */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [JourneyAddToDigestNode]. */ + class Builder internal constructor() { + + private var subscriptionTopicId: JsonField? = null + private var type: JsonField? = null + private var id: JsonField = JsonMissing.of() + private var conditions: JsonField = JsonMissing.of() + private var additionalProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(journeyAddToDigestNode: JourneyAddToDigestNode) = apply { + subscriptionTopicId = journeyAddToDigestNode.subscriptionTopicId + type = journeyAddToDigestNode.type + id = journeyAddToDigestNode.id + conditions = journeyAddToDigestNode.conditions + additionalProperties = journeyAddToDigestNode.additionalProperties.toMutableMap() + } + + /** The subscription topic that owns the digest the event is added to. */ + fun subscriptionTopicId(subscriptionTopicId: String) = + subscriptionTopicId(JsonField.of(subscriptionTopicId)) + + /** + * Sets [Builder.subscriptionTopicId] to an arbitrary JSON value. + * + * You should usually call [Builder.subscriptionTopicId] with a well-typed [String] + * value instead. This method is primarily for setting the field to an undocumented or + * not yet supported value. + */ + fun subscriptionTopicId(subscriptionTopicId: JsonField) = apply { + this.subscriptionTopicId = subscriptionTopicId + } + + fun type(type: Type) = type(JsonField.of(type)) + + /** + * Sets [Builder.type] to an arbitrary JSON value. + * + * You should usually call [Builder.type] with a well-typed [Type] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun type(type: JsonField) = apply { this.type = type } + + fun id(id: String) = id(JsonField.of(id)) + + /** + * Sets [Builder.id] to an arbitrary JSON value. + * + * You should usually call [Builder.id] with a well-typed [String] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun id(id: JsonField) = apply { this.id = id } + + /** + * Condition spec for a journey node. Accepts a single condition atom, an AND/OR group, + * or an AND/OR nested group. Omit the `conditions` property entirely to express "no + * conditions". + */ + fun conditions(conditions: JourneyConditionsField) = + conditions(JsonField.of(conditions)) + + /** + * Sets [Builder.conditions] to an arbitrary JSON value. + * + * You should usually call [Builder.conditions] with a well-typed + * [JourneyConditionsField] value instead. This method is primarily for setting the + * field to an undocumented or not yet supported value. + */ + fun conditions(conditions: JsonField) = apply { + this.conditions = conditions + } + + /** + * Alias for calling [conditions] with + * `JourneyConditionsField.ofConditionAtom(conditionAtom)`. + */ + fun conditionsOfConditionAtom(conditionAtom: List) = + conditions(JourneyConditionsField.ofConditionAtom(conditionAtom)) + + /** + * Alias for calling [conditions] with + * `JourneyConditionsField.ofConditionGroup(conditionGroup)`. + */ + fun conditions(conditionGroup: JourneyConditionGroup) = + conditions(JourneyConditionsField.ofConditionGroup(conditionGroup)) + + /** + * Alias for calling [conditions] with + * `JourneyConditionsField.ofConditionNestedGroup(conditionNestedGroup)`. + */ + fun conditions(conditionNestedGroup: JourneyConditionNestedGroup) = + conditions(JourneyConditionsField.ofConditionNestedGroup(conditionNestedGroup)) + + fun additionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.clear() + putAllAdditionalProperties(additionalProperties) + } + + fun putAdditionalProperty(key: String, value: JsonValue) = apply { + additionalProperties.put(key, value) + } + + fun putAllAdditionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.putAll(additionalProperties) + } + + fun removeAdditionalProperty(key: String) = apply { additionalProperties.remove(key) } + + fun removeAllAdditionalProperties(keys: Set) = apply { + keys.forEach(::removeAdditionalProperty) + } + + /** + * Returns an immutable instance of [JourneyAddToDigestNode]. + * + * Further updates to this [Builder] will not mutate the returned instance. + * + * The following fields are required: + * ```java + * .subscriptionTopicId() + * .type() + * ``` + * + * @throws IllegalStateException if any required field is unset. + */ + fun build(): JourneyAddToDigestNode = + JourneyAddToDigestNode( + checkRequired("subscriptionTopicId", subscriptionTopicId), + checkRequired("type", type), + id, + conditions, + additionalProperties.toMutableMap(), + ) + } + + private var validated: Boolean = false + + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws CourierInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): JourneyAddToDigestNode = apply { + if (validated) { + return@apply + } + + subscriptionTopicId() + type().validate() + id() + conditions().ifPresent { it.validate() } + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: CourierInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object + * recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic + internal fun validity(): Int = + (if (subscriptionTopicId.asKnown().isPresent) 1 else 0) + + (type.asKnown().getOrNull()?.validity() ?: 0) + + (if (id.asKnown().isPresent) 1 else 0) + + (conditions.asKnown().getOrNull()?.validity() ?: 0) + + class Type @JsonCreator private constructor(private val value: JsonField) : Enum { + + /** + * Returns this class instance's raw value. + * + * This is usually only useful if this instance was deserialized from data that doesn't + * match any known member, and you want to know that value. For example, if the SDK is + * on an older version than the API, then the API may respond with new members that the + * SDK is unaware of. + */ + @com.fasterxml.jackson.annotation.JsonValue fun _value(): JsonField = value + + companion object { + + @JvmField val ADD_TO_DIGEST = of("add-to-digest") + + @JvmStatic fun of(value: String) = Type(JsonField.of(value)) + } + + /** An enum containing [Type]'s known values. */ + enum class Known { + ADD_TO_DIGEST + } + + /** + * An enum containing [Type]'s known values, as well as an [_UNKNOWN] member. + * + * An instance of [Type] can contain an unknown value in a couple of cases: + * - It was deserialized from data that doesn't match any known member. For example, if + * the SDK is on an older version than the API, then the API may respond with new + * members that the SDK is unaware of. + * - It was constructed with an arbitrary value using the [of] method. + */ + enum class Value { + ADD_TO_DIGEST, + /** An enum member indicating that [Type] was instantiated with an unknown value. */ + _UNKNOWN, + } + + /** + * Returns an enum member corresponding to this class instance's value, or + * [Value._UNKNOWN] if the class was instantiated with an unknown value. + * + * Use the [known] method instead if you're certain the value is always known or if you + * want to throw for the unknown case. + */ + fun value(): Value = + when (this) { + ADD_TO_DIGEST -> Value.ADD_TO_DIGEST + else -> Value._UNKNOWN + } + + /** + * Returns an enum member corresponding to this class instance's value. + * + * Use the [value] method instead if you're uncertain the value is always known and + * don't want to throw for the unknown case. + * + * @throws CourierInvalidDataException if this class instance's value is a not a known + * member. + */ + fun known(): Known = + when (this) { + ADD_TO_DIGEST -> Known.ADD_TO_DIGEST + else -> throw CourierInvalidDataException("Unknown Type: $value") + } + + /** + * Returns this class instance's primitive wire representation. + * + * This differs from the [toString] method because that method is primarily for + * debugging and generally doesn't throw. + * + * @throws CourierInvalidDataException if this class instance's value does not have the + * expected primitive type. + */ + fun asString(): String = + _value().asString().orElseThrow { + CourierInvalidDataException("Value is not a String") + } + + private var validated: Boolean = false + + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing + * fields. + * + * @throws CourierInvalidDataException if any value type in this object doesn't match + * its expected type. + */ + fun validate(): Type = apply { + if (validated) { + return@apply + } + + known() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: CourierInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object + * recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic internal fun validity(): Int = if (value() == Value._UNKNOWN) 0 else 1 + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is Type && value == other.value + } + + override fun hashCode() = value.hashCode() + + override fun toString() = value.toString() + } + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is JourneyAddToDigestNode && + subscriptionTopicId == other.subscriptionTopicId && + type == other.type && + id == other.id && + conditions == other.conditions && + additionalProperties == other.additionalProperties + } + + private val hashCode: Int by lazy { + Objects.hash(subscriptionTopicId, type, id, conditions, additionalProperties) + } + + override fun hashCode(): Int = hashCode + + override fun toString() = + "JourneyAddToDigestNode{subscriptionTopicId=$subscriptionTopicId, type=$type, id=$id, conditions=$conditions, additionalProperties=$additionalProperties}" + } + /** * Branch node. Routes to the first entry in `paths[]` whose `conditions` match, else falls * through to `default.nodes`. @@ -2364,6 +2858,10 @@ private constructor( /** Alias for calling [addNode] with `JourneyNode.ofBatch(batch)`. */ fun addNode(batch: JourneyBatchNode) = addNode(JourneyNode.ofBatch(batch)) + /** Alias for calling [addNode] with `JourneyNode.ofAddToDigest(addToDigest)`. */ + fun addNode(addToDigest: JourneyAddToDigestNode) = + addNode(JourneyNode.ofAddToDigest(addToDigest)) + /** Alias for calling [addNode] with `JourneyNode.ofExit(exit)`. */ fun addNode(exit: JourneyExitNode) = addNode(JourneyNode.ofExit(exit)) @@ -2715,6 +3213,10 @@ private constructor( /** Alias for calling [addNode] with `JourneyNode.ofBatch(batch)`. */ fun addNode(batch: JourneyBatchNode) = addNode(JourneyNode.ofBatch(batch)) + /** Alias for calling [addNode] with `JourneyNode.ofAddToDigest(addToDigest)`. */ + fun addNode(addToDigest: JourneyAddToDigestNode) = + addNode(JourneyNode.ofAddToDigest(addToDigest)) + /** Alias for calling [addNode] with `JourneyNode.ofExit(exit)`. */ fun addNode(exit: JourneyExitNode) = addNode(JourneyNode.ofExit(exit)) diff --git a/courier-java-core/src/main/kotlin/com/courier/models/journeys/JourneyResponse.kt b/courier-java-core/src/main/kotlin/com/courier/models/journeys/JourneyResponse.kt index 0d3636b8..64a31e0e 100644 --- a/courier-java-core/src/main/kotlin/com/courier/models/journeys/JourneyResponse.kt +++ b/courier-java-core/src/main/kotlin/com/courier/models/journeys/JourneyResponse.kt @@ -387,6 +387,10 @@ private constructor( /** Alias for calling [addNode] with `JourneyNode.ofBatch(batch)`. */ fun addNode(batch: JourneyNode.JourneyBatchNode) = addNode(JourneyNode.ofBatch(batch)) + /** Alias for calling [addNode] with `JourneyNode.ofAddToDigest(addToDigest)`. */ + fun addNode(addToDigest: JourneyNode.JourneyAddToDigestNode) = + addNode(JourneyNode.ofAddToDigest(addToDigest)) + /** Alias for calling [addNode] with `JourneyNode.ofExit(exit)`. */ fun addNode(exit: JourneyExitNode) = addNode(JourneyNode.ofExit(exit)) diff --git a/courier-java-core/src/test/kotlin/com/courier/models/journeys/JourneyNodeTest.kt b/courier-java-core/src/test/kotlin/com/courier/models/journeys/JourneyNodeTest.kt index 800d19ad..92ab4947 100644 --- a/courier-java-core/src/test/kotlin/com/courier/models/journeys/JourneyNodeTest.kt +++ b/courier-java-core/src/test/kotlin/com/courier/models/journeys/JourneyNodeTest.kt @@ -42,6 +42,7 @@ internal class JourneyNodeTest { assertThat(journeyNode.throttleStatic()).isEmpty assertThat(journeyNode.throttleDynamic()).isEmpty assertThat(journeyNode.batch()).isEmpty + assertThat(journeyNode.addToDigest()).isEmpty assertThat(journeyNode.exit()).isEmpty assertThat(journeyNode.branch()).isEmpty } @@ -98,6 +99,7 @@ internal class JourneyNodeTest { assertThat(journeyNode.throttleStatic()).isEmpty assertThat(journeyNode.throttleDynamic()).isEmpty assertThat(journeyNode.batch()).isEmpty + assertThat(journeyNode.addToDigest()).isEmpty assertThat(journeyNode.exit()).isEmpty assertThat(journeyNode.branch()).isEmpty } @@ -168,6 +170,7 @@ internal class JourneyNodeTest { assertThat(journeyNode.throttleStatic()).isEmpty assertThat(journeyNode.throttleDynamic()).isEmpty assertThat(journeyNode.batch()).isEmpty + assertThat(journeyNode.addToDigest()).isEmpty assertThat(journeyNode.exit()).isEmpty assertThat(journeyNode.branch()).isEmpty } @@ -240,6 +243,7 @@ internal class JourneyNodeTest { assertThat(journeyNode.throttleStatic()).isEmpty assertThat(journeyNode.throttleDynamic()).isEmpty assertThat(journeyNode.batch()).isEmpty + assertThat(journeyNode.addToDigest()).isEmpty assertThat(journeyNode.exit()).isEmpty assertThat(journeyNode.branch()).isEmpty } @@ -291,6 +295,7 @@ internal class JourneyNodeTest { assertThat(journeyNode.throttleStatic()).isEmpty assertThat(journeyNode.throttleDynamic()).isEmpty assertThat(journeyNode.batch()).isEmpty + assertThat(journeyNode.addToDigest()).isEmpty assertThat(journeyNode.exit()).isEmpty assertThat(journeyNode.branch()).isEmpty } @@ -358,6 +363,7 @@ internal class JourneyNodeTest { assertThat(journeyNode.throttleStatic()).isEmpty assertThat(journeyNode.throttleDynamic()).isEmpty assertThat(journeyNode.batch()).isEmpty + assertThat(journeyNode.addToDigest()).isEmpty assertThat(journeyNode.exit()).isEmpty assertThat(journeyNode.branch()).isEmpty } @@ -442,6 +448,7 @@ internal class JourneyNodeTest { assertThat(journeyNode.throttleStatic()).isEmpty assertThat(journeyNode.throttleDynamic()).isEmpty assertThat(journeyNode.batch()).isEmpty + assertThat(journeyNode.addToDigest()).isEmpty assertThat(journeyNode.exit()).isEmpty assertThat(journeyNode.branch()).isEmpty } @@ -516,6 +523,7 @@ internal class JourneyNodeTest { assertThat(journeyNode.throttleStatic()).isEmpty assertThat(journeyNode.throttleDynamic()).isEmpty assertThat(journeyNode.batch()).isEmpty + assertThat(journeyNode.addToDigest()).isEmpty assertThat(journeyNode.exit()).isEmpty assertThat(journeyNode.branch()).isEmpty } @@ -574,6 +582,7 @@ internal class JourneyNodeTest { assertThat(journeyNode.throttleStatic()).contains(throttleStatic) assertThat(journeyNode.throttleDynamic()).isEmpty assertThat(journeyNode.batch()).isEmpty + assertThat(journeyNode.addToDigest()).isEmpty assertThat(journeyNode.exit()).isEmpty assertThat(journeyNode.branch()).isEmpty } @@ -628,6 +637,7 @@ internal class JourneyNodeTest { assertThat(journeyNode.throttleStatic()).isEmpty assertThat(journeyNode.throttleDynamic()).contains(throttleDynamic) assertThat(journeyNode.batch()).isEmpty + assertThat(journeyNode.addToDigest()).isEmpty assertThat(journeyNode.exit()).isEmpty assertThat(journeyNode.branch()).isEmpty } @@ -691,6 +701,7 @@ internal class JourneyNodeTest { assertThat(journeyNode.throttleStatic()).isEmpty assertThat(journeyNode.throttleDynamic()).isEmpty assertThat(journeyNode.batch()).contains(batch) + assertThat(journeyNode.addToDigest()).isEmpty assertThat(journeyNode.exit()).isEmpty assertThat(journeyNode.branch()).isEmpty } @@ -728,6 +739,56 @@ internal class JourneyNodeTest { assertThat(roundtrippedJourneyNode).isEqualTo(journeyNode) } + @Test + fun ofAddToDigest() { + val addToDigest = + JourneyNode.JourneyAddToDigestNode.builder() + .subscriptionTopicId("x") + .type(JourneyNode.JourneyAddToDigestNode.Type.ADD_TO_DIGEST) + .id("x") + .conditionsOfConditionAtom(listOf("string", "string")) + .build() + + val journeyNode = JourneyNode.ofAddToDigest(addToDigest) + + assertThat(journeyNode.apiInvokeTrigger()).isEmpty + assertThat(journeyNode.segmentTrigger()).isEmpty + assertThat(journeyNode.send()).isEmpty + assertThat(journeyNode.delayDuration()).isEmpty + assertThat(journeyNode.delayUntil()).isEmpty + assertThat(journeyNode.fetchGetDelete()).isEmpty + assertThat(journeyNode.fetchPostPut()).isEmpty + assertThat(journeyNode.ai()).isEmpty + assertThat(journeyNode.throttleStatic()).isEmpty + assertThat(journeyNode.throttleDynamic()).isEmpty + assertThat(journeyNode.batch()).isEmpty + assertThat(journeyNode.addToDigest()).contains(addToDigest) + assertThat(journeyNode.exit()).isEmpty + assertThat(journeyNode.branch()).isEmpty + } + + @Test + fun ofAddToDigestRoundtrip() { + val jsonMapper = jsonMapper() + val journeyNode = + JourneyNode.ofAddToDigest( + JourneyNode.JourneyAddToDigestNode.builder() + .subscriptionTopicId("x") + .type(JourneyNode.JourneyAddToDigestNode.Type.ADD_TO_DIGEST) + .id("x") + .conditionsOfConditionAtom(listOf("string", "string")) + .build() + ) + + val roundtrippedJourneyNode = + jsonMapper.readValue( + jsonMapper.writeValueAsString(journeyNode), + jacksonTypeRef(), + ) + + assertThat(roundtrippedJourneyNode).isEqualTo(journeyNode) + } + @Test fun ofExit() { val exit = JourneyExitNode.builder().type(JourneyExitNode.Type.EXIT).id("x").build() @@ -745,6 +806,7 @@ internal class JourneyNodeTest { assertThat(journeyNode.throttleStatic()).isEmpty assertThat(journeyNode.throttleDynamic()).isEmpty assertThat(journeyNode.batch()).isEmpty + assertThat(journeyNode.addToDigest()).isEmpty assertThat(journeyNode.exit()).contains(exit) assertThat(journeyNode.branch()).isEmpty } @@ -812,6 +874,7 @@ internal class JourneyNodeTest { assertThat(journeyNode.throttleStatic()).isEmpty assertThat(journeyNode.throttleDynamic()).isEmpty assertThat(journeyNode.batch()).isEmpty + assertThat(journeyNode.addToDigest()).isEmpty assertThat(journeyNode.exit()).isEmpty assertThat(journeyNode.branch()).contains(branch) } From 3b9dc21f963f226493c10c14bed0a4b34fc2d82d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 17 Jun 2026 20:13:31 +0000 Subject: [PATCH 3/3] release: 4.16.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 9 +++++++++ build.gradle.kts | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 34f6b414..a5dd041d 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "4.15.0" + ".": "4.16.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bdab1ac..bc7e9cbf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 4.16.0 (2026-06-17) + +Full Changelog: [v4.15.0...v4.16.0](https://github.com/trycourier/courier-java/compare/v4.15.0...v4.16.0) + +### Features + +* **openapi:** add add-to-digest JourneyNode variant ([01ef61c](https://github.com/trycourier/courier-java/commit/01ef61c19aef27fb6df26735cf5e1d9ee94cc6b6)) +* **openapi:** add DELETE /users/{id}/preferences/{topicId} ([34315dd](https://github.com/trycourier/courier-java/commit/34315ddb9b1b1663efbaf95d9ac9b594039dcd60)) + ## 4.15.0 (2026-06-12) Full Changelog: [v4.14.0...v4.15.0](https://github.com/trycourier/courier-java/compare/v4.14.0...v4.15.0) diff --git a/build.gradle.kts b/build.gradle.kts index 31189891..6308174d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,7 +8,7 @@ repositories { allprojects { group = "com.courier" - version = "4.15.0" // x-release-please-version + version = "4.16.0" // x-release-please-version } subprojects {