diff --git a/json-logs/samples/api/blocks.validate.json b/json-logs/samples/api/blocks.validate.json new file mode 100644 index 000000000..6253142d7 --- /dev/null +++ b/json-logs/samples/api/blocks.validate.json @@ -0,0 +1,26 @@ +{ + "ok": false, + "error": "", + "response_metadata": { + "messages": [ + "" + ] + }, + "needed": "", + "provided": "", + "errors": [ + { + "code": "", + "message": "", + "pointer": "", + "constraint": { + "type": "", + "expected": [ + "" + ], + "got": "" + } + } + ], + "warning": "" +} \ No newline at end of file diff --git a/metadata/web-api/rate_limit_tiers.json b/metadata/web-api/rate_limit_tiers.json index b969c8997..8457aae74 100644 --- a/metadata/web-api/rate_limit_tiers.json +++ b/metadata/web-api/rate_limit_tiers.json @@ -122,6 +122,7 @@ "auth.revoke": "Tier3", "auth.teams.list": "Tier2", "auth.test": "SpecialTier_auth_test", + "blocks.validate": "SpecialTier_blocks_validate", "bookmarks.add": "Tier2", "bookmarks.edit": "Tier2", "bookmarks.list": "Tier3", diff --git a/slack-api-client/src/main/java/com/slack/api/methods/AsyncMethodsClient.java b/slack-api-client/src/main/java/com/slack/api/methods/AsyncMethodsClient.java index 47d0dcf1d..07eca3c4c 100644 --- a/slack-api-client/src/main/java/com/slack/api/methods/AsyncMethodsClient.java +++ b/slack-api-client/src/main/java/com/slack/api/methods/AsyncMethodsClient.java @@ -52,6 +52,7 @@ import com.slack.api.methods.request.auth.AuthRevokeRequest; import com.slack.api.methods.request.auth.AuthTestRequest; import com.slack.api.methods.request.auth.teams.AuthTeamsListRequest; +import com.slack.api.methods.request.blocks.BlocksValidateRequest; import com.slack.api.methods.request.bookmarks.BookmarksAddRequest; import com.slack.api.methods.request.bookmarks.BookmarksEditRequest; import com.slack.api.methods.request.bookmarks.BookmarksListRequest; @@ -193,6 +194,7 @@ import com.slack.api.methods.response.auth.AuthRevokeResponse; import com.slack.api.methods.response.auth.AuthTestResponse; import com.slack.api.methods.response.auth.teams.AuthTeamsListResponse; +import com.slack.api.methods.response.blocks.BlocksValidateResponse; import com.slack.api.methods.response.bookmarks.BookmarksAddResponse; import com.slack.api.methods.response.bookmarks.BookmarksEditResponse; import com.slack.api.methods.response.bookmarks.BookmarksListResponse; @@ -911,6 +913,14 @@ CompletableFuture CompletableFuture authTeamsList(RequestConfigurator req); + // ------------------------------ + // blocks + // ------------------------------ + + CompletableFuture blocksValidate(BlocksValidateRequest req); + + CompletableFuture blocksValidate(RequestConfigurator req); + // ------------------------------ // bookmarks // ------------------------------ diff --git a/slack-api-client/src/main/java/com/slack/api/methods/Methods.java b/slack-api-client/src/main/java/com/slack/api/methods/Methods.java index 6d1c8c66e..975e71ed6 100644 --- a/slack-api-client/src/main/java/com/slack/api/methods/Methods.java +++ b/slack-api-client/src/main/java/com/slack/api/methods/Methods.java @@ -311,6 +311,12 @@ private Methods() { public static final String AUTH_TEAMS_LIST = "auth.teams.list"; + // ------------------------------ + // blocks + // ------------------------------ + + public static final String BLOCKS_VALIDATE = "blocks.validate"; + // ------------------------------ // bookmarks // ------------------------------ diff --git a/slack-api-client/src/main/java/com/slack/api/methods/MethodsClient.java b/slack-api-client/src/main/java/com/slack/api/methods/MethodsClient.java index 70bafa7a5..819602cce 100644 --- a/slack-api-client/src/main/java/com/slack/api/methods/MethodsClient.java +++ b/slack-api-client/src/main/java/com/slack/api/methods/MethodsClient.java @@ -58,6 +58,7 @@ import com.slack.api.methods.request.auth.AuthRevokeRequest; import com.slack.api.methods.request.auth.AuthTestRequest; import com.slack.api.methods.request.auth.teams.AuthTeamsListRequest; +import com.slack.api.methods.request.blocks.BlocksValidateRequest; import com.slack.api.methods.request.bookmarks.BookmarksAddRequest; import com.slack.api.methods.request.bookmarks.BookmarksEditRequest; import com.slack.api.methods.request.bookmarks.BookmarksListRequest; @@ -212,6 +213,7 @@ import com.slack.api.methods.response.auth.AuthRevokeResponse; import com.slack.api.methods.response.auth.AuthTestResponse; import com.slack.api.methods.response.auth.teams.AuthTeamsListResponse; +import com.slack.api.methods.response.blocks.BlocksValidateResponse; import com.slack.api.methods.response.bookmarks.BookmarksAddResponse; import com.slack.api.methods.response.bookmarks.BookmarksEditResponse; import com.slack.api.methods.response.bookmarks.BookmarksListResponse; @@ -1377,6 +1379,16 @@ AuthTestResponse authTest(RequestConfigurator req) throws IOException, SlackApiException; + // ------------------------------ + // blocks + // ------------------------------ + + BlocksValidateResponse blocksValidate(BlocksValidateRequest req) throws IOException, SlackApiException; + + BlocksValidateResponse blocksValidate( + RequestConfigurator req) + throws IOException, SlackApiException; + // ------------------------------ // bookmarks // ------------------------------ diff --git a/slack-api-client/src/main/java/com/slack/api/methods/MethodsRateLimitTier.java b/slack-api-client/src/main/java/com/slack/api/methods/MethodsRateLimitTier.java index cbbfca4f9..7f3b0becc 100644 --- a/slack-api-client/src/main/java/com/slack/api/methods/MethodsRateLimitTier.java +++ b/slack-api-client/src/main/java/com/slack/api/methods/MethodsRateLimitTier.java @@ -57,7 +57,13 @@ public enum MethodsRateLimitTier { /** * This method allows hundreds of requests per minute. Use it as often as is reasonably required. */ - SpecialTier_chat_getPermalink; + SpecialTier_chat_getPermalink, + + /** + * blocks.validate requires no token, so it has special rate limiting conditions rather than a + * standard tier. See https://docs.slack.dev/reference/methods/blocks.validate. + */ + SpecialTier_blocks_validate; // -------------------------------------------------------------------------------------------- @@ -72,6 +78,7 @@ public enum MethodsRateLimitTier { allowedRequestsPerMinute.put(MethodsRateLimitTier.SpecialTier_chat_getPermalink, 600); allowedRequestsPerMinute.put(MethodsRateLimitTier.SpecialTier_chat_postMessage, 60); // per channel allowedRequestsPerMinute.put(MethodsRateLimitTier.SpecialTier_assistant_threads_setStatus, 60); // per DM + allowedRequestsPerMinute.put(MethodsRateLimitTier.SpecialTier_blocks_validate, 60); } public static Integer getAllowedRequestsPerMinute(MethodsRateLimitTier tier) { diff --git a/slack-api-client/src/main/java/com/slack/api/methods/MethodsRateLimits.java b/slack-api-client/src/main/java/com/slack/api/methods/MethodsRateLimits.java index 51fecbad9..6f2302d48 100644 --- a/slack-api-client/src/main/java/com/slack/api/methods/MethodsRateLimits.java +++ b/slack-api-client/src/main/java/com/slack/api/methods/MethodsRateLimits.java @@ -240,6 +240,8 @@ public static void setRateLimitTier(String methodName, MethodsRateLimitTier tier setRateLimitTier(AUTH_TEST, SpecialTier_auth_test); setRateLimitTier(AUTH_TEAMS_LIST, Tier2); + setRateLimitTier(BLOCKS_VALIDATE, SpecialTier_blocks_validate); + setRateLimitTier(BOOKMARKS_ADD, Tier2); setRateLimitTier(BOOKMARKS_EDIT, Tier2); setRateLimitTier(BOOKMARKS_LIST, Tier3); diff --git a/slack-api-client/src/main/java/com/slack/api/methods/RequestFormBuilder.java b/slack-api-client/src/main/java/com/slack/api/methods/RequestFormBuilder.java index bdafc2cf0..b088be3f9 100644 --- a/slack-api-client/src/main/java/com/slack/api/methods/RequestFormBuilder.java +++ b/slack-api-client/src/main/java/com/slack/api/methods/RequestFormBuilder.java @@ -59,6 +59,7 @@ import com.slack.api.methods.request.auth.AuthRevokeRequest; import com.slack.api.methods.request.auth.AuthTestRequest; import com.slack.api.methods.request.auth.teams.AuthTeamsListRequest; +import com.slack.api.methods.request.blocks.BlocksValidateRequest; import com.slack.api.methods.request.bookmarks.BookmarksAddRequest; import com.slack.api.methods.request.bookmarks.BookmarksEditRequest; import com.slack.api.methods.request.bookmarks.BookmarksListRequest; @@ -1242,6 +1243,35 @@ public static FormBody.Builder toForm(AuthTeamsListRequest req) { return form; } + public static FormBody.Builder toForm(BlocksValidateRequest req) { + FormBody.Builder form = new FormBody.Builder(); + if (req.getBlocksAsString() != null) { + form.add("blocks", req.getBlocksAsString()); + } else if (req.getBlocks() != null) { + form.add("blocks", getJsonWithGsonAnonymInnerClassHandling(req.getBlocks())); + } + if (req.getBlocksAsString() != null && req.getBlocks() != null) { + log.warn("Although you set both blocksAsString and blocks, only blocksAsString was used."); + } + if (req.getMessageAsString() != null) { + form.add("message", req.getMessageAsString()); + } else if (req.getMessage() != null) { + form.add("message", GSON.toJson(req.getMessage())); + } + if (req.getMessageAsString() != null && req.getMessage() != null) { + log.warn("Although you set both messageAsString and message, only messageAsString was used."); + } + if (req.getViewAsString() != null) { + form.add("view", req.getViewAsString()); + } else if (req.getView() != null) { + form.add("view", GSON.toJson(req.getView())); + } + if (req.getViewAsString() != null && req.getView() != null) { + log.warn("Although you set both viewAsString and view, only viewAsString was used."); + } + return form; + } + public static FormBody.Builder toForm(BookmarksAddRequest req) { FormBody.Builder form = new FormBody.Builder(); setIfNotNull("channel_id", req.getChannelId(), form); diff --git a/slack-api-client/src/main/java/com/slack/api/methods/impl/AsyncMethodsClientImpl.java b/slack-api-client/src/main/java/com/slack/api/methods/impl/AsyncMethodsClientImpl.java index b7ddd6735..e883dc308 100644 --- a/slack-api-client/src/main/java/com/slack/api/methods/impl/AsyncMethodsClientImpl.java +++ b/slack-api-client/src/main/java/com/slack/api/methods/impl/AsyncMethodsClientImpl.java @@ -57,6 +57,7 @@ import com.slack.api.methods.request.auth.AuthRevokeRequest; import com.slack.api.methods.request.auth.AuthTestRequest; import com.slack.api.methods.request.auth.teams.AuthTeamsListRequest; +import com.slack.api.methods.request.blocks.BlocksValidateRequest; import com.slack.api.methods.request.bookmarks.BookmarksAddRequest; import com.slack.api.methods.request.bookmarks.BookmarksEditRequest; import com.slack.api.methods.request.bookmarks.BookmarksListRequest; @@ -198,6 +199,7 @@ import com.slack.api.methods.response.auth.AuthRevokeResponse; import com.slack.api.methods.response.auth.AuthTestResponse; import com.slack.api.methods.response.auth.teams.AuthTeamsListResponse; +import com.slack.api.methods.response.blocks.BlocksValidateResponse; import com.slack.api.methods.response.bookmarks.BookmarksAddResponse; import com.slack.api.methods.response.bookmarks.BookmarksEditResponse; import com.slack.api.methods.response.bookmarks.BookmarksListResponse; @@ -1504,6 +1506,16 @@ public CompletableFuture authTeamsList(RequestConfigurato return authTeamsList(req.configure(AuthTeamsListRequest.builder()).build()); } + @Override + public CompletableFuture blocksValidate(BlocksValidateRequest req) { + return executor.execute(BLOCKS_VALIDATE, toMap(req), () -> methods.blocksValidate(req)); + } + + @Override + public CompletableFuture blocksValidate(RequestConfigurator req) { + return blocksValidate(req.configure(BlocksValidateRequest.builder()).build()); + } + @Override public CompletableFuture bookmarksAdd(BookmarksAddRequest req) { return executor.execute(BOOKMARKS_ADD, toMap(req), () -> methods.bookmarksAdd(req)); diff --git a/slack-api-client/src/main/java/com/slack/api/methods/impl/MethodsClientImpl.java b/slack-api-client/src/main/java/com/slack/api/methods/impl/MethodsClientImpl.java index 2c3895fdf..b503d1551 100644 --- a/slack-api-client/src/main/java/com/slack/api/methods/impl/MethodsClientImpl.java +++ b/slack-api-client/src/main/java/com/slack/api/methods/impl/MethodsClientImpl.java @@ -60,6 +60,7 @@ import com.slack.api.methods.request.auth.AuthRevokeRequest; import com.slack.api.methods.request.auth.AuthTestRequest; import com.slack.api.methods.request.auth.teams.AuthTeamsListRequest; +import com.slack.api.methods.request.blocks.BlocksValidateRequest; import com.slack.api.methods.request.bookmarks.BookmarksAddRequest; import com.slack.api.methods.request.bookmarks.BookmarksEditRequest; import com.slack.api.methods.request.bookmarks.BookmarksListRequest; @@ -214,6 +215,7 @@ import com.slack.api.methods.response.auth.AuthRevokeResponse; import com.slack.api.methods.response.auth.AuthTestResponse; import com.slack.api.methods.response.auth.teams.AuthTeamsListResponse; +import com.slack.api.methods.response.blocks.BlocksValidateResponse; import com.slack.api.methods.response.bookmarks.BookmarksAddResponse; import com.slack.api.methods.response.bookmarks.BookmarksEditResponse; import com.slack.api.methods.response.bookmarks.BookmarksListResponse; @@ -1582,6 +1584,16 @@ public AuthTeamsListResponse authTeamsList(RequestConfigurator req) throws IOException, SlackApiException { + return blocksValidate(req.configure(BlocksValidateRequest.builder()).build()); + } + @Override public BookmarksAddResponse bookmarksAdd(BookmarksAddRequest req) throws IOException, SlackApiException { return postFormWithTokenAndParseResponse(toForm(req), Methods.BOOKMARKS_ADD, getToken(req), BookmarksAddResponse.class); diff --git a/slack-api-client/src/main/java/com/slack/api/methods/request/blocks/BlocksValidateRequest.java b/slack-api-client/src/main/java/com/slack/api/methods/request/blocks/BlocksValidateRequest.java new file mode 100644 index 000000000..5db253fd9 --- /dev/null +++ b/slack-api-client/src/main/java/com/slack/api/methods/request/blocks/BlocksValidateRequest.java @@ -0,0 +1,60 @@ +package com.slack.api.methods.request.blocks; + +import com.slack.api.methods.SlackApiRequest; +import com.slack.api.model.Attachment; +import com.slack.api.model.block.LayoutBlock; +import com.slack.api.model.view.View; +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +/** + * https://docs.slack.dev/reference/methods/blocks.validate + */ +@Data +@Builder +public class BlocksValidateRequest implements SlackApiRequest { + + /** + * An array of blocks to validate. + */ + private List blocks; + + /** + * An array of blocks to validate, as a JSON-encoded string. + */ + private String blocksAsString; + + /** + * A message payload to validate. + */ + private MessagePayload message; + + /** + * A message payload to validate, as a JSON-encoded string. + */ + private String messageAsString; + + /** + * A view payload to validate. + */ + private View view; + + /** + * A view payload to validate, as a JSON-encoded string. + */ + private String viewAsString; + + @Override + public String getToken() { + return null; + } + + @Data + @Builder + public static class MessagePayload { + private List blocks; + private List attachments; + } +} diff --git a/slack-api-client/src/main/java/com/slack/api/methods/response/blocks/BlocksValidateResponse.java b/slack-api-client/src/main/java/com/slack/api/methods/response/blocks/BlocksValidateResponse.java new file mode 100644 index 000000000..caf4b2d79 --- /dev/null +++ b/slack-api-client/src/main/java/com/slack/api/methods/response/blocks/BlocksValidateResponse.java @@ -0,0 +1,41 @@ +package com.slack.api.methods.response.blocks; + +import com.google.gson.JsonElement; +import com.slack.api.methods.SlackApiTextResponse; +import com.slack.api.model.ResponseMetadata; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Data +public class BlocksValidateResponse implements SlackApiTextResponse { + + private boolean ok; + private String warning; + private String error; + private String needed; + private String provided; + private transient Map> httpResponseHeaders; + + private ResponseMetadata responseMetadata; + private List errors; + + // https://docs.slack.dev/reference/methods/blocks.validate#validation-errors + @Data + public static class Error { + private String pointer; + private String code; + private String message; + private Constraint constraint; + + // expected/got are JsonElement because their shape varies by constraint type + // (a string array for "enum", a number for length/count constraints, or absent). + @Data + public static class Constraint { + private String type; + private JsonElement expected; + private JsonElement got; + } + } +} diff --git a/slack-api-client/src/test/java/test_locally/api/MethodsTest.java b/slack-api-client/src/test/java/test_locally/api/MethodsTest.java index 39f74b8bb..013183e63 100644 --- a/slack-api-client/src/test/java/test_locally/api/MethodsTest.java +++ b/slack-api-client/src/test/java/test_locally/api/MethodsTest.java @@ -19,8 +19,8 @@ public class MethodsTest { public void verifyTheCoverage() { // https://docs.slack.dev/reference/methods // var methodNames = [].slice.call(document.getElementsByClassName('apiReferenceFilterableList__listItemLink')).map(e => e.href.replace("https://docs.slack.dev/reference/methods/", ""));console.log(methodNames.toString());console.log(methodNames.length); - // 308 endpoints as of February 26, 2026 - String methods = "admin.analytics.getFile,admin.apps.activities.list,admin.apps.approve,admin.apps.clearResolution,admin.apps.restrict,admin.apps.uninstall,admin.apps.approved.list,admin.apps.config.lookup,admin.apps.config.set,admin.apps.requests.cancel,admin.apps.requests.list,admin.apps.restricted.list,admin.audit.anomaly.allow.getItem,admin.audit.anomaly.allow.updateItem,admin.auth.policy.assignEntities,admin.auth.policy.getEntities,admin.auth.policy.removeEntities,admin.barriers.create,admin.barriers.delete,admin.barriers.list,admin.barriers.update,admin.conversations.archive,admin.conversations.bulkArchive,admin.conversations.bulkDelete,admin.conversations.bulkMove,admin.conversations.convertToPrivate,admin.conversations.convertToPublic,admin.conversations.create,admin.conversations.createForObjects,admin.conversations.delete,admin.conversations.disconnectShared,admin.conversations.getConversationPrefs,admin.conversations.getCustomRetention,admin.conversations.getTeams,admin.conversations.invite,admin.conversations.linkObjects,admin.conversations.lookup,admin.conversations.removeCustomRetention,admin.conversations.rename,admin.conversations.search,admin.conversations.setConversationPrefs,admin.conversations.setCustomRetention,admin.conversations.setTeams,admin.conversations.unarchive,admin.conversations.unlinkObjects,admin.conversations.ekm.listOriginalConnectedChannelInfo,admin.conversations.restrictAccess.addGroup,admin.conversations.restrictAccess.listGroups,admin.conversations.restrictAccess.removeGroup,admin.emoji.add,admin.emoji.addAlias,admin.emoji.list,admin.emoji.remove,admin.emoji.rename,admin.functions.list,admin.functions.permissions.lookup,admin.functions.permissions.set,admin.inviteRequests.approve,admin.inviteRequests.deny,admin.inviteRequests.list,admin.inviteRequests.approved.list,admin.inviteRequests.denied.list,admin.roles.addAssignments,admin.roles.listAssignments,admin.roles.removeAssignments,admin.teams.admins.list,admin.teams.create,admin.teams.list,admin.teams.owners.list,admin.teams.settings.info,admin.teams.settings.setDefaultChannels,admin.teams.settings.setDescription,admin.teams.settings.setDiscoverability,admin.teams.settings.setIcon,admin.teams.settings.setName,admin.usergroups.addChannels,admin.usergroups.addTeams,admin.usergroups.listChannels,admin.usergroups.removeChannels,admin.users.assign,admin.users.getExpiration,admin.users.invite,admin.users.list,admin.users.remove,admin.users.setAdmin,admin.users.setExpiration,admin.users.setOwner,admin.users.setRegular,admin.users.session.clearSettings,admin.users.session.getSettings,admin.users.session.invalidate,admin.users.session.list,admin.users.session.reset,admin.users.session.resetBulk,admin.users.session.setSettings,admin.users.unsupportedVersions.export,admin.workflows.collaborators.add,admin.workflows.collaborators.remove,admin.workflows.permissions.lookup,admin.workflows.search,admin.workflows.unpublish,admin.workflows.triggers.types.permissions.lookup,admin.workflows.triggers.types.permissions.set,api.test,apps.activities.list,apps.auth.external.delete,apps.auth.external.get,apps.connections.open,apps.datastore.bulkDelete,apps.datastore.bulkGet,apps.datastore.bulkPut,apps.datastore.count,apps.datastore.delete,apps.datastore.get,apps.datastore.put,apps.datastore.query,apps.datastore.update,apps.event.authorizations.list,apps.manifest.create,apps.manifest.delete,apps.manifest.export,apps.manifest.update,apps.manifest.validate,apps.uninstall,apps.user.connection.update,assistant.search.context,assistant.threads.setStatus,assistant.threads.setSuggestedPrompts,assistant.threads.setTitle,auth.revoke,auth.test,auth.teams.list,bookmarks.add,bookmarks.edit,bookmarks.list,bookmarks.remove,bots.info,calls.add,calls.end,calls.info,calls.update,calls.participants.add,calls.participants.remove,canvases.access.delete,canvases.access.set,canvases.create,canvases.delete,canvases.edit,canvases.sections.lookup,channels.mark,chat.appendStream,chat.delete,chat.deleteScheduledMessage,chat.getPermalink,chat.meMessage,chat.postEphemeral,chat.postMessage,chat.scheduleMessage,chat.startStream,chat.stopStream,chat.unfurl,chat.update,chat.scheduledMessages.list,conversations.acceptSharedInvite,conversations.approveSharedInvite,conversations.archive,conversations.close,conversations.create,conversations.declineSharedInvite,conversations.history,conversations.info,conversations.invite,conversations.inviteShared,conversations.join,conversations.kick,conversations.leave,conversations.list,conversations.listConnectInvites,conversations.mark,conversations.members,conversations.open,conversations.rename,conversations.replies,conversations.setPurpose,conversations.setTopic,conversations.unarchive,conversations.canvases.create,conversations.externalInvitePermissions.set,conversations.requestSharedInvite.approve,conversations.requestSharedInvite.deny,conversations.requestSharedInvite.list,dialog.open,dnd.endDnd,dnd.endSnooze,dnd.info,dnd.setSnooze,dnd.teamInfo,emoji.list,files.comments.delete,files.completeUploadExternal,files.delete,files.getUploadURLExternal,files.info,files.list,files.revokePublicURL,files.sharedPublicURL,files.upload,files.remote.add,files.remote.info,files.remote.list,files.remote.remove,files.remote.share,files.remote.update,functions.completeError,functions.completeSuccess,functions.distributions.permissions.add,functions.distributions.permissions.list,functions.distributions.permissions.remove,functions.distributions.permissions.set,functions.workflows.steps.list,functions.workflows.steps.responses.export,groups.mark,migration.exchange,oauth.access,oauth.v2.access,oauth.v2.exchange,openid.connect.token,openid.connect.userInfo,pins.add,pins.list,pins.remove,reactions.add,reactions.get,reactions.list,reactions.remove,reminders.add,reminders.complete,reminders.delete,reminders.info,reminders.list,rtm.connect,rtm.start,search.all,search.files,search.messages,slackLists.access.delete,slackLists.access.set,slackLists.create,slackLists.download.get,slackLists.download.start,slackLists.items.create,slackLists.items.delete,slackLists.items.deleteMultiple,slackLists.items.info,slackLists.items.list,slackLists.items.update,slackLists.update,stars.add,stars.list,stars.remove,team.accessLogs,team.billableInfo,team.info,team.integrationLogs,team.billing.info,team.externalTeams.disconnect,team.externalTeams.list,team.preferences.list,team.profile.get,tooling.tokens.rotate,usergroups.create,usergroups.disable,usergroups.enable,usergroups.list,usergroups.update,usergroups.users.list,usergroups.users.update,users.conversations,users.deletePhoto,users.getPresence,users.identity,users.info,users.list,users.lookupByEmail,users.setActive,users.setPhoto,users.setPresence,users.discoverableContacts.lookup,users.profile.get,users.profile.set,views.open,views.publish,views.push,views.update,workflows.featured.add,workflows.featured.list,workflows.featured.remove,workflows.featured.set,workflows.stepCompleted,workflows.stepFailed,workflows.updateStep,workflows.triggers.permissions.add,workflows.triggers.permissions.list,workflows.triggers.permissions.remove,workflows.triggers.permissions.set,im.list,im.mark,mpim.list,mpim.mark"; + // 310 endpoints as of August 14, 2026 + String methods = "admin.analytics.getFile,admin.apps.activities.list,admin.apps.approve,admin.apps.clearResolution,admin.apps.restrict,admin.apps.uninstall,admin.apps.approved.list,admin.apps.config.lookup,admin.apps.config.set,admin.apps.requests.cancel,admin.apps.requests.list,admin.apps.restricted.list,admin.audit.anomaly.allow.getItem,admin.audit.anomaly.allow.updateItem,admin.auth.policy.assignEntities,admin.auth.policy.getEntities,admin.auth.policy.removeEntities,admin.barriers.create,admin.barriers.delete,admin.barriers.list,admin.barriers.update,admin.conversations.archive,admin.conversations.bulkArchive,admin.conversations.bulkDelete,admin.conversations.bulkMove,admin.conversations.convertToPrivate,admin.conversations.convertToPublic,admin.conversations.create,admin.conversations.createForObjects,admin.conversations.delete,admin.conversations.disconnectShared,admin.conversations.getConversationPrefs,admin.conversations.getCustomRetention,admin.conversations.getTeams,admin.conversations.invite,admin.conversations.linkObjects,admin.conversations.lookup,admin.conversations.removeCustomRetention,admin.conversations.rename,admin.conversations.search,admin.conversations.setConversationPrefs,admin.conversations.setCustomRetention,admin.conversations.setTeams,admin.conversations.unarchive,admin.conversations.unlinkObjects,admin.conversations.ekm.listOriginalConnectedChannelInfo,admin.conversations.restrictAccess.addGroup,admin.conversations.restrictAccess.listGroups,admin.conversations.restrictAccess.removeGroup,admin.emoji.add,admin.emoji.addAlias,admin.emoji.list,admin.emoji.remove,admin.emoji.rename,admin.functions.list,admin.functions.permissions.lookup,admin.functions.permissions.set,admin.inviteRequests.approve,admin.inviteRequests.deny,admin.inviteRequests.list,admin.inviteRequests.approved.list,admin.inviteRequests.denied.list,admin.roles.addAssignments,admin.roles.listAssignments,admin.roles.removeAssignments,admin.teams.admins.list,admin.teams.create,admin.teams.list,admin.teams.owners.list,admin.teams.settings.info,admin.teams.settings.setDefaultChannels,admin.teams.settings.setDescription,admin.teams.settings.setDiscoverability,admin.teams.settings.setIcon,admin.teams.settings.setName,admin.usergroups.addChannels,admin.usergroups.addTeams,admin.usergroups.listChannels,admin.usergroups.removeChannels,admin.users.assign,admin.users.getExpiration,admin.users.invite,admin.users.list,admin.users.remove,admin.users.setAdmin,admin.users.setExpiration,admin.users.setOwner,admin.users.setRegular,admin.users.session.clearSettings,admin.users.session.getSettings,admin.users.session.invalidate,admin.users.session.list,admin.users.session.reset,admin.users.session.resetBulk,admin.users.session.setSettings,admin.users.unsupportedVersions.export,admin.workflows.collaborators.add,admin.workflows.collaborators.remove,admin.workflows.permissions.lookup,admin.workflows.search,admin.workflows.unpublish,admin.workflows.triggers.types.permissions.lookup,admin.workflows.triggers.types.permissions.set,api.test,apps.activities.list,apps.auth.external.delete,apps.auth.external.get,apps.connections.open,apps.datastore.bulkDelete,apps.datastore.bulkGet,apps.datastore.bulkPut,apps.datastore.count,apps.datastore.delete,apps.datastore.get,apps.datastore.put,apps.datastore.query,apps.datastore.update,apps.event.authorizations.list,apps.manifest.create,apps.manifest.delete,apps.manifest.export,apps.manifest.update,apps.manifest.validate,apps.uninstall,apps.user.connection.update,assistant.search.context,assistant.threads.setStatus,assistant.threads.setSuggestedPrompts,assistant.threads.setTitle,auth.revoke,auth.test,auth.teams.list,blocks.validate,bookmarks.add,bookmarks.edit,bookmarks.list,bookmarks.remove,bots.info,calls.add,calls.end,calls.info,calls.update,calls.participants.add,calls.participants.remove,canvases.access.delete,canvases.access.set,canvases.create,canvases.delete,canvases.edit,canvases.sections.lookup,channels.mark,chat.appendStream,chat.delete,chat.deleteScheduledMessage,chat.getPermalink,chat.meMessage,chat.postEphemeral,chat.postMessage,chat.scheduleMessage,chat.startStream,chat.stopStream,chat.unfurl,chat.update,chat.scheduledMessages.list,conversations.acceptSharedInvite,conversations.approveSharedInvite,conversations.archive,conversations.close,conversations.create,conversations.declineSharedInvite,conversations.history,conversations.info,conversations.invite,conversations.inviteShared,conversations.join,conversations.kick,conversations.leave,conversations.list,conversations.listConnectInvites,conversations.mark,conversations.members,conversations.open,conversations.rename,conversations.replies,conversations.setPurpose,conversations.setTopic,conversations.unarchive,conversations.canvases.create,conversations.externalInvitePermissions.set,conversations.requestSharedInvite.approve,conversations.requestSharedInvite.deny,conversations.requestSharedInvite.list,dialog.open,dnd.endDnd,dnd.endSnooze,dnd.info,dnd.setSnooze,dnd.teamInfo,emoji.list,files.comments.delete,files.completeUploadExternal,files.delete,files.getUploadURLExternal,files.info,files.list,files.revokePublicURL,files.sharedPublicURL,files.upload,files.remote.add,files.remote.info,files.remote.list,files.remote.remove,files.remote.share,files.remote.update,functions.completeError,functions.completeSuccess,functions.distributions.permissions.add,functions.distributions.permissions.list,functions.distributions.permissions.remove,functions.distributions.permissions.set,functions.workflows.steps.list,functions.workflows.steps.responses.export,groups.mark,migration.exchange,oauth.access,oauth.v2.access,oauth.v2.exchange,openid.connect.token,openid.connect.userInfo,pins.add,pins.list,pins.remove,reactions.add,reactions.get,reactions.list,reactions.remove,reminders.add,reminders.complete,reminders.delete,reminders.info,reminders.list,rtm.connect,rtm.start,search.all,search.files,search.messages,slackLists.access.delete,slackLists.access.set,slackLists.create,slackLists.download.get,slackLists.download.start,slackLists.items.create,slackLists.items.delete,slackLists.items.deleteMultiple,slackLists.items.info,slackLists.items.list,slackLists.items.update,slackLists.update,stars.add,stars.list,stars.remove,team.accessLogs,team.billableInfo,team.info,team.integrationLogs,team.billing.info,team.externalTeams.disconnect,team.externalTeams.list,team.preferences.list,team.profile.get,tooling.tokens.rotate,usergroups.create,usergroups.disable,usergroups.enable,usergroups.list,usergroups.update,usergroups.users.list,usergroups.users.update,users.conversations,users.deletePhoto,users.getPresence,users.identity,users.info,users.list,users.lookupByEmail,users.setActive,users.setPhoto,users.setPresence,users.discoverableContacts.lookup,users.profile.get,users.profile.set,views.open,views.publish,views.push,views.update,workflows.featured.add,workflows.featured.list,workflows.featured.remove,workflows.featured.set,workflows.stepCompleted,workflows.stepFailed,workflows.updateStep,workflows.triggers.permissions.add,workflows.triggers.permissions.list,workflows.triggers.permissions.remove,workflows.triggers.permissions.set,im.list,im.mark,mpim.list,mpim.mark"; final List existingMethods = new ArrayList<>(); for (Field f : Methods.class.getDeclaredFields()) { int modifiers = f.getModifiers(); diff --git a/slack-api-client/src/test/java/test_locally/api/methods/BlocksTest.java b/slack-api-client/src/test/java/test_locally/api/methods/BlocksTest.java new file mode 100644 index 000000000..42cbaa058 --- /dev/null +++ b/slack-api-client/src/test/java/test_locally/api/methods/BlocksTest.java @@ -0,0 +1,84 @@ +package test_locally.api.methods; + +import com.slack.api.Slack; +import com.slack.api.SlackConfig; +import com.slack.api.methods.request.blocks.BlocksValidateRequest; +import com.slack.api.methods.response.blocks.BlocksValidateResponse; +import com.slack.api.model.block.LayoutBlock; +import com.slack.api.model.view.View; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import util.MockSlackApiServer; + +import java.util.Arrays; +import java.util.List; + +import static com.slack.api.model.block.Blocks.section; +import static com.slack.api.model.block.composition.BlockCompositions.plainText; +import static com.slack.api.model.view.Views.view; +import static com.slack.api.model.view.Views.viewTitle; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; + +public class BlocksTest { + + MockSlackApiServer server = new MockSlackApiServer(); + SlackConfig config = new SlackConfig(); + Slack slack = Slack.getInstance(config); + + private static final List BLOCKS = Arrays.asList( + section(s -> s.text(plainText("Hello world")))); + private static final String BLOCKS_AS_STRING = + "[{\"type\":\"section\",\"text\":{\"type\":\"plain_text\",\"text\":\"Hello world\"}}]"; + + @Before + public void setup() throws Exception { + server.start(); + config.setMethodsEndpointUrlPrefix(server.getMethodsEndpointPrefix()); + } + + @After + public void tearDown() throws Exception { + server.stop(); + } + + @Test + public void validate() throws Exception { + BlocksValidateResponse response = slack.methods().blocksValidate(r -> r.blocks(BLOCKS)); + assertThat(response.isOk(), is(true)); + assertThat(response.getErrors(), is(notNullValue())); + BlocksValidateResponse.Error error = response.getErrors().get(0); + assertThat(error.getConstraint(), is(notNullValue())); + } + + @Test + public void validate_async() throws Exception { + BlocksValidateResponse response = slack.methodsAsync() + .blocksValidate(r -> r.blocksAsString(BLOCKS_AS_STRING)).get(); + assertThat(response.isOk(), is(true)); + assertThat(response.getErrors(), is(notNullValue())); + BlocksValidateResponse.Error error = response.getErrors().get(0); + assertThat(error.getConstraint(), is(notNullValue())); + } + + @Test + public void validate_message() throws Exception { + BlocksValidateRequest.MessagePayload message = BlocksValidateRequest.MessagePayload.builder() + .blocks(BLOCKS) + .build(); + BlocksValidateResponse response = slack.methods().blocksValidate(r -> r.message(message)); + assertThat(response.isOk(), is(true)); + } + + @Test + public void validate_view() throws Exception { + View modal = view(v -> v + .type("modal") + .title(viewTitle(t -> t.type("plain_text").text("Title"))) + .blocks(BLOCKS)); + BlocksValidateResponse response = slack.methods().blocksValidate(r -> r.view(modal)); + assertThat(response.isOk(), is(true)); + } +} diff --git a/slack-api-client/src/test/java/test_with_remote_apis/methods/blocks_Test.java b/slack-api-client/src/test/java/test_with_remote_apis/methods/blocks_Test.java new file mode 100644 index 000000000..fec3f6a96 --- /dev/null +++ b/slack-api-client/src/test/java/test_with_remote_apis/methods/blocks_Test.java @@ -0,0 +1,72 @@ +package test_with_remote_apis.methods; + +import com.slack.api.Slack; +import com.slack.api.methods.response.blocks.BlocksValidateResponse; +import com.slack.api.model.block.DividerBlock; +import com.slack.api.model.block.LayoutBlock; +import config.SlackTestConfig; +import org.junit.AfterClass; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static com.slack.api.model.block.Blocks.section; +import static com.slack.api.model.block.composition.BlockCompositions.markdownText; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; + +public class blocks_Test { + + static SlackTestConfig testConfig = SlackTestConfig.getInstance(); + static Slack slack = Slack.getInstance(testConfig.getConfig()); + + @AfterClass + public static void tearDown() throws InterruptedException { + SlackTestConfig.awaitCompletion(testConfig); + } + + @Test + public void validate_wellFormed() throws Exception { + BlocksValidateResponse response = slack.methods().blocksValidate(r -> r + .blocks(Arrays.asList(section(s -> s.text(markdownText("Hello")))))); + assertThat(response.getError(), is(nullValue())); + assertThat(response.isOk(), is(true)); + assertThat(response.getErrors() == null || response.getErrors().isEmpty(), is(true)); + } + + @Test + public void validate_malformed() throws Exception { + String invalidBlocks = "[{\"type\":\"section\",\"text\":{\"type\":\"invalid\",\"text\":\"Hello\"}}]"; + BlocksValidateResponse response = slack.methods().blocksValidate(r -> r.blocksAsString(invalidBlocks)); + assertThat(response.isOk(), is(false)); + assertThat(response.getError(), is("invalid_blocks")); + List errors = response.getErrors(); + assertThat(errors, is(notNullValue())); + assertThat(errors.isEmpty(), is(false)); + BlocksValidateResponse.Error first = errors.get(0); + assertThat(first.getPointer(), is(notNullValue())); + assertThat(first.getCode(), is(notNullValue())); + assertThat(first.getMessage(), is(notNullValue())); + } + + @Test + public void validate_numericConstraint() throws Exception { + // Exceeding the block limit returns a max_items constraint whose expected/got are numbers, + // not the enum case's string array — exercising the polymorphic constraint fields. + List tooMany = new ArrayList<>(); + for (int i = 0; i < 60; i++) { + tooMany.add(DividerBlock.builder().build()); + } + BlocksValidateResponse response = slack.methods().blocksValidate(r -> r.blocks(tooMany)); + assertThat(response.isOk(), is(false)); + assertThat(response.getError(), is("invalid_blocks")); + BlocksValidateResponse.Error.Constraint constraint = response.getErrors().get(0).getConstraint(); + assertThat(constraint, is(notNullValue())); + assertThat(constraint.getExpected().getAsInt(), is(50)); + assertThat(constraint.getGot().getAsInt(), is(60)); + } +} diff --git a/slack-api-client/src/test/java/util/MockSlackApi.java b/slack-api-client/src/test/java/util/MockSlackApi.java index 1d02d0c3b..bbe26688b 100644 --- a/slack-api-client/src/test/java/util/MockSlackApi.java +++ b/slack-api-client/src/test/java/util/MockSlackApi.java @@ -37,6 +37,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I } String methodName = req.getRequestURI().replaceFirst("^/api/", ""); if (!methodName.equals("api.test") + && !methodName.equals("blocks.validate") && !methodName.startsWith("oauth.") && !methodName.startsWith("openid.connect.")) { String authorizationHeader = req.getHeader("Authorization");