Skip to content

πŸŽ™οΈ task - feat: let forApiGateway return the handler's exact wire response (statusCode/headers/body, incl 204/308/xml)Β #15

Description

@uladkasach

πŸ¦«πŸŽ™οΈ dispatch to foreman

πŸ’§ task enqueued
   β”œβ”€ priority = ?
   β”œβ”€ yieldage = ?
   └─ leverage = ?

title
feat: let forApiGateway return the handler's exact wire response (statusCode/headers/body, incl 204/308/xml)
description

.what

sdk-aws-lambda should let an api-gateway lambda return its exact wire response β€”
arbitrary statusCode, headers, and body (incl. an empty body) β€” rather than hardcode a
single success shape. today forApiGateway fixes { statusCode: 200, body: JSON.stringify(x) },
so a handler cannot express a 204 (no body), a 308 (Location redirect), or an xml/text body.

proposed: a response-passthrough mode (or a separate wrapper) where the logic fn returns
{ statusCode, headers?, body? } and the sdk passes it through verbatim, plus the same
error-conversion contract the prior simple-lambda-handlers middy stack gave:

  • BadRequestError -> 400 { errorMessage, errorType, causeMessage? } (client fault; the
    invocation stays a success, so no cloudwatch error + no retry signal)
  • any other error -> log loudly, return 500 with no body (no internal/secret leak)

.why

forApiGateway hardcodes { statusCode: 200, body: json }. real webhooks need other shapes:

  • twilio voice/sms webhooks return 204 (no body) and twiml xml bodies
  • cloudfront / short-url redirects return 308 with a Location header
  • neither can be expressed through the fixed 200+json contract

evidence β€” svc-notifications had to hand-roll a forApiGatewayPlain adapter to replace the
migrated-away createApiGatewayHandler, purely to pass statusCode / headers / body
through and to keep the middy error-conversion contract:

export const forApiGatewayPlain =
  (logic: (event: APIGatewayProxyEvent) => Promise<PlainApiGatewayResponse>) =>
  async (event): Promise<APIGatewayProxyResult> => {
    try {
      const response = await logic(event);
      return { statusCode: response.statusCode, headers: response.headers, body: response.body ?? '' };
    } catch (error) {
      if (!(error instanceof Error)) throw error;
      if (error instanceof BadRequestError)
        return {
          statusCode: 400,
          body: JSON.stringify({
            errorMessage: error.message,
            errorType: 'BadRequestError',
            ...(error.cause instanceof Error ? { causeMessage: error.cause.message } : {}),
          }),
        };
      log.error('handler.error', { errorMessage: error.message, stackTrace: error.stack });
      return { statusCode: 500, body: '' };
    }
  };

every repo whose api-gateway lambdas need a non-200 / non-json wire response re-writes this same
adapter. a shipped sdk feature eliminates the per-repo duplication + keeps the error-conversion
contract consistent.

scope / caveat

  • the prior middy stack also added owasp security headers + cors + v1/v2 event normalization;
    svc-notifications omitted those deliberately (these webhooks configured no cors, and neither
    twilio nor cloudfront depend on the security headers for the wire contract). the sdk feature
    can offer those as opt-in, but the core ask is just: let the handler own the wire response.

refs

  • svc-notifications branch beav/feat-declapract-upgrade,
    src/contract/handlers/utils/forApiGatewayPlain.ts (the hand-rolled adapter this would retire).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions