3333 AsyncCallableAuthorize ,
3434 AsyncInstallationStoreAuthorize ,
3535)
36- from slack_bolt .error import BoltError
36+ from slack_bolt .error import BoltError , BoltUnhandledRequestError
3737from slack_bolt .logger .messages import (
3838 warning_client_prioritized_and_token_skipped ,
3939 warning_token_skipped ,
5151 debug_return_listener_middleware_response ,
5252 info_default_oauth_settings_loaded ,
5353 error_installation_store_required_for_builtin_listeners ,
54+ warning_unhandled_by_global_middleware ,
5455)
5556from slack_bolt .lazy_listener .asyncio_runner import AsyncioLazyListenerRunner
5657from slack_bolt .listener .async_listener import AsyncListener , AsyncCustomListener
@@ -96,13 +97,16 @@ def __init__(
9697 name : Optional [str ] = None ,
9798 # Set True when you run this app on a FaaS platform
9899 process_before_response : bool = False ,
100+ # Set True if you want to handle an unhandled request as an exception
101+ raise_error_for_unhandled_request : bool = False ,
99102 # Basic Information > Credentials > Signing Secret
100103 signing_secret : Optional [str ] = None ,
101104 # for single-workspace apps
102105 token : Optional [str ] = None ,
103106 client : Optional [AsyncWebClient ] = None ,
104107 # for multi-workspace apps
105108 installation_store : Optional [AsyncInstallationStore ] = None ,
109+ # for either only bot scope usage or v1.0.x compatibility
106110 installation_store_bot_only : Optional [bool ] = None ,
107111 authorize : Optional [Callable [..., Awaitable [AuthorizeResult ]]] = None ,
108112 # for the OAuth flow
@@ -141,6 +145,9 @@ async def message_hello(message, say): # async function
141145 logger: The custom logger that can be used in this app.
142146 name: The application name that will be used in logging. If absent, the source file name will be used.
143147 process_before_response: True if this app runs on Function as a Service. (Default: False)
148+ raise_error_for_unhandled_request: True if you want to raise exceptions for unhandled requests
149+ and use @app.error listeners instead of
150+ the built-in handler, which pints warning logs and returns 404 to Slack (Default: False)
144151 signing_secret: The Signing Secret value used for verifying requests from Slack.
145152 token: The bot/user access token required only for single-workspace app.
146153 client: The singleton `slack_sdk.web.async_client.AsyncWebClient` instance for this app.
@@ -161,6 +168,7 @@ async def message_hello(message, say): # async function
161168 "SLACK_VERIFICATION_TOKEN" , None
162169 )
163170 self ._framework_logger = logger or get_bolt_logger (AsyncApp )
171+ self ._raise_error_for_unhandled_request = raise_error_for_unhandled_request
164172
165173 self ._token : Optional [str ] = token
166174
@@ -464,9 +472,26 @@ async def async_middleware_next():
464472 )
465473 if not middleware_state ["next_called" ]:
466474 if resp is None :
467- return BoltResponse (
475+ # next() method was not called without providing the response to return to Slack
476+ # This should not be an intentional handling in usual use cases.
477+ resp = BoltResponse (
468478 status = 404 , body = {"error" : "no next() calls in middleware" }
469479 )
480+ if self ._raise_error_for_unhandled_request is True :
481+ await self ._async_listener_runner .listener_error_handler .handle (
482+ error = BoltUnhandledRequestError (
483+ request = req ,
484+ current_response = resp ,
485+ last_global_middleware_name = middleware .name ,
486+ ),
487+ request = req ,
488+ response = resp ,
489+ )
490+ return resp
491+ self ._framework_logger .warning (
492+ warning_unhandled_by_global_middleware (middleware .name , req )
493+ )
494+ return resp
470495 return resp
471496
472497 for listener in self ._async_listeners :
@@ -508,8 +533,27 @@ async def async_middleware_next():
508533 if listener_response is not None :
509534 return listener_response
510535
536+ if resp is None :
537+ resp = BoltResponse (status = 404 , body = {"error" : "unhandled request" })
538+ if self ._raise_error_for_unhandled_request is True :
539+ await self ._async_listener_runner .listener_error_handler .handle (
540+ error = BoltUnhandledRequestError (
541+ request = req ,
542+ current_response = resp ,
543+ ),
544+ request = req ,
545+ response = resp ,
546+ )
547+ return resp
548+ return self ._handle_unmatched_requests (req , resp )
549+
550+ def _handle_unmatched_requests (
551+ self , req : AsyncBoltRequest , resp : BoltResponse
552+ ) -> BoltResponse :
553+ # TODO: provide more info like suggestion of listeners
554+ # e.g., You can handle this type of message with @app.event("app_mention")
511555 self ._framework_logger .warning (warning_unhandled_request (req ))
512- return BoltResponse ( status = 404 , body = { "error" : "unhandled request" })
556+ return resp
513557
514558 # -------------------------
515559 # middleware
@@ -623,8 +667,8 @@ def step(
623667 # global error handler
624668
625669 def error (
626- self , func : Callable [..., Awaitable [None ]]
627- ) -> Callable [..., Awaitable [None ]]:
670+ self , func : Callable [..., Awaitable [Optional [ BoltResponse ] ]]
671+ ) -> Callable [..., Awaitable [Optional [ BoltResponse ] ]]:
628672 """Updates the global error handler. This method can be used as either a decorator or a method.
629673
630674 # Use this method as a decorator
@@ -642,6 +686,9 @@ async def custom_error_handler(error, body, logger):
642686 func: The function that is supposed to be executed
643687 when getting an unhandled error in Bolt app.
644688 """
689+ if not inspect .iscoroutinefunction (func ):
690+ name = get_name_for_callable (func )
691+ raise BoltError (error_listener_function_must_be_coro_func (name ))
645692 self ._async_listener_runner .listener_error_handler = (
646693 AsyncCustomListenerErrorHandler (
647694 logger = self ._framework_logger ,
0 commit comments