Skip to content

Docs: Version Mismatch Errors #4025

Closed
@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

completedThis item is complete and has been merged/shippeddocumentationImprovements or additions to documentation

Type

No type

Projects

Status

Shipped

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions