asteasolutions / zod-to-openapi

A library that generates OpenAPI (Swagger) docs from Zod schemas
MIT License
788 stars 52 forks source link

[v6.4.0] openapi() method does not let you override the "in" property for parameters #213

Closed GraysonCAdams closed 3 months ago

GraysonCAdams commented 3 months ago
  const userId = registry.registerParameter(
    'userId',
    z.string().openapi({
      param: {
        name: 'userId',
        in: 'query',
      },
      example: '1212121',
    })
  );

  registry.registerPath({
    method: 'get',
    path: '/user/',
    tags: ['Users],
    description: '',
    request: { params: z.object({ userId }) },
    security: [{ [bearerAuthName]: [] }],
    responses: createApiResponse(UserSchema, 'Success'),
  });

This code does not work as intended. It looks like if I specify "query" for "in", it just adds it as an additional property, versus overwriting the default.

This error gets thrown:

node:internal/process/esm_loader:34 internalBinding('errors').triggerUncaughtException( ^ ConflictError { message: 'Conflicting location for parameter userId', data: { key: 'in', values: [ 'query', 'path', 'query' ] } }

I reviewed the documentation and cannot find any mention of overwrites versus just adding additional properties. So is this not supported? How would I change the parameter from path-based to query-based if not this way?

AGalabov commented 3 months ago

@GraysonCAdams yes this cannot be overriden and it is not meant to be. The intended use is:

  1. If you want to register the parameter as it is (i.e it is saved under /components/parameters then it needs a "hardcoded" definition of where it would be used and what the name is (the in and name properties).
  2. So by that definition if you want to use the same thing - you would need to define a separate parameter that goes in the query instead

However seeing your example I believe what you are trying to achieve is the following:

// Register a schema so that it can be reused (the example as well)
const userIdSchema = z.string().openapi('UserId',{
  example: '1212121',
});

registry.registerPath({
  method: 'get',
  path: '/user/',
  tags: ['Users],
  description: '',
  request: {
    params: z.object({ userId: userIdSchema }), // use it as a path parameter
    query: z.object({ userId: userIdSchema }), // use it as a query parameter
  },
  security: [{ [bearerAuthName]: [] }],
  responses: createApiResponse(UserSchema, 'Success'),
});

Notice how there is no need to manually provide the in/name properties in that case - they are infered from the usage of the request.params and/or request.query.