Skip to content

Docs: Version Mismatch Errors #4025

Open
@alexbaileyuk

Description

@alexbaileyuk

What were you searching in the docs?

There is a recommendation for esbuild and the CDK which excludes some packages when bundling function code.

new NodejsFunction(this, 'Function', {
  ...
  bundling: {
    externalModules: [
      '@aws-lambda-powertools/*',
      '@aws-sdk/*',
    ],
  }
});

This can cause version mismatches and cause unexpected behaviour in code if versions are not managed properly. Namely, when using instanceof as a condition, the result will always be false if the Lambda Layer version and package version do not match.

As an example, take this function definition deployed through the CDK:

import type { LambdaInterface } from '@aws-lambda-powertools/commons/types';
import { Logger } from '@aws-lambda-powertools/logger';
import { ParseError, parser } from '@aws-lambda-powertools/parser';
import { Tracer } from '@aws-lambda-powertools/tracer';
import { APIGatewayProxyEventSchema } from '@aws-lambda-powertools/parser/schemas';
import type { APIGatewayProxyResult, Context } from 'aws-lambda';
import { z, ZodError, ZodIssue } from 'zod';

const logger = new Logger();
const tracer = new Tracer();

export const requestSchema = APIGatewayProxyEventSchema.extend({
  queryStringParameters: z.object({
    message: z.string().max(10),
  }),
});

const handleZodError = (error: ZodError) => {
  const flattenedErrors = error.flatten((issue: ZodIssue) => ({
    field: issue.path.join('.'),
    issue: issue.message,
  }));

  return {
    statusCode: 422,
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      message: 'Validation error',
      data: { validationErrors: flattenedErrors.fieldErrors },
    }),
  };
};

export function httpErrorHandler(): MethodDecorator {
  return (_target, _propertyKey, descriptor: PropertyDescriptor) => {
    const originalMethod = descriptor.value;

    descriptor.value = async function (...args: unknown[]) {
      try {
        return await originalMethod.apply(this, args);
      } catch (error) {
        if (error instanceof Error) {
          logger.error('Error in API handler', error);
          tracer.addErrorAsMetadata(error);
        } else {
          logger.error('Unknown error in API handler', { error });
        }

        if (error instanceof ParseError && error.cause instanceof ZodError) {
          return handleZodError(error.cause);
        }

        if (error instanceof ZodError) {
          return handleZodError(error);
        }

        return {
          statusCode: 500,
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ message: 'Internal server error' }),
        };
      }
    };

    return descriptor;
  };
}

class Lambda implements LambdaInterface {
  @httpErrorHandler()
  @tracer.captureLambdaHandler({ captureResponse: true })
  @logger.injectLambdaContext({ logEvent: true })
  @parser({ schema: requestSchema })
  public async handler(event: z.infer<typeof requestSchema>, _context: Context): Promise<APIGatewayProxyResult> {
    logger.info('Processing item', { item: event.queryStringParameters.message });

    return {
      statusCode: 200,
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ message: 'Hello, world!' }),
    };
  }
}

const myFunction = new Lambda();
export const handler = myFunction.handler.bind(myFunction);

Taking the parser component at version 2.20.0, we can see that the Lambda Layer version is supposed to be 27:

$ aws ssm get-parameter --name /aws/service/powertools/typescript/generic/all/2.20.0 --region eu-west-1
{
    "Parameter": {
...
        "Value": "arn:aws:lambda:eu-west-1:094274105915:layer:AWSLambdaPowertoolsTypeScriptV2:27",
...
    }
}

Taking /test?message=testing126123gds as an example request, different behaviour is observed if different layer versions are used.

Parser Package Version Layer Version esbuild externalModules Result
2.20.0 26 ['@aws-sdk/*'] 422 Validation Error (correct)
2.20.0 26 ['@aws-lambda-powertools/', '@aws-sdk/'] 500 Internal Server Error ("wrong")
2.20.0 27 ['@aws-sdk/*'] 422 Validation Error (correct)
2.20.0 27 ['@aws-lambda-powertools/', '@aws-sdk/'] 422 Validation Error (correct)

This can become especially difficult to manage when installing components at different times. For example, let's say you install Parser one day with yarn add @aws-lambda-powertools/parser and then at a later date, you install the @aws-lambda-powertools/tracer component. You're now in a position where not only the packages might be misaligned but also the Lambda Layers required.

Is this related to an existing documentation section?

https://docs.powertools.aws.dev/lambda/typescript/latest/getting-started/lambda-layers/#how-to-use-with-infrastructure-as-code

How can we improve?

I was originally going to report this as a bug, however, it's probably expected really. You can't expect to use different versions of packages and get the same result.

Whilst it's possible to write a dirty unit test in my project to make sure the package versions align, fetching the layer version for a particular version requires AWS credentials which I don't want to generate every time I have to run tests locally. Therefore, I'm just going to rely on API tests to pick this up and stick a big notice in the readme. Not ideal but will probably do okay. Maybe it's possible to do this if the parameter was a publicly accessible parameter?

Got a suggestion in mind?

I think the best course of action would be to simply update the documentation where it talks about excluding modules from esbuild output. I think the best result we can expect is a clear notice that all the versions have to be aligned and the Lambda Layer has to align to that version.

Acknowledgment

  • I understand the final update might be different from my proposed suggestion, or refused.

Metadata

Metadata

Assignees

Labels

documentationImprovements or additions to documentationresearchingThis item is currently under consideration and we'll update once we have more info

Type

No type

Projects

Status

Working on it

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions