fastify / fastify-swagger

Swagger documentation generator for Fastify
MIT License
915 stars 201 forks source link

Hide schema in swagger dcoument #686

Closed imjuni closed 1 year ago

imjuni commented 1 year ago

Prerequisites

🚀 Feature Proposal

Schema hide field add on option.

const server = fastify();

await server.register(fastifySwagger, {
  openapi: {
    info: {
      title: "Maeum boilerplate",
      description: "Maeum boilerplate Swagger Document",
      version: "0.2.0",
    },
  },
  // add new option
  hideSchemas: [
    "IReqPokeDetailQuerystring",
    "IReqPokeDetailParams",
    "IConfiguration",
    "IServer",
    "IEndpoint",
  ],
});

await server.register(fastifySwaggerUI, swaggerUiConfig());

server.addSchema({ $id: 'IReqPokeDetailQuerystring' /* ... another configuration */ });
server.addSchema({ $id: 'IReqPokeDetailParams' /* ... another configuration */ });
server.addSchema({ $id: 'IConfiguration' /* ... another configuration */ });
server.addSchema({ $id: 'IServer' /* ... another configuration */ });
server.addSchema({ $id: 'IEndpoint' /* ... another configuration */ });

server.get('/pokemon/:name', {
  querystring: { $ref: 'IReqPokeDetailQuerystring' }
  params: { $ref: 'IReqPokeDetailParams' }
}, () => { /** server code */ });

Motivation

I prefer to add and use all schemas on fastify schema store. So I meet some problem like below,

fastify-swagger-schemas

If all schemas are registered in fastify schema store, all schemas are exposed to the document as shown in the picture. This problem make to complex document.

So I think that schema Request Schema, Server Config have to hide for document reader.

Add content at 2022-11-02

Case01

server.get('/pokemon/:name', {
  querystring: { type: 'object', properties: { id: { type: 'number' } } },
  params: { type: 'object', properties: { name: { type: 'string' } } }
}, () => { /** server code */ });

Case02

server.addSchema({ $id: 'IReqPokeDetailQuerystring' /* ... another configuration */ });
server.addSchema({ $id: 'IReqPokeDetailParams' /* ... another configuration */ });

server.get('/pokemon/:name', {
  querystring: { $ref: 'IReqPokeDetailQuerystring' }
  params: { $ref: 'IReqPokeDetailParams' }
}, () => { /** server code */ });

Case01, Case02 exactly same work but generate difference document. The more content the document has, the bigger the difference will be. So I hope add hideSchemas option in fastify-swagger.

Add content 2022-11-03

const fastify = require('fastify');
const server = fastify();

// case 01. error raise when server start
server.addSchema({
  $id: 'IReqPokeDetailQuerystring',
  type: 'object',
  properties: {
    tid: {
      $ref: 'missing reference!',
    },
  },
  required: ['tid'],
});

// case 02. error raise when request call
server.get(
  '/pokemon/:name',
  {
    schema: {
      querystring: {
        type: 'object',
        properties: {
          tid: {
            $ref: 'missing reference!',
          },
        },
        required: ['tid'],
      },
    },
  },
  () => {
    /* ... */
  },
);

I prefer what case01 then case02. Because more than quickly aware error from validation schema.

Example

I create example repo.

git clone https://github.com/imjuni/maeum.git
cd maeum
npm install
npm run dev

And you can see swagger document http://localhost:7878/swager.io

mcollina commented 1 year ago

I don't see how this could work. Aren't you using those types in the route definition? They are needed for the schema of query and params to work.

imjuni commented 1 year ago

@mcollina Thank you for your kind advice!

Your right, I found some problem from swagger (OpenAPI v2). So I moved the logic of hiding the schema to the last time of the document building.

    ref = Ref()
    swaggerObject.definitions = prepareSwaggerDefinitions({
      ...swaggerObject.definitions,
      ...(ref.definitions().definitions)
    }, ref)

    swaggerObject.paths = {}
    for (const route of routes) {
      // document building ...
    }

    // document building complete, don't effect to reference finding logic
    const definitions = Object.entries(swaggerObject.definitions)
      .filter(([, schema]) => defOpts.hideSchemas.findIndex(hideSchema => hideSchema === schema.title) < 0)
      .reduce((defs, [$id, schema]) => ({ ...defs, [$id]: schema }), {})
    swaggerObject.definitions = definitions

OpenAPI v3 works find, because reference find from openapiObject.definitions not openapiObject.components.schemas.

Uzlopak commented 1 year ago

I reopen this because i think it adresses a valid issue.

From the closed PR: Imho you are just removing the DTOs from the final document. I mean your unit tests actually show it beatifully. So despite you added schema-0, schema-1 and schema-2 you manually remove schema-1, and because you dont use it in your schema building the schema succeeds . If we would remove schema-2, we should get a FST_ERR_SCH_SERIALIZATION_BUILD-Error.

We should actually fist add a flag which removes unused definitions from the final swagger-documentation.

Example unit test:

test('remove unused schemas', async t => {
  t.plan(2)
  const fastify = Fastify()
  await fastify.register(fastifySwagger, { ...openapiOption, removeUnusedSchemas: true })

  fastify.addSchema({
    $id: 'schema-01',
    type: 'object',
    properties: {
      id: { type: 'number', description: 'data id' }
    },
    required: ['id']
  })

  fastify.addSchema({
    $id: 'schema-02',
    type: 'object',
    properties: {
      id: { type: 'number', description: 'data id' }
    },
    required: ['id']
  })

  fastify.addSchema({
    $id: 'schema-03',
    type: 'object',
    properties: {
      id: { type: 'number', description: 'data id' }
    },
    required: ['id']
  })

  fastify.get('/', {
    schema: {
      querystring: { $ref: 'schema-02' },
      response: {
        200: {
          type: 'object'
        }
      }
    }
  }, () => {})

  await fastify.ready()

  const openapiObject = fastify.swagger()
  await Swagger.validate(openapiObject)

  const definitions = openapiObject.components.schemas
  t.ok(definitions)
  t.same(definitions, {
    'def-2': {
      type: 'object',
      properties: {
        id: { type: 'number', description: 'data id' }
      },
      required: ['id'],
      title: 'schema-03'
    }
  })
})

In a second step, we should implement a flag, which forces to resolve specified DTOs. And then remove them from being exposed in the final swagger document.

climba03003 commented 1 year ago

If we would remove schema-2, we should get a FST_ERR_SCH_SERIALIZATION_BUILD-Error.

This plugin never touch the schema store of fastify which means the error will never raise.

The whole issue is about the final output of document which I think it is a non-issue anyway. You can always mutate the output on your needs because the current UI already splitted from this package. That being said, it is one of the use-case when https://github.com/fastify/fastify-swagger-ui/pull/20 landed.

climba03003 commented 1 year ago

Released https://github.com/fastify/fastify-swagger-ui/commit/38a0de291058bf2f2a316ec2570eec950704360a I believe it can solve this issue. If not, feel free to reopen again.