Repository: getmoto/moto
Pull request #7626 (2024-04-24 21:28:58)

APIGateway: Refactor request handling
Remove the manual request dispatching where possible, and just let the `dispatch`-method handle this.

This PR just adds tests for `(un)tag_resource`, as the functionality already existed

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/getmoto/moto/pull/7626?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) Report
Attention: Patch coverage is `99.29742%` with `3 lines` in your changes are missing coverage. Please review.
> Project coverage is 94.26%. Comparing base [(`9aef694`)](https://app.codecov.io/gh/getmoto/moto/commit/9aef6944da6a6f1b84be99cefb34f1724e6ca58f?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) to head [(`2bc1d1c`)](https://app.codecov.io/gh/getmoto/moto/pull/7626?dropdown=coverage&src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto).
> Report is 202 commits behind head on master.

| [Files](https://app.codecov.io/gh/getmoto/moto/pull/7626?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | Patch % | Lines |
|---|---|---|
| [moto/apigateway/responses.py](https://app.codecov.io/gh/getmoto/moto/pull/7626?src=pr&el=tree&filepath=moto%2Fapigateway%2Fresponses.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto#diff-bW90by9hcGlnYXRld2F5L3Jlc3BvbnNlcy5weQ==) | 99.29% | [3 Missing :warning: ](https://app.codecov.io/gh/getmoto/moto/pull/7626?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #7626      +/-   ##
==========================================
- Coverage   95.88%   94.26%   -1.62%     
==========================================
  Files         843     1072     +229     
  Lines       82578    92188    +9610     
==========================================
+ Hits        79178    86899    +7721     
- Misses       3400     5289    +1889     
```

| [Flag](https://app.codecov.io/gh/getmoto/moto/pull/7626/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | Coverage Δ | |
|---|---|---|
| [servertests](https://app.codecov.io/gh/getmoto/moto/pull/7626/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | `29.45% <24.59%> (-3.07%)` | :arrow_down: |
| [unittests](https://app.codecov.io/gh/getmoto/moto/pull/7626/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | `94.23% <99.29%> (-1.62%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto#carryforward-flags-in-the-pull-request-comment) to find out more.


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/getmoto/moto/pull/7626?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto).


Patch:
diff --git a/moto/apigateway/models.py b/moto/apigateway/models.py
index a3ea772e5..0c2c29b9c 100644
--- a/moto/apigateway/models.py
+++ b/moto/apigateway/models.py
@@ -604,7 +604,9 @@ class Authorizer(BaseModel):
             elif "/type" in op["path"]:
                 self.type = op["value"]
             else:
-                raise Exception(f'Patch operation "{op["op"]}" not implemented')
+                raise BadRequestException(
+                    f'Patch operation "{op["op"]}" not implemented'
+                )
         return self
 
 
diff --git a/moto/apigateway/responses.py b/moto/apigateway/responses.py
index 6e52cb447..a28eaf9b9 100644
--- a/moto/apigateway/responses.py
+++ b/moto/apigateway/responses.py
@@ -20,6 +20,7 @@ class APIGatewayResponse(BaseResponse):
 
     def error(self, type_: str, message: str, status: int = 400) -> TYPE_RESPONSE:
         headers = self.response_headers or {}
+        headers["status"] = f"{status}"
         headers["X-Amzn-Errortype"] = type_
         return (status, headers, json.dumps({"__type": type_, "message": message}))
 
@@ -57,53 +58,49 @@ class APIGatewayResponse(BaseResponse):
                     ).format(endpoint_type=invalid_types[0]),
                 )
 
-    def restapis(  # type: ignore[return]
-        self, request: Any, full_url: str, headers: Dict[str, str]
-    ) -> TYPE_RESPONSE:
-        self.setup_class(request, full_url, headers)
-
-        if self.method == "GET":
-            apis = self.backend.list_apis()
-            return 200, {}, json.dumps({"item": [api.to_dict() for api in apis]})
-        elif self.method == "POST":
-            api_doc = deserialize_body(self.body)
-            if api_doc:
-                fail_on_warnings = self._get_bool_param("failonwarnings") or False
-                rest_api = self.backend.import_rest_api(api_doc, fail_on_warnings)
+    def create_rest_api(self) -> TYPE_RESPONSE:
+        api_doc = deserialize_body(self.body)
+        if api_doc:
+            fail_on_warnings = self._get_bool_param("failonwarnings") or False
+            rest_api = self.backend.import_rest_api(api_doc, fail_on_warnings)
 
-                return 200, {}, json.dumps(rest_api.to_dict())
+            return 200, {}, json.dumps(rest_api.to_dict())
 
-            name = self._get_param("name")
-            description = self._get_param("description")
+        name = self._get_param("name")
+        description = self._get_param("description")
+
+        api_key_source = self._get_param("apiKeySource")
+        endpoint_configuration = self._get_param("endpointConfiguration")
+        tags = self._get_param("tags")
+        policy = self._get_param("policy")
+        minimum_compression_size = self._get_param("minimumCompressionSize")
+        disable_execute_api_endpoint = self._get_param("disableExecuteApiEndpoint")
+
+        # Param validation
+        response = self.__validate_api_key_source(api_key_source)
+        if response is not None:
+            return response
+
+        response = self.__validate_endpoint_configuration(endpoint_configuration)
+        if response is not None:
+            return response
+
+        rest_api = self.backend.create_rest_api(
+            name,
+            description,
+            api_key_source=api_key_source,
+            endpoint_configuration=endpoint_configuration,
+            tags=tags,
+            policy=policy,
+            minimum_compression_size=minimum_compression_size,
+            disable_execute_api_endpoint=disable_execute_api_endpoint,
+        )
 
-            api_key_source = self._get_param("apiKeySource")
-            endpoint_configuration = self._get_param("endpointConfiguration")
-            tags = self._get_param("tags")
-            policy = self._get_param("policy")
-            minimum_compression_size = self._get_param("minimumCompressionSize")
-            disable_execute_api_endpoint = self._get_param("disableExecuteApiEndpoint")
-
-            # Param validation
-            response = self.__validate_api_key_source(api_key_source)
-            if response is not None:
-                return response
-
-            response = self.__validate_endpoint_configuration(endpoint_configuration)
-            if response is not None:
-                return response
-
-            rest_api = self.backend.create_rest_api(
-                name,
-                description,
-                api_key_source=api_key_source,
-                endpoint_configuration=endpoint_configuration,
-                tags=tags,
-                policy=policy,
-                minimum_compression_size=minimum_compression_size,
-                disable_execute_api_endpoint=disable_execute_api_endpoint,
-            )
+        return 200, {}, json.dumps(rest_api.to_dict())
 
-            return 200, {}, json.dumps(rest_api.to_dict())
+    def get_rest_apis(self) -> str:
+        apis = self.backend.list_apis()
+        return json.dumps({"item": [api.to_dict() for api in apis]})
 
     def __validte_rest_patch_operations(  # type: ignore[return]
         self, patch_operations: List[Dict[str, str]]
@@ -114,316 +111,315 @@ class APIGatewayResponse(BaseResponse):
                 value = op["value"]
                 return self.__validate_api_key_source(value)
 
-    def restapis_individual(
-        self, request: Any, full_url: str, headers: Dict[str, str]
-    ) -> TYPE_RESPONSE:
-        self.setup_class(request, full_url, headers)
+    def delete_rest_api(self) -> TYPE_RESPONSE:
         function_id = self.path.replace("/restapis/", "", 1).split("/")[0]
+        rest_api = self.backend.delete_rest_api(function_id)
+        return 200, {}, json.dumps(rest_api.to_dict())
 
-        if self.method == "GET":
-            rest_api = self.backend.get_rest_api(function_id)
-        elif self.method == "DELETE":
-            rest_api = self.backend.delete_rest_api(function_id)
-        elif self.method == "PUT":
-            mode = self._get_param("mode", "merge")
-            fail_on_warnings = self._get_bool_param("failonwarnings") or False
+    def get_rest_api(self) -> TYPE_RESPONSE:
+        function_id = self.path.replace("/restapis/", "", 1).split("/")[0]
+        rest_api = self.backend.get_rest_api(function_id)
+        return 200, {}, json.dumps(rest_api.to_dict())
 
-            api_doc = deserialize_body(self.body)
+    def put_rest_api(self) -> TYPE_RESPONSE:
+        function_id = self.path.replace("/restapis/", "", 1).split("/")[0]
+        mode = self._get_param("mode", "merge")
+        fail_on_warnings = self._get_bool_param("failonwarnings") or False
 
-            rest_api = self.backend.put_rest_api(
-                function_id, api_doc, mode=mode, fail_on_warnings=fail_on_warnings
-            )
-        elif self.method == "PATCH":
-            patch_operations = self._get_param("patchOperations")
-            response = self.__validte_rest_patch_operations(patch_operations)
-            if response is not None:
-                return response
-            rest_api = self.backend.update_rest_api(function_id, patch_operations)
+        api_doc = deserialize_body(self.body)
+        rest_api = self.backend.put_rest_api(
+            function_id, api_doc, mode=mode, fail_on_warnings=fail_on_warnings
+        )
+        return 200, {}, json.dumps(rest_api.to_dict())
 
+    def update_rest_api(self) -> TYPE_RESPONSE:
+        function_id = self.path.replace("/restapis/", "", 1).split("/")[0]
+        patch_operations = self._get_param("patchOperations")
+        response = self.__validte_rest_patch_operations(patch_operations)
+        if response is not None:
+            return response
+
+        rest_api = self.backend.update_rest_api(function_id, patch_operations)
         return 200, {}, json.dumps(rest_api.to_dict())
 
-    def resources(  # type: ignore[return]
-        self, request: Any, full_url: str, headers: Dict[str, str]
-    ) -> TYPE_RESPONSE:
-        self.setup_class(request, full_url, headers)
+    def get_resources(self) -> str:
         function_id = self.path.replace("/restapis/", "", 1).split("/")[0]
+        resources = self.backend.get_resources(function_id)
+        return json.dumps({"item": [resource.to_dict() for resource in resources]})
 
-        if self.method == "GET":
-            resources = self.backend.get_resources(function_id)
-            return (
-                200,
-                {},
-                json.dumps({"item": [resource.to_dict() for resource in resources]}),
-            )
+    def create_resource(self) -> TYPE_RESPONSE:
+        function_id = self.path.replace("/restapis/", "", 1).split("/")[0]
+        resource_id = self.path.split("/")[-1]
+        path_part = self._get_param("pathPart")
+        resource = self.backend.create_resource(function_id, resource_id, path_part)
+        return 201, {"status": 201}, json.dumps(resource.to_dict())
 
-    def gateway_response(  # type: ignore[return]
-        self, request: Any, full_url: str, headers: Dict[str, str]
-    ) -> TYPE_RESPONSE:
-        self.setup_class(request, full_url, headers)
-        if request.method == "PUT":
-            return self.put_gateway_response()
-        elif request.method == "GET":
-            return self.get_gateway_response()
-        elif request.method == "DELETE":
-            return self.delete_gateway_response()
-
-    def gateway_responses(  # type: ignore[return]
-        self, request: Any, full_url: str, headers: Dict[str, str]
-    ) -> TYPE_RESPONSE:
-        self.setup_class(request, full_url, headers)
-        if request.method == "GET":
-            return self.get_gateway_responses()
+    def delete_resource(self) -> TYPE_RESPONSE:
+        function_id = self.path.replace("/restapis/", "", 1).split("/")[0]
+        resource_id = self.path.split("/")[-1]
+        resource = self.backend.delete_resource(function_id, resource_id)
+        return 202, {"status": 202}, json.dumps(resource.to_dict())
 
-    def resource_individual(  # type: ignore[return]
-        self, request: Any, full_url: str, headers: Dict[str, str]
-    ) -> TYPE_RESPONSE:
-        self.setup_class(request, full_url, headers)
+    def get_resource(self) -> str:
         function_id = self.path.replace("/restapis/", "", 1).split("/")[0]
         resource_id = self.path.split("/")[-1]
+        resource = self.backend.get_resource(function_id, resource_id)
+        return json.dumps(resource.to_dict())
 
-        if self.method == "GET":
-            resource = self.backend.get_resource(function_id, resource_id)
-            return 200, {}, json.dumps(resource.to_dict())
-        elif self.method == "POST":
-            path_part = self._get_param("pathPart")
-            resource = self.backend.create_resource(function_id, resource_id, path_part)
-            return 201, {}, json.dumps(resource.to_dict())
-        elif self.method == "DELETE":
-            resource = self.backend.delete_resource(function_id, resource_id)
-            return 202, {}, json.dumps(resource.to_dict())
-
-    def resource_methods(
-        self, request: Any, full_url: str, headers: Dict[str, str]
-    ) -> TYPE_RESPONSE:
-        self.setup_class(request, full_url, headers)
+    def delete_method(self) -> TYPE_RESPONSE:
         url_path_parts = self.path.split("/")
         function_id = url_path_parts[2]
         resource_id = url_path_parts[4]
         method_type = url_path_parts[6]
+        self.backend.delete_method(function_id, resource_id, method_type)
+        return 204, {"status": 204}, ""
 
-        if self.method == "GET":
-            method = self.backend.get_method(function_id, resource_id, method_type)
-            return 200, {}, json.dumps(method.to_json())
-        elif self.method == "PUT":
-            authorization_type = self._get_param("authorizationType")
-            api_key_required = self._get_param("apiKeyRequired")
-            request_models = self._get_param("requestModels")
-            operation_name = self._get_param("operationName")
-            authorizer_id = self._get_param("authorizerId")
-            authorization_scopes = self._get_param("authorizationScopes")
-            request_validator_id = self._get_param("requestValidatorId")
-            request_parameters = self._get_param("requestParameters")
-            method = self.backend.put_method(
-                function_id,
-                resource_id,
-                method_type,
-                authorization_type,
-                api_key_required,
-                request_models=request_models,
-                request_parameters=request_parameters,
-                operation_name=operation_name,
-                authorizer_id=authorizer_id,
-                authorization_scopes=authorization_scopes,
-                request_validator_id=request_validator_id,
-            )
-            return 201, {}, json.dumps(method.to_json())
+    def get_method(self) -> str:
+        url_path_parts = self.path.split("/")
+        function_id = url_path_parts[2]
+        resource_id = url_path_parts[4]
+        method_type = url_path_parts[6]
+        method = self.backend.get_method(function_id, resource_id, method_type)
+        return json.dumps(method.to_json())
 
-        elif self.method == "DELETE":
-            self.backend.delete_method(function_id, resource_id, method_type)
-            return 204, {}, ""
+    def put_method(self) -> TYPE_RESPONSE:
+        url_path_parts = self.path.split("/")
+        function_id = url_path_parts[2]
+        resource_id = url_path_parts[4]
+        method_type = url_path_parts[6]
+        authorization_type = self._get_param("authorizationType")
+        api_key_required = self._get_param("apiKeyRequired")
+        request_models = self._get_param("requestModels")
+        operation_name = self._get_param("operationName")
+        authorizer_id = self._get_param("authorizerId")
+        authorization_scopes = self._get_param("authorizationScopes")
+        request_validator_id = self._get_param("requestValidatorId")
+        request_parameters = self._get_param("requestParameters")
+        method = self.backend.put_method(
+            function_id,
+            resource_id,
+            method_type,
+            authorization_type,
+            api_key_required,
+            request_models=request_models,
+            request_parameters=request_parameters,
+            operation_name=operation_name,
+            authorizer_id=authorizer_id,
+            authorization_scopes=authorization_scopes,
+            request_validator_id=request_validator_id,
+        )
+        return 201, {"status": 201}, json.dumps(method.to_json())
 
-        return 200, {}, ""
+    def delete_method_response(self) -> TYPE_RESPONSE:
+        url_path_parts = self.path.split("/")
+        function_id = url_path_parts[2]
+        resource_id = url_path_parts[4]
+        method_type = url_path_parts[6]
+        response_code = url_path_parts[8]
+        method_response = self.backend.delete_method_response(
+            function_id, resource_id, method_type, response_code
+        )
+        return 204, {"status": 204}, json.dumps(method_response.to_json())  # type: ignore[union-attr]
 
-    def resource_method_responses(
-        self, request: Any, full_url: str, headers: Dict[str, str]
-    ) -> TYPE_RESPONSE:
-        self.setup_class(request, full_url, headers)
+    def get_method_response(self) -> str:
         url_path_parts = self.path.split("/")
         function_id = url_path_parts[2]
         resource_id = url_path_parts[4]
         method_type = url_path_parts[6]
         response_code = url_path_parts[8]
 
-        if self.method == "GET":
-            method_response = self.backend.get_method_response(
-                function_id, resource_id, method_type, response_code
-            )
-            return 200, {}, json.dumps(method_response.to_json())  # type: ignore[union-attr]
-        elif self.method == "PUT":
-            response_models = self._get_param("responseModels")
-            response_parameters = self._get_param("responseParameters")
-            method_response = self.backend.put_method_response(
-                function_id,
-                resource_id,
-                method_type,
-                response_code,
-                response_models,
-                response_parameters,
-            )
-            return 201, {}, json.dumps(method_response.to_json())
-        elif self.method == "DELETE":
-            method_response = self.backend.delete_method_response(
-                function_id, resource_id, method_type, response_code
-            )
-            return 204, {}, json.dumps(method_response.to_json())  # type: ignore[union-attr]
-        raise Exception(f'Unexpected HTTP method "{self.method}"')
+        method_response = self.backend.get_method_response(
+            function_id, resource_id, method_type, response_code
+        )
+        return json.dumps(method_response.to_json())  # type: ignore[union-attr]
 
-    def restapis_authorizers(  # type: ignore[return]
-        self, request: Any, full_url: str, headers: Dict[str, str]
-    ) -> TYPE_RESPONSE:
-        self.setup_class(request, full_url, headers)
+    def put_method_response(self) -> TYPE_RESPONSE:
         url_path_parts = self.path.split("/")
-        restapi_id = url_path_parts[2]
+        function_id = url_path_parts[2]
+        resource_id = url_path_parts[4]
+        method_type = url_path_parts[6]
+        response_code = url_path_parts[8]
+        response_models = self._get_param("responseModels")
+        response_parameters = self._get_param("responseParameters")
+        method_response = self.backend.put_method_response(
+            function_id,
+            resource_id,
+            method_type,
+            response_code,
+            response_models,
+            response_parameters,
+        )
+        return 201, {"status": 201}, json.dumps(method_response.to_json())
+
+    def create_authorizer(self) -> TYPE_RESPONSE:
+        restapi_id = self.path.split("/")[2]
+        name = self._get_param("name")
+        authorizer_type = self._get_param("type")
+
+        provider_arns = self._get_param("providerARNs")
+        auth_type = self._get_param("authType")
+        authorizer_uri = self._get_param("authorizerUri")
+        authorizer_credentials = self._get_param("authorizerCredentials")
+        identity_source = self._get_param("identitySource")
+        identiy_validation_expression = self._get_param("identityValidationExpression")
+        authorizer_result_ttl = self._get_param(
+            "authorizerResultTtlInSeconds", if_none=300
+        )
 
-        if self.method == "POST":
-            name = self._get_param("name")
-            authorizer_type = self._get_param("type")
-
-            provider_arns = self._get_param("providerARNs")
-            auth_type = self._get_param("authType")
-            authorizer_uri = self._get_param("authorizerUri")
-            authorizer_credentials = self._get_param("authorizerCredentials")
-            identity_source = self._get_param("identitySource")
-            identiy_validation_expression = self._get_param(
-                "identityValidationExpression"
-            )
-            authorizer_result_ttl = self._get_param(
-                "authorizerResultTtlInSeconds", if_none=300
-            )
+        # Param validation
+        if authorizer_type and authorizer_type not in AUTHORIZER_TYPES:
+            return self.error(
+                "ValidationException",
+                (
+                    "1 validation error detected: "
+                    "Value '{authorizer_type}' at 'createAuthorizerInput.type' failed "
+                    "to satisfy constraint: Member must satisfy enum value set: "
+                    "[TOKEN, REQUEST, COGNITO_USER_POOLS]"
+                ).format(authorizer_type=authorizer_type),
+            )
+
+        authorizer_response = self.backend.create_authorizer(
+            restapi_id=restapi_id,
+            name=name,
+            authorizer_type=authorizer_type,
+            provider_arns=provider_arns,
+            auth_type=auth_type,
+            authorizer_uri=authorizer_uri,
+            authorizer_credentials=authorizer_credentials,
+            identity_source=identity_source,
+            identiy_validation_expression=identiy_validation_expression,
+            authorizer_result_ttl=authorizer_result_ttl,
+        )
+        return 201, {"status": 201}, json.dumps(authorizer_response.to_json())
 
-            # Param validation
-            if authorizer_type and authorizer_type not in AUTHORIZER_TYPES:
-                return self.error(
-                    "ValidationException",
-                    (
-                        "1 validation error detected: "
-                        "Value '{authorizer_type}' at 'createAuthorizerInput.type' failed "
-                        "to satisfy constraint: Member must satisfy enum value set: "
-                        "[TOKEN, REQUEST, COGNITO_USER_POOLS]"
-                    ).format(authorizer_type=authorizer_type),
-                )
+    def delete_authorizer(self) -> TYPE_RESPONSE:
+        url_path_parts = self.path.split("/")
+        restapi_id = url_path_parts[2]
+        authorizer_id = url_path_parts[4]
+        self.backend.delete_authorizer(restapi_id, authorizer_id)
+        return 202, {"status": 202}, "{}"
 
-            authorizer_response = self.backend.create_authorizer(
-                restapi_id=restapi_id,
-                name=name,
-                authorizer_type=authorizer_type,
-                provider_arns=provider_arns,
-                auth_type=auth_type,
-                authorizer_uri=authorizer_uri,
-                authorizer_credentials=authorizer_credentials,
-                identity_source=identity_source,
-                identiy_validation_expression=identiy_validation_expression,
-                authorizer_result_ttl=authorizer_result_ttl,
-            )
-            return 201, {}, json.dumps(authorizer_response.to_json())
-        elif self.method == "GET":
-            authorizers = self.backend.get_authorizers(restapi_id)
-            return 200, {}, json.dumps({"item": [a.to_json() for a in authorizers]})
+    def get_authorizer(self) -> str:
+        url_path_parts = self.path.split("/")
+        restapi_id = url_path_parts[2]
+        authorizer_id = url_path_parts[4]
+        authorizer_response = self.backend.get_authorizer(restapi_id, authorizer_id)
+        return json.dumps(authorizer_response.to_json())
 
-    def request_validators(  # type: ignore[return]
-        self, request: Any, full_url: str, headers: Dict[str, str]
-    ) -> TYPE_RESPONSE:
-        self.setup_class(request, full_url, headers)
+    def get_authorizers(self) -> str:
+        restapi_id = self.path.split("/")[2]
+        authorizers = self.backend.get_authorizers(restapi_id)
+        return json.dumps({"item": [a.to_json() for a in authorizers]})
+
+    def update_authorizer(self) -> str:
         url_path_parts = self.path.split("/")
         restapi_id = url_path_parts[2]
+        authorizer_id = url_path_parts[4]
+        patch_operations = self._get_param("patchOperations")
+        authorizer_response = self.backend.update_authorizer(
+            restapi_id, authorizer_id, patch_operations
+        )
+        return json.dumps(authorizer_response.to_json())
+
+    def create_request_validator(self) -> TYPE_RESPONSE:
+        restapi_id = self.path.split("/")[2]
+        name = self._get_param("name")
+        body = self._get_bool_param("validateRequestBody")
+        params = self._get_bool_param("validateRequestParameters")
+        validator = self.backend.create_request_validator(
+            restapi_id, name, body, params
+        )
+        return 201, {"status": 201}, json.dumps(validator.to_dict())
 
-        if self.method == "GET":
-            validators = self.backend.get_request_validators(restapi_id)
-            res = json.dumps(
-                {"item": [validator.to_dict() for validator in validators]}
-            )
-            return 200, {}, res
-        if self.method == "POST":
-            name = self._get_param("name")
-            body = self._get_bool_param("validateRequestBody")
-            params = self._get_bool_param("validateRequestParameters")
-            validator = self.backend.create_request_validator(
-                restapi_id, name, body, params
-            )
-            return 201, {}, json.dumps(validator.to_dict())
+    def delete_request_validator(self) -> TYPE_RESPONSE:
+        url_path_parts = self.path.split("/")
+        restapi_id = url_path_parts[2]
+        validator_id = url_path_parts[4]
+        self.backend.delete_request_validator(restapi_id, validator_id)
+        return 202, {"status": 202}, ""
 
-    def request_validator_individual(  # type: ignore[return]
-        self, request: Any, full_url: str, headers: Dict[str, str]
-    ) -> TYPE_RESPONSE:
-        self.setup_class(request, full_url, headers)
+    def get_request_validator(self) -> str:
         url_path_parts = self.path.split("/")
         restapi_id = url_path_parts[2]
         validator_id = url_path_parts[4]
+        validator = self.backend.get_request_validator(restapi_id, validator_id)
+        return json.dumps(validator.to_dict())
 
-        if self.method == "GET":
-            validator = self.backend.get_request_validator(restapi_id, validator_id)
-            return 200, {}, json.dumps(validator.to_dict())
-        if self.method == "DELETE":
-            self.backend.delete_request_validator(restapi_id, validator_id)
-            return 202, {}, ""
-        if self.method == "PATCH":
-            patch_operations = self._get_param("patchOperations")
-            validator = self.backend.update_request_validator(
-                restapi_id, validator_id, patch_operations
-            )
-            return 200, {}, json.dumps(validator.to_dict())
+    def get_request_validators(self) -> str:
+        restapi_id = self.path.split("/")[2]
+        validators = self.backend.get_request_validators(restapi_id)
+        return json.dumps({"item": [validator.to_dict() for validator in validators]})
 
-    def authorizers(  # type: ignore[return]
-        self, request: Any, full_url: str, headers: Dict[str, str]
-    ) -> TYPE_RESPONSE:
-        self.setup_class(request, full_url, headers)
+    def update_request_validator(self) -> str:
         url_path_parts = self.path.split("/")
         restapi_id = url_path_parts[2]
-        authorizer_id = url_path_parts[4]
+        validator_id = url_path_parts[4]
+        patch_ops = self._get_param("patchOperations")
+        validator = self.backend.update_request_validator(
+            restapi_id, validator_id, patch_ops
+        )
+        return json.dumps(validator.to_dict())
+
+    def create_stage(self) -> TYPE_RESPONSE:
+        function_id = self.path.split("/")[2]
+        stage_name = self._get_param("stageName")
+        deployment_id = self._get_param("deploymentId")
+        stage_variables = self._get_param("variables", if_none={})
+        description = self._get_param("description", if_none="")
+        cacheClusterEnabled = self._get_param("cacheClusterEnabled", if_none=False)
+        cacheClusterSize = self._get_param("cacheClusterSize")
+        tags = self._get_param("tags")
+        tracing_enabled = self._get_param("tracingEnabled")
+
+        stage_response = self.backend.create_stage(
+            function_id,
+            stage_name,
+            deployment_id,
+            variables=stage_variables,
+            description=description,
+            cacheClusterEnabled=cacheClusterEnabled,
+            cacheClusterSize=cacheClusterSize,
+            tags=tags,
+            tracing_enabled=tracing_enabled,
+        )
+        return 201, {"status": 201}, json.dumps(stage_response.to_json())
 
-        if self.method == "GET":
-            authorizer_response = self.backend.get_authorizer(restapi_id, authorizer_id)
-            return 200, {}, json.dumps(authorizer_response.to_json())
-        elif self.method == "PATCH":
-            patch_operations = self._get_param("patchOperations")
-            authorizer_response = self.backend.update_authorizer(
-                restapi_id, authorizer_id, patch_operations
-            )
-            return 200, {}, json.dumps(authorizer_response.to_json())
-        elif self.method == "DELETE":
-            self.backend.delete_authorizer(restapi_id, authorizer_id)
-            return 202, {}, "{}"
+    def delete_stage(self) -> TYPE_RESPONSE:
+        url_path_parts = self.path.split("/")
+        function_id = url_path_parts[2]
+        stage_name = url_path_parts[4]
+        self.backend.delete_stage(function_id, stage_name)
+        return 202, {"status": 202}, "{}"
 
-    def restapis_stages(  # type: ignore[return]
-        self, request: Any, full_url: str, headers: Dict[str, str]
-    ) -> TYPE_RESPONSE:
-        self.setup_class(request, full_url, headers)
+    def get_stage(self) -> str:
         url_path_parts = self.path.split("/")
         function_id = url_path_parts[2]
+        stage_name = url_path_parts[4]
+        stage_response = self.backend.get_stage(function_id, stage_name)
+        return json.dumps(stage_response.to_json())
 
-        if self.method == "POST":
-            stage_name = self._get_param("stageName")
-            deployment_id = self._get_param("deploymentId")
-            stage_variables = self._get_param("variables", if_none={})
-            description = self._get_param("description", if_none="")
-            cacheClusterEnabled = self._get_param("cacheClusterEnabled", if_none=False)
-            cacheClusterSize = self._get_param("cacheClusterSize")
-            tags = self._get_param("tags")
-            tracing_enabled = self._get_param("tracingEnabled")
-
-            stage_response = self.backend.create_stage(
-                function_id,
-                stage_name,
-                deployment_id,
-                variables=stage_variables,
-                description=description,
-                cacheClusterEnabled=cacheClusterEnabled,
-                cacheClusterSize=cacheClusterSize,
-                tags=tags,
-                tracing_enabled=tracing_enabled,
-            )
-            return 201, {}, json.dumps(stage_response.to_json())
-        elif self.method == "GET":
-            stages = self.backend.get_stages(function_id)
-            return 200, {}, json.dumps({"item": [s.to_json() for s in stages]})
+    def get_stages(self) -> str:
+        function_id = self.path.split("/")[2]
+        stages = self.backend.get_stages(function_id)
+        return json.dumps({"item": [s.to_json() for s in stages]})
+
+    def update_stage(self) -> str:
+        url_path_parts = self.path.split("/")
+        function_id = url_path_parts[2]
+        stage_name = url_path_parts[4]
+        patch_operations = self._get_param("patchOperations")
+        stage_response = self.backend.update_stage(
+            function_id, stage_name, patch_operations
+        )
+        return json.dumps(stage_response.to_json())
 
     def restapis_stages_tags(  # type: ignore[return]
         self, request: Any, full_url: str, headers: Dict[str, str]
     ) -> TYPE_RESPONSE:
         self.setup_class(request, full_url, headers)
-        url_path_parts = self.path.split("/")
-        function_id = url_path_parts[4]
-        stage_name = url_path_parts[6]
+        url_path_parts = unquote(self.path.split("/tags/")[1]).split("/")
+        function_id = url_path_parts[-3]
+        stage_name = url_path_parts[-1]
         if self.method == "PUT":
             tags = self._get_param("tags")
             if tags:
@@ -437,31 +433,7 @@ class APIGatewayResponse(BaseResponse):
                     stage.tags.pop(tag, None)  # type: ignore[union-attr]
             return 200, {}, json.dumps({"item": ""})
 
-    def stages(  # type: ignore[return]
-        self, request: Any, full_url: str, headers: Dict[str, str]
-    ) -> TYPE_RESPONSE:
-        self.setup_class(request, full_url, headers)
-        url_path_parts = self.path.split("/")
-        function_id = url_path_parts[2]
-        stage_name = url_path_parts[4]
-
-        if self.method == "GET":
-            stage_response = self.backend.get_stage(function_id, stage_name)
-            return 200, {}, json.dumps(stage_response.to_json())
-        elif self.method == "PATCH":
-            patch_operations = self._get_param("patchOperations")
-            stage_response = self.backend.update_stage(
-                function_id, stage_name, patch_operations
-            )
-            return 200, {}, json.dumps(stage_response.to_json())
-        elif self.method == "DELETE":
-            self.backend.delete_stage(function_id, stage_name)
-            return 202, {}, "{}"
-
-    def export(
-        self, request: Any, full_url: str, headers: Dict[str, str]
-    ) -> TYPE_RESPONSE:
-        self.setup_class(request, full_url, headers)
+    def get_export(self) -> TYPE_RESPONSE:
         url_path_parts = self.path.split("/")
         rest_api_id = url_path_parts[-5]
         export_type = url_path_parts[-1]
@@ -476,427 +448,360 @@ class APIGatewayResponse(BaseResponse):
         }
         return 200, headers, json.dumps(body).encode("utf-8")
 
-    def integrations(  # type: ignore[return]
-        self, request: Any, full_url: str, headers: Dict[str, str]
-    ) -> TYPE_RESPONSE:
-        self.setup_class(request, full_url, headers)
+    def delete_integration(self) -> TYPE_RESPONSE:
         url_path_parts = self.path.split("/")
         function_id = url_path_parts[2]
         resource_id = url_path_parts[4]
         method_type = url_path_parts[6]
+        integration_response = self.backend.delete_integration(
+            function_id, resource_id, method_type
+        )
+        return 204, {"status": 204}, json.dumps(integration_response.to_json())
 
-        if self.method == "GET":
-            integration_response = self.backend.get_integration(
-                function_id, resource_id, method_type
-            )
-            if integration_response:
-                return 200, {}, json.dumps(integration_response.to_json())
-            return 200, {}, "{}"
-        elif self.method == "PUT":
-            integration_type = self._get_param("type")
-            uri = self._get_param("uri")
-            credentials = self._get_param("credentials")
-            request_templates = self._get_param("requestTemplates")
-            passthrough_behavior = self._get_param("passthroughBehavior")
-            tls_config = self._get_param("tlsConfig")
-            cache_namespace = self._get_param("cacheNamespace")
-            timeout_in_millis = self._get_param("timeoutInMillis")
-            request_parameters = self._get_param("requestParameters")
-            content_handling = self._get_param("contentHandling")
-            connection_type = self._get_param("connectionType")
-            self.backend.get_method(function_id, resource_id, method_type)
-
-            integration_http_method = self._get_param(
-                "httpMethod"
-            )  # default removed because it's a required parameter
-
-            integration_response = self.backend.put_integration(
-                function_id,
-                resource_id,
-                method_type,
-                integration_type,
-                uri,
-                credentials=credentials,
-                integration_method=integration_http_method,
-                request_templates=request_templates,
-                passthrough_behavior=passthrough_behavior,
-                tls_config=tls_config,
-                cache_namespace=cache_namespace,
-                timeout_in_millis=timeout_in_millis,
-                request_parameters=request_parameters,
-                content_handling=content_handling,
-                connection_type=connection_type,
-            )
-            return 201, {}, json.dumps(integration_response.to_json())
-        elif self.method == "DELETE":
-            integration_response = self.backend.delete_integration(
-                function_id, resource_id, method_type
-            )
-            return 204, {}, json.dumps(integration_response.to_json())
+    def get_integration(self) -> str:
+        url_path_parts = self.path.split("/")
+        function_id = url_path_parts[2]
+        resource_id = url_path_parts[4]
+        method_type = url_path_parts[6]
+        integration_response = self.backend.get_integration(
+            function_id, resource_id, method_type
+        )
+        if integration_response:
+            return json.dumps(integration_response.to_json())
+        return "{}"
 
-    def integration_responses(  # type: ignore[return]
-        self, request: Any, full_url: str, headers: Dict[str, str]
-    ) -> TYPE_RESPONSE:
-        self.setup_class(request, full_url, headers)
+    def put_integration(self) -> TYPE_RESPONSE:
+        url_path_parts = self.path.split("/")
+        function_id = url_path_parts[2]
+        resource_id = url_path_parts[4]
+        method_type = url_path_parts[6]
+        integration_type = self._get_param("type")
+        uri = self._get_param("uri")
+        credentials = self._get_param("credentials")
+        request_templates = self._get_param("requestTemplates")
+        passthrough_behavior = self._get_param("passthroughBehavior")
+        tls_config = self._get_param("tlsConfig")
+        cache_namespace = self._get_param("cacheNamespace")
+        timeout_in_millis = self._get_param("timeoutInMillis")
+        request_parameters = self._get_param("requestParameters")
+        content_handling = self._get_param("contentHandling")
+        connection_type = self._get_param("connectionType")
+        self.backend.get_method(function_id, resource_id, method_type)
+
+        integration_http_method = self._get_param("httpMethod")
+
+        integration_response = self.backend.put_integration(
+            function_id,
+            resource_id,
+            method_type,
+            integration_type,
+            uri,
+            credentials=credentials,
+            integration_method=integration_http_method,
+            request_templates=request_templates,
+            passthrough_behavior=passthrough_behavior,
+            tls_config=tls_config,
+            cache_namespace=cache_namespace,
+            timeout_in_millis=timeout_in_millis,
+            request_parameters=request_parameters,
+            content_handling=content_handling,
+            connection_type=connection_type,
+        )
+        return 201, {"status": 201}, json.dumps(integration_response.to_json())
+
+    def delete_integration_response(self) -> TYPE_RESPONSE:
         url_path_parts = self.path.split("/")
         function_id = url_path_parts[2]
         resource_id = url_path_parts[4]
         method_type = url_path_parts[6]
         status_code = url_path_parts[9]
+        integration_response = self.backend.delete_integration_response(
+            function_id, resource_id, method_type, status_code
+        )
+        return 204, {"status": 204}, json.dumps(integration_response.to_json())
 
-        if self.method == "GET":
-            integration_response = self.backend.get_integration_response(
-                function_id, resource_id, method_type, status_code
-            )
-            return 200, {}, json.dumps(integration_response.to_json())
-        elif self.method == "PUT":
-            if not self.body:
-                raise InvalidRequestInput()
-
-            selection_pattern = self._get_param("selectionPattern")
-            response_templates = self._get_param("responseTemplates")
-            response_parameters = self._get_param("responseParameters")
-            content_handling = self._get_param("contentHandling")
-            integration_response = self.backend.put_integration_response(
-                function_id,
-                resource_id,
-                method_type,
-                status_code,
-                selection_pattern,
-                response_templates,
-                response_parameters,
-                content_handling,
-            )
-            return 201, {}, json.dumps(integration_response.to_json())
-        elif self.method == "DELETE":
-            integration_response = self.backend.delete_integration_response(
-                function_id, resource_id, method_type, status_code
-            )
-            return 204, {}, json.dumps(integration_response.to_json())
+    def get_integration_response(self) -> str:
+        url_path_parts = self.path.split("/")
+        function_id = url_path_parts[2]
+        resource_id = url_path_parts[4]
+        method_type = url_path_parts[6]
+        status_code = url_path_parts[9]
+        integration_response = self.backend.get_integration_response(
+            function_id, resource_id, method_type, status_code
+        )
+        return json.dumps(integration_response.to_json())
 
-    def deployments(  # type: ignore[return]
-        self, request: Any, full_url: str, headers: Dict[str, str]
-    ) -> TYPE_RESPONSE:
-        self.setup_class(request, full_url, headers)
-        function_id = self.path.replace("/restapis/", "", 1).split("/")[0]
+    def put_integration_response(self) -> TYPE_RESPONSE:
+        url_path_parts = self.path.split("/")
+        function_id = url_path_parts[2]
+        resource_id = url_path_parts[4]
+        method_type = url_path_parts[6]
+        status_code = url_path_parts[9]
+        if not self.body:
+            raise InvalidRequestInput()
+
+        selection_pattern = self._get_param("selectionPattern")
+        response_templates = self._get_param("responseTemplates")
+        response_parameters = self._get_param("responseParameters")
+        content_handling = self._get_param("contentHandling")
+        integration_response = self.backend.put_integration_response(
+            function_id,
+            resource_id,
+            method_type,
+            status_code,
+            selection_pattern,
+            response_templates,
+            response_parameters,
+            content_handling,
+        )
+        return 201, {"status": 201}, json.dumps(integration_response.to_json())
 
-        if self.method == "GET":
-            deployments = self.backend.get_deployments(function_id)
-            return 200, {}, json.dumps({"item": [d.to_json() for d in deployments]})
-        elif self.method == "POST":
-            name = self._get_param("stageName")
-            description = self._get_param("description")
-            stage_variables = self._get_param("variables", if_none={})
-            deployment = self.backend.create_deployment(
-                function_id, name, description, stage_variables
-            )
-            return 201, {}, json.dumps(deployment.to_json())
+    def create_deployment(self) -> TYPE_RESPONSE:
+        function_id = self.path.replace("/restapis/", "", 1).split("/")[0]
+        name = self._get_param("stageName")
+        description = self._get_param("description")
+        stage_variables = self._get_param("variables", if_none={})
+        deployment = self.backend.create_deployment(
+            function_id, name, description, stage_variables
+        )
+        return 201, {"status": 201}, json.dumps(deployment.to_json())
 
-    def individual_deployment(  # type: ignore[return]
-        self, request: Any, full_url: str, headers: Dict[str, str]
-    ) -> TYPE_RESPONSE:
-        self.setup_class(request, full_url, headers)
+    def delete_deployment(self) -> TYPE_RESPONSE:
         url_path_parts = self.path.split("/")
         function_id = url_path_parts[2]
         deployment_id = url_path_parts[4]
+        deployment = self.backend.delete_deployment(function_id, deployment_id)
+        return 202, {"status": 202}, json.dumps(deployment.to_json())
 
-        if self.method == "GET":
-            deployment = self.backend.get_deployment(function_id, deployment_id)
-            return 200, {}, json.dumps(deployment.to_json())
-        elif self.method == "DELETE":
-            deployment = self.backend.delete_deployment(function_id, deployment_id)
-            return 202, {}, json.dumps(deployment.to_json())
-
-    def apikeys(  # type: ignore[return]
-        self, request: Any, full_url: str, headers: Dict[str, str]
-    ) -> TYPE_RESPONSE:
-        self.setup_class(request, full_url, headers)
-
-        if self.method == "POST":
-            apikey_response = self.backend.create_api_key(json.loads(self.body))
-            return 201, {}, json.dumps(apikey_response.to_json())
-
-        elif self.method == "GET":
-            include_values = self._get_bool_param("includeValues") or False
-            apikeys_response = self.backend.get_api_keys()
-            resp = [a.to_json() for a in apikeys_response]
-            if not include_values:
-                for key in resp:
-                    key.pop("value")
-            return 200, {}, json.dumps({"item": resp})
-
-    def apikey_individual(
-        self, request: Any, full_url: str, headers: Dict[str, str]
-    ) -> TYPE_RESPONSE:
-        self.setup_class(request, full_url, headers)
-
+    def get_deployment(self) -> str:
         url_path_parts = self.path.split("/")
-        apikey = url_path_parts[2]
-
-        if self.method == "GET":
-            include_value = self._get_bool_param("includeValue") or False
-            apikey_resp = self.backend.get_api_key(apikey).to_json()
-            if not include_value:
-                apikey_resp.pop("value")
-        elif self.method == "PATCH":
-            patch_operations = self._get_param("patchOperations")
-            apikey_resp = self.backend.update_api_key(
-                apikey, patch_operations
-            ).to_json()
-        elif self.method == "DELETE":
-            self.backend.delete_api_key(apikey)
-            return 202, {}, "{}"
-
-        return 200, {}, json.dumps(apikey_resp)
-
-    def usage_plans(  # type: ignore[return]
-        self, request: Any, full_url: str, headers: Dict[str, str]
-    ) -> TYPE_RESPONSE:
-        self.setup_class(request, full_url, headers)
-        if self.method == "POST":
-            usage_plan_response = self.backend.create_usage_plan(json.loads(self.body))
-            return 201, {}, json.dumps(usage_plan_response.to_json())
-        elif self.method == "GET":
-            api_key_id = self.querystring.get("keyId", [None])[0]
-            usage_plans_response = self.backend.get_usage_plans(api_key_id=api_key_id)
-            return (
-                200,
-                {},
-                json.dumps({"item": [u.to_json() for u in usage_plans_response]}),
-            )
-
-    def usage_plan_individual(  # type: ignore[return]
-        self, request: Any, full_url: str, headers: Dict[str, str]
-    ) -> TYPE_RESPONSE:
-        self.setup_class(request, full_url, headers)
+        function_id = url_path_parts[2]
+        deployment_id = url_path_parts[4]
+        deployment = self.backend.get_deployment(function_id, deployment_id)
+        return json.dumps(deployment.to_json())
 
-        url_path_parts = self.path.split("/")
-        usage_plan = url_path_parts[2]
-
-        if self.method == "GET":
-            usage_plan_response = self.backend.get_usage_plan(usage_plan)
-            return 200, {}, json.dumps(usage_plan_response.to_json())
-        elif self.method == "DELETE":
-            self.backend.delete_usage_plan(usage_plan)
-            return 202, {}, "{}"
-        elif self.method == "PATCH":
-            patch_operations = self._get_param("patchOperations")
-            usage_plan_response = self.backend.update_usage_plan(
-                usage_plan, patch_operations
-            )
-            return 200, {}, json.dumps(usage_plan_response.to_json())
+    def get_deployments(self) -> str:
+        function_id = self.path.replace("/restapis/", "", 1).split("/")[0]
+        deployments = self.backend.get_deployments(function_id)
+        return json.dumps({"item": [d.to_json() for d in deployments]})
+
+    def create_api_key(self) -> TYPE_RESPONSE:
+        apikey_response = self.backend.create_api_key(json.loads(self.body))
+        return 201, {"status": 201}, json.dumps(apikey_response.to_json())
+
+    def delete_api_key(self) -> TYPE_RESPONSE:
+        apikey = self.path.split("/")[2]
+        self.backend.delete_api_key(apikey)
+        return 202, {"status": 202}, "{}"
+
+    def get_api_key(self) -> str:
+        apikey = self.path.split("/")[2]
+        include_value = self._get_bool_param("includeValue") or False
+        apikey_resp = self.backend.get_api_key(apikey).to_json()
+        if not include_value:
+            apikey_resp.pop("value")
+        return json.dumps(apikey_resp)
+
+    def get_api_keys(self) -> str:
+        include_values = self._get_bool_param("includeValues") or False
+        apikeys_response = self.backend.get_api_keys()
+        resp = [a.to_json() for a in apikeys_response]
+        if not include_values:
+            for key in resp:
+                key.pop("value")
+        return json.dumps({"item": resp})
+
+    def update_api_key(self) -> str:
+        apikey = self.path.split("/")[2]
+        patch_operations = self._get_param("patchOperations")
+        apikey_resp = self.backend.update_api_key(apikey, patch_operations).to_json()
+        return json.dumps(apikey_resp)
+
+    def create_usage_plan(self) -> TYPE_RESPONSE:
+        usage_plan_response = self.backend.create_usage_plan(json.loads(self.body))
+        return 201, {"status": 201}, json.dumps(usage_plan_response.to_json())
+
+    def delete_usage_plan(self) -> TYPE_RESPONSE:
+        usage_plan = self.path.split("/")[2]
+        self.backend.delete_usage_plan(usage_plan)
+        return 202, {"status": 202}, "{}"
+
+    def get_usage_plan(self) -> str:
+        usage_plan = self.path.split("/")[2]
+
+        usage_plan_response = self.backend.get_usage_plan(usage_plan)
+        return json.dumps(usage_plan_response.to_json())
+
+    def get_usage_plans(self) -> str:
+        api_key_id = self.querystring.get("keyId", [None])[0]
+        usage_plans_response = self.backend.get_usage_plans(api_key_id=api_key_id)
+        return json.dumps({"item": [u.to_json() for u in usage_plans_response]})
+
+    def update_usage_plan(self) -> str:
+        usage_plan = self.path.split("/")[2]
+        patch_operations = self._get_param("patchOperations")
+        usage_plan_response = self.backend.update_usage_plan(
+            usage_plan, patch_operations
+        )
+        return json.dumps(usage_plan_response.to_json())
 
-    def usage_plan_keys(  # type: ignore[return]
-        self, request: Any, full_url: str, headers: Dict[str, str]
-    ) -> TYPE_RESPONSE:
-        self.setup_class(request, full_url, headers)
+    def create_usage_plan_key(self) -> TYPE_RESPONSE:
+        usage_plan_id = self.path.split("/")[2]
+        usage_plan = self.backend.create_usage_plan_key(
+            usage_plan_id, json.loads(self.body)
+        )
+        return 201, {"status": 201}, json.dumps(usage_plan.to_json())
 
+    def delete_usage_plan_key(self) -> TYPE_RESPONSE:
         url_path_parts = self.path.split("/")
         usage_plan_id = url_path_parts[2]
+        key_id = url_path_parts[4]
+        self.backend.delete_usage_plan_key(usage_plan_id, key_id)
+        return 202, {"status": 202}, "{}"
 
-        if self.method == "POST":
-            usage_plan_response = self.backend.create_usage_plan_key(
-                usage_plan_id, json.loads(self.body)
-            )
-            return 201, {}, json.dumps(usage_plan_response.to_json())
-        elif self.method == "GET":
-            usage_plans_response = self.backend.get_usage_plan_keys(usage_plan_id)
-            return (
-                200,
-                {},
-                json.dumps({"item": [u.to_json() for u in usage_plans_response]}),
-            )
-
-    def usage_plan_key_individual(  # type: ignore[return]
-        self, request: Any, full_url: str, headers: Dict[str, str]
-    ) -> TYPE_RESPONSE:
-        self.setup_class(request, full_url, headers)
-
+    def get_usage_plan_key(self) -> str:
         url_path_parts = self.path.split("/")
         usage_plan_id = url_path_parts[2]
         key_id = url_path_parts[4]
+        usage_plan = self.backend.get_usage_plan_key(usage_plan_id, key_id)
+        return json.dumps(usage_plan.to_json())
+
+    def get_usage_plan_keys(self) -> str:
+        usage_plan_id = self.path.split("/")[2]
+        usage_plans_response = self.backend.get_usage_plan_keys(usage_plan_id)
+        return json.dumps({"item": [u.to_json() for u in usage_plans_response]})
+
+    def create_domain_name(self) -> TYPE_RESPONSE:
+        domain_name = self._get_param("domainName")
+        certificate_name = self._get_param("certificateName")
+        tags = self._get_param("tags")
+        certificate_arn = self._get_param("certificateArn")
+        certificate_body = self._get_param("certificateBody")
+        certificate_private_key = self._get_param("certificatePrivateKey")
+        certificate_chain = self._get_param("certificateChain")
+        regional_certificate_name = self._get_param("regionalCertificateName")
+        regional_certificate_arn = self._get_param("regionalCertificateArn")
+        endpoint_configuration = self._get_param("endpointConfiguration")
+        security_policy = self._get_param("securityPolicy")
+        domain_name_resp = self.backend.create_domain_name(
+            domain_name,
+            certificate_name,
+            tags,
+            certificate_arn,
+            certificate_body,
+            certificate_private_key,
+            certificate_chain,
+            regional_certificate_name,
+            regional_certificate_arn,
+            endpoint_configuration,
+            security_policy,
+        )
+        return 201, {"status": 201}, json.dumps(domain_name_resp.to_json())
 
-        if self.method == "GET":
-            usage_plan_response = self.backend.get_usage_plan_key(usage_plan_id, key_id)
-            return 200, {}, json.dumps(usage_plan_response.to_json())
-        elif self.method == "DELETE":
-            self.backend.delete_usage_plan_key(usage_plan_id, key_id)
-            return 202, {}, "{}"
-
-    def domain_names(  # type: ignore[return]
-        self, request: Any, full_url: str, headers: Dict[str, str]
-    ) -> TYPE_RESPONSE:
-        self.setup_class(request, full_url, headers)
-
-        if self.method == "GET":
-            domain_names = self.backend.get_domain_names()
-            return 200, {}, json.dumps({"item": [d.to_json() for d in domain_names]})
-
-        elif self.method == "POST":
-            domain_name = self._get_param("domainName")
-            certificate_name = self._get_param("certificateName")
-            tags = self._get_param("tags")
-            certificate_arn = self._get_param("certificateArn")
-            certificate_body = self._get_param("certificateBody")
-            certificate_private_key = self._get_param("certificatePrivateKey")
-            certificate_chain = self._get_param("certificateChain")
-            regional_certificate_name = self._get_param("regionalCertificateName")
-            regional_certificate_arn = self._get_param("regionalCertificateArn")
-            endpoint_configuration = self._get_param("endpointConfiguration")
-            security_policy = self._get_param("securityPolicy")
-            domain_name_resp = self.backend.create_domain_name(
-                domain_name,
-                certificate_name,
-                tags,
-                certificate_arn,
-                certificate_body,
-                certificate_private_key,
-                certificate_chain,
-                regional_certificate_name,
-                regional_certificate_arn,
-                endpoint_configuration,
-                security_policy,
-            )
-            return 201, {}, json.dumps(domain_name_resp.to_json())
+    def delete_domain_name(self) -> TYPE_RESPONSE:
+        domain_name = self.path.split("/")[2]
+        self.backend.delete_domain_name(domain_name)
+        return 202, {"status": 202}, "{}"
 
-    def domain_name_induvidual(
-        self, request: Any, full_url: str, headers: Dict[str, str]
-    ) -> TYPE_RESPONSE:
-        self.setup_class(request, full_url, headers)
+    def get_domain_name(self) -> str:
+        domain_name = self.path.split("/")[2]
+        domain = self.backend.get_domain_name(domain_name)
+        return json.dumps(domain.to_json())
 
-        url_path_parts = self.path.split("/")
-        domain_name = url_path_parts[2]
+    def get_domain_names(self) -> str:
+        domain_names = self.backend.get_domain_names()
+        return json.dumps({"item": [d.to_json() for d in domain_names]})
 
-        if self.method == "GET":
-            if domain_name is not None:
-                domain_names = self.backend.get_domain_name(domain_name)
-                return 200, {}, json.dumps(domain_names.to_json())
-            return 200, {}, "{}"
-        elif self.method == "DELETE":
-            if domain_name is not None:
-                self.backend.delete_domain_name(domain_name)
-            return 202, {}, json.dumps({})
-        else:
-            msg = f'Method "{self.method}" for API GW domain names not implemented'
-            return 404, {}, json.dumps({"error": msg})
-
-    def models(  # type: ignore[return]
-        self, request: Any, full_url: str, headers: Dict[str, str]
-    ) -> TYPE_RESPONSE:
-        self.setup_class(request, full_url, headers)
+    def create_model(self) -> TYPE_RESPONSE:
         rest_api_id = self.path.replace("/restapis/", "", 1).split("/")[0]
+        name = self._get_param("name")
+        description = self._get_param("description")
+        schema = self._get_param("schema")
+        content_type = self._get_param("contentType")
+        model = self.backend.create_model(
+            rest_api_id,
+            name,
+            content_type,
+            description,
+            schema,
+        )
+        return 201, {"status": 201}, json.dumps(model.to_json())
 
-        if self.method == "GET":
-            models = self.backend.get_models(rest_api_id)
-            return 200, {}, json.dumps({"item": [m.to_json() for m in models]})
-
-        elif self.method == "POST":
-            name = self._get_param("name")
-            description = self._get_param("description")
-            schema = self._get_param("schema")
-            content_type = self._get_param("contentType")
-            model = self.backend.create_model(
-                rest_api_id,
-                name,
-                content_type,
-                description,
-                schema,
-            )
-            return 201, {}, json.dumps(model.to_json())
+    def get_models(self) -> str:
+        rest_api_id = self.path.replace("/restapis/", "", 1).split("/")[0]
+        models = self.backend.get_models(rest_api_id)
+        return json.dumps({"item": [m.to_json() for m in models]})
 
-    def model_induvidual(
-        self, request: Any, full_url: str, headers: Dict[str, str]
-    ) -> TYPE_RESPONSE:
-        self.setup_class(request, full_url, headers)
+    def get_model(self) -> str:
         url_path_parts = self.path.split("/")
         rest_api_id = url_path_parts[2]
         model_name = url_path_parts[4]
+        model_info = self.backend.get_model(rest_api_id, model_name)
+        return json.dumps(model_info.to_json())
 
-        if self.method == "GET":
-            model_info = self.backend.get_model(rest_api_id, model_name)
-            return 200, {}, json.dumps(model_info.to_json())
-        return 200, {}, "{}"
+    def create_base_path_mapping(self) -> TYPE_RESPONSE:
+        domain_name = self.path.split("/")[2]
+        base_path = self._get_param("basePath")
+        rest_api_id = self._get_param("restApiId")
+        stage = self._get_param("stage")
 
-    def base_path_mappings(  # type: ignore[return]
-        self, request: Any, full_url: str, headers: Dict[str, str]
-    ) -> TYPE_RESPONSE:
-        self.setup_class(request, full_url, headers)
+        base_path_mapping = self.backend.create_base_path_mapping(
+            domain_name, rest_api_id, base_path, stage
+        )
+        return 201, {"status": 201}, json.dumps(base_path_mapping.to_json())
 
+    def delete_base_path_mapping(self) -> TYPE_RESPONSE:
         url_path_parts = self.path.split("/")
         domain_name = url_path_parts[2]
+        base_path = unquote(url_path_parts[4])
+        self.backend.delete_base_path_mapping(domain_name, base_path)
+        return 202, {"status": 202}, ""
 
-        if self.method == "GET":
-            base_path_mappings = self.backend.get_base_path_mappings(domain_name)
-            return (
-                200,
-                {},
-                json.dumps({"item": [m.to_json() for m in base_path_mappings]}),
-            )
-        elif self.method == "POST":
-            base_path = self._get_param("basePath")
-            rest_api_id = self._get_param("restApiId")
-            stage = self._get_param("stage")
-
-            base_path_mapping_resp = self.backend.create_base_path_mapping(
-                domain_name, rest_api_id, base_path, stage
-            )
-            return 201, {}, json.dumps(base_path_mapping_resp.to_json())
-
-    def base_path_mapping_individual(  # type: ignore[return]
-        self, request: Any, full_url: str, headers: Dict[str, str]
-    ) -> TYPE_RESPONSE:
-        self.setup_class(request, full_url, headers)
-
+    def get_base_path_mapping(self) -> str:
         url_path_parts = self.path.split("/")
         domain_name = url_path_parts[2]
         base_path = unquote(url_path_parts[4])
+        base_path_mapping = self.backend.get_base_path_mapping(domain_name, base_path)
+        return json.dumps(base_path_mapping.to_json())
 
-        if self.method == "GET":
-            base_path_mapping = self.backend.get_base_path_mapping(
-                domain_name, base_path
-            )
-            return 200, {}, json.dumps(base_path_mapping.to_json())
-        elif self.method == "DELETE":
-            self.backend.delete_base_path_mapping(domain_name, base_path)
-            return 202, {}, ""
-        elif self.method == "PATCH":
-            patch_operations = self._get_param("patchOperations")
-            base_path_mapping = self.backend.update_base_path_mapping(
-                domain_name, base_path, patch_operations
-            )
-            return 200, {}, json.dumps(base_path_mapping.to_json())
+    def get_base_path_mappings(self) -> str:
+        domain_name = self.path.split("/")[2]
+        base_path_mappings = self.backend.get_base_path_mappings(domain_name)
+        return json.dumps({"item": [m.to_json() for m in base_path_mappings]})
 
-    def vpc_link(  # type: ignore[return]
-        self, request: Any, full_url: str, headers: Dict[str, str]
-    ) -> TYPE_RESPONSE:
-        self.setup_class(request, full_url, headers)
+    def update_base_path_mapping(self) -> str:
         url_path_parts = self.path.split("/")
-        vpc_link_id = url_path_parts[-1]
+        domain_name = url_path_parts[2]
+        base_path = unquote(url_path_parts[4])
+        patch_ops = self._get_param("patchOperations")
+        base_path_mapping = self.backend.update_base_path_mapping(
+            domain_name, base_path, patch_ops
+        )
+        return json.dumps(base_path_mapping.to_json())
+
+    def create_vpc_link(self) -> TYPE_RESPONSE:
+        name = self._get_param("name")
+        description = self._get_param("description")
+        target_arns = self._get_param("targetArns")
+        tags = self._get_param("tags")
+        vpc_link = self.backend.create_vpc_link(
+            name=name, description=description, target_arns=target_arns, tags=tags
+        )
+        return 202, {"status": 202}, json.dumps(vpc_link.to_json())
 
-        if self.method == "DELETE":
-            self.backend.delete_vpc_link(vpc_link_id=vpc_link_id)
-            return 202, {}, "{}"
-        if self.method == "GET":
-            vpc_link = self.backend.get_vpc_link(vpc_link_id=vpc_link_id)
-            return 200, {}, json.dumps(vpc_link.to_json())
+    def delete_vpc_link(self) -> TYPE_RESPONSE:
+        vpc_link_id = self.path.split("/")[-1]
+        self.backend.delete_vpc_link(vpc_link_id=vpc_link_id)
+        return 202, {"status": 202}, "{}"
 
-    def vpc_links(  # type: ignore[return]
-        self, request: Any, full_url: str, headers: Dict[str, str]
-    ) -> TYPE_RESPONSE:
-        self.setup_class(request, full_url, headers)
+    def get_vpc_link(self) -> str:
+        vpc_link_id = self.path.split("/")[-1]
+        vpc_link = self.backend.get_vpc_link(vpc_link_id=vpc_link_id)
+        return json.dumps(vpc_link.to_json())
 
-        if self.method == "GET":
-            vpc_links = self.backend.get_vpc_links()
-            return 200, {}, json.dumps({"item": [v.to_json() for v in vpc_links]})
-        if self.method == "POST":
-            name = self._get_param("name")
-            description = self._get_param("description")
-            target_arns = self._get_param("targetArns")
-            tags = self._get_param("tags")
-            vpc_link = self.backend.create_vpc_link(
-                name=name, description=description, target_arns=target_arns, tags=tags
-            )
-            return 202, {}, json.dumps(vpc_link.to_json())
+    def get_vpc_links(self) -> str:
+        vpc_links = self.backend.get_vpc_links()
+        return json.dumps({"item": [v.to_json() for v in vpc_links]})
 
     def put_gateway_response(self) -> TYPE_RESPONSE:
         rest_api_id = self.path.split("/")[-3]
diff --git a/moto/apigateway/urls.py b/moto/apigateway/urls.py
index 081b83e66..2d80fcf44 100644
--- a/moto/apigateway/urls.py
+++ b/moto/apigateway/urls.py
@@ -4,105 +4,46 @@ from .responses import APIGatewayResponse
 url_bases = [r"https?://apigateway\.(.+)\.amazonaws.com"]
 
 url_paths = {
-    "{0}/restapis$": APIGatewayResponse.method_dispatch(APIGatewayResponse.restapis),
-    "{0}/restapis/(?P<function_id>[^/]+)/?$": APIGatewayResponse.method_dispatch(
-        APIGatewayResponse.restapis_individual
-    ),
-    "{0}/restapis/(?P<function_id>[^/]+)/resources$": APIGatewayResponse.method_dispatch(
-        APIGatewayResponse.resources
-    ),
-    "{0}/restapis/(?P<function_id>[^/]+)/authorizers$": APIGatewayResponse.method_dispatch(
-        APIGatewayResponse.restapis_authorizers
-    ),
-    "{0}/restapis/(?P<function_id>[^/]+)/authorizers/(?P<authorizer_id>[^/]+)/?$": APIGatewayResponse.method_dispatch(
-        APIGatewayResponse.authorizers
-    ),
-    "{0}/restapis/(?P<function_id>[^/]+)/stages$": APIGatewayResponse.method_dispatch(
-        APIGatewayResponse.restapis_stages
-    ),
-    "{0}/tags/arn:aws:apigateway:(?P<region_name>[^/]+)::/restapis/(?P<function_id>[^/]+)/stages/(?P<stage_name>[^/]+)/?$": APIGatewayResponse.method_dispatch(
+    "{0}/restapis$": APIGatewayResponse.dispatch,
+    "{0}/restapis/(?P<function_id>[^/]+)/?$": APIGatewayResponse.dispatch,
+    "{0}/restapis/(?P<function_id>[^/]+)/resources$": APIGatewayResponse.dispatch,
+    "{0}/restapis/(?P<function_id>[^/]+)/authorizers$": APIGatewayResponse.dispatch,
+    "{0}/restapis/(?P<function_id>[^/]+)/authorizers/(?P<authorizer_id>[^/]+)/?$": APIGatewayResponse.dispatch,
+    "{0}/restapis/(?P<function_id>[^/]+)/stages$": APIGatewayResponse.dispatch,
+    "{0}/tags/(?P<resourceArn>[^/]+)$": APIGatewayResponse.method_dispatch(
         APIGatewayResponse.restapis_stages_tags
     ),
-    "{0}/restapis/(?P<function_id>[^/]+)/stages/(?P<stage_name>[^/]+)/?$": APIGatewayResponse.method_dispatch(
-        APIGatewayResponse.stages
-    ),
-    "{0}/restapis/(?P<function_id>[^/]+)/stages/(?P<stage_name>[^/]+)/exports/(?P<export_type>[^/]+)/?$": APIGatewayResponse.method_dispatch(
-        APIGatewayResponse.export
-    ),
-    "{0}/restapis/(?P<function_id>[^/]+)/deployments$": APIGatewayResponse.method_dispatch(
-        APIGatewayResponse.deployments
-    ),
-    "{0}/restapis/(?P<function_id>[^/]+)/deployments/(?P<deployment_id>[^/]+)/?$": APIGatewayResponse.method_dispatch(
-        APIGatewayResponse.individual_deployment
-    ),
-    "{0}/restapis/(?P<function_id>[^/]+)/resources/(?P<resource_id>[^/]+)/?$": APIGatewayResponse.method_dispatch(
-        APIGatewayResponse.resource_individual
-    ),
-    "{0}/restapis/(?P<function_id>[^/]+)/resources/(?P<resource_id>[^/]+)/methods/(?P<method_name>[^/]+)/?$": APIGatewayResponse.method_dispatch(
-        APIGatewayResponse.resource_methods
-    ),
-    r"{0}/restapis/(?P<function_id>[^/]+)/resources/(?P<resource_id>[^/]+)/methods/(?P<method_name>[^/]+)/responses/(?P<status_code>\d+)$": APIGatewayResponse.method_dispatch(
-        APIGatewayResponse.resource_method_responses
-    ),
-    r"{0}/restapis/(?P<function_id>[^/]+)/resources/(?P<resource_id>[^/]+)/methods/(?P<method_name>[^/]+)/integration$": APIGatewayResponse.method_dispatch(
-        APIGatewayResponse.integrations
-    ),
-    r"{0}/restapis/(?P<function_id>[^/]+)/resources/(?P<resource_id>[^/]+)/methods/(?P<method_name>[^/]+)/integration/responses/(?P<status_code>\d+)$": APIGatewayResponse.method_dispatch(
-        APIGatewayResponse.integration_responses
-    ),
-    r"{0}/restapis/(?P<function_id>[^/]+)/resources/(?P<resource_id>[^/]+)/methods/(?P<method_name>[^/]+)/integration/responses/(?P<status_code>\d+)/$": APIGatewayResponse.method_dispatch(
-        APIGatewayResponse.integration_responses
-    ),
-    "{0}/apikeys$": APIGatewayResponse.method_dispatch(APIGatewayResponse.apikeys),
-    "{0}/apikeys/(?P<apikey>[^/]+)": APIGatewayResponse.method_dispatch(
-        APIGatewayResponse.apikey_individual
-    ),
-    "{0}/usageplans$": APIGatewayResponse.method_dispatch(
-        APIGatewayResponse.usage_plans
-    ),
-    "{0}/domainnames$": APIGatewayResponse.method_dispatch(
-        APIGatewayResponse.domain_names
-    ),
-    "{0}/restapis/(?P<function_id>[^/]+)/models$": APIGatewayResponse.method_dispatch(
-        APIGatewayResponse.models
-    ),
-    "{0}/restapis/(?P<function_id>[^/]+)/models/(?P<model_name>[^/]+)/?$": APIGatewayResponse.method_dispatch(
-        APIGatewayResponse.model_induvidual
-    ),
-    "{0}/domainnames/(?P<domain_name>[^/]+)/?$": APIGatewayResponse.method_dispatch(
-        APIGatewayResponse.domain_name_induvidual
-    ),
-    "{0}/domainnames/(?P<domain_name>[^/]+)/basepathmappings$": APIGatewayResponse.method_dispatch(
-        APIGatewayResponse.base_path_mappings
-    ),
-    "{0}/domainnames/(?P<domain_name>[^/]+)/basepathmappings/(?P<base_path_mapping>[^/]+)$": APIGatewayResponse.method_dispatch(
-        APIGatewayResponse.base_path_mapping_individual
-    ),
-    "{0}/usageplans/(?P<usage_plan_id>[^/]+)/?$": APIGatewayResponse.method_dispatch(
-        APIGatewayResponse.usage_plan_individual
-    ),
-    "{0}/usageplans/(?P<usage_plan_id>[^/]+)/keys$": APIGatewayResponse.method_dispatch(
-        APIGatewayResponse.usage_plan_keys
-    ),
-    "{0}/usageplans/(?P<usage_plan_id>[^/]+)/keys/(?P<api_key_id>[^/]+)/?$": APIGatewayResponse.method_dispatch(
-        APIGatewayResponse.usage_plan_key_individual
-    ),
-    "{0}/restapis/(?P<function_id>[^/]+)/requestvalidators$": APIGatewayResponse.method_dispatch(
-        APIGatewayResponse.request_validators
-    ),
-    "{0}/restapis/(?P<api_id>[^/]+)/requestvalidators/(?P<validator_id>[^/]+)/?$": APIGatewayResponse.method_dispatch(
-        APIGatewayResponse.request_validator_individual
-    ),
-    "{0}/restapis/(?P<api_id>[^/]+)/gatewayresponses/?$": APIGatewayResponse.method_dispatch(
-        APIGatewayResponse.gateway_responses
-    ),
-    "{0}/restapis/(?P<api_id>[^/]+)/gatewayresponses/(?P<response_type>[^/]+)/?$": APIGatewayResponse.method_dispatch(
-        APIGatewayResponse.gateway_response
-    ),
-    "{0}/vpclinks$": APIGatewayResponse.method_dispatch(APIGatewayResponse.vpc_links),
-    "{0}/vpclinks/(?P<vpclink_id>[^/]+)": APIGatewayResponse.method_dispatch(
-        APIGatewayResponse.vpc_link
+    "{0}/tags/arn:aws:apigateway:(?P<region_name>[^/]+)::/restapis/(?P<function_id>[^/]+)/stages/(?P<stage_name>[^/]+)$": APIGatewayResponse.method_dispatch(
+        APIGatewayResponse.restapis_stages_tags
     ),
+    "{0}/restapis/(?P<function_id>[^/]+)/stages/(?P<stage_name>[^/]+)/?$": APIGatewayResponse.dispatch,
+    "{0}/restapis/(?P<function_id>[^/]+)/stages/(?P<stage_name>[^/]+)/exports/(?P<export_type>[^/]+)/?$": APIGatewayResponse.dispatch,
+    "{0}/restapis/(?P<function_id>[^/]+)/deployments$": APIGatewayResponse.dispatch,
+    "{0}/restapis/(?P<function_id>[^/]+)/deployments/(?P<deployment_id>[^/]+)/?$": APIGatewayResponse.dispatch,
+    "{0}/restapis/(?P<function_id>[^/]+)/resources/(?P<resource_id>[^/]+)/?$": APIGatewayResponse.dispatch,
+    "{0}/restapis/(?P<function_id>[^/]+)/resources/(?P<resource_id>[^/]+)/methods/(?P<method_name>[^/]+)/?$": APIGatewayResponse.dispatch,
+    r"{0}/restapis/(?P<function_id>[^/]+)/resources/(?P<resource_id>[^/]+)/methods/(?P<method_name>[^/]+)/responses/(?P<status_code>\d+)$": APIGatewayResponse.dispatch,
+    r"{0}/restapis/(?P<function_id>[^/]+)/resources/(?P<resource_id>[^/]+)/methods/(?P<method_name>[^/]+)/integration$": APIGatewayResponse.dispatch,
+    r"{0}/restapis/(?P<function_id>[^/]+)/resources/(?P<resource_id>[^/]+)/methods/(?P<method_name>[^/]+)/integration/responses/(?P<status_code>\d+)$": APIGatewayResponse.dispatch,
+    r"{0}/restapis/(?P<function_id>[^/]+)/resources/(?P<resource_id>[^/]+)/methods/(?P<method_name>[^/]+)/integration/responses/(?P<status_code>\d+)/$": APIGatewayResponse.dispatch,
+    "{0}/apikeys$": APIGatewayResponse.dispatch,
+    "{0}/apikeys/(?P<apikey>[^/]+)": APIGatewayResponse.dispatch,
+    "{0}/usageplans$": APIGatewayResponse.dispatch,
+    "{0}/domainnames$": APIGatewayResponse.dispatch,
+    "{0}/restapis/(?P<function_id>[^/]+)/models$": APIGatewayResponse.dispatch,
+    "{0}/restapis/(?P<function_id>[^/]+)/models/(?P<model_name>[^/]+)/?$": APIGatewayResponse.dispatch,
+    "{0}/domainnames/(?P<domain_name>[^/]+)/?$": APIGatewayResponse.dispatch,
+    "{0}/domainnames/(?P<domain_name>[^/]+)/basepathmappings$": APIGatewayResponse.dispatch,
+    "{0}/domainnames/(?P<domain_name>[^/]+)/basepathmappings/(?P<base_path_mapping>[^/]+)$": APIGatewayResponse.dispatch,
+    "{0}/usageplans/(?P<usage_plan_id>[^/]+)/?$": APIGatewayResponse.dispatch,
+    "{0}/usageplans/(?P<usage_plan_id>[^/]+)/keys$": APIGatewayResponse.dispatch,
+    "{0}/usageplans/(?P<usage_plan_id>[^/]+)/keys/(?P<api_key_id>[^/]+)/?$": APIGatewayResponse.dispatch,
+    "{0}/restapis/(?P<function_id>[^/]+)/requestvalidators$": APIGatewayResponse.dispatch,
+    "{0}/restapis/(?P<api_id>[^/]+)/requestvalidators/(?P<validator_id>[^/]+)/?$": APIGatewayResponse.dispatch,
+    "{0}/restapis/(?P<api_id>[^/]+)/gatewayresponses/?$": APIGatewayResponse.dispatch,
+    "{0}/restapis/(?P<api_id>[^/]+)/gatewayresponses/(?P<response_type>[^/]+)/?$": APIGatewayResponse.dispatch,
+    "{0}/vpclinks$": APIGatewayResponse.dispatch,
+    "{0}/vpclinks/(?P<vpclink_id>[^/]+)": APIGatewayResponse.dispatch,
 }
 
 # Also manages the APIGatewayV2


Test patch:
diff --git a/tests/test_apigateway/test_apigateway.py b/tests/test_apigateway/test_apigateway.py
index ea20f4758..3eb8047bb 100644
--- a/tests/test_apigateway/test_apigateway.py
+++ b/tests/test_apigateway/test_apigateway.py
@@ -5,7 +5,7 @@ import pytest
 from botocore.exceptions import ClientError
 from freezegun import freeze_time
 
-from moto import mock_aws, settings
+from moto import mock_aws
 from moto.core import DEFAULT_ACCOUNT_ID as ACCOUNT_ID
 from tests import DEFAULT_ACCOUNT_ID
 
@@ -857,16 +857,14 @@ def test_update_authorizer_configuration():
 
     # TODO: implement mult-update tests
 
-    with pytest.raises(Exception) as exc:
+    with pytest.raises(ClientError) as exc:
         client.update_authorizer(
             restApiId=api_id,
             authorizerId=authorizer_id,
-            patchOperations=[
-                {"op": "add", "path": "/notasetting", "value": "eu-west-1"}
-            ],
+            patchOperations=[{"op": "add", "path": "/notasetting", "value": "v"}],
         )
-    if settings.TEST_DECORATOR_MODE:
-        assert 'Patch operation "add" not implemented' in str(exc.value)
+    err = exc.value.response["Error"]
+    assert err["Message"] == 'Patch operation "add" not implemented'
 
 
 @mock_aws
diff --git a/tests/test_apigateway/test_apigateway_stage.py b/tests/test_apigateway/test_apigateway_stage.py
index 6805b71d9..4b68d8a43 100644
--- a/tests/test_apigateway/test_apigateway_stage.py
+++ b/tests/test_apigateway/test_apigateway_stage.py
@@ -493,3 +493,36 @@ def test_non_existent_stage():
         client.get_stage(restApiId=api_id, stageName="xxx")
     err = exc.value.response["Error"]
     assert err["Code"] == "NotFoundException"
+
+
+@mock_aws
+def test_stage_tags():
+    client = boto3.client("apigateway", region_name="us-west-2")
+    stage_name = "staging"
+    response = client.create_rest_api(name="my_api", description="this is my api")
+    api_id = response["id"]
+
+    create_method_integration(client, api_id)
+    response = client.create_deployment(restApiId=api_id, stageName=stage_name)
+    deployment_id = response["id"]
+
+    new_stage_name = "current"
+    response = client.create_stage(
+        restApiId=api_id,
+        stageName=new_stage_name,
+        deploymentId=deployment_id,
+        tags={"k": "v"},
+    )
+    assert response["tags"] == {"k": "v"}
+
+    stage_arn = (
+        f"arn:aws:apigateway:us-east-1::/restapis/{api_id}/stages/{new_stage_name}"
+    )
+    client.tag_resource(resourceArn=stage_arn, tags={"k2": "v2"})
+
+    stage = client.get_stage(restApiId=api_id, stageName=new_stage_name)
+    assert stage["tags"] == {"k": "v", "k2": "v2"}
+
+    client.untag_resource(resourceArn=stage_arn, tagKeys=["k"])
+    stage = client.get_stage(restApiId=api_id, stageName=new_stage_name)
+    assert stage["tags"] == {"k2": "v2"}
diff --git a/tests/test_apigateway/test_server.py b/tests/test_apigateway/test_server.py
index 9e81da173..50946196d 100644
--- a/tests/test_apigateway/test_server.py
+++ b/tests/test_apigateway/test_server.py
@@ -137,7 +137,7 @@ def test_put_integration_response_without_body():
     test_client = backend.test_client()
 
     res = test_client.put(
-        "/restapis/f_id/resources/r_id/methods/m_id/integration/responses/200/"
+        "/restapis/f_id/resources/r_id/methods/m_id/integration/responses/200"
     )
     assert res.status_code == 400
     assert json.loads(res.data) == {