From 815f092362606058abe1a6d36842449349f95284 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 9 Jul 2026 00:13:12 +0000 Subject: [PATCH 1/3] chore: update api-spec with new message resend endpoint --- .stats.yml | 8 +- .../models/messages/MessageResendParams.kt | 234 ++++++++++++++++++ .../models/messages/MessageResendResponse.kt | 186 ++++++++++++++ .../services/async/MessageServiceAsync.kt | 83 +++++++ .../services/async/MessageServiceAsyncImpl.kt | 43 ++++ .../services/blocking/MessageService.kt | 83 +++++++ .../services/blocking/MessageServiceImpl.kt | 40 +++ .../messages/MessageResendParamsTest.kt | 23 ++ .../messages/MessageResendResponseTest.kt | 35 +++ .../services/async/MessageServiceAsyncTest.kt | 12 + .../services/blocking/MessageServiceTest.kt | 11 + 11 files changed, 754 insertions(+), 4 deletions(-) create mode 100644 courier-java-core/src/main/kotlin/com/courier/models/messages/MessageResendParams.kt create mode 100644 courier-java-core/src/main/kotlin/com/courier/models/messages/MessageResendResponse.kt create mode 100644 courier-java-core/src/test/kotlin/com/courier/models/messages/MessageResendParamsTest.kt create mode 100644 courier-java-core/src/test/kotlin/com/courier/models/messages/MessageResendResponseTest.kt diff --git a/.stats.yml b/.stats.yml index 6620784f..fa82f667 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 135 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/courier/courier-0b80b9b90e51b4d19f5cebadddf91c345090af9e6ad46cfb9364f0421e35e0bb.yml -openapi_spec_hash: 5283fb7d76bbdd7dcf48784f7128513b -config_hash: 66d7703eac15d2affc326ac25b55bcd6 +configured_endpoints: 136 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/courier/courier-d2b3bdb6747f4acc997702dbd9f24340a3d85f22c00e1f17af2bd6621358bc77.yml +openapi_spec_hash: b6791652567d04fd87c6117090de7635 +config_hash: 627f8592c19dfb77f91ffbec2a057c3a diff --git a/courier-java-core/src/main/kotlin/com/courier/models/messages/MessageResendParams.kt b/courier-java-core/src/main/kotlin/com/courier/models/messages/MessageResendParams.kt new file mode 100644 index 00000000..9b6b037c --- /dev/null +++ b/courier-java-core/src/main/kotlin/com/courier/models/messages/MessageResendParams.kt @@ -0,0 +1,234 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.courier.models.messages + +import com.courier.core.JsonValue +import com.courier.core.Params +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 + +/** + * Resend a previously sent message. The original send request is loaded from storage and a + * brand-new send is enqueued for the same recipient and content, producing a **new** `messageId` — + * the original message is not modified. Throttled by a per-message rate limit; a repeat inside the + * limit window returns `429 Too Many Requests`. + */ +class MessageResendParams +private constructor( + private val messageId: String?, + private val additionalHeaders: Headers, + private val additionalQueryParams: QueryParams, + private val additionalBodyProperties: Map, +) : Params { + + fun messageId(): Optional = Optional.ofNullable(messageId) + + /** 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 { + + @JvmStatic fun none(): MessageResendParams = builder().build() + + /** Returns a mutable builder for constructing an instance of [MessageResendParams]. */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [MessageResendParams]. */ + class Builder internal constructor() { + + private var messageId: 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(messageResendParams: MessageResendParams) = apply { + messageId = messageResendParams.messageId + additionalHeaders = messageResendParams.additionalHeaders.toBuilder() + additionalQueryParams = messageResendParams.additionalQueryParams.toBuilder() + additionalBodyProperties = messageResendParams.additionalBodyProperties.toMutableMap() + } + + fun messageId(messageId: String?) = apply { this.messageId = messageId } + + /** Alias for calling [Builder.messageId] with `messageId.orElse(null)`. */ + fun messageId(messageId: Optional) = messageId(messageId.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 [MessageResendParams]. + * + * Further updates to this [Builder] will not mutate the returned instance. + */ + fun build(): MessageResendParams = + MessageResendParams( + messageId, + additionalHeaders.build(), + additionalQueryParams.build(), + additionalBodyProperties.toImmutable(), + ) + } + + fun _body(): Optional> = + Optional.ofNullable(additionalBodyProperties.ifEmpty { null }) + + fun _pathParam(index: Int): String = + when (index) { + 0 -> messageId ?: "" + else -> "" + } + + override fun _headers(): Headers = additionalHeaders + + override fun _queryParams(): QueryParams = additionalQueryParams + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is MessageResendParams && + messageId == other.messageId && + additionalHeaders == other.additionalHeaders && + additionalQueryParams == other.additionalQueryParams && + additionalBodyProperties == other.additionalBodyProperties + } + + override fun hashCode(): Int = + Objects.hash(messageId, additionalHeaders, additionalQueryParams, additionalBodyProperties) + + override fun toString() = + "MessageResendParams{messageId=$messageId, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams, additionalBodyProperties=$additionalBodyProperties}" +} diff --git a/courier-java-core/src/main/kotlin/com/courier/models/messages/MessageResendResponse.kt b/courier-java-core/src/main/kotlin/com/courier/models/messages/MessageResendResponse.kt new file mode 100644 index 00000000..13d20433 --- /dev/null +++ b/courier-java-core/src/main/kotlin/com/courier/models/messages/MessageResendResponse.kt @@ -0,0 +1,186 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.courier.models.messages + +import com.courier.core.ExcludeMissing +import com.courier.core.JsonField +import com.courier.core.JsonMissing +import com.courier.core.JsonValue +import com.courier.core.checkRequired +import com.courier.errors.CourierInvalidDataException +import com.fasterxml.jackson.annotation.JsonAnyGetter +import com.fasterxml.jackson.annotation.JsonAnySetter +import com.fasterxml.jackson.annotation.JsonCreator +import com.fasterxml.jackson.annotation.JsonProperty +import java.util.Collections +import java.util.Objects + +class MessageResendResponse +@JsonCreator(mode = JsonCreator.Mode.DISABLED) +private constructor( + private val messageId: JsonField, + private val additionalProperties: MutableMap, +) { + + @JsonCreator + private constructor( + @JsonProperty("messageId") @ExcludeMissing messageId: JsonField = JsonMissing.of() + ) : this(messageId, mutableMapOf()) + + /** + * The new message id for the resent message. It is distinct from the id of the original message + * that was resent. + * + * @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 messageId(): String = messageId.getRequired("messageId") + + /** + * Returns the raw JSON value of [messageId]. + * + * Unlike [messageId], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("messageId") @ExcludeMissing fun _messageId(): JsonField = messageId + + @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 [MessageResendResponse]. + * + * The following fields are required: + * ```java + * .messageId() + * ``` + */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [MessageResendResponse]. */ + class Builder internal constructor() { + + private var messageId: JsonField? = null + private var additionalProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(messageResendResponse: MessageResendResponse) = apply { + messageId = messageResendResponse.messageId + additionalProperties = messageResendResponse.additionalProperties.toMutableMap() + } + + /** + * The new message id for the resent message. It is distinct from the id of the original + * message that was resent. + */ + fun messageId(messageId: String) = messageId(JsonField.of(messageId)) + + /** + * Sets [Builder.messageId] to an arbitrary JSON value. + * + * You should usually call [Builder.messageId] with a well-typed [String] value instead. + * This method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun messageId(messageId: JsonField) = apply { this.messageId = messageId } + + 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 [MessageResendResponse]. + * + * Further updates to this [Builder] will not mutate the returned instance. + * + * The following fields are required: + * ```java + * .messageId() + * ``` + * + * @throws IllegalStateException if any required field is unset. + */ + fun build(): MessageResendResponse = + MessageResendResponse( + checkRequired("messageId", messageId), + 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(): MessageResendResponse = apply { + if (validated) { + return@apply + } + + messageId() + 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 (messageId.asKnown().isPresent) 1 else 0) + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is MessageResendResponse && + messageId == other.messageId && + additionalProperties == other.additionalProperties + } + + private val hashCode: Int by lazy { Objects.hash(messageId, additionalProperties) } + + override fun hashCode(): Int = hashCode + + override fun toString() = + "MessageResendResponse{messageId=$messageId, additionalProperties=$additionalProperties}" +} diff --git a/courier-java-core/src/main/kotlin/com/courier/services/async/MessageServiceAsync.kt b/courier-java-core/src/main/kotlin/com/courier/services/async/MessageServiceAsync.kt index a240dd13..cb502b47 100644 --- a/courier-java-core/src/main/kotlin/com/courier/services/async/MessageServiceAsync.kt +++ b/courier-java-core/src/main/kotlin/com/courier/services/async/MessageServiceAsync.kt @@ -13,6 +13,8 @@ import com.courier.models.messages.MessageHistoryParams import com.courier.models.messages.MessageHistoryResponse import com.courier.models.messages.MessageListParams import com.courier.models.messages.MessageListResponse +import com.courier.models.messages.MessageResendParams +import com.courier.models.messages.MessageResendResponse import com.courier.models.messages.MessageRetrieveParams import com.courier.models.messages.MessageRetrieveResponse import java.util.concurrent.CompletableFuture @@ -196,6 +198,46 @@ interface MessageServiceAsync { ): CompletableFuture = history(messageId, MessageHistoryParams.none(), requestOptions) + /** + * Resend a previously sent message. The original send request is loaded from storage and a + * brand-new send is enqueued for the same recipient and content, producing a **new** + * `messageId` — the original message is not modified. Throttled by a per-message rate limit; a + * repeat inside the limit window returns `429 Too Many Requests`. + */ + fun resend(messageId: String): CompletableFuture = + resend(messageId, MessageResendParams.none()) + + /** @see resend */ + fun resend( + messageId: String, + params: MessageResendParams = MessageResendParams.none(), + requestOptions: RequestOptions = RequestOptions.none(), + ): CompletableFuture = + resend(params.toBuilder().messageId(messageId).build(), requestOptions) + + /** @see resend */ + fun resend( + messageId: String, + params: MessageResendParams = MessageResendParams.none(), + ): CompletableFuture = resend(messageId, params, RequestOptions.none()) + + /** @see resend */ + fun resend( + params: MessageResendParams, + requestOptions: RequestOptions = RequestOptions.none(), + ): CompletableFuture + + /** @see resend */ + fun resend(params: MessageResendParams): CompletableFuture = + resend(params, RequestOptions.none()) + + /** @see resend */ + fun resend( + messageId: String, + requestOptions: RequestOptions, + ): CompletableFuture = + resend(messageId, MessageResendParams.none(), requestOptions) + /** * A view of [MessageServiceAsync] that provides access to raw HTTP responses for each method. */ @@ -400,5 +442,46 @@ interface MessageServiceAsync { requestOptions: RequestOptions, ): CompletableFuture> = history(messageId, MessageHistoryParams.none(), requestOptions) + + /** + * Returns a raw HTTP response for `post /messages/{message_id}/resend`, but is otherwise + * the same as [MessageServiceAsync.resend]. + */ + fun resend(messageId: String): CompletableFuture> = + resend(messageId, MessageResendParams.none()) + + /** @see resend */ + fun resend( + messageId: String, + params: MessageResendParams = MessageResendParams.none(), + requestOptions: RequestOptions = RequestOptions.none(), + ): CompletableFuture> = + resend(params.toBuilder().messageId(messageId).build(), requestOptions) + + /** @see resend */ + fun resend( + messageId: String, + params: MessageResendParams = MessageResendParams.none(), + ): CompletableFuture> = + resend(messageId, params, RequestOptions.none()) + + /** @see resend */ + fun resend( + params: MessageResendParams, + requestOptions: RequestOptions = RequestOptions.none(), + ): CompletableFuture> + + /** @see resend */ + fun resend( + params: MessageResendParams + ): CompletableFuture> = + resend(params, RequestOptions.none()) + + /** @see resend */ + fun resend( + messageId: String, + requestOptions: RequestOptions, + ): CompletableFuture> = + resend(messageId, MessageResendParams.none(), requestOptions) } } diff --git a/courier-java-core/src/main/kotlin/com/courier/services/async/MessageServiceAsyncImpl.kt b/courier-java-core/src/main/kotlin/com/courier/services/async/MessageServiceAsyncImpl.kt index ebf725e8..a102fcd2 100644 --- a/courier-java-core/src/main/kotlin/com/courier/services/async/MessageServiceAsyncImpl.kt +++ b/courier-java-core/src/main/kotlin/com/courier/services/async/MessageServiceAsyncImpl.kt @@ -24,6 +24,8 @@ import com.courier.models.messages.MessageHistoryParams import com.courier.models.messages.MessageHistoryResponse import com.courier.models.messages.MessageListParams import com.courier.models.messages.MessageListResponse +import com.courier.models.messages.MessageResendParams +import com.courier.models.messages.MessageResendResponse import com.courier.models.messages.MessageRetrieveParams import com.courier.models.messages.MessageRetrieveResponse import java.util.concurrent.CompletableFuture @@ -77,6 +79,13 @@ class MessageServiceAsyncImpl internal constructor(private val clientOptions: Cl // get /messages/{message_id}/history withRawResponse().history(params, requestOptions).thenApply { it.parse() } + override fun resend( + params: MessageResendParams, + requestOptions: RequestOptions, + ): CompletableFuture = + // post /messages/{message_id}/resend + withRawResponse().resend(params, requestOptions).thenApply { it.parse() } + class WithRawResponseImpl internal constructor(private val clientOptions: ClientOptions) : MessageServiceAsync.WithRawResponse { @@ -252,5 +261,39 @@ class MessageServiceAsyncImpl internal constructor(private val clientOptions: Cl } } } + + private val resendHandler: Handler = + jsonHandler(clientOptions.jsonMapper) + + override fun resend( + params: MessageResendParams, + requestOptions: RequestOptions, + ): CompletableFuture> { + // We check here instead of in the params builder because this can be specified + // positionally or in the params class. + checkRequired("messageId", params.messageId().getOrNull()) + val request = + HttpRequest.builder() + .method(HttpMethod.POST) + .baseUrl(clientOptions.baseUrl()) + .addPathSegments("messages", params._pathParam(0), "resend") + .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 { resendHandler.handle(it) } + .also { + if (requestOptions.responseValidation!!) { + it.validate() + } + } + } + } + } } } diff --git a/courier-java-core/src/main/kotlin/com/courier/services/blocking/MessageService.kt b/courier-java-core/src/main/kotlin/com/courier/services/blocking/MessageService.kt index 18101a63..cefd97f3 100644 --- a/courier-java-core/src/main/kotlin/com/courier/services/blocking/MessageService.kt +++ b/courier-java-core/src/main/kotlin/com/courier/services/blocking/MessageService.kt @@ -13,6 +13,8 @@ import com.courier.models.messages.MessageHistoryParams import com.courier.models.messages.MessageHistoryResponse import com.courier.models.messages.MessageListParams import com.courier.models.messages.MessageListResponse +import com.courier.models.messages.MessageResendParams +import com.courier.models.messages.MessageResendResponse import com.courier.models.messages.MessageRetrieveParams import com.courier.models.messages.MessageRetrieveResponse import com.google.errorprone.annotations.MustBeClosed @@ -179,6 +181,43 @@ interface MessageService { fun history(messageId: String, requestOptions: RequestOptions): MessageHistoryResponse = history(messageId, MessageHistoryParams.none(), requestOptions) + /** + * Resend a previously sent message. The original send request is loaded from storage and a + * brand-new send is enqueued for the same recipient and content, producing a **new** + * `messageId` — the original message is not modified. Throttled by a per-message rate limit; a + * repeat inside the limit window returns `429 Too Many Requests`. + */ + fun resend(messageId: String): MessageResendResponse = + resend(messageId, MessageResendParams.none()) + + /** @see resend */ + fun resend( + messageId: String, + params: MessageResendParams = MessageResendParams.none(), + requestOptions: RequestOptions = RequestOptions.none(), + ): MessageResendResponse = + resend(params.toBuilder().messageId(messageId).build(), requestOptions) + + /** @see resend */ + fun resend( + messageId: String, + params: MessageResendParams = MessageResendParams.none(), + ): MessageResendResponse = resend(messageId, params, RequestOptions.none()) + + /** @see resend */ + fun resend( + params: MessageResendParams, + requestOptions: RequestOptions = RequestOptions.none(), + ): MessageResendResponse + + /** @see resend */ + fun resend(params: MessageResendParams): MessageResendResponse = + resend(params, RequestOptions.none()) + + /** @see resend */ + fun resend(messageId: String, requestOptions: RequestOptions): MessageResendResponse = + resend(messageId, MessageResendParams.none(), requestOptions) + /** A view of [MessageService] that provides access to raw HTTP responses for each method. */ interface WithRawResponse { @@ -392,5 +431,49 @@ interface MessageService { requestOptions: RequestOptions, ): HttpResponseFor = history(messageId, MessageHistoryParams.none(), requestOptions) + + /** + * Returns a raw HTTP response for `post /messages/{message_id}/resend`, but is otherwise + * the same as [MessageService.resend]. + */ + @MustBeClosed + fun resend(messageId: String): HttpResponseFor = + resend(messageId, MessageResendParams.none()) + + /** @see resend */ + @MustBeClosed + fun resend( + messageId: String, + params: MessageResendParams = MessageResendParams.none(), + requestOptions: RequestOptions = RequestOptions.none(), + ): HttpResponseFor = + resend(params.toBuilder().messageId(messageId).build(), requestOptions) + + /** @see resend */ + @MustBeClosed + fun resend( + messageId: String, + params: MessageResendParams = MessageResendParams.none(), + ): HttpResponseFor = resend(messageId, params, RequestOptions.none()) + + /** @see resend */ + @MustBeClosed + fun resend( + params: MessageResendParams, + requestOptions: RequestOptions = RequestOptions.none(), + ): HttpResponseFor + + /** @see resend */ + @MustBeClosed + fun resend(params: MessageResendParams): HttpResponseFor = + resend(params, RequestOptions.none()) + + /** @see resend */ + @MustBeClosed + fun resend( + messageId: String, + requestOptions: RequestOptions, + ): HttpResponseFor = + resend(messageId, MessageResendParams.none(), requestOptions) } } diff --git a/courier-java-core/src/main/kotlin/com/courier/services/blocking/MessageServiceImpl.kt b/courier-java-core/src/main/kotlin/com/courier/services/blocking/MessageServiceImpl.kt index 67b3262a..e57937f4 100644 --- a/courier-java-core/src/main/kotlin/com/courier/services/blocking/MessageServiceImpl.kt +++ b/courier-java-core/src/main/kotlin/com/courier/services/blocking/MessageServiceImpl.kt @@ -24,6 +24,8 @@ import com.courier.models.messages.MessageHistoryParams import com.courier.models.messages.MessageHistoryResponse import com.courier.models.messages.MessageListParams import com.courier.models.messages.MessageListResponse +import com.courier.models.messages.MessageResendParams +import com.courier.models.messages.MessageResendResponse import com.courier.models.messages.MessageRetrieveParams import com.courier.models.messages.MessageRetrieveResponse import java.util.function.Consumer @@ -76,6 +78,13 @@ class MessageServiceImpl internal constructor(private val clientOptions: ClientO // get /messages/{message_id}/history withRawResponse().history(params, requestOptions).parse() + override fun resend( + params: MessageResendParams, + requestOptions: RequestOptions, + ): MessageResendResponse = + // post /messages/{message_id}/resend + withRawResponse().resend(params, requestOptions).parse() + class WithRawResponseImpl internal constructor(private val clientOptions: ClientOptions) : MessageService.WithRawResponse { @@ -236,5 +245,36 @@ class MessageServiceImpl internal constructor(private val clientOptions: ClientO } } } + + private val resendHandler: Handler = + jsonHandler(clientOptions.jsonMapper) + + override fun resend( + params: MessageResendParams, + requestOptions: RequestOptions, + ): HttpResponseFor { + // We check here instead of in the params builder because this can be specified + // positionally or in the params class. + checkRequired("messageId", params.messageId().getOrNull()) + val request = + HttpRequest.builder() + .method(HttpMethod.POST) + .baseUrl(clientOptions.baseUrl()) + .addPathSegments("messages", params._pathParam(0), "resend") + .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 { resendHandler.handle(it) } + .also { + if (requestOptions.responseValidation!!) { + it.validate() + } + } + } + } } } diff --git a/courier-java-core/src/test/kotlin/com/courier/models/messages/MessageResendParamsTest.kt b/courier-java-core/src/test/kotlin/com/courier/models/messages/MessageResendParamsTest.kt new file mode 100644 index 00000000..a6d1cc50 --- /dev/null +++ b/courier-java-core/src/test/kotlin/com/courier/models/messages/MessageResendParamsTest.kt @@ -0,0 +1,23 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.courier.models.messages + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class MessageResendParamsTest { + + @Test + fun create() { + MessageResendParams.builder().messageId("message_id").build() + } + + @Test + fun pathParams() { + val params = MessageResendParams.builder().messageId("message_id").build() + + assertThat(params._pathParam(0)).isEqualTo("message_id") + // out-of-bound path param + assertThat(params._pathParam(1)).isEqualTo("") + } +} diff --git a/courier-java-core/src/test/kotlin/com/courier/models/messages/MessageResendResponseTest.kt b/courier-java-core/src/test/kotlin/com/courier/models/messages/MessageResendResponseTest.kt new file mode 100644 index 00000000..a40a79db --- /dev/null +++ b/courier-java-core/src/test/kotlin/com/courier/models/messages/MessageResendResponseTest.kt @@ -0,0 +1,35 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.courier.models.messages + +import com.courier.core.jsonMapper +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class MessageResendResponseTest { + + @Test + fun create() { + val messageResendResponse = + MessageResendResponse.builder().messageId("1-5e2b2615-05efbb3acab9172f88dd3f6f").build() + + assertThat(messageResendResponse.messageId()) + .isEqualTo("1-5e2b2615-05efbb3acab9172f88dd3f6f") + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val messageResendResponse = + MessageResendResponse.builder().messageId("1-5e2b2615-05efbb3acab9172f88dd3f6f").build() + + val roundtrippedMessageResendResponse = + jsonMapper.readValue( + jsonMapper.writeValueAsString(messageResendResponse), + jacksonTypeRef(), + ) + + assertThat(roundtrippedMessageResendResponse).isEqualTo(messageResendResponse) + } +} diff --git a/courier-java-core/src/test/kotlin/com/courier/services/async/MessageServiceAsyncTest.kt b/courier-java-core/src/test/kotlin/com/courier/services/async/MessageServiceAsyncTest.kt index 25c1b1bf..d76c52a8 100644 --- a/courier-java-core/src/test/kotlin/com/courier/services/async/MessageServiceAsyncTest.kt +++ b/courier-java-core/src/test/kotlin/com/courier/services/async/MessageServiceAsyncTest.kt @@ -90,4 +90,16 @@ internal class MessageServiceAsyncTest { val response = responseFuture.get() response.validate() } + + @Disabled("Mock server tests are disabled") + @Test + fun resend() { + val client = CourierOkHttpClientAsync.builder().apiKey("My API Key").build() + val messageServiceAsync = client.messages() + + val responseFuture = messageServiceAsync.resend("message_id") + + val response = responseFuture.get() + response.validate() + } } diff --git a/courier-java-core/src/test/kotlin/com/courier/services/blocking/MessageServiceTest.kt b/courier-java-core/src/test/kotlin/com/courier/services/blocking/MessageServiceTest.kt index 0b288d44..c0edcc7a 100644 --- a/courier-java-core/src/test/kotlin/com/courier/services/blocking/MessageServiceTest.kt +++ b/courier-java-core/src/test/kotlin/com/courier/services/blocking/MessageServiceTest.kt @@ -85,4 +85,15 @@ internal class MessageServiceTest { response.validate() } + + @Disabled("Mock server tests are disabled") + @Test + fun resend() { + val client = CourierOkHttpClient.builder().apiKey("My API Key").build() + val messageService = client.messages() + + val response = messageService.resend("message_id") + + response.validate() + } } From ef901ea02480190a6060e2a81ec290e03d98f283 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 9 Jul 2026 04:47:31 +0000 Subject: [PATCH 2/3] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index fa82f667..606cf6c2 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 136 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/courier/courier-d2b3bdb6747f4acc997702dbd9f24340a3d85f22c00e1f17af2bd6621358bc77.yml -openapi_spec_hash: b6791652567d04fd87c6117090de7635 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/courier/courier-7cc3e4b60242e0446ddcf3cd170b5694842c0a54a13572df20a4e91a75d6b189.yml +openapi_spec_hash: 5992603d99d6100bad823c6932a4bd06 config_hash: 627f8592c19dfb77f91ffbec2a057c3a From 21d2d8beb0eb72302a264844c3eac6534c0a2261 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 9 Jul 2026 04:47:51 +0000 Subject: [PATCH 3/3] release: 4.19.2 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 8 ++++++++ build.gradle.kts | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index a4588547..abeb3ccf 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "4.19.1" + ".": "4.19.2" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index b1b26bdc..3fc8a176 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 4.19.2 (2026-07-09) + +Full Changelog: [v4.19.1...v4.19.2](https://github.com/trycourier/courier-java/compare/v4.19.1...v4.19.2) + +### Chores + +* update api-spec with new message resend endpoint ([815f092](https://github.com/trycourier/courier-java/commit/815f092362606058abe1a6d36842449349f95284)) + ## 4.19.1 (2026-07-07) Full Changelog: [v4.19.0...v4.19.1](https://github.com/trycourier/courier-java/compare/v4.19.0...v4.19.1) diff --git a/build.gradle.kts b/build.gradle.kts index feb1dad2..b6360b74 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,7 +8,7 @@ repositories { allprojects { group = "com.courier" - version = "4.19.1" // x-release-please-version + version = "4.19.2" // x-release-please-version } subprojects {