Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question - Composing multiple middleware #36

Closed
kimsagro1 opened this issue Oct 22, 2020 · 10 comments · Fixed by #37 or #38
Closed

Question - Composing multiple middleware #36

kimsagro1 opened this issue Oct 22, 2020 · 10 comments · Fixed by #37 or #38
Assignees
Labels
question Further information is requested

Comments

@kimsagro1
Copy link

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
Copy link

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

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

@issue-label-bot issue-label-bot bot added the question Further information is requested label Oct 22, 2020
@dbartholomae
Copy link
Owner

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.

@dbartholomae dbartholomae self-assigned this Oct 23, 2020
@kimsagro1
Copy link
Author

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
Copy link
Owner

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
Copy link
Owner

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
Copy link
Author

@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 dbartholomae reopened this Oct 25, 2020
@dbartholomae
Copy link
Owner

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

@dbartholomae dbartholomae mentioned this issue Oct 29, 2020
2 tasks
@dbartholomae
Copy link
Owner

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

@kimsagro1
Copy link
Author

@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
Copy link
Owner

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
2 participants