From a7c05c7a8c111d4e56610eb09edbc23360940b6f Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Tue, 14 Jul 2026 15:15:34 -0700 Subject: [PATCH 01/15] feat(methods): add blocks.validate method Add the blocks.validate Web API method to the Slack API client. Callers can validate Block Kit payloads via methods().blocksValidate(req) (and the async equivalent), passing blocks, message, or view as JSON-encoded strings. No scopes are required. Adds the endpoint constant, sync and async interface methods and impls, the request form builder, the request/response model classes (with an errors[] list of code/message/pointer/relatedComponent), the Tier3 rate limit, a local test, and a response sample for type generation. Co-Authored-By: Claude --- json-logs/samples/api/blocks.validate.json | 20 +++++++++ metadata/web-api/rate_limit_tiers.json | 1 + .../slack/api/methods/AsyncMethodsClient.java | 10 +++++ .../java/com/slack/api/methods/Methods.java | 6 +++ .../com/slack/api/methods/MethodsClient.java | 12 ++++++ .../slack/api/methods/MethodsRateLimits.java | 2 + .../slack/api/methods/RequestFormBuilder.java | 9 ++++ .../methods/impl/AsyncMethodsClientImpl.java | 12 ++++++ .../api/methods/impl/MethodsClientImpl.java | 12 ++++++ .../request/blocks/BlocksValidateRequest.java | 27 ++++++++++++ .../blocks/BlocksValidateResponse.java | 30 +++++++++++++ .../java/test_locally/api/MethodsTest.java | 2 +- .../test_locally/api/methods/BlocksTest.java | 42 +++++++++++++++++++ 13 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 json-logs/samples/api/blocks.validate.json create mode 100644 slack-api-client/src/main/java/com/slack/api/methods/request/blocks/BlocksValidateRequest.java create mode 100644 slack-api-client/src/main/java/com/slack/api/methods/response/blocks/BlocksValidateResponse.java create mode 100644 slack-api-client/src/test/java/test_locally/api/methods/BlocksTest.java diff --git a/json-logs/samples/api/blocks.validate.json b/json-logs/samples/api/blocks.validate.json new file mode 100644 index 000000000..b99a88772 --- /dev/null +++ b/json-logs/samples/api/blocks.validate.json @@ -0,0 +1,20 @@ +{ + "ok": false, + "error": "", + "response_metadata": { + "messages": [ + "" + ] + }, + "needed": "", + "provided": "", + "errors": [ + { + "code": "", + "message": "", + "pointer": "", + "related_component": "" + } + ], + "warning": "" +} diff --git a/metadata/web-api/rate_limit_tiers.json b/metadata/web-api/rate_limit_tiers.json index 93f929e67..472b4e56a 100644 --- a/metadata/web-api/rate_limit_tiers.json +++ b/metadata/web-api/rate_limit_tiers.json @@ -124,6 +124,7 @@ "bookmarks.edit": "Tier2", "bookmarks.list": "Tier3", "bookmarks.remove": "Tier2", + "blocks.validate": "Tier3", "bots.info": "Tier3", "calls.add": "Tier2", "calls.end": "Tier2", 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 4d9b57ead..5427aa341 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 @@ -54,6 +54,7 @@ import com.slack.api.methods.request.bookmarks.BookmarksEditRequest; import com.slack.api.methods.request.bookmarks.BookmarksListRequest; import com.slack.api.methods.request.bookmarks.BookmarksRemoveRequest; +import com.slack.api.methods.request.blocks.BlocksValidateRequest; import com.slack.api.methods.request.bots.BotsInfoRequest; import com.slack.api.methods.request.calls.CallsAddRequest; import com.slack.api.methods.request.calls.CallsEndRequest; @@ -193,6 +194,7 @@ import com.slack.api.methods.response.bookmarks.BookmarksEditResponse; import com.slack.api.methods.response.bookmarks.BookmarksListResponse; import com.slack.api.methods.response.bookmarks.BookmarksRemoveResponse; +import com.slack.api.methods.response.blocks.BlocksValidateResponse; import com.slack.api.methods.response.bots.BotsInfoResponse; import com.slack.api.methods.response.calls.CallsAddResponse; import com.slack.api.methods.response.calls.CallsEndResponse; @@ -915,6 +917,14 @@ CompletableFuture CompletableFuture bookmarksRemove(RequestConfigurator req); + // ------------------------------ + // blocks + // ------------------------------ + + CompletableFuture blocksValidate(BlocksValidateRequest req); + + CompletableFuture blocksValidate(RequestConfigurator req); + // ------------------------------ // bots // ------------------------------ 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 2e39655d4..3c28410f3 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 @@ -304,6 +304,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 3a4722eb0..73c68f6ba 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 @@ -60,6 +60,7 @@ import com.slack.api.methods.request.bookmarks.BookmarksEditRequest; import com.slack.api.methods.request.bookmarks.BookmarksListRequest; import com.slack.api.methods.request.bookmarks.BookmarksRemoveRequest; +import com.slack.api.methods.request.blocks.BlocksValidateRequest; import com.slack.api.methods.request.bots.BotsInfoRequest; import com.slack.api.methods.request.calls.CallsAddRequest; import com.slack.api.methods.request.calls.CallsEndRequest; @@ -212,6 +213,7 @@ import com.slack.api.methods.response.bookmarks.BookmarksEditResponse; import com.slack.api.methods.response.bookmarks.BookmarksListResponse; import com.slack.api.methods.response.bookmarks.BookmarksRemoveResponse; +import com.slack.api.methods.response.blocks.BlocksValidateResponse; import com.slack.api.methods.response.bots.BotsInfoResponse; import com.slack.api.methods.response.calls.CallsAddResponse; import com.slack.api.methods.response.calls.CallsEndResponse; @@ -1355,6 +1357,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/MethodsRateLimits.java b/slack-api-client/src/main/java/com/slack/api/methods/MethodsRateLimits.java index 9ecc6fee6..d26e4abe7 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 @@ -242,6 +242,8 @@ public static void setRateLimitTier(String methodName, MethodsRateLimitTier tier setRateLimitTier(BOOKMARKS_LIST, Tier3); setRateLimitTier(BOOKMARKS_REMOVE, Tier2); + setRateLimitTier(BLOCKS_VALIDATE, Tier3); + setRateLimitTier(BOTS_INFO, Tier3); setRateLimitTier(CALLS_ADD, Tier2); 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 cb5884a7e..75677e9eb 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 @@ -61,6 +61,7 @@ import com.slack.api.methods.request.bookmarks.BookmarksEditRequest; import com.slack.api.methods.request.bookmarks.BookmarksListRequest; import com.slack.api.methods.request.bookmarks.BookmarksRemoveRequest; +import com.slack.api.methods.request.blocks.BlocksValidateRequest; import com.slack.api.methods.request.bots.BotsInfoRequest; import com.slack.api.methods.request.calls.CallsAddRequest; import com.slack.api.methods.request.calls.CallsEndRequest; @@ -1251,6 +1252,14 @@ public static FormBody.Builder toForm(BookmarksRemoveRequest req) { return form; } + public static FormBody.Builder toForm(BlocksValidateRequest req) { + FormBody.Builder form = new FormBody.Builder(); + setIfNotNull("blocks", req.getBlocks(), form); + setIfNotNull("message", req.getMessage(), form); + setIfNotNull("view", req.getView(), form); + return form; + } + public static FormBody.Builder toForm(BotsInfoRequest req) { FormBody.Builder form = new FormBody.Builder(); setIfNotNull("bot", req.getBot(), 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 21e007510..5b1e3bbf1 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 @@ -59,6 +59,7 @@ import com.slack.api.methods.request.bookmarks.BookmarksEditRequest; import com.slack.api.methods.request.bookmarks.BookmarksListRequest; import com.slack.api.methods.request.bookmarks.BookmarksRemoveRequest; +import com.slack.api.methods.request.blocks.BlocksValidateRequest; import com.slack.api.methods.request.bots.BotsInfoRequest; import com.slack.api.methods.request.calls.CallsAddRequest; import com.slack.api.methods.request.calls.CallsEndRequest; @@ -198,6 +199,7 @@ import com.slack.api.methods.response.bookmarks.BookmarksEditResponse; import com.slack.api.methods.response.bookmarks.BookmarksListResponse; import com.slack.api.methods.response.bookmarks.BookmarksRemoveResponse; +import com.slack.api.methods.response.blocks.BlocksValidateResponse; import com.slack.api.methods.response.bots.BotsInfoResponse; import com.slack.api.methods.response.calls.CallsAddResponse; import com.slack.api.methods.response.calls.CallsEndResponse; @@ -1520,6 +1522,16 @@ public CompletableFuture bookmarksRemove(RequestConfigu return bookmarksRemove(req.configure(BookmarksRemoveRequest.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 botsInfo(BotsInfoRequest req) { return executor.execute(BOTS_INFO, toMap(req), () -> methods.botsInfo(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 f1d26b619..9d4303b1c 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 @@ -62,6 +62,7 @@ import com.slack.api.methods.request.bookmarks.BookmarksEditRequest; import com.slack.api.methods.request.bookmarks.BookmarksListRequest; import com.slack.api.methods.request.bookmarks.BookmarksRemoveRequest; +import com.slack.api.methods.request.blocks.BlocksValidateRequest; import com.slack.api.methods.request.bots.BotsInfoRequest; import com.slack.api.methods.request.calls.CallsAddRequest; import com.slack.api.methods.request.calls.CallsEndRequest; @@ -214,6 +215,7 @@ import com.slack.api.methods.response.bookmarks.BookmarksEditResponse; import com.slack.api.methods.response.bookmarks.BookmarksListResponse; import com.slack.api.methods.response.bookmarks.BookmarksRemoveResponse; +import com.slack.api.methods.response.blocks.BlocksValidateResponse; import com.slack.api.methods.response.bots.BotsInfoResponse; import com.slack.api.methods.response.calls.CallsAddResponse; import com.slack.api.methods.response.calls.CallsEndResponse; @@ -1598,6 +1600,16 @@ public BookmarksRemoveResponse bookmarksRemove(RequestConfigurator req) throws IOException, SlackApiException { + return blocksValidate(req.configure(BlocksValidateRequest.builder()).build()); + } + @Override public BotsInfoResponse botsInfo(BotsInfoRequest req) throws IOException, SlackApiException { this.teamId.ifPresent(currentTeamId -> { 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..62476a338 --- /dev/null +++ b/slack-api-client/src/main/java/com/slack/api/methods/request/blocks/BlocksValidateRequest.java @@ -0,0 +1,27 @@ +package com.slack.api.methods.request.blocks; + +import com.slack.api.methods.SlackApiRequest; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class BlocksValidateRequest implements SlackApiRequest { + + private String token; + + /** + * A JSON-encoded string of an array of blocks to validate. + */ + private String blocks; + + /** + * A JSON-encoded string of a message payload to validate. + */ + private String message; + + /** + * A JSON-encoded string of a view payload to validate. + */ + private String view; +} 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..493f3b548 --- /dev/null +++ b/slack-api-client/src/main/java/com/slack/api/methods/response/blocks/BlocksValidateResponse.java @@ -0,0 +1,30 @@ +package com.slack.api.methods.response.blocks; + +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; + + @Data + public static class Error { + private String code; + private String message; + private String pointer; + private String relatedComponent; + } +} 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..15d1c3643 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 @@ -20,7 +20,7 @@ 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"; + 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,blocks.validate,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..7225e2de6 --- /dev/null +++ b/slack-api-client/src/test/java/test_locally/api/methods/BlocksTest.java @@ -0,0 +1,42 @@ +package test_locally.api.methods; + +import com.slack.api.Slack; +import com.slack.api.SlackConfig; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import util.MockSlackApiServer; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static test_locally.api.status.ApiTest.ValidToken; + +public class BlocksTest { + + MockSlackApiServer server = new MockSlackApiServer(); + SlackConfig config = new SlackConfig(); + Slack slack = Slack.getInstance(config); + + @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 { + assertThat(slack.methods(ValidToken).blocksValidate(r -> r + .blocks("[{\"type\":\"section\",\"text\":{\"type\":\"plain_text\",\"text\":\"Hello world\"}}]")).isOk(), is(true)); + } + + @Test + public void validate_async() throws Exception { + assertThat(slack.methodsAsync(ValidToken).blocksValidate(r -> r + .blocks("[{\"type\":\"section\",\"text\":{\"type\":\"plain_text\",\"text\":\"Hello world\"}}]")).get().isOk(), is(true)); + } +} From 02a29a7a34bf9824fb19661ffa4b57b53da901de Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Fri, 14 Aug 2026 18:30:33 -0700 Subject: [PATCH 02/15] fix(methods): classify blocks.validate as a special rate-limit tier MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit blocks.validate has special rate-limiting conditions rather than a standard tier (see https://docs.slack.dev/reference/methods/blocks.validate), so model it as SpecialTier_blocks_validate instead of Tier3. Regenerate the rate-limit metadata so the committed rate_limit_tiers.json matches the generated output, which also sorts blocks.validate into its correct alphabetical position — this is what was tripping the CI tree-drift check. Co-Authored-By: Claude --- metadata/web-api/rate_limit_tiers.json | 2 +- .../com/slack/api/methods/MethodsRateLimitTier.java | 10 +++++++++- .../java/com/slack/api/methods/MethodsRateLimits.java | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/metadata/web-api/rate_limit_tiers.json b/metadata/web-api/rate_limit_tiers.json index 472b4e56a..8884d448f 100644 --- a/metadata/web-api/rate_limit_tiers.json +++ b/metadata/web-api/rate_limit_tiers.json @@ -120,11 +120,11 @@ "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", "bookmarks.remove": "Tier2", - "blocks.validate": "Tier3", "bots.info": "Tier3", "calls.add": "Tier2", "calls.end": "Tier2", 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..894522e84 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,14 @@ 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 has special rate limiting conditions rather than a standard tier. + * It requires no scopes and is a lightweight validation call, so it allows hundreds of + * requests per minute. See https://docs.slack.dev/reference/methods/blocks.validate. + */ + SpecialTier_blocks_validate; // -------------------------------------------------------------------------------------------- @@ -72,6 +79,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, 600); } 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 d26e4abe7..cde4fc138 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 @@ -242,7 +242,7 @@ public static void setRateLimitTier(String methodName, MethodsRateLimitTier tier setRateLimitTier(BOOKMARKS_LIST, Tier3); setRateLimitTier(BOOKMARKS_REMOVE, Tier2); - setRateLimitTier(BLOCKS_VALIDATE, Tier3); + setRateLimitTier(BLOCKS_VALIDATE, SpecialTier_blocks_validate); setRateLimitTier(BOTS_INFO, Tier3); From efb20b636486d51f005f2dc98f3f6991231bfcc7 Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Fri, 14 Aug 2026 18:30:39 -0700 Subject: [PATCH 03/15] test(methods): add remote coverage for blocks.validate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a blocks_Test integration test under test_with_remote_apis that exercises blocks.validate against the real API: a well-formed payload validates with no errors, and a malformed payload surfaces the live validation feedback (recording the actual contract — ok=false with an error, or ok=true with a populated errors[] carrying code/message/pointer). Refresh the method-coverage scrape marker in MethodsTest to reflect the current 310-endpoint list. Co-Authored-By: Claude --- .../java/test_locally/api/MethodsTest.java | 2 +- .../methods/blocks_Test.java | 60 +++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 slack-api-client/src/test/java/test_with_remote_apis/methods/blocks_Test.java 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 15d1c3643..f3dc32a23 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,7 +19,7 @@ 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 + // 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,bookmarks.add,bookmarks.edit,bookmarks.list,bookmarks.remove,blocks.validate,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()) { 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..406ea17c0 --- /dev/null +++ b/slack-api-client/src/test/java/test_with_remote_apis/methods/blocks_Test.java @@ -0,0 +1,60 @@ +package test_with_remote_apis.methods; + +import com.slack.api.Slack; +import com.slack.api.methods.response.blocks.BlocksValidateResponse; +import config.Constants; +import config.SlackTestConfig; +import org.junit.AfterClass; +import org.junit.Test; + +import java.util.List; + +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 { + + String botToken = System.getenv(Constants.SLACK_SDK_TEST_BOT_TOKEN); + + 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 { + // A well-formed block payload should validate without errors. + String validBlocks = "[{\"type\":\"section\",\"text\":{\"type\":\"mrkdwn\",\"text\":\"Hello\"}}]"; + BlocksValidateResponse response = slack.methods(botToken).blocksValidate(r -> r.blocks(validBlocks)); + 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 { + // A malformed block payload (a section with neither text nor fields) should surface + // validation feedback. This test records the live contract rather than asserting a fixed + // shape: blocks.validate may return ok=false with an error, or ok=true with a populated + // errors[] carrying code/message/pointer/relatedComponent. + String invalidBlocks = "[{\"type\":\"section\"}]"; + BlocksValidateResponse response = slack.methods(botToken).blocksValidate(r -> r.blocks(invalidBlocks)); + if (response.isOk()) { + List errors = response.getErrors(); + assertThat(errors, is(notNullValue())); + assertThat(errors.isEmpty(), is(false)); + BlocksValidateResponse.Error first = errors.get(0); + // The error entry carries the structured validation feedback contract. + assertThat(first.getCode(), is(notNullValue())); + assertThat(first.getMessage(), is(notNullValue())); + assertThat(first.getPointer(), is(notNullValue())); + } else { + assertThat(response.getError(), is(notNullValue())); + } + } +} From eb983efa6cdb314695fb66d0ca48c029986e931d Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Fri, 14 Aug 2026 18:35:40 -0700 Subject: [PATCH 04/15] style(methods): sort blocks.validate into its alphabetical position MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The method was consistently placed after the bookmarks family, but "blocks" sorts before "bookmarks". Move every insertion site into alphabetical order: request/response imports, the sync/async interface declarations and their implementations, the RequestFormBuilder toForm mapping, the rate-limit tier registration, and the method-coverage string in MethodsTest. Pure reordering — no behavior change. Co-Authored-By: Claude --- .../slack/api/methods/AsyncMethodsClient.java | 20 ++++++++-------- .../com/slack/api/methods/MethodsClient.java | 4 ++-- .../slack/api/methods/MethodsRateLimits.java | 4 ++-- .../slack/api/methods/RequestFormBuilder.java | 18 +++++++------- .../methods/impl/AsyncMethodsClientImpl.java | 24 +++++++++---------- .../api/methods/impl/MethodsClientImpl.java | 24 +++++++++---------- .../java/test_locally/api/MethodsTest.java | 2 +- 7 files changed, 48 insertions(+), 48 deletions(-) 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 5427aa341..128e0b619 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 @@ -50,11 +50,11 @@ 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; import com.slack.api.methods.request.bookmarks.BookmarksRemoveRequest; -import com.slack.api.methods.request.blocks.BlocksValidateRequest; import com.slack.api.methods.request.bots.BotsInfoRequest; import com.slack.api.methods.request.calls.CallsAddRequest; import com.slack.api.methods.request.calls.CallsEndRequest; @@ -190,11 +190,11 @@ 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; import com.slack.api.methods.response.bookmarks.BookmarksRemoveResponse; -import com.slack.api.methods.response.blocks.BlocksValidateResponse; import com.slack.api.methods.response.bots.BotsInfoResponse; import com.slack.api.methods.response.calls.CallsAddResponse; import com.slack.api.methods.response.calls.CallsEndResponse; @@ -897,6 +897,14 @@ CompletableFuture CompletableFuture authTeamsList(RequestConfigurator req); + // ------------------------------ + // blocks + // ------------------------------ + + CompletableFuture blocksValidate(BlocksValidateRequest req); + + CompletableFuture blocksValidate(RequestConfigurator req); + // ------------------------------ // bookmarks // ------------------------------ @@ -917,14 +925,6 @@ CompletableFuture CompletableFuture bookmarksRemove(RequestConfigurator req); - // ------------------------------ - // blocks - // ------------------------------ - - CompletableFuture blocksValidate(BlocksValidateRequest req); - - CompletableFuture blocksValidate(RequestConfigurator req); - // ------------------------------ // bots // ------------------------------ 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 73c68f6ba..c5048447f 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 @@ -56,11 +56,11 @@ 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; import com.slack.api.methods.request.bookmarks.BookmarksRemoveRequest; -import com.slack.api.methods.request.blocks.BlocksValidateRequest; import com.slack.api.methods.request.bots.BotsInfoRequest; import com.slack.api.methods.request.calls.CallsAddRequest; import com.slack.api.methods.request.calls.CallsEndRequest; @@ -209,11 +209,11 @@ 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; import com.slack.api.methods.response.bookmarks.BookmarksRemoveResponse; -import com.slack.api.methods.response.blocks.BlocksValidateResponse; import com.slack.api.methods.response.bots.BotsInfoResponse; import com.slack.api.methods.response.calls.CallsAddResponse; import com.slack.api.methods.response.calls.CallsEndResponse; 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 cde4fc138..2c268c478 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 @@ -237,13 +237,13 @@ 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); setRateLimitTier(BOOKMARKS_REMOVE, Tier2); - setRateLimitTier(BLOCKS_VALIDATE, SpecialTier_blocks_validate); - setRateLimitTier(BOTS_INFO, Tier3); setRateLimitTier(CALLS_ADD, Tier2); 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 75677e9eb..167f6471f 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 @@ -57,11 +57,11 @@ 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; import com.slack.api.methods.request.bookmarks.BookmarksRemoveRequest; -import com.slack.api.methods.request.blocks.BlocksValidateRequest; import com.slack.api.methods.request.bots.BotsInfoRequest; import com.slack.api.methods.request.calls.CallsAddRequest; import com.slack.api.methods.request.calls.CallsEndRequest; @@ -1217,6 +1217,14 @@ public static FormBody.Builder toForm(AuthTeamsListRequest req) { return form; } + public static FormBody.Builder toForm(BlocksValidateRequest req) { + FormBody.Builder form = new FormBody.Builder(); + setIfNotNull("blocks", req.getBlocks(), form); + setIfNotNull("message", req.getMessage(), form); + setIfNotNull("view", req.getView(), form); + return form; + } + public static FormBody.Builder toForm(BookmarksAddRequest req) { FormBody.Builder form = new FormBody.Builder(); setIfNotNull("channel_id", req.getChannelId(), form); @@ -1252,14 +1260,6 @@ public static FormBody.Builder toForm(BookmarksRemoveRequest req) { return form; } - public static FormBody.Builder toForm(BlocksValidateRequest req) { - FormBody.Builder form = new FormBody.Builder(); - setIfNotNull("blocks", req.getBlocks(), form); - setIfNotNull("message", req.getMessage(), form); - setIfNotNull("view", req.getView(), form); - return form; - } - public static FormBody.Builder toForm(BotsInfoRequest req) { FormBody.Builder form = new FormBody.Builder(); setIfNotNull("bot", req.getBot(), 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 5b1e3bbf1..2c20f6532 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 @@ -55,11 +55,11 @@ 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; import com.slack.api.methods.request.bookmarks.BookmarksRemoveRequest; -import com.slack.api.methods.request.blocks.BlocksValidateRequest; import com.slack.api.methods.request.bots.BotsInfoRequest; import com.slack.api.methods.request.calls.CallsAddRequest; import com.slack.api.methods.request.calls.CallsEndRequest; @@ -195,11 +195,11 @@ 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; import com.slack.api.methods.response.bookmarks.BookmarksRemoveResponse; -import com.slack.api.methods.response.blocks.BlocksValidateResponse; import com.slack.api.methods.response.bots.BotsInfoResponse; import com.slack.api.methods.response.calls.CallsAddResponse; import com.slack.api.methods.response.calls.CallsEndResponse; @@ -1482,6 +1482,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)); @@ -1522,16 +1532,6 @@ public CompletableFuture bookmarksRemove(RequestConfigu return bookmarksRemove(req.configure(BookmarksRemoveRequest.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 botsInfo(BotsInfoRequest req) { return executor.execute(BOTS_INFO, toMap(req), () -> methods.botsInfo(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 9d4303b1c..596726714 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 @@ -58,11 +58,11 @@ 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; import com.slack.api.methods.request.bookmarks.BookmarksRemoveRequest; -import com.slack.api.methods.request.blocks.BlocksValidateRequest; import com.slack.api.methods.request.bots.BotsInfoRequest; import com.slack.api.methods.request.calls.CallsAddRequest; import com.slack.api.methods.request.calls.CallsEndRequest; @@ -211,11 +211,11 @@ 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; import com.slack.api.methods.response.bookmarks.BookmarksRemoveResponse; -import com.slack.api.methods.response.blocks.BlocksValidateResponse; import com.slack.api.methods.response.bots.BotsInfoResponse; import com.slack.api.methods.response.calls.CallsAddResponse; import com.slack.api.methods.response.calls.CallsEndResponse; @@ -1560,6 +1560,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); @@ -1600,16 +1610,6 @@ public BookmarksRemoveResponse bookmarksRemove(RequestConfigurator req) throws IOException, SlackApiException { - return blocksValidate(req.configure(BlocksValidateRequest.builder()).build()); - } - @Override public BotsInfoResponse botsInfo(BotsInfoRequest req) throws IOException, SlackApiException { this.teamId.ifPresent(currentTeamId -> { 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 f3dc32a23..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 @@ -20,7 +20,7 @@ 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); // 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,bookmarks.add,bookmarks.edit,bookmarks.list,bookmarks.remove,blocks.validate,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"; + 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(); From 2764f5265a9199071e36ce843fca96e7b31b43ee Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Fri, 14 Aug 2026 18:45:34 -0700 Subject: [PATCH 05/15] fix(methods): treat blocks.validate as unauthenticated blocks.validate requires no token or scopes (https://docs.slack.dev/reference/methods/blocks.validate), so it should not send an Authorization header. Model it like api.test: drop the token field from BlocksValidateRequest (overriding getToken() to return null) and call it through the tokenless postFormAndParseResponse path. The local BlocksTest now asserts the request round-trips (the shared mock answers a tokenless call with not_authed); end-to-end ok/errors[] behavior is covered by the remote blocks_Test. Co-Authored-By: Claude --- .../slack/api/methods/impl/MethodsClientImpl.java | 2 +- .../request/blocks/BlocksValidateRequest.java | 11 +++++++++-- .../java/test_locally/api/methods/BlocksTest.java | 15 ++++++++++----- 3 files changed, 20 insertions(+), 8 deletions(-) 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 596726714..c527e75ef 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 @@ -1562,7 +1562,7 @@ public AuthTeamsListResponse authTeamsList(RequestConfigurator r - .blocks("[{\"type\":\"section\",\"text\":{\"type\":\"plain_text\",\"text\":\"Hello world\"}}]")).isOk(), is(true)); + assertThat(slack.methods().blocksValidate(r -> r + .blocks("[{\"type\":\"section\",\"text\":{\"type\":\"plain_text\",\"text\":\"Hello world\"}}]")).getError(), is("not_authed")); } @Test public void validate_async() throws Exception { - assertThat(slack.methodsAsync(ValidToken).blocksValidate(r -> r - .blocks("[{\"type\":\"section\",\"text\":{\"type\":\"plain_text\",\"text\":\"Hello world\"}}]")).get().isOk(), is(true)); + assertThat(slack.methodsAsync().blocksValidate(r -> r + .blocks("[{\"type\":\"section\",\"text\":{\"type\":\"plain_text\",\"text\":\"Hello world\"}}]")).get().getError(), is("not_authed")); } } From 226fa235c57030d58239d2e04af6f4825e6537ed Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Tue, 25 Aug 2026 15:03:35 -0700 Subject: [PATCH 06/15] fix(methods): align blocks.validate errors[] with documented contract MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `blocks.validate` response `errors[]` entries carry pointer / code / message / constraint per the method reference. The Error model previously declared a `relatedComponent` field (copied from apps.manifest.validate), which is not part of the blocks.validate contract, and omitted `constraint` — the structured object describing what was expected (e.g. {"type": "enum", "expected": ["plain_text", "mrkdwn"]}). Replace `relatedComponent` with a `Constraint` nested type, fix the mock sample to the documented shape, and clarify the remote-test comments and assertions to reflect the real contract. Ref: https://docs.slack.dev/reference/methods/blocks.validate#validation-errors Co-Authored-By: Claude --- json-logs/samples/api/blocks.validate.json | 16 +++++--- .../blocks/BlocksValidateResponse.java | 16 +++++++- .../methods/blocks_Test.java | 38 +++++++++++-------- 3 files changed, 47 insertions(+), 23 deletions(-) diff --git a/json-logs/samples/api/blocks.validate.json b/json-logs/samples/api/blocks.validate.json index b99a88772..97c76e53f 100644 --- a/json-logs/samples/api/blocks.validate.json +++ b/json-logs/samples/api/blocks.validate.json @@ -1,6 +1,6 @@ { "ok": false, - "error": "", + "error": "invalid_blocks", "response_metadata": { "messages": [ "" @@ -10,10 +10,16 @@ "provided": "", "errors": [ { - "code": "", - "message": "", - "pointer": "", - "related_component": "" + "code": "failed_constraint", + "message": "unsupported type: invalid", + "pointer": "/0/text/type", + "constraint": { + "type": "enum", + "expected": [ + "plain_text", + "mrkdwn" + ] + } } ], "warning": "" 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 index 493f3b548..22f2cdd5a 100644 --- 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 @@ -22,9 +22,21 @@ public class BlocksValidateResponse implements SlackApiTextResponse { @Data public static class Error { + // The fields below mirror the documented errors[] contract: + // https://docs.slack.dev/reference/methods/blocks.validate#validation-errors + // a JSON pointer path to the invalid element (e.g. "/0/text/type") + private String pointer; + // the error code (e.g. "failed_constraint") private String code; + // a human-readable description of the issue private String message; - private String pointer; - private String relatedComponent; + // structured details about what was expected (e.g. {"type": "enum", "expected": ["plain_text", "mrkdwn"]}) + private Constraint constraint; + + @Data + public static class Constraint { + private String type; + private List expected; + } } } 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 index 406ea17c0..506782c5f 100644 --- 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 @@ -28,7 +28,9 @@ public static void tearDown() throws InterruptedException { @Test public void validate_wellFormed() throws Exception { - // A well-formed block payload should validate without errors. + // A well-formed blocks payload validates cleanly: ok=true, no top-level error, and an + // empty (or absent) errors[]. See the "Typical success response" in the method reference: + // https://docs.slack.dev/reference/methods/blocks.validate#response String validBlocks = "[{\"type\":\"section\",\"text\":{\"type\":\"mrkdwn\",\"text\":\"Hello\"}}]"; BlocksValidateResponse response = slack.methods(botToken).blocksValidate(r -> r.blocks(validBlocks)); assertThat(response.getError(), is(nullValue())); @@ -38,23 +40,27 @@ public void validate_wellFormed() throws Exception { @Test public void validate_malformed() throws Exception { - // A malformed block payload (a section with neither text nor fields) should surface - // validation feedback. This test records the live contract rather than asserting a fixed - // shape: blocks.validate may return ok=false with an error, or ok=true with a populated - // errors[] carrying code/message/pointer/relatedComponent. + // A malformed blocks payload (a section with neither text nor fields) surfaces validation + // feedback. This test records the live contract rather than asserting a fixed shape: + // blocks.validate may answer with ok=false and error="invalid_blocks" plus a populated + // errors[], or (defensively) ok=true with errors[]. Each Error mirrors the documented + // contract — pointer / code / message / constraint — where constraint is a structured + // object (e.g. {"type":"enum","expected":["plain_text","mrkdwn"]}), NOT a flat string. + // https://docs.slack.dev/reference/methods/blocks.validate#validation-errors String invalidBlocks = "[{\"type\":\"section\"}]"; BlocksValidateResponse response = slack.methods(botToken).blocksValidate(r -> r.blocks(invalidBlocks)); - if (response.isOk()) { - List errors = response.getErrors(); - assertThat(errors, is(notNullValue())); - assertThat(errors.isEmpty(), is(false)); - BlocksValidateResponse.Error first = errors.get(0); - // The error entry carries the structured validation feedback contract. - assertThat(first.getCode(), is(notNullValue())); - assertThat(first.getMessage(), is(notNullValue())); - assertThat(first.getPointer(), is(notNullValue())); - } else { - assertThat(response.getError(), is(notNullValue())); + List errors = response.getErrors(); + if (!response.isOk()) { + // Failed validation reports "invalid_blocks" and enumerates each issue in errors[]. + assertThat(response.getError(), is("invalid_blocks")); } + // Whether ok is true or false, a malformed payload should enumerate at least one issue. + assertThat(errors, is(notNullValue())); + assertThat(errors.isEmpty(), is(false)); + BlocksValidateResponse.Error first = errors.get(0); + // The three always-present fields of the structured validation feedback contract. + assertThat(first.getPointer(), is(notNullValue())); + assertThat(first.getCode(), is(notNullValue())); + assertThat(first.getMessage(), is(notNullValue())); } } From 8c560f6180c588e440ccdd9eca436fe071d1b3d4 Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Tue, 25 Aug 2026 15:06:33 -0700 Subject: [PATCH 07/15] test(methods): ground blocks.validate errors[] in the live API contract Verified against the real (unauthenticated) blocks.validate endpoint: - A section with an unsupported text "type" fails; a section with no text/fields returns ok=true. Switch the malformed remote test to the former so it deterministically exercises the errors[] path, and assert the confirmed shape (ok=false, error="invalid_blocks"). - constraint carries a "got" field for enum failures (e.g. {"type":"enum","expected":[...],"got":"invalid"}) and may be just {"type":"all_of"} for composite-schema failures; add Constraint.got. - Update the mock fixture to a real captured response, and trim the redundant comments down to the field contract + doc link. Co-Authored-By: Claude --- json-logs/samples/api/blocks.validate.json | 11 +++++++++- .../blocks/BlocksValidateResponse.java | 8 ++----- .../methods/blocks_Test.java | 21 ++++--------------- 3 files changed, 16 insertions(+), 24 deletions(-) diff --git a/json-logs/samples/api/blocks.validate.json b/json-logs/samples/api/blocks.validate.json index 97c76e53f..ace39d667 100644 --- a/json-logs/samples/api/blocks.validate.json +++ b/json-logs/samples/api/blocks.validate.json @@ -9,6 +9,14 @@ "needed": "", "provided": "", "errors": [ + { + "code": "failed_constraint", + "message": "failed to match all allowed schemas", + "pointer": "/0/text", + "constraint": { + "type": "all_of" + } + }, { "code": "failed_constraint", "message": "unsupported type: invalid", @@ -18,7 +26,8 @@ "expected": [ "plain_text", "mrkdwn" - ] + ], + "got": "invalid" } } ], 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 index 22f2cdd5a..ef1ee7bc0 100644 --- 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 @@ -20,23 +20,19 @@ public class BlocksValidateResponse implements SlackApiTextResponse { private ResponseMetadata responseMetadata; private List errors; + // https://docs.slack.dev/reference/methods/blocks.validate#validation-errors @Data public static class Error { - // The fields below mirror the documented errors[] contract: - // https://docs.slack.dev/reference/methods/blocks.validate#validation-errors - // a JSON pointer path to the invalid element (e.g. "/0/text/type") private String pointer; - // the error code (e.g. "failed_constraint") private String code; - // a human-readable description of the issue private String message; - // structured details about what was expected (e.g. {"type": "enum", "expected": ["plain_text", "mrkdwn"]}) private Constraint constraint; @Data public static class Constraint { private String type; private List expected; + private String got; } } } 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 index 506782c5f..8fa51b33e 100644 --- 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 @@ -28,9 +28,6 @@ public static void tearDown() throws InterruptedException { @Test public void validate_wellFormed() throws Exception { - // A well-formed blocks payload validates cleanly: ok=true, no top-level error, and an - // empty (or absent) errors[]. See the "Typical success response" in the method reference: - // https://docs.slack.dev/reference/methods/blocks.validate#response String validBlocks = "[{\"type\":\"section\",\"text\":{\"type\":\"mrkdwn\",\"text\":\"Hello\"}}]"; BlocksValidateResponse response = slack.methods(botToken).blocksValidate(r -> r.blocks(validBlocks)); assertThat(response.getError(), is(nullValue())); @@ -40,25 +37,15 @@ public void validate_wellFormed() throws Exception { @Test public void validate_malformed() throws Exception { - // A malformed blocks payload (a section with neither text nor fields) surfaces validation - // feedback. This test records the live contract rather than asserting a fixed shape: - // blocks.validate may answer with ok=false and error="invalid_blocks" plus a populated - // errors[], or (defensively) ok=true with errors[]. Each Error mirrors the documented - // contract — pointer / code / message / constraint — where constraint is a structured - // object (e.g. {"type":"enum","expected":["plain_text","mrkdwn"]}), NOT a flat string. - // https://docs.slack.dev/reference/methods/blocks.validate#validation-errors - String invalidBlocks = "[{\"type\":\"section\"}]"; + // A section whose text carries an unsupported "type" fails validation. + String invalidBlocks = "[{\"type\":\"section\",\"text\":{\"type\":\"invalid\",\"text\":\"Hello\"}}]"; BlocksValidateResponse response = slack.methods(botToken).blocksValidate(r -> r.blocks(invalidBlocks)); + assertThat(response.isOk(), is(false)); + assertThat(response.getError(), is("invalid_blocks")); List errors = response.getErrors(); - if (!response.isOk()) { - // Failed validation reports "invalid_blocks" and enumerates each issue in errors[]. - assertThat(response.getError(), is("invalid_blocks")); - } - // Whether ok is true or false, a malformed payload should enumerate at least one issue. assertThat(errors, is(notNullValue())); assertThat(errors.isEmpty(), is(false)); BlocksValidateResponse.Error first = errors.get(0); - // The three always-present fields of the structured validation feedback contract. assertThat(first.getPointer(), is(notNullValue())); assertThat(first.getCode(), is(notNullValue())); assertThat(first.getMessage(), is(notNullValue())); From bd42793e9cf65afb93e3e45750300bcb2be8d590 Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Tue, 25 Aug 2026 15:08:49 -0700 Subject: [PATCH 08/15] fix(methods): use blank-skeleton mock fixture for blocks.validate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The json-logs/samples/api fixtures are schema skeletons the mock server replays (blank strings, placeholder ids), not real responses — see apps.manifest.validate.json. Revert the fixture to that convention: one errors[] entry with every field blanked, including the constraint object (type/expected/got) so deserialization is still exercised. The real contract learned from the live API stays reflected in the model and the remote test, which is where it belongs. Co-Authored-By: Claude --- json-logs/samples/api/blocks.validate.json | 23 +++++++--------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/json-logs/samples/api/blocks.validate.json b/json-logs/samples/api/blocks.validate.json index ace39d667..17d4d0cf4 100644 --- a/json-logs/samples/api/blocks.validate.json +++ b/json-logs/samples/api/blocks.validate.json @@ -1,6 +1,6 @@ { "ok": false, - "error": "invalid_blocks", + "error": "", "response_metadata": { "messages": [ "" @@ -10,24 +10,15 @@ "provided": "", "errors": [ { - "code": "failed_constraint", - "message": "failed to match all allowed schemas", - "pointer": "/0/text", + "code": "", + "message": "", + "pointer": "", "constraint": { - "type": "all_of" - } - }, - { - "code": "failed_constraint", - "message": "unsupported type: invalid", - "pointer": "/0/text/type", - "constraint": { - "type": "enum", + "type": "", "expected": [ - "plain_text", - "mrkdwn" + "" ], - "got": "invalid" + "got": "" } } ], From 8fbb2c3067030c0930d5579b4379f7af06e866c7 Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Tue, 25 Aug 2026 15:22:17 -0700 Subject: [PATCH 09/15] fix(methods): correct blocks.validate rate limit and strengthen tests - Set the SpecialTier_blocks_validate quota to 60 requests/minute (was an unverified 600 copied from other special tiers). - Call blocks.validate without a token in both tests, matching its unauthenticated contract; exempt it from the mock server's token gate (like api.test) so the recorded fixture is served. - Local BlocksTest now asserts the deserialized errors[]/constraint shape instead of not_authed; the remote test exercises a payload that actually fails validation. Regenerate the mock fixture via the recorder. - Drop the unsourced "hundreds of requests per minute" claim from the tier Javadoc. Verified: test_locally.api.methods.BlocksTest, test_locally.api.MethodsTest, and test_with_remote_apis.methods.blocks_Test all pass (JDK 17). Co-Authored-By: Claude --- json-logs/samples/api/blocks.validate.json | 2 +- .../api/methods/MethodsRateLimitTier.java | 5 ++-- .../test_locally/api/methods/BlocksTest.java | 25 +++++++++++-------- .../methods/blocks_Test.java | 8 ++---- .../src/test/java/util/MockSlackApi.java | 1 + 5 files changed, 21 insertions(+), 20 deletions(-) diff --git a/json-logs/samples/api/blocks.validate.json b/json-logs/samples/api/blocks.validate.json index 17d4d0cf4..6253142d7 100644 --- a/json-logs/samples/api/blocks.validate.json +++ b/json-logs/samples/api/blocks.validate.json @@ -23,4 +23,4 @@ } ], "warning": "" -} +} \ No newline at end of file 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 894522e84..ec9fe0da7 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 @@ -61,8 +61,7 @@ public enum MethodsRateLimitTier { /** * blocks.validate has special rate limiting conditions rather than a standard tier. - * It requires no scopes and is a lightweight validation call, so it allows hundreds of - * requests per minute. See https://docs.slack.dev/reference/methods/blocks.validate. + * See https://docs.slack.dev/reference/methods/blocks.validate. */ SpecialTier_blocks_validate; @@ -79,7 +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, 600); + allowedRequestsPerMinute.put(MethodsRateLimitTier.SpecialTier_blocks_validate, 60); } public static Integer getAllowedRequestsPerMinute(MethodsRateLimitTier tier) { 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 index e5631b2d0..6256a0bbe 100644 --- 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 @@ -2,12 +2,14 @@ import com.slack.api.Slack; import com.slack.api.SlackConfig; +import com.slack.api.methods.response.blocks.BlocksValidateResponse; import org.junit.After; import org.junit.Before; import org.junit.Test; import util.MockSlackApiServer; import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; public class BlocksTest { @@ -16,6 +18,9 @@ public class BlocksTest { SlackConfig config = new SlackConfig(); Slack slack = Slack.getInstance(config); + private static final String BLOCKS = + "[{\"type\":\"section\",\"text\":{\"type\":\"plain_text\",\"text\":\"Hello world\"}}]"; + @Before public void setup() throws Exception { server.start(); @@ -27,21 +32,21 @@ public void tearDown() throws Exception { server.stop(); } - // blocks.validate is unauthenticated (no token or scopes — see - // https://docs.slack.dev/reference/methods/blocks.validate), so the SDK sends no - // Authorization header for it. The shared mock API gates every request on a valid token and - // therefore answers a tokenless call with "not_authed"; these tests assert that the request is - // built and round-trips to the endpoint, which is what a mock harness can verify without a - // real Slack backend. End-to-end ok/errors[] behavior is covered by the remote blocks_Test. @Test public void validate() throws Exception { - assertThat(slack.methods().blocksValidate(r -> r - .blocks("[{\"type\":\"section\",\"text\":{\"type\":\"plain_text\",\"text\":\"Hello world\"}}]")).getError(), is("not_authed")); + 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 { - assertThat(slack.methodsAsync().blocksValidate(r -> r - .blocks("[{\"type\":\"section\",\"text\":{\"type\":\"plain_text\",\"text\":\"Hello world\"}}]")).get().getError(), is("not_authed")); + BlocksValidateResponse response = slack.methodsAsync().blocksValidate(r -> r.blocks(BLOCKS)).get(); + assertThat(response.isOk(), is(true)); + assertThat(response.getErrors(), is(notNullValue())); + BlocksValidateResponse.Error error = response.getErrors().get(0); + assertThat(error.getConstraint(), is(notNullValue())); } } 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 index 8fa51b33e..a997c6ab8 100644 --- 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 @@ -2,7 +2,6 @@ import com.slack.api.Slack; import com.slack.api.methods.response.blocks.BlocksValidateResponse; -import config.Constants; import config.SlackTestConfig; import org.junit.AfterClass; import org.junit.Test; @@ -16,8 +15,6 @@ public class blocks_Test { - String botToken = System.getenv(Constants.SLACK_SDK_TEST_BOT_TOKEN); - static SlackTestConfig testConfig = SlackTestConfig.getInstance(); static Slack slack = Slack.getInstance(testConfig.getConfig()); @@ -29,7 +26,7 @@ public static void tearDown() throws InterruptedException { @Test public void validate_wellFormed() throws Exception { String validBlocks = "[{\"type\":\"section\",\"text\":{\"type\":\"mrkdwn\",\"text\":\"Hello\"}}]"; - BlocksValidateResponse response = slack.methods(botToken).blocksValidate(r -> r.blocks(validBlocks)); + BlocksValidateResponse response = slack.methods().blocksValidate(r -> r.blocks(validBlocks)); assertThat(response.getError(), is(nullValue())); assertThat(response.isOk(), is(true)); assertThat(response.getErrors() == null || response.getErrors().isEmpty(), is(true)); @@ -37,9 +34,8 @@ public void validate_wellFormed() throws Exception { @Test public void validate_malformed() throws Exception { - // A section whose text carries an unsupported "type" fails validation. String invalidBlocks = "[{\"type\":\"section\",\"text\":{\"type\":\"invalid\",\"text\":\"Hello\"}}]"; - BlocksValidateResponse response = slack.methods(botToken).blocksValidate(r -> r.blocks(invalidBlocks)); + BlocksValidateResponse response = slack.methods().blocksValidate(r -> r.blocks(invalidBlocks)); assertThat(response.isOk(), is(false)); assertThat(response.getError(), is("invalid_blocks")); List errors = response.getErrors(); 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"); From 11739e15a91edcec6881d38168e7eab66f21980d Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Tue, 25 Aug 2026 15:32:29 -0700 Subject: [PATCH 10/15] docs(methods): note blocks.validate special tier follows from no token Co-Authored-By: Claude --- .../main/java/com/slack/api/methods/MethodsRateLimitTier.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 ec9fe0da7..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 @@ -60,8 +60,8 @@ public enum MethodsRateLimitTier { SpecialTier_chat_getPermalink, /** - * blocks.validate has special rate limiting conditions rather than a standard tier. - * See https://docs.slack.dev/reference/methods/blocks.validate. + * 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; From 5f8be9b0a53f7fa675cdcf37531998623db3d74f Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Tue, 25 Aug 2026 15:43:37 -0700 Subject: [PATCH 11/15] feat(methods): accept typed blocks on blocks.validate Callers building Block Kit with the SDK's own LayoutBlock builders can now pass them directly instead of hand-serializing to JSON. Add a typed List blocks field and rename the raw-JSON field to blocksAsString, mirroring ChatPostMessageRequest (blocksAsString wins if both are set). Tests exercise both the typed and string paths. Verified: test_locally.api.methods.BlocksTest and test_with_remote_apis.methods.blocks_Test pass (JDK 17). Co-Authored-By: Claude --- .../com/slack/api/methods/RequestFormBuilder.java | 9 ++++++++- .../request/blocks/BlocksValidateRequest.java | 12 ++++++++++-- .../java/test_locally/api/methods/BlocksTest.java | 13 +++++++++++-- .../test_with_remote_apis/methods/blocks_Test.java | 9 ++++++--- 4 files changed, 35 insertions(+), 8 deletions(-) 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 908509f10..eb85759bb 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 @@ -1245,7 +1245,14 @@ public static FormBody.Builder toForm(AuthTeamsListRequest req) { public static FormBody.Builder toForm(BlocksValidateRequest req) { FormBody.Builder form = new FormBody.Builder(); - setIfNotNull("blocks", req.getBlocks(), form); + 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."); + } setIfNotNull("message", req.getMessage(), form); setIfNotNull("view", req.getView(), form); return form; 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 index 32d600558..96b84022e 100644 --- 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 @@ -1,17 +1,25 @@ package com.slack.api.methods.request.blocks; import com.slack.api.methods.SlackApiRequest; +import com.slack.api.model.block.LayoutBlock; import lombok.Builder; import lombok.Data; +import java.util.List; + @Data @Builder public class BlocksValidateRequest implements SlackApiRequest { /** - * A JSON-encoded string of an array of blocks to validate. + * An array of blocks to validate. + */ + private List blocks; + + /** + * An array of blocks to validate, as a JSON-encoded string. */ - private String blocks; + private String blocksAsString; /** * A JSON-encoded string of a message payload to validate. 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 index 6256a0bbe..9931ff485 100644 --- 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 @@ -3,11 +3,17 @@ import com.slack.api.Slack; import com.slack.api.SlackConfig; import com.slack.api.methods.response.blocks.BlocksValidateResponse; +import com.slack.api.model.block.LayoutBlock; 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 org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; @@ -18,7 +24,9 @@ public class BlocksTest { SlackConfig config = new SlackConfig(); Slack slack = Slack.getInstance(config); - private static final String BLOCKS = + 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 @@ -43,7 +51,8 @@ public void validate() throws Exception { @Test public void validate_async() throws Exception { - BlocksValidateResponse response = slack.methodsAsync().blocksValidate(r -> r.blocks(BLOCKS)).get(); + 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); 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 index a997c6ab8..0ef242bd4 100644 --- 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 @@ -6,8 +6,11 @@ import org.junit.AfterClass; import org.junit.Test; +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; @@ -25,8 +28,8 @@ public static void tearDown() throws InterruptedException { @Test public void validate_wellFormed() throws Exception { - String validBlocks = "[{\"type\":\"section\",\"text\":{\"type\":\"mrkdwn\",\"text\":\"Hello\"}}]"; - BlocksValidateResponse response = slack.methods().blocksValidate(r -> r.blocks(validBlocks)); + 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)); @@ -35,7 +38,7 @@ public void validate_wellFormed() throws Exception { @Test public void validate_malformed() throws Exception { String invalidBlocks = "[{\"type\":\"section\",\"text\":{\"type\":\"invalid\",\"text\":\"Hello\"}}]"; - BlocksValidateResponse response = slack.methods().blocksValidate(r -> r.blocks(invalidBlocks)); + BlocksValidateResponse response = slack.methods().blocksValidate(r -> r.blocksAsString(invalidBlocks)); assertThat(response.isOk(), is(false)); assertThat(response.getError(), is("invalid_blocks")); List errors = response.getErrors(); From 816a861ba73013883e4e9ef1ee8c7721ccf004da Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Tue, 25 Aug 2026 15:55:06 -0700 Subject: [PATCH 12/15] feat(methods): accept typed message and view on blocks.validate Add typed inputs for the message and view payloads, alongside the existing *AsString escape hatches (string wins if both are set), matching the blocks handling and views.open's view/viewAsString precedent. - view uses the existing View model. - message uses a small nested MessagePayload{blocks, attachments} rather than the response-shaped Message model. The accepted shape was verified against the live blocks.validate endpoint: attachments (with nested blocks) are validated, whereas text is rejected as an additional property, so text is intentionally omitted. Verified: test_locally.api.methods.BlocksTest (typed blocks/message/view + string paths) and test_with_remote_apis.methods.blocks_Test pass (JDK 17). Co-Authored-By: Claude --- .../slack/api/methods/RequestFormBuilder.java | 18 +++++++++++-- .../request/blocks/BlocksValidateRequest.java | 27 ++++++++++++++++--- .../test_locally/api/methods/BlocksTest.java | 23 ++++++++++++++++ 3 files changed, 62 insertions(+), 6 deletions(-) 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 eb85759bb..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 @@ -1253,8 +1253,22 @@ public static FormBody.Builder toForm(BlocksValidateRequest req) { if (req.getBlocksAsString() != null && req.getBlocks() != null) { log.warn("Although you set both blocksAsString and blocks, only blocksAsString was used."); } - setIfNotNull("message", req.getMessage(), form); - setIfNotNull("view", req.getView(), form); + 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; } 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 index 96b84022e..cdff13b41 100644 --- 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 @@ -1,7 +1,9 @@ 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; @@ -22,14 +24,24 @@ public class BlocksValidateRequest implements SlackApiRequest { private String blocksAsString; /** - * A JSON-encoded string of a message payload to validate. + * A message payload to validate. */ - private String message; + private MessagePayload message; /** - * A JSON-encoded string of a view payload to validate. + * A message payload to validate, as a JSON-encoded string. */ - private String view; + private String messageAsString; + + /** + * A view payload to validate. + */ + private View view; + + /** + * A view payload to validate, as a JSON-encoded string. + */ + private String viewAsString; /** * blocks.validate requires no token or scopes. @@ -39,4 +51,11 @@ public class BlocksValidateRequest implements SlackApiRequest { public String getToken() { return null; } + + @Data + @Builder + public static class MessagePayload { + private List blocks; + private List attachments; + } } 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 index 9931ff485..42cbaa058 100644 --- 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 @@ -2,8 +2,10 @@ 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; @@ -14,6 +16,8 @@ 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; @@ -58,4 +62,23 @@ public void validate_async() throws Exception { 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)); + } } From 94bcce3fd20eddc3abaa1f98408f7fa09584aa84 Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Tue, 25 Aug 2026 16:23:48 -0700 Subject: [PATCH 13/15] fix(methods): type blocks.validate constraint expected/got as JsonElement The constraint's expected/got fields are polymorphic: an array of strings for enum constraints (e.g. ["plain_text","mrkdwn"]), but a number for length/count constraints (min_length/max_length/max_items) and absent for all_of. Modeling expected as List caused a Gson JsonSyntaxException ("Expected BEGIN_ARRAY but was NUMBER") whenever a numeric constraint was returned. Use JsonElement so any shape deserializes; callers inspect it via getAsInt()/getAsJsonArray()/etc. Adds a remote test that triggers a max_items constraint (numeric expected/got) to guard the regression. Verified: test_locally.api.methods.BlocksTest (4) and test_with_remote_apis.methods.blocks_Test (3) pass (JDK 17). Co-Authored-By: Claude --- .../blocks/BlocksValidateResponse.java | 9 +++++++-- .../methods/blocks_Test.java | 20 +++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) 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 index ef1ee7bc0..a9c86f433 100644 --- 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 @@ -1,5 +1,6 @@ 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; @@ -30,9 +31,13 @@ public static class Error { @Data public static class Constraint { + // the constraint kind, e.g. "enum", "min_length", "max_length", "max_items", "all_of" private String type; - private List expected; - private String got; + // the accepted value(s); shape varies by constraint type: an array of strings for + // "enum", a number for length/count constraints, or absent (e.g. for "all_of") + private JsonElement expected; + // the offending value that was supplied; likewise a string, number, or absent + private JsonElement got; } } } 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 index 0ef242bd4..fec3f6a96 100644 --- 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 @@ -2,10 +2,13 @@ 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; @@ -49,4 +52,21 @@ public void validate_malformed() throws Exception { 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)); + } } From d5be43ecb31f4c322557d266d2014027e8d23bcb Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Tue, 25 Aug 2026 16:25:34 -0700 Subject: [PATCH 14/15] style(methods): trim blocks.validate Constraint field comments Co-Authored-By: Claude --- .../api/methods/response/blocks/BlocksValidateResponse.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) 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 index a9c86f433..caf4b2d79 100644 --- 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 @@ -29,14 +29,12 @@ public static class Error { 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 { - // the constraint kind, e.g. "enum", "min_length", "max_length", "max_items", "all_of" private String type; - // the accepted value(s); shape varies by constraint type: an array of strings for - // "enum", a number for length/count constraints, or absent (e.g. for "all_of") private JsonElement expected; - // the offending value that was supplied; likewise a string, number, or absent private JsonElement got; } } From 69ea47773bd1131cbf9acb3954280b2a80299a41 Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Tue, 25 Aug 2026 16:47:44 -0700 Subject: [PATCH 15/15] style(methods): match request-class doc convention for blocks.validate Move the docs link to a class-level Javadoc and drop the comment on the getToken() override, matching ApiTestRequest and other unauthenticated request objects. Co-Authored-By: Claude --- .../api/methods/request/blocks/BlocksValidateRequest.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) 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 index cdff13b41..5db253fd9 100644 --- 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 @@ -9,6 +9,9 @@ import java.util.List; +/** + * https://docs.slack.dev/reference/methods/blocks.validate + */ @Data @Builder public class BlocksValidateRequest implements SlackApiRequest { @@ -43,10 +46,6 @@ public class BlocksValidateRequest implements SlackApiRequest { */ private String viewAsString; - /** - * blocks.validate requires no token or scopes. - * See https://docs.slack.dev/reference/methods/blocks.validate. - */ @Override public String getToken() { return null;