From 53ba02d193d6432016e3e26d7435a576c4a1d2e7 Mon Sep 17 00:00:00 2001 From: Yurii Yakhnytsia Date: Mon, 11 May 2026 13:05:15 +0300 Subject: [PATCH 1/2] Add new stream methods to the SlackClient with the required models. --- .../slack/client/methods/SlackMethods.java | 15 +++ .../stream/ChatAppendStreamParamsIF.java | 23 ++++ .../stream/ChatStartStreamParamsIF.java | 43 +++++++ .../params/stream/ChatStopStreamParamsIF.java | 23 ++++ .../params/stream/StreamMetadataIF.java | 16 +++ .../methods/params/stream/StreamParams.java | 26 ++++ .../params/stream/chunks/BlocksChunkIF.java | 21 +++ .../stream/chunks/MarkdownTextChunkIF.java | 19 +++ .../stream/chunks/PlanUpdateChunkIF.java | 19 +++ .../params/stream/chunks/StreamChunk.java | 21 +++ .../stream/chunks/TaskUpdateChunkIF.java | 40 ++++++ .../stream/chunks/UnknownStreamChunk.java | 13 ++ .../chat/ChatStartStreamResponseIF.java | 13 ++ .../chat/ChatStopStreamResponseIF.java | 19 +++ .../ChatStreamParamsValidationTest.java | 121 ++++++++++++++++++ .../slack/client/SlackChatStreamClient.java | 32 +++++ .../com/hubspot/slack/client/SlackClient.java | 2 +- .../hubspot/slack/client/SlackWebClient.java | 38 ++++++ 18 files changed, 503 insertions(+), 1 deletion(-) create mode 100644 slack-base/src/main/java/com/hubspot/slack/client/methods/params/stream/ChatAppendStreamParamsIF.java create mode 100644 slack-base/src/main/java/com/hubspot/slack/client/methods/params/stream/ChatStartStreamParamsIF.java create mode 100644 slack-base/src/main/java/com/hubspot/slack/client/methods/params/stream/ChatStopStreamParamsIF.java create mode 100644 slack-base/src/main/java/com/hubspot/slack/client/methods/params/stream/StreamMetadataIF.java create mode 100644 slack-base/src/main/java/com/hubspot/slack/client/methods/params/stream/StreamParams.java create mode 100644 slack-base/src/main/java/com/hubspot/slack/client/methods/params/stream/chunks/BlocksChunkIF.java create mode 100644 slack-base/src/main/java/com/hubspot/slack/client/methods/params/stream/chunks/MarkdownTextChunkIF.java create mode 100644 slack-base/src/main/java/com/hubspot/slack/client/methods/params/stream/chunks/PlanUpdateChunkIF.java create mode 100644 slack-base/src/main/java/com/hubspot/slack/client/methods/params/stream/chunks/StreamChunk.java create mode 100644 slack-base/src/main/java/com/hubspot/slack/client/methods/params/stream/chunks/TaskUpdateChunkIF.java create mode 100644 slack-base/src/main/java/com/hubspot/slack/client/methods/params/stream/chunks/UnknownStreamChunk.java create mode 100644 slack-base/src/main/java/com/hubspot/slack/client/models/response/chat/ChatStartStreamResponseIF.java create mode 100644 slack-base/src/main/java/com/hubspot/slack/client/models/response/chat/ChatStopStreamResponseIF.java create mode 100644 slack-base/src/test/java/com/hubspot/slack/client/methods/params/stream/ChatStreamParamsValidationTest.java create mode 100644 slack-java-client/src/main/java/com/hubspot/slack/client/SlackChatStreamClient.java diff --git a/slack-base/src/main/java/com/hubspot/slack/client/methods/SlackMethods.java b/slack-base/src/main/java/com/hubspot/slack/client/methods/SlackMethods.java index b92bfe2e..cf886b25 100644 --- a/slack-base/src/main/java/com/hubspot/slack/client/methods/SlackMethods.java +++ b/slack-base/src/main/java/com/hubspot/slack/client/methods/SlackMethods.java @@ -172,6 +172,21 @@ public enum SlackMethods implements SlackMethod { RateLimitingTiers.TIER_3, JsonStatus.ACCEPTS_JSON ), + chat_startStream( + MethodWriteMode.WRITE, + RateLimitingTiers.TIER_2, + JsonStatus.ACCEPTS_JSON + ), + chat_stopStream( + MethodWriteMode.WRITE, + RateLimitingTiers.TIER_2, + JsonStatus.ACCEPTS_JSON + ), + chat_appendStream( + MethodWriteMode.WRITE, + RateLimitingTiers.TIER_4, + JsonStatus.ACCEPTS_JSON + ), conversations_archive( MethodWriteMode.WRITE, diff --git a/slack-base/src/main/java/com/hubspot/slack/client/methods/params/stream/ChatAppendStreamParamsIF.java b/slack-base/src/main/java/com/hubspot/slack/client/methods/params/stream/ChatAppendStreamParamsIF.java new file mode 100644 index 00000000..75b1f20a --- /dev/null +++ b/slack-base/src/main/java/com/hubspot/slack/client/methods/params/stream/ChatAppendStreamParamsIF.java @@ -0,0 +1,23 @@ +package com.hubspot.slack.client.methods.params.stream; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import com.google.common.base.Preconditions; +import com.hubspot.immutables.style.HubSpotStyle; +import org.immutables.value.Value.Check; +import org.immutables.value.Value.Immutable; + +@Immutable +@HubSpotStyle +@JsonNaming(SnakeCaseStrategy.class) +public interface ChatAppendStreamParamsIF extends StreamParams { + String getTs(); + + @Check + default void checkExactlyOnePresent() { + Preconditions.checkState( + getMarkdownText().isPresent() ^ !getChunks().isEmpty(), + "Exactly one of markdownText or chunks must be provided" + ); + } +} diff --git a/slack-base/src/main/java/com/hubspot/slack/client/methods/params/stream/ChatStartStreamParamsIF.java b/slack-base/src/main/java/com/hubspot/slack/client/methods/params/stream/ChatStartStreamParamsIF.java new file mode 100644 index 00000000..2e355a5b --- /dev/null +++ b/slack-base/src/main/java/com/hubspot/slack/client/methods/params/stream/ChatStartStreamParamsIF.java @@ -0,0 +1,43 @@ +package com.hubspot.slack.client.methods.params.stream; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import com.google.common.base.Preconditions; +import com.hubspot.immutables.style.HubSpotStyle; +import java.util.Optional; +import org.immutables.value.Value.Check; +import org.immutables.value.Value.Immutable; + +@Immutable +@HubSpotStyle +@JsonNaming(SnakeCaseStrategy.class) +public interface ChatStartStreamParamsIF extends StreamParams { + String getThreadTs(); + + @JsonInclude(JsonInclude.Include.NON_ABSENT) + Optional getRecipientUserId(); + + @JsonInclude(JsonInclude.Include.NON_ABSENT) + Optional getRecipientTeamId(); + + @JsonInclude(JsonInclude.Include.NON_ABSENT) + Optional getTaskDisplayMode(); + + @JsonInclude(JsonInclude.Include.NON_ABSENT) + Optional getIconEmoji(); + + @JsonInclude(JsonInclude.Include.NON_ABSENT) + Optional getIconUrl(); + + @JsonInclude(JsonInclude.Include.NON_ABSENT) + Optional getUsername(); + + @Check + default void checkNotBothPresent() { + Preconditions.checkState( + !(getMarkdownText().isPresent() && !getChunks().isEmpty()), + "markdownText and chunks are mutually exclusive" + ); + } +} diff --git a/slack-base/src/main/java/com/hubspot/slack/client/methods/params/stream/ChatStopStreamParamsIF.java b/slack-base/src/main/java/com/hubspot/slack/client/methods/params/stream/ChatStopStreamParamsIF.java new file mode 100644 index 00000000..cd1caec0 --- /dev/null +++ b/slack-base/src/main/java/com/hubspot/slack/client/methods/params/stream/ChatStopStreamParamsIF.java @@ -0,0 +1,23 @@ +package com.hubspot.slack.client.methods.params.stream; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import com.google.common.collect.ImmutableList; +import com.hubspot.immutables.style.HubSpotStyle; +import com.hubspot.slack.client.models.blocks.Block; +import java.util.Optional; +import org.immutables.value.Value.Immutable; + +@Immutable +@HubSpotStyle +@JsonNaming(SnakeCaseStrategy.class) +public interface ChatStopStreamParamsIF extends StreamParams { + String getTs(); + + @JsonInclude(JsonInclude.Include.NON_EMPTY) + ImmutableList getBlocks(); + + @JsonInclude(JsonInclude.Include.NON_ABSENT) + Optional getMetadata(); +} diff --git a/slack-base/src/main/java/com/hubspot/slack/client/methods/params/stream/StreamMetadataIF.java b/slack-base/src/main/java/com/hubspot/slack/client/methods/params/stream/StreamMetadataIF.java new file mode 100644 index 00000000..4b33f04e --- /dev/null +++ b/slack-base/src/main/java/com/hubspot/slack/client/methods/params/stream/StreamMetadataIF.java @@ -0,0 +1,16 @@ +package com.hubspot.slack.client.methods.params.stream; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import com.hubspot.immutables.style.HubSpotStyle; +import java.util.Map; +import org.immutables.value.Value.Immutable; + +@Immutable +@HubSpotStyle +@JsonNaming(SnakeCaseStrategy.class) +public interface StreamMetadataIF { + String getEventType(); + + Map getEventPayload(); +} diff --git a/slack-base/src/main/java/com/hubspot/slack/client/methods/params/stream/StreamParams.java b/slack-base/src/main/java/com/hubspot/slack/client/methods/params/stream/StreamParams.java new file mode 100644 index 00000000..2fdd25e4 --- /dev/null +++ b/slack-base/src/main/java/com/hubspot/slack/client/methods/params/stream/StreamParams.java @@ -0,0 +1,26 @@ +package com.hubspot.slack.client.methods.params.stream; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.hubspot.slack.client.methods.params.stream.chunks.StreamChunk; +import java.util.Optional; +import org.immutables.value.Value.Check; + +public interface StreamParams { + String getChannel(); + + @JsonInclude(JsonInclude.Include.NON_ABSENT) + Optional getMarkdownText(); + + @JsonInclude(JsonInclude.Include.NON_EMPTY) + ImmutableList getChunks(); + + @Check + default void checkMutuallyExclusive() { + Preconditions.checkState( + !(getMarkdownText().isPresent() && !getChunks().isEmpty()), + "markdownText and chunks are mutually exclusive" + ); + } +} diff --git a/slack-base/src/main/java/com/hubspot/slack/client/methods/params/stream/chunks/BlocksChunkIF.java b/slack-base/src/main/java/com/hubspot/slack/client/methods/params/stream/chunks/BlocksChunkIF.java new file mode 100644 index 00000000..aed0f9d9 --- /dev/null +++ b/slack-base/src/main/java/com/hubspot/slack/client/methods/params/stream/chunks/BlocksChunkIF.java @@ -0,0 +1,21 @@ +package com.hubspot.slack.client.methods.params.stream.chunks; + +import com.google.common.collect.ImmutableList; +import com.hubspot.immutables.style.HubSpotStyle; +import com.hubspot.slack.client.models.blocks.elements.BlockElement; +import org.immutables.value.Value.Derived; +import org.immutables.value.Value.Immutable; + +@Immutable +@HubSpotStyle +public interface BlocksChunkIF extends StreamChunk { + String TYPE = "blocks"; + + @Override + @Derived + default String getType() { + return TYPE; + } + + ImmutableList getBlocks(); +} diff --git a/slack-base/src/main/java/com/hubspot/slack/client/methods/params/stream/chunks/MarkdownTextChunkIF.java b/slack-base/src/main/java/com/hubspot/slack/client/methods/params/stream/chunks/MarkdownTextChunkIF.java new file mode 100644 index 00000000..48785c00 --- /dev/null +++ b/slack-base/src/main/java/com/hubspot/slack/client/methods/params/stream/chunks/MarkdownTextChunkIF.java @@ -0,0 +1,19 @@ +package com.hubspot.slack.client.methods.params.stream.chunks; + +import com.hubspot.immutables.style.HubSpotStyle; +import org.immutables.value.Value.Derived; +import org.immutables.value.Value.Immutable; + +@Immutable +@HubSpotStyle +public interface MarkdownTextChunkIF extends StreamChunk { + String TYPE = "markdown_text"; + + @Override + @Derived + default String getType() { + return TYPE; + } + + String getText(); +} diff --git a/slack-base/src/main/java/com/hubspot/slack/client/methods/params/stream/chunks/PlanUpdateChunkIF.java b/slack-base/src/main/java/com/hubspot/slack/client/methods/params/stream/chunks/PlanUpdateChunkIF.java new file mode 100644 index 00000000..397a2aed --- /dev/null +++ b/slack-base/src/main/java/com/hubspot/slack/client/methods/params/stream/chunks/PlanUpdateChunkIF.java @@ -0,0 +1,19 @@ +package com.hubspot.slack.client.methods.params.stream.chunks; + +import com.hubspot.immutables.style.HubSpotStyle; +import org.immutables.value.Value.Derived; +import org.immutables.value.Value.Immutable; + +@Immutable +@HubSpotStyle +public interface PlanUpdateChunkIF extends StreamChunk { + String TYPE = "plan_update"; + + @Override + @Derived + default String getType() { + return TYPE; + } + + String getTitle(); +} diff --git a/slack-base/src/main/java/com/hubspot/slack/client/methods/params/stream/chunks/StreamChunk.java b/slack-base/src/main/java/com/hubspot/slack/client/methods/params/stream/chunks/StreamChunk.java new file mode 100644 index 00000000..5197958f --- /dev/null +++ b/slack-base/src/main/java/com/hubspot/slack/client/methods/params/stream/chunks/StreamChunk.java @@ -0,0 +1,21 @@ +package com.hubspot.slack.client.methods.params.stream.chunks; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + property = "type", + defaultImpl = UnknownStreamChunk.class +) +@JsonSubTypes( + { + @JsonSubTypes.Type(value = MarkdownTextChunk.class, name = MarkdownTextChunk.TYPE), + @JsonSubTypes.Type(value = TaskUpdateChunk.class, name = TaskUpdateChunk.TYPE), + @JsonSubTypes.Type(value = PlanUpdateChunk.class, name = PlanUpdateChunk.TYPE), + @JsonSubTypes.Type(value = BlocksChunk.class, name = BlocksChunk.TYPE), + } +) +public interface StreamChunk { + String getType(); +} diff --git a/slack-base/src/main/java/com/hubspot/slack/client/methods/params/stream/chunks/TaskUpdateChunkIF.java b/slack-base/src/main/java/com/hubspot/slack/client/methods/params/stream/chunks/TaskUpdateChunkIF.java new file mode 100644 index 00000000..a8affab9 --- /dev/null +++ b/slack-base/src/main/java/com/hubspot/slack/client/methods/params/stream/chunks/TaskUpdateChunkIF.java @@ -0,0 +1,40 @@ +package com.hubspot.slack.client.methods.params.stream.chunks; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import com.google.common.collect.ImmutableList; +import com.hubspot.immutables.style.HubSpotStyle; +import com.hubspot.slack.client.models.blocks.TaskCardBlockStatus; +import com.hubspot.slack.client.models.blocks.elements.UrlSource; +import java.util.Optional; +import org.immutables.value.Value.Derived; +import org.immutables.value.Value.Immutable; + +@Immutable +@HubSpotStyle +@JsonNaming(SnakeCaseStrategy.class) +public interface TaskUpdateChunkIF extends StreamChunk { + String TYPE = "task_update"; + + @Override + @Derived + default String getType() { + return TYPE; + } + + String getId(); + + String getTitle(); + + @JsonInclude(JsonInclude.Include.NON_ABSENT) + Optional getStatus(); + + @JsonInclude(JsonInclude.Include.NON_ABSENT) + Optional getDetails(); + + @JsonInclude(JsonInclude.Include.NON_ABSENT) + Optional getOutput(); + + ImmutableList getSources(); +} diff --git a/slack-base/src/main/java/com/hubspot/slack/client/methods/params/stream/chunks/UnknownStreamChunk.java b/slack-base/src/main/java/com/hubspot/slack/client/methods/params/stream/chunks/UnknownStreamChunk.java new file mode 100644 index 00000000..42d990a4 --- /dev/null +++ b/slack-base/src/main/java/com/hubspot/slack/client/methods/params/stream/chunks/UnknownStreamChunk.java @@ -0,0 +1,13 @@ +package com.hubspot.slack.client.methods.params.stream.chunks; + +public class UnknownStreamChunk implements StreamChunk { + + public static final String TYPE = "unknown"; + + protected UnknownStreamChunk() {} + + @Override + public String getType() { + return TYPE; + } +} diff --git a/slack-base/src/main/java/com/hubspot/slack/client/models/response/chat/ChatStartStreamResponseIF.java b/slack-base/src/main/java/com/hubspot/slack/client/models/response/chat/ChatStartStreamResponseIF.java new file mode 100644 index 00000000..5f4e9f54 --- /dev/null +++ b/slack-base/src/main/java/com/hubspot/slack/client/models/response/chat/ChatStartStreamResponseIF.java @@ -0,0 +1,13 @@ +package com.hubspot.slack.client.models.response.chat; + +import com.hubspot.immutables.style.HubSpotStyle; +import com.hubspot.slack.client.models.response.SlackResponse; +import org.immutables.value.Value.Immutable; + +@Immutable +@HubSpotStyle +public interface ChatStartStreamResponseIF extends SlackResponse { + String getChannel(); + + String getTs(); +} diff --git a/slack-base/src/main/java/com/hubspot/slack/client/models/response/chat/ChatStopStreamResponseIF.java b/slack-base/src/main/java/com/hubspot/slack/client/models/response/chat/ChatStopStreamResponseIF.java new file mode 100644 index 00000000..361a9d20 --- /dev/null +++ b/slack-base/src/main/java/com/hubspot/slack/client/models/response/chat/ChatStopStreamResponseIF.java @@ -0,0 +1,19 @@ +package com.hubspot.slack.client.models.response.chat; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import com.hubspot.immutables.style.HubSpotStyle; +import com.hubspot.slack.client.models.response.SlackResponse; +import java.util.Map; +import org.immutables.value.Value.Immutable; + +@Immutable +@HubSpotStyle +@JsonNaming(SnakeCaseStrategy.class) +public interface ChatStopStreamResponseIF extends SlackResponse { + String getChannel(); + + String getTs(); + + Map getMessage(); +} diff --git a/slack-base/src/test/java/com/hubspot/slack/client/methods/params/stream/ChatStreamParamsValidationTest.java b/slack-base/src/test/java/com/hubspot/slack/client/methods/params/stream/ChatStreamParamsValidationTest.java new file mode 100644 index 00000000..c371bf42 --- /dev/null +++ b/slack-base/src/test/java/com/hubspot/slack/client/methods/params/stream/ChatStreamParamsValidationTest.java @@ -0,0 +1,121 @@ +package com.hubspot.slack.client.methods.params.stream; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.hubspot.slack.client.methods.params.stream.chunks.MarkdownTextChunk; +import org.junit.Test; + +public class ChatStreamParamsValidationTest { + private static final String CHANNNEL_ID = "C123"; + private static final String THREAD_TS = "1234.5678"; + private static final String TEST_TEXT = "hello"; + + + + // ChatStartStreamParams: neither, one, or the other is valid; both fails + + @Test + public void itBuildsStartStreamWithNeitherMarkdownNorChunks() { + assertThatCode(() -> + ChatStartStreamParams + .builder() + .setChannel(CHANNNEL_ID) + .setThreadTs(THREAD_TS) + .build() + ).doesNotThrowAnyException(); + } + + @Test + public void itBuildsStartStreamWithMarkdownTextOnly() { + assertThatCode(() -> + ChatStartStreamParams + .builder() + .setChannel(CHANNNEL_ID) + .setThreadTs(THREAD_TS) + .setMarkdownText(TEST_TEXT) + .build() + ).doesNotThrowAnyException(); + } + + @Test + public void itBuildsStartStreamWithChunksOnly() { + assertThatCode(() -> + ChatStartStreamParams + .builder() + .setChannel(CHANNNEL_ID) + .setThreadTs(THREAD_TS) + .addChunks(MarkdownTextChunk.builder().setText(TEST_TEXT).build()) + .build() + ).doesNotThrowAnyException(); + } + + @Test + public void itFailsToBuildStartStreamWithBothMarkdownAndChunks() { + assertThatThrownBy(() -> + ChatStartStreamParams + .builder() + .setChannel(CHANNNEL_ID) + .setThreadTs(THREAD_TS) + .setMarkdownText(TEST_TEXT) + .addChunks(MarkdownTextChunk.builder().setText(TEST_TEXT).build()) + .build() + ) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("mutually exclusive"); + } + + // ChatAppendStreamParams: exactly one of markdown or chunks required; both and neither fail + + @Test + public void itBuildsAppendStreamWithMarkdownTextOnly() { + assertThatCode(() -> + ChatAppendStreamParams + .builder() + .setChannel(CHANNNEL_ID) + .setTs(THREAD_TS) + .setMarkdownText(TEST_TEXT) + .build() + ).doesNotThrowAnyException(); + } + + @Test + public void itBuildsAppendStreamWithChunksOnly() { + assertThatCode(() -> + ChatAppendStreamParams + .builder() + .setChannel(CHANNNEL_ID) + .setTs(THREAD_TS) + .addChunks(MarkdownTextChunk.builder().setText(TEST_TEXT).build()) + .build() + ).doesNotThrowAnyException(); + } + + @Test + public void itFailsToBuildAppendStreamWithNeitherMarkdownNorChunks() { + assertThatThrownBy(() -> + ChatAppendStreamParams + .builder() + .setChannel(CHANNNEL_ID) + .setTs(THREAD_TS) + .build() + ) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("Exactly one of markdownText or chunks must be provided"); + } + + @Test + public void itFailsToBuildAppendStreamWithBothMarkdownAndChunks() { + assertThatThrownBy(() -> + ChatAppendStreamParams + .builder() + .setChannel(CHANNNEL_ID) + .setTs(THREAD_TS) + .setMarkdownText(TEST_TEXT) + .addChunks(MarkdownTextChunk.builder().setText(TEST_TEXT).build()) + .build() + ) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("Exactly one of markdownText or chunks must be provided"); + } +} diff --git a/slack-java-client/src/main/java/com/hubspot/slack/client/SlackChatStreamClient.java b/slack-java-client/src/main/java/com/hubspot/slack/client/SlackChatStreamClient.java new file mode 100644 index 00000000..7cad2a8f --- /dev/null +++ b/slack-java-client/src/main/java/com/hubspot/slack/client/SlackChatStreamClient.java @@ -0,0 +1,32 @@ +package com.hubspot.slack.client; + +import com.hubspot.algebra.Result; +import com.hubspot.slack.client.methods.params.stream.ChatAppendStreamParams; +import com.hubspot.slack.client.methods.params.stream.ChatStartStreamParams; +import com.hubspot.slack.client.methods.params.stream.ChatStopStreamParams; +import com.hubspot.slack.client.models.response.SlackError; +import com.hubspot.slack.client.models.response.chat.ChatStartStreamResponse; +import com.hubspot.slack.client.models.response.chat.ChatStopStreamResponse; +import java.util.concurrent.CompletableFuture; + +public interface SlackChatStreamClient { + String NOT_IMPLEMENTED = "Method not implemented"; + + default CompletableFuture> startStream( + ChatStartStreamParams params + ) { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + default CompletableFuture> appendStream( + ChatAppendStreamParams params + ) { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + default CompletableFuture> stopStream( + ChatStopStreamParams params + ) { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } +} diff --git a/slack-java-client/src/main/java/com/hubspot/slack/client/SlackClient.java b/slack-java-client/src/main/java/com/hubspot/slack/client/SlackClient.java index 5eda61e2..12d5b0a9 100644 --- a/slack-java-client/src/main/java/com/hubspot/slack/client/SlackClient.java +++ b/slack-java-client/src/main/java/com/hubspot/slack/client/SlackClient.java @@ -155,7 +155,7 @@ import java.util.concurrent.CompletableFuture; // See https://api.slack.com/changelog/2020-01-deprecating-antecedents-to-the-conversations-api for details on deprecations starting 7 January 2020 -public interface SlackClient extends Closeable { +public interface SlackClient extends Closeable, SlackChatStreamClient { String NOT_IMPLEMENTED = "Method not implemented"; // auth CompletableFuture> testAuth(); diff --git a/slack-java-client/src/main/java/com/hubspot/slack/client/SlackWebClient.java b/slack-java-client/src/main/java/com/hubspot/slack/client/SlackWebClient.java index e698e96f..d3e21f1e 100644 --- a/slack-java-client/src/main/java/com/hubspot/slack/client/SlackWebClient.java +++ b/slack-java-client/src/main/java/com/hubspot/slack/client/SlackWebClient.java @@ -95,6 +95,9 @@ import com.hubspot.slack.client.methods.params.reactions.ReactionsAddParams; import com.hubspot.slack.client.methods.params.reactions.ReactionsRemoveParams; import com.hubspot.slack.client.methods.params.search.SearchMessagesParams; +import com.hubspot.slack.client.methods.params.stream.ChatAppendStreamParams; +import com.hubspot.slack.client.methods.params.stream.ChatStartStreamParams; +import com.hubspot.slack.client.methods.params.stream.ChatStopStreamParams; import com.hubspot.slack.client.methods.params.usergroups.UsergroupCreateParams; import com.hubspot.slack.client.methods.params.usergroups.UsergroupDisableParams; import com.hubspot.slack.client.methods.params.usergroups.UsergroupEnableParams; @@ -143,6 +146,8 @@ import com.hubspot.slack.client.models.response.chat.ChatPostMessageResponse; import com.hubspot.slack.client.models.response.chat.ChatScheduleMessageResponse; import com.hubspot.slack.client.models.response.chat.ChatScheduledMessagesListResponse; +import com.hubspot.slack.client.models.response.chat.ChatStartStreamResponse; +import com.hubspot.slack.client.models.response.chat.ChatStopStreamResponse; import com.hubspot.slack.client.models.response.chat.ChatUnfurlResponse; import com.hubspot.slack.client.models.response.chat.ChatUpdateMessageResponse; import com.hubspot.slack.client.models.response.chat.EntityPresentDetailsResponse; @@ -1723,6 +1728,39 @@ public CompletableFuture> assistantSetTi ); } + @Override + public CompletableFuture> startStream( + ChatStartStreamParams params + ) { + return postSlackCommand( + SlackMethods.chat_startStream, + params, + ChatStartStreamResponse.class + ); + } + + @Override + public CompletableFuture> stopStream( + ChatStopStreamParams params + ) { + return postSlackCommand( + SlackMethods.chat_stopStream, + params, + ChatStopStreamResponse.class + ); + } + + @Override + public CompletableFuture> appendStream( + ChatAppendStreamParams params + ) { + return postSlackCommand( + SlackMethods.chat_appendStream, + params, + ChatStartStreamResponse.class + ); + } + @Override public CompletableFuture> postSlackCommand( SlackMethod method, From fd61c1dd95160f65b3dd4d11d2c3547314bd9f56 Mon Sep 17 00:00:00 2001 From: Yurii Yakhnytsia Date: Mon, 11 May 2026 14:10:39 +0300 Subject: [PATCH 2/2] Apply Spotless formatting. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .../ChatStreamParamsValidationTest.java | 116 +++++++++--------- 1 file changed, 58 insertions(+), 58 deletions(-) diff --git a/slack-base/src/test/java/com/hubspot/slack/client/methods/params/stream/ChatStreamParamsValidationTest.java b/slack-base/src/test/java/com/hubspot/slack/client/methods/params/stream/ChatStreamParamsValidationTest.java index c371bf42..1b37808e 100644 --- a/slack-base/src/test/java/com/hubspot/slack/client/methods/params/stream/ChatStreamParamsValidationTest.java +++ b/slack-base/src/test/java/com/hubspot/slack/client/methods/params/stream/ChatStreamParamsValidationTest.java @@ -7,60 +7,62 @@ import org.junit.Test; public class ChatStreamParamsValidationTest { + private static final String CHANNNEL_ID = "C123"; private static final String THREAD_TS = "1234.5678"; private static final String TEST_TEXT = "hello"; - - // ChatStartStreamParams: neither, one, or the other is valid; both fails @Test public void itBuildsStartStreamWithNeitherMarkdownNorChunks() { assertThatCode(() -> - ChatStartStreamParams - .builder() - .setChannel(CHANNNEL_ID) - .setThreadTs(THREAD_TS) - .build() - ).doesNotThrowAnyException(); + ChatStartStreamParams + .builder() + .setChannel(CHANNNEL_ID) + .setThreadTs(THREAD_TS) + .build() + ) + .doesNotThrowAnyException(); } @Test public void itBuildsStartStreamWithMarkdownTextOnly() { assertThatCode(() -> - ChatStartStreamParams - .builder() - .setChannel(CHANNNEL_ID) - .setThreadTs(THREAD_TS) - .setMarkdownText(TEST_TEXT) - .build() - ).doesNotThrowAnyException(); + ChatStartStreamParams + .builder() + .setChannel(CHANNNEL_ID) + .setThreadTs(THREAD_TS) + .setMarkdownText(TEST_TEXT) + .build() + ) + .doesNotThrowAnyException(); } @Test public void itBuildsStartStreamWithChunksOnly() { assertThatCode(() -> - ChatStartStreamParams - .builder() - .setChannel(CHANNNEL_ID) - .setThreadTs(THREAD_TS) - .addChunks(MarkdownTextChunk.builder().setText(TEST_TEXT).build()) - .build() - ).doesNotThrowAnyException(); + ChatStartStreamParams + .builder() + .setChannel(CHANNNEL_ID) + .setThreadTs(THREAD_TS) + .addChunks(MarkdownTextChunk.builder().setText(TEST_TEXT).build()) + .build() + ) + .doesNotThrowAnyException(); } @Test public void itFailsToBuildStartStreamWithBothMarkdownAndChunks() { assertThatThrownBy(() -> - ChatStartStreamParams - .builder() - .setChannel(CHANNNEL_ID) - .setThreadTs(THREAD_TS) - .setMarkdownText(TEST_TEXT) - .addChunks(MarkdownTextChunk.builder().setText(TEST_TEXT).build()) - .build() - ) + ChatStartStreamParams + .builder() + .setChannel(CHANNNEL_ID) + .setThreadTs(THREAD_TS) + .setMarkdownText(TEST_TEXT) + .addChunks(MarkdownTextChunk.builder().setText(TEST_TEXT).build()) + .build() + ) .isInstanceOf(IllegalStateException.class) .hasMessageContaining("mutually exclusive"); } @@ -70,36 +72,34 @@ public void itFailsToBuildStartStreamWithBothMarkdownAndChunks() { @Test public void itBuildsAppendStreamWithMarkdownTextOnly() { assertThatCode(() -> - ChatAppendStreamParams - .builder() - .setChannel(CHANNNEL_ID) - .setTs(THREAD_TS) - .setMarkdownText(TEST_TEXT) - .build() - ).doesNotThrowAnyException(); + ChatAppendStreamParams + .builder() + .setChannel(CHANNNEL_ID) + .setTs(THREAD_TS) + .setMarkdownText(TEST_TEXT) + .build() + ) + .doesNotThrowAnyException(); } @Test public void itBuildsAppendStreamWithChunksOnly() { assertThatCode(() -> - ChatAppendStreamParams - .builder() - .setChannel(CHANNNEL_ID) - .setTs(THREAD_TS) - .addChunks(MarkdownTextChunk.builder().setText(TEST_TEXT).build()) - .build() - ).doesNotThrowAnyException(); + ChatAppendStreamParams + .builder() + .setChannel(CHANNNEL_ID) + .setTs(THREAD_TS) + .addChunks(MarkdownTextChunk.builder().setText(TEST_TEXT).build()) + .build() + ) + .doesNotThrowAnyException(); } @Test public void itFailsToBuildAppendStreamWithNeitherMarkdownNorChunks() { assertThatThrownBy(() -> - ChatAppendStreamParams - .builder() - .setChannel(CHANNNEL_ID) - .setTs(THREAD_TS) - .build() - ) + ChatAppendStreamParams.builder().setChannel(CHANNNEL_ID).setTs(THREAD_TS).build() + ) .isInstanceOf(IllegalStateException.class) .hasMessageContaining("Exactly one of markdownText or chunks must be provided"); } @@ -107,14 +107,14 @@ public void itFailsToBuildAppendStreamWithNeitherMarkdownNorChunks() { @Test public void itFailsToBuildAppendStreamWithBothMarkdownAndChunks() { assertThatThrownBy(() -> - ChatAppendStreamParams - .builder() - .setChannel(CHANNNEL_ID) - .setTs(THREAD_TS) - .setMarkdownText(TEST_TEXT) - .addChunks(MarkdownTextChunk.builder().setText(TEST_TEXT).build()) - .build() - ) + ChatAppendStreamParams + .builder() + .setChannel(CHANNNEL_ID) + .setTs(THREAD_TS) + .setMarkdownText(TEST_TEXT) + .addChunks(MarkdownTextChunk.builder().setText(TEST_TEXT).build()) + .build() + ) .isInstanceOf(IllegalStateException.class) .hasMessageContaining("Exactly one of markdownText or chunks must be provided"); }