fastify / fastify-swagger

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

Include shared schemas in response #172

Closed PatrickHeneise closed 3 years ago

PatrickHeneise commented 5 years ago

I use a shared schema:

{
  "$id": "https://foo/common.json",
  "type": "object",
  "definitions": {
    "foo": {
      "$id": "#bar",
      "type": "object",
      "properties": {
        "id": { "type": "string" }
      }
    }
  }
}

And trying to use that schema in the response validation:

It works perfectly fine in a list/index route:

200: {
  type: 'array',
  items: {
    $ref: 'https://foo/common.json#bar'
  }
}

but not for get /:id

response: {
  200: 'https://foo/common.json#bar'
}

I'm trying to track down the issue but would appreciate a hint to where to start. Everything works fine if I add the schema "bar" individually before:

  fastify.addSchema({
    $id: 'bar',
    type: 'object',
    properties: {
      id: { type: 'number' }
    }
  })

...

works: 200: 'bar#'
mcollina commented 5 years ago

What does it mean “it works”? @Eomm is the master of shared schemas, he might take a look here as well.

PatrickHeneise commented 5 years ago

Using "bar#" (which is not a common/shared schema) works. Shared schema doesn't.

Eomm commented 5 years ago

This should work (with last fast-json-stringify installed):

response: {
  200: {$ref: 'https://foo/common.json#bar'}
}

When you write someId# (hash at the end) you are using the "replace way" of the shared schema feature, but if you write the $ref as you did, you are using the $ref-way so you need to use the standard schema definition.

PatrickHeneise commented 5 years ago

I thought I tested that case and it didn't work, but I'll try again in the morning.

PatrickHeneise commented 5 years ago
response: {
  200: { $ref: 'https://foo/common.js#bar' }
}

UnhandledPromiseRejectionWarning: TypeError: Cannot read property '$ref' of undefined

Eomm commented 5 years ago

Looking here: https://github.com/fastify/fastify-swagger/blob/c41d37cd4a2ceb6b1a667f3dd03f8ccf5f53e2bf/dynamic.js#L96

Maybe, the shared schemas are not provided to swagger.

I can't go deeper now, could you check it?

PatrickHeneise commented 5 years ago

when using 'bar#', the schema is transformed to an object, with the ref it's not.

I've decided to move forward with multiple schema files instead of common.json.

PatrickHeneise commented 5 years ago

Going back and forth experimenting with the schemas. I found some inconsistencies:

works:

body: { $ref: '/foo.json#/definitions/bar' }

fails:

response: {
  200: { $ref: '/foo.json#/definitions/bar' }
}

TypeError: Cannot read property '$ref' of undefined in fast-json-stringify/index.js:485:15

Then I tried

response: {
  200: '/foo.json#/definitions/bar'
}

Error: schema is invalid: data should be object,boolean in fast-json-stringify/index.js:30:17

Eomm commented 5 years ago

Yeah, that doesn't surprise me:

For the trouble:

PatrickHeneise commented 5 years ago

It's a long list ... I selected the path that might be the one you're looking for:

├─┬ fastify@2.3.0
│ ├── ajv@6.10.0 deduped
│ ├── UNMET DEPENDENCY ajv-merge-patch@^4.1.0
│ ├─┬ fast-json-stringify@1.15.2
│ │ ├── ajv@6.10.0 deduped

started the project from scratch, so the dependencies should all be up-to-date.

If you point me into the direction, I'm happy to help fix this, as I need it in my current project.

Eomm commented 5 years ago

Sorry @PatrickHeneise , I missed your comment. Did you solve meanwhile?

Otherwise, I will check this problem in next days

PatrickHeneise commented 5 years ago

No, I worked around the issue by placing the schema I need in /index.js and reference from there, but it's not pretty.

vladlen-volkov commented 4 years ago

I've almost the same problem. The swagger doesn't recognize some shared schemas. Here is a repo with reproducing of error.

There are 4 routes: GET /api/auth -> works fine, 'cause I wrap my shared schema in additional property "userSource" GET /api/chats/ -> is ok with shared schema

GET /api/auth-broken -> fails in UI with Could not resolve reference: undefined undefined PUT ​/api​/chats​/{chatId}​/messages -> fails in UI like a previous [despite of wrapping]

Are there any ways to avoid this? Or should we waiting for fixing?

dannysofftie commented 3 years ago

Came across the same issue today.

This works, when the response is of type array

response: {
 200: {
    description: 'Successful response',
    type: 'array',
      items: {
        $ref: '#Journal',
       },
   },
},

But this doesn't work when response is an object. I don't know if this is the intended behaviour.

response: {
 200: {
    description: 'Successful response',
    type: 'object',
    $ref: '#Journal',
   },
},

As a workaround, I used fastify instance to pull the already compiled schema and it works as expected: Option 1.

response: {
 200: {
    description: 'Successful response',
    type: 'object',
   ...(fastify.getSchema('#Journal') as object), // silence typescript
   },
},

Option 2.

response: {
 200: fastify.getSchema('#Journal')
},
pelepelin commented 3 years ago

Does anybody here have a full example of referencing the models from dynamic routes the Swagger way?

    "responses": {
      "500": {
        "schema": {
          "$ref": "#/definitions/ErrorModel"
        }
      }
    }

I've tried to put "definitions" into their place in the plugin options, but that did not work. Should it be a separate feature request?

pelepelin commented 3 years ago

So when I did

addSchema({
  $id: 'shared',
  definitions
});

then this response definition does not blow up fastify

500: {
      $ref: 'shared#/definitions/ErrorResponse'
    }

However, in this case the schema is nested and the path is transformed in output: "500":{"schema":{"$ref":"#/definitions/def-0/definitions/ErrorResponse"} which violates Swagger spec as definitions object must not be nested https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#definitionsObject

melroy89 commented 4 months ago

Coud somebody share an example of multiple responses? Eg. I want to define both 404 & 500 objects in a single shared definition. Eg. $ref: 'shared#/definitions/responses'. Example:

fastify.addSchema({
$id: 'shared',
type: 'object',
definitions: {
  responses: {
    type: 'array',
    properties: {
      404: {
        description: 'Not Found',
        type: 'object',
        properties: {
          message: { type: 'string' },
        },
        example: {
          message: 'Not found',
        }
      },
      500: {
        description: 'Internal Server Error',
        type: 'object',
        properties: {
          message: { type: 'string' },
        },
        example: {
          message: 'Unknown column....',
        }
      }
    }
  }
}

And use multiple responses in an schema:

// Example schema
{
  description: 'Example',
  response: {
      200: {
        description: 'Successful response',
        type: 'object',
        properties: {
          user_id: { type: 'number' },
          user_name: { type: 'string' },
          user_mail_address: { type: 'string' }
        },
        // And then use the shared responses (multiple)
        $ref: 'shared#/definitions/responses',
     }
  }
}

Too bad this doesn't work.. But it would be very cool, if it actually did work like this.

melroy89 commented 4 months ago

Using:

fastify.addSchema({
    $id: 'shared',
    type: 'object',
    definitions: {
      internalServerErrorResponse: {
        type: 'object',
        description: '500 Internal Server Error',
        properties: {
          message: {
            type: 'string',
            description: 'Error message',
            example: 'Some internal error message'
          }
        }
      }
    }
  })

And then use the shared definition:

    500: {
      $ref: 'shared#/definitions/internalServerErrorResponse'
    }

I will get:

image

melroy89 commented 4 months ago

OpenAPI v3 component responses also doesn't work in Fastify Swagger, so I created a ticket for that: https://github.com/fastify/fastify-swagger/issues/793

melroy89 commented 4 months ago

Now I'm using addSchema without definitions, but only a top level ID:

fastify.addSchema({
  $id: 'internalServerErrorResponse',
  type: 'object',       
  description: '500 Internal Server Error',
  properties: {
    message: {
      type: 'string',
      description: 'Error message',
      example: 'Some internal error message'
    }
  }
})

Then use it in your schema like this:

schema: {
  description: 'example',
  response: {
    500: {
      $ref: 'internalServerErrorResponse#'
    }
  }
}

This ALMOST works.. Except the description message still says "Default Response" in Swagger UI:

image