diff --git a/bolt/src/main/java/com/slack/api/bolt/App.java b/bolt/src/main/java/com/slack/api/bolt/App.java index 41731562e..92e8201bf 100644 --- a/bolt/src/main/java/com/slack/api/bolt/App.java +++ b/bolt/src/main/java/com/slack/api/bolt/App.java @@ -25,6 +25,7 @@ import com.slack.api.bolt.service.builtin.FileInstallationService; import com.slack.api.bolt.service.builtin.oauth.*; import com.slack.api.bolt.service.builtin.oauth.default_impl.*; +import com.slack.api.bolt.util.ListenerCodeSuggestion; import com.slack.api.methods.MethodsClient; import com.slack.api.methods.SlackApiException; import com.slack.api.methods.response.auth.AuthTestResponse; @@ -993,7 +994,9 @@ protected Response runHandler(Request slackRequest) throws IOException, SlackApi } } } - log.warn("No SlashCommandHandler registered for command: {}", request.getPayload().getCommand()); + command = request.getPayload().getCommand(); + log.warn("No SlashCommandHandler registered for command: {}\n{}", + command, ListenerCodeSuggestion.command(command)); break; } case Event: { @@ -1007,7 +1010,8 @@ protected Response runHandler(Request slackRequest) throws IOException, SlackApi EventsApiPayload payload = buildEventPayload(request); return handler.apply(payload, request.getContext()); } - log.warn("No BoltEventHandler registered for event: {}", request.getEventTypeAndSubtype()); + log.warn("No BoltEventHandler registered for event: {}\n{}", + request.getEventTypeAndSubtype(), ListenerCodeSuggestion.event(request.getEventTypeAndSubtype())); break; } case UrlVerification: { @@ -1029,7 +1033,9 @@ protected Response runHandler(Request slackRequest) throws IOException, SlackApi } } } - log.warn("No AttachmentActionHandler registered for callback_id: {}", request.getPayload().getCallbackId()); + callbackId = request.getPayload().getCallbackId(); + log.warn("No AttachmentActionHandler registered for callback_id: {}\n{}", + callbackId, ListenerCodeSuggestion.attachmentAction(callbackId)); break; } case BlockAction: { @@ -1048,7 +1054,9 @@ protected Response runHandler(Request slackRequest) throws IOException, SlackApi } } } - log.warn("No BlockActionHandler registered for action_id: {}", actions.get(0).getActionId()); + actionId = actions.get(0).getActionId(); + log.warn("No BlockActionHandler registered for action_id: {}\n{}", + actionId, ListenerCodeSuggestion.blockAction(actionId)); } else { for (BlockActionPayload.Action action : request.getPayload().getActions()) { // Returned response values will be ignored @@ -1070,7 +1078,8 @@ protected Response runHandler(Request slackRequest) throws IOException, SlackApi } } } - log.warn("No BlockSuggestionHandler registered for action_id: {}", actionId); + log.warn("No BlockSuggestionHandler registered for action_id: {}\n{}", + actionId, ListenerCodeSuggestion.blockSuggestion(actionId)); break; } case GlobalShortcut: { @@ -1084,7 +1093,8 @@ protected Response runHandler(Request slackRequest) throws IOException, SlackApi } } } - log.warn("No GlobalShortcutHandler registered for callback_id: {}", callbackId); + log.warn("No GlobalShortcutHandler registered for callback_id: {}\n{}", + callbackId, ListenerCodeSuggestion.globalShortcut(callbackId)); break; } case MessageShortcut: { @@ -1098,7 +1108,8 @@ protected Response runHandler(Request slackRequest) throws IOException, SlackApi } } } - log.warn("No MessageShortcutHandler registered for callback_id: {}", callbackId); + log.warn("No MessageShortcutHandler registered for callback_id: {}\n{}", + callbackId, ListenerCodeSuggestion.messageShortcut(callbackId)); break; } case DialogSubmission: { @@ -1112,7 +1123,8 @@ protected Response runHandler(Request slackRequest) throws IOException, SlackApi } } } - log.warn("No DialogSubmissionHandler registered for callback_id: {}", callbackId); + log.warn("No DialogSubmissionHandler registered for callback_id: {}\n{}", + callbackId, ListenerCodeSuggestion.dialogSubmission(callbackId)); break; } case DialogCancellation: { @@ -1126,7 +1138,8 @@ protected Response runHandler(Request slackRequest) throws IOException, SlackApi } } } - log.warn("No DialogCancellationHandler registered for callback_id: {}", callbackId); + log.warn("No DialogCancellationHandler registered for callback_id: {}\n{}", + callbackId, ListenerCodeSuggestion.dialogCancellation(callbackId)); break; } case DialogSuggestion: { @@ -1140,7 +1153,8 @@ protected Response runHandler(Request slackRequest) throws IOException, SlackApi } } } - log.warn("No DialogSuggestionHandler registered for callback_id: {}", callbackId); + log.warn("No DialogSuggestionHandler registered for callback_id: {}\n{}", + callbackId, ListenerCodeSuggestion.dialogSuggestion(callbackId)); break; } case ViewSubmission: { @@ -1154,7 +1168,8 @@ protected Response runHandler(Request slackRequest) throws IOException, SlackApi } } } - log.warn("No ViewSubmissionHandler registered for callback_id: {}", callbackId); + log.warn("No ViewSubmissionHandler registered for callback_id: {}\n{}", + callbackId, ListenerCodeSuggestion.viewSubmission(callbackId)); break; } case ViewClosed: { @@ -1168,7 +1183,8 @@ protected Response runHandler(Request slackRequest) throws IOException, SlackApi } } } - log.warn("No ViewClosedHandler registered for callback_id: {}", callbackId); + log.warn("No ViewClosedHandler registered for callback_id: {}\n{}", + callbackId, ListenerCodeSuggestion.viewClosed(callbackId)); break; } case WorkflowStepEdit: { @@ -1182,7 +1198,8 @@ protected Response runHandler(Request slackRequest) throws IOException, SlackApi } } } - log.warn("No WorkflowStepEditHandler registered for callback_id: {}", callbackId); + log.warn("No WorkflowStepEditHandler registered for callback_id: {}\n{}", + callbackId, ListenerCodeSuggestion.WORKFLOW_STEP); break; } case WorkflowStepSave: { @@ -1196,7 +1213,8 @@ protected Response runHandler(Request slackRequest) throws IOException, SlackApi } } } - log.warn("No WorkflowStepSaveHandler registered for callback_id: {}", callbackId); + log.warn("No WorkflowStepSaveHandler registered for callback_id: {}\n{}", + callbackId, ListenerCodeSuggestion.WORKFLOW_STEP); break; } case WorkflowStepExecute: { @@ -1221,12 +1239,13 @@ protected Response runHandler(Request slackRequest) throws IOException, SlackApi EventsApiPayload payload = buildEventPayload(request); return handler.apply(payload, request.getContext()); } - log.warn("No BoltEventHandler registered for event: {}", request.getEventTypeAndSubtype()); + log.warn("No BoltEventHandler registered for event: {}\n{}", + request.getEventTypeAndSubtype(), ListenerCodeSuggestion.WORKFLOW_STEP); break; } default: } - return Response.json(404, "{\"error\":\"no handler found\"}"); + return appConfig.getUnmatchedRequestHandler().handle(slackRequest); } finally { if (log.isDebugEnabled()) { log.debug("The handler completed (request type: {})", slackRequest.getRequestType()); diff --git a/bolt/src/main/java/com/slack/api/bolt/AppConfig.java b/bolt/src/main/java/com/slack/api/bolt/AppConfig.java index 163456009..e26b58f15 100644 --- a/bolt/src/main/java/com/slack/api/bolt/AppConfig.java +++ b/bolt/src/main/java/com/slack/api/bolt/AppConfig.java @@ -2,6 +2,8 @@ import com.slack.api.Slack; import com.slack.api.SlackConfig; +import com.slack.api.bolt.handler.UnmatchedRequestHandler; +import com.slack.api.bolt.handler.builtin.DefaultUnmatchedRequestHandler; import com.slack.api.bolt.meta.BoltLibraryVersion; import com.slack.api.bolt.service.builtin.oauth.view.OAuthInstallPageRenderer; import com.slack.api.bolt.service.builtin.oauth.view.OAuthRedirectUriPageRenderer; @@ -157,6 +159,13 @@ public void setOAuthCallbackEnabled(boolean enabled) { private transient OAuthRedirectUriPageRenderer oAuthRedirectUriPageRenderer = new OAuthDefaultRedirectUriPageRenderer(); + /** + * Handles unmatched requests (default behavior is simply returning 404 Not Found). + */ + @Builder.Default + private transient UnmatchedRequestHandler unmatchedRequestHandler = + new DefaultUnmatchedRequestHandler(); + // -------------------------- /** diff --git a/bolt/src/main/java/com/slack/api/bolt/handler/UnmatchedRequestHandler.java b/bolt/src/main/java/com/slack/api/bolt/handler/UnmatchedRequestHandler.java new file mode 100644 index 000000000..cbe37fdf5 --- /dev/null +++ b/bolt/src/main/java/com/slack/api/bolt/handler/UnmatchedRequestHandler.java @@ -0,0 +1,11 @@ +package com.slack.api.bolt.handler; + +import com.slack.api.bolt.request.Request; +import com.slack.api.bolt.response.Response; + +@FunctionalInterface +public interface UnmatchedRequestHandler { + + Response handle(Request request); + +} diff --git a/bolt/src/main/java/com/slack/api/bolt/handler/builtin/DefaultUnmatchedRequestHandler.java b/bolt/src/main/java/com/slack/api/bolt/handler/builtin/DefaultUnmatchedRequestHandler.java new file mode 100644 index 000000000..e40a99987 --- /dev/null +++ b/bolt/src/main/java/com/slack/api/bolt/handler/builtin/DefaultUnmatchedRequestHandler.java @@ -0,0 +1,12 @@ +package com.slack.api.bolt.handler.builtin; + +import com.slack.api.bolt.request.Request; +import com.slack.api.bolt.response.Response; +import com.slack.api.bolt.handler.UnmatchedRequestHandler; + +public class DefaultUnmatchedRequestHandler implements UnmatchedRequestHandler { + @Override + public Response handle(Request request) { + return Response.json(404, "{\"error\":\"no handler found\"}"); + } +} diff --git a/bolt/src/main/java/com/slack/api/bolt/util/ListenerCodeSuggestion.java b/bolt/src/main/java/com/slack/api/bolt/util/ListenerCodeSuggestion.java new file mode 100644 index 000000000..9d6696742 --- /dev/null +++ b/bolt/src/main/java/com/slack/api/bolt/util/ListenerCodeSuggestion.java @@ -0,0 +1,142 @@ +package com.slack.api.bolt.util; + +public class ListenerCodeSuggestion { + private ListenerCodeSuggestion() { + } + + public static final String COMMON_PREFIX = "---\n" + + "[Suggestion] You can handle this type of event with the following listener function:\n\n"; + + public static final String WORKFLOW_STEP = COMMON_PREFIX + + "WorkflowStep step = WorkflowStep.builder()\n" + + " .callbackId(\"copy_review\")\n" + + " .edit((req, ctx) -> { return ctx.ack(); })\n" + + " .save((req, ctx) -> { return ctx.ack(); })\n" + + " .execute((req, ctx) -> { return ctx.ack(); })\n" + + " .build();\n" + + "\n" + + "app.step(step);\n"; + + public static final String viewSubmission(String callbackId) { + return COMMON_PREFIX + + "app.viewSubmission(\"" + callbackId + "\", (req, ctx) -> {\n" + + " // Sent inputs: req.getPayload().getView().getState().getValues()\n" + + " return ctx.ack();\n" + + "});\n"; + } + + public static final String viewClosed(String callbackId) { + return COMMON_PREFIX + + "app.viewClosed(\"" + callbackId + "\", (req, ctx) -> {\n" + + " // Do something where\n" + + " return ctx.ack();\n" + + "});\n"; + } + + public static final String dialogSubmission(String callbackId) { + return COMMON_PREFIX + + "app.dialogSubmission(\"" + callbackId + "\", (req, ctx) -> {\n" + + " // Do something where\n" + + " return ctx.ack();\n" + + "});\n"; + } + + public static final String dialogSuggestion(String callbackId) { + return COMMON_PREFIX + + "app.dialogSubmission(\"" + callbackId + "\", (req, ctx) -> {\n" + + " List