dbartholomae / lambda-middleware

A collection of middleware for AWS lambda functions.
https://dbartholomae.github.io/lambda-middleware/
MIT License
151 stars 18 forks source link

Question - Composing multiple middleware #36

Closed kimsagro1 closed 3 years ago

kimsagro1 commented 3 years ago

I'm trying to expose a wrapper function so that all my lambda handlers have the same middleware applied to them.

const wrapper = (handler) =>
  compose(
    // add cors headers last so even error responses from the
    // errorHandler middleware have cors headers applied
    cors(),
    errorHandler(),
    jsonSerializer(),
    classValidator(),
  )(handler)

However I'm struggling to get the type information to line up.

Is it possible to chain these middlewares like this as you can do using middy?

issue-label-bot[bot] commented 3 years ago

Issue-Label Bot is automatically applying the label question to this issue, with a confidence of 0.75. Please mark this comment with :thumbsup: or :thumbsdown: to give our bot feedback!

Links: app homepage, dashboard and code for this bot.

dbartholomae commented 3 years ago

It should work like this. if you configure the middlewares correctly. E. g. classValidator requires a bodyType. You could even write it as:

const wrapper = compose(
    // add cors headers last so even error responses from the
    // errorHandler middleware have cors headers applied
    cors(),
    errorHandler(),
    jsonSerializer(),
    classValidator(),
  )

If it is not working, I would need to see the actual error and more context.

kimsagro1 commented 3 years ago

So the error I'm getting is

Argument of type '<Event_1 extends { headers: { [name: string]: string; }; httpMethod: string; }>(handler: PromiseHandler<Event_1, APIGatewayProxyResult>) => (event: Event_1, context: Context) => Promise<...>' is not assignable to parameter of type '(x: unknown) => unknown'.
  Types of parameters 'handler' and 'x' are incompatible.
    Type 'unknown' is not assignable to type 'PromiseHandler<{ headers: { [name: string]: string; }; httpMethod: string; }, APIGatewayProxyResult>'

I have created a codesandbox that demonstrates the issue

dbartholomae commented 3 years ago

Thanks! This is related to TypeScript not being able to infer some generic types yet. I've changed the types in the newest versions of packages so that the error will not appear, as generics are only needed to cover some rather obscure cases of usage. I will post a list of new versions once all have been published.

dbartholomae commented 3 years ago

Here's the list of updated versions: "@lambda-middleware/class-validator": "^2.0.0", "@lambda-middleware/compose": "^1.1.0", "@lambda-middleware/cors": "^2.0.0", "@lambda-middleware/json-serializer": "^2.0.0", "@lambda-middleware/jwt-auth": "^1.0.2", "@lambda-middleware/http-error-handler": "^2.0.0",

kimsagro1 commented 3 years ago

@dbartholomae This is still not working for me. Now I get the error

Argument of type '<R>(handler: PromiseHandler<WithBody<APIGatewayProxyEvent, NameBody>, R>) => (event: APIGatewayProxyEvent, context: Context) => Promise<...>' is not assignable to parameter of type '(handler: PromiseHandler<WithBody<APIGatewayProxyEvent, NameBody>, unknown>) => PromiseHandler<APIGatewayProxyEvent, { ...; } | ... 1 more ... | undefined>'.
  Call signature return types '(event: APIGatewayProxyEvent, context: Context) => Promise<unknown>' and 'PromiseHandler<APIGatewayProxyEvent, { [key: string]: JSONPrimitive; } | JSONObject[] | undefined>' are incompatible.
    Type 'Promise<unknown>' is not assignable to type 'Promise<{ [key: string]: JSONPrimitive; } | JSONObject[] | undefined>'.
      Type 'unknown' is not assignable to type '{ [key: string]: JSONPrimitive; } | JSONObject[] | undefined'.
        Type 'unknown' is not assignable to type 'JSONObject[]'.ts(2345)

I have created a codesandbox that demonstrates the issue

I did some investigation and I think it only occurs when you set "strict": true in your tsconfig.json

dbartholomae commented 3 years ago

Thanks, I'll adapt it so it also work in strict mode next week :)

dbartholomae commented 3 years ago

Unfortunately, TypeScript does not possess the capabilities to resolve the generics in a compose function in strict mode (see https://github.com/microsoft/TypeScript/issues/29904 point 1). I've added composeHandler as a helper function that can be used to achieve full type safety.

kimsagro1 commented 3 years ago

@dbartholomae composeHandler is not working for me under specific scenarios

Manual function chaining works with no compilation errors

const wrapper_manual = 
  cors()(
    errorHandler()(
      jsonSerializer()(
        bodyParser()(
          zodValidator(nameSchema)(
            handler
          )
        )
      )
    )
  );

composeHandler works as long as I invoke the second last function with the handler

const composeHandlerWrapper = composeHandler(
  cors(),
  errorHandler(),
  jsonSerializer(),
  bodyParser(),
  zodValidator(nameSchema)(handler)
)

composeHandler causes a compilation error when invoked as per the documentation

const composeHandlerWrapper = composeHandler(
  cors(),
  errorHandler(),
  jsonSerializer(),
  bodyParser(),
  zodValidator(nameSchema),
  handler
)
Argument of type '<TEvent extends APIGatewayProxyEvent, TResult>(handler: PromiseHandler<WithBody<TEvent, object>, TResult>) => (event: TEvent, context: Context) => Promise<TResult>' is not assignable to parameter of type '(x: unknown) => PromiseHandler<APIGatewayProxyEvent, { [key: string]: JSONPrimitive; } | JSONObject[] | undefined>'.
  Types of parameters 'handler' and 'x' are incompatible.
    Type 'unknown' is not assignable to type 'PromiseHandler<WithBody<APIGatewayProxyEvent, object>, { [key: string]: JSONPrimitive; } | JSONObject[] | undefined>'

I have created a codesandbox that demonstrates the issue

dbartholomae commented 3 years ago

That's the same problem - unfortunately currently not fully solvable in TypeScript. I would recommend going with the solution where the handler is wrapped for now until TypeScript has better inference for generics.