fastify / fastify-swagger

Swagger documentation generator for Fastify
MIT License
889 stars 199 forks source link

Using custom AJV keywords results in openAPI error #794

Open SeanReece opened 4 months ago

SeanReece commented 4 months ago

Prerequisites

Fastify version

4.26.2

Plugin version

8.14.0

Node.js version

20

Operating system

macOS

Operating system version (i.e. 20.04, 11.3, 10)

14.3

Description

When using custom keywords with AJV, those keywords are added to the resulting swagger/openAPI definition which results in a "structural error" being reported in the swagger editor.

Results in open api spec giving this validation error

image

Normally I wouldn't care about an error like this but we have teams attempting to generate Java clients using swagger generator and it throws an error because of this structure error.

Steps to Reproduce

Example

import Fastify from 'fastify'
import fastifySwagger from '@fastify/swagger'

// Any custom keyword
const evenKeyword: FuncKeywordDefinition = {
  keyword: 'even',
  type: 'number',
  schemaType: 'boolean',
  compile: (schema, parentSchema) => {
    return (data) => {
      return data % 2 === 0
    }
  },
}

// Add our custom keyword to fastify
const fastify = Fastify({
  logger: true,
  ajv: {
    customOptions: {
      keywords: [evenKeyword]
    }
  }
})

// Register @fastify/swagger
await fastify.register(fastifySwagger, { openapi: { ... } })

// Define our schema using our custom keyword
const schema = {
  body: {
    type: 'object',
    properties: {
      value: { type: 'number', even: true },
    },
  },
}

// Declare our route using schema
fastify.post('/', { schema }, function (request, reply) {
  reply.send({ hello: 'world' })
})

// Run the server!
fastify.listen({ port: 3000 }, function (err, address) {...})

The resulting swagger json then contains the custom keyword:

Trimmed for brevity

{
"openapi": "3.0.3",
"paths": {
"/": {
"post": {
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"value": {
"type": "number",
"even": true                <-- this is not a valid openapi JSON Schema property
}
}
}
}
}
}
}
}
},
}

Expected Behavior

I would expect @fastify/swagger to generate valid openAPI spec regardless of AJV keywords used, but I understand this may be a difficult problem considering AJV keywords might need to be added which add valid openAPI keywords (because of openAPIs "extended" JSON Schema spec I think this is a non-trivial issue)

I'm thinking about solving it this way using the "transform" function to remove any invalid keywords, but this seems fragile and inefficient. Not sure if there is something better

  await fastify.register(fastifySwagger, {
    openapi: {
      transform(transform) {
        if (transform.schema && !transform.schema.hide) {
          // Deep clone original schema (unsure if necessary)
          transform.schema = structuredClone(transform.schema)
          // TODO do this in a more flexible way
          delete transform.schema.body.properties.value.even
        }

        return transform
      },
    },
  })