{"record":{"id":"c8815da3aba9773c","repo":"floci-io/floci","slug":"missingaction","errorCode":"MissingAction","errorMessage":"The request must contain the parameter Action","messagePattern":"The request must contain the parameter Action","errorType":"validation","errorClass":"AwsException","httpStatus":400,"severity":"error","filePath":"src/main/java/io/github/hectorvent/floci/services/apigateway/AwsServiceRouter.java","lineNumber":198,"sourceCode":"    /**\n     * Dispatches an AWS query-protocol (form-encoded) integration request.\n     *\n     * <p>Used for {@code path/}-style integration URIs whose VTL request template renders an\n     * {@code application/x-www-form-urlencoded} body in the AWS query protocol, e.g.\n     * {@code Action=SendMessage&QueueUrl=...&MessageBody=...}. The {@code Action} parameter\n     * selects the operation, mirroring {@link io.github.hectorvent.floci.core.common.AwsQueryController}.\n     *\n     * @param service the AWS service name from the URI (e.g., \"sqs\")\n     * @param params  the parsed form parameters, including {@code Action}\n     * @param region  the AWS region\n     * @return the service response (query-protocol XML)\n     */\n    public Response invokeQuery(String service, MultivaluedMap<String, String> params, String region) {\n        String action = params.getFirst(\"Action\");\n        LOG.debugv(\"AWS query integration dispatch: {0}:{1} in {2}\", service, action, region);\n\n        if (action == null || action.isBlank()) {\n            throw new AwsException(\"MissingAction\",\n                    \"The request must contain the parameter Action\", 400);\n        }\n\n        try {\n            return switch (service) {\n                case \"sqs\" -> sqsQueryHandler.handle(action, params, region);\n                default -> throw new AwsException(\"UnknownService\",\n                        \"Unsupported AWS query-protocol service integration: \" + service, 400);\n            };\n        } catch (AwsException e) {\n            throw e;\n        } catch (Exception e) {\n            throw new AwsException(\"InternalError\",\n                    e.getMessage() != null ? e.getMessage() : \"Service invocation failed\", 500);\n        }\n    }\n}\n","sourceCodeStart":180,"sourceCodeEnd":216,"githubUrl":"https://github.com/floci-io/floci/blob/62ff490619e7bd3554597c28c704081b4c15add5/src/main/java/io/github/hectorvent/floci/services/apigateway/AwsServiceRouter.java#L180-L216","documentation":"Thrown by the AWS query-protocol integration path (invokeQuery) when the form-encoded body produced by an integration's VTL template has no 'Action' parameter, or its value is blank. In the AWS query protocol (used by SQS-style APIs), 'Action' selects the operation, mirroring AwsQueryController; without it Floci cannot pick an operation and returns MissingAction with HTTP 400 before dispatching to any handler.","triggerScenarios":"An API Gateway method with type AWS whose URI is 'path/'-style and whose request template renders application/x-www-form-urlencoded content that omits Action=, e.g. only 'QueueUrl=...&MessageBody=...'. Also triggered when the VTL template sets Action from a variable that is null or empty at runtime.","commonSituations":"Copying a JSON-integration template into a query-protocol integration. Template variables like $input.params('Action') that are absent on the actual request, so Action renders blank. POST bodies sent as JSON while the integration expects form-encoding, so the form parse yields no parameters.","solutions":["Make the VTL request template always emit a literal Action, e.g. 'Action=SendMessage&QueueUrl=$input.path('$.queueUrl')&MessageBody=$util.urlEncode($input.body)'.","Ensure Content-Type handling matches: the integration must parse the body as application/x-www-form-urlencoded, not JSON.","Use test-invoke on the method and inspect the rendered request to confirm 'Action' is present and non-empty.","Verify the integration URI style: this code path only serves path/-style query integrations (currently SQS-only); JSON-body integrations go through invokeJson() instead."],"exampleFix":"# before (request template)\nQueueUrl=$input.path('$.queueUrl')&MessageBody=$util.urlEncode($input.body)\n\n# after\nAction=SendMessage&QueueUrl=$input.path('$.queueUrl')&MessageBody=$util.urlEncode($input.body)","handlingStrategy":"validation","validationCode":"# Validate the rendered template locally before deploy\nRENDERED=$(vtl-render request.vtl sample-input.json)\nif ! echo \"$RENDERED\" | grep -qE '(^|&)Action=[^&]+'; then\n  echo \"Template must render a non-empty Action parameter\" >&2; exit 1\nfi","typeGuard":null,"tryCatchPattern":"catch (AwsException e) {\n    if (\"MissingAction\".equals(e.getCode())) {\n        // template bug: add Action=<Op> to the form body; no retry will help\n        throw new TemplateConfigurationException(\"Query-protocol template must emit Action=...\", e);\n    }\n    throw e;\n}","preventionTips":["Start every query-protocol template with a literal Action=<Operation>.","Keep request templates for JSON and query integrations in separate, clearly named files.","Use test-invoke in CI to assert the rendered request contains 'Action='."],"tags":["apigateway","query-protocol","vtl","sqs","validation"],"backgroundTag":null,"analyzedSha":"62ff490619e7bd3554597c28c704081b4c15add5","analyzedAt":"2026-08-14T14:25:23.764Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}