jlalmes / trpc-openapi

OpenAPI support for tRPC 🧩
https://www.npmjs.com/package/trpc-openapi
MIT License
2.12k stars 136 forks source link

GET request interference for query parameters #425

Open ronsigter opened 7 months ago

ronsigter commented 7 months ago

I have two API endpoints:

  getUserById: loggedInProcedure
    .meta({
      openapi: {
        tags: [USER_TAG],
        method: 'GET',
        path: '/users/{id}', /* 👈 */
        contentTypes: ['application/json'],
        summary: 'Gets user by ID',
        protect: true,

        errors: [
          {
            type: 'NOT_FOUND',
            errorMessage: 'User not found.',
          },
        ],
      },
    })
    .input(UsersSchema.getUserByIdParamsSchema) /* 👈 z.object({ id: z.string().uuid() } */
    .output(UsersSchema.getUserByIdDTOSchema)
    .query(async ({ input }) => {
      return UsersService.getUserById(input);
    }),

AND

  getUserClaim: loggedInProcedure
    .meta({
      openapi: {
        tags: [USER_TAG],
        method: 'GET',
        path: '/users/claim', /* 👈 */
        contentTypes: ['application/json'],
        summary: 'Gets the current user to be claimed',
        protect: true,

        errors: [],
      },
    })
    .input(UsersSchema.getUserClaimParamsSchema) /* 👈 z.void() */
    .output(UsersSchema.getUserClaimDTOSchema)
    .query(async ({ ctx }) => {
      return UsersService.getUserClaim({ id: ctx.user.id });
    }),

And when I try to hit /users/claim

I get:

{
  "message": "Input validation failed",
  "code": "BAD_REQUEST",
  "issues": [
    {
      "validation": "uuid",
      "code": "invalid_string",
      "message": "Invalid uuid",
      "path": [
        "id"
      ]
    }
  ]
}

It works if I remove the /users/{id} endpoint OR if I put the path: /users/claim on top of /users/{id}

Any ideas as to why this happens? Thanks!

DJanocha commented 7 months ago

So you have 2 endpoints:

  1. '/users/{id}'
  2. '/users/claim'.

When you try to access the '/users/claim' route, trpc-openapi checks all of the routes, one by one, to check if there is a one that matches the endpoint you are trying to reach. If the matching route is found, searching stops(it searches until it finds). I believe both your endpoint declarations (listed above) match the endpoint you are trying to reach so the first one wins.

It looks similar to endpoint declaration in pure express.js where, if you have two routes declarations, and one is a subset of the other, you need to declare them from the most specific to the most broad. In your case ‘/users/{id}’ is broader than ‘/users/claim’ because there is enormous combination of string you can theoretically use as id (eg “claim”) vs single word “claim”. An example of even more broad endpoint declaration is ‘/’. in my understanding’ if you declare endpoint ‘/’ and put it before your two declarations, it will shadow/take precedence over your two endpoint declarations because it matches the ‘/users/claim’ as your endpoints and is declared first. hope it helps anyhow. For further ingestion, look for ‘dynamic path segments’ or something like that in express.js docs.

I will check later if I’m 100% sure about that shadowing. Happy coding!

ronsigter commented 7 months ago

@DJanocha thanks for this! Will take a look at this shadowing 👌

For now, the workaround is, as you suggest, to make it from the most specific going to a broader scope.