@fastify/swagger
Advanced tools
Comparing version 8.14.0 to 8.15.0
@@ -61,2 +61,8 @@ import { FastifyPluginCallback, FastifySchema, RouteOptions, onRequestHookHandler, preHandlerHookHandler } from 'fastify'; | ||
type SwaggerDocumentObject = { | ||
swaggerObject: Partial<OpenAPIV2.Document>; | ||
} | { | ||
openapiObject: Partial<OpenAPIV3.Document | OpenAPIV3_1.Document>; | ||
} | ||
type SwaggerTransform = <S extends FastifySchema = FastifySchema>({ | ||
@@ -66,4 +72,3 @@ schema, | ||
route, | ||
swaggerObject, | ||
openapiObject, | ||
...documentObject | ||
}: { | ||
@@ -73,6 +78,6 @@ schema: S; | ||
route: RouteOptions; | ||
swaggerObject: Partial<OpenAPIV2.Document>; | ||
openapiObject: Partial<OpenAPIV3.Document | OpenAPIV3_1.Document>; | ||
}) => { schema: FastifySchema; url: string } | ||
} & SwaggerDocumentObject) => { schema: FastifySchema; url: string } | ||
type SwaggerTransformObject = (documentObject: SwaggerDocumentObject) => Partial<OpenAPIV2.Document> | Partial<OpenAPIV3.Document | OpenAPIV3_1.Document>; | ||
type FastifySwagger = FastifyPluginCallback<fastifySwagger.SwaggerOptions> | ||
@@ -153,6 +158,3 @@ | ||
*/ | ||
transformObject?: ({ swaggerObject, openapiObject }: { | ||
swaggerObject: Partial<OpenAPIV2.Document>; | ||
openapiObject: Partial<OpenAPIV3.Document | OpenAPIV3_1.Document>; | ||
}) => Partial<OpenAPIV2.Document> | Partial<OpenAPIV3.Document | OpenAPIV3_1.Document>; | ||
transformObject?: SwaggerTransformObject; | ||
@@ -159,0 +161,0 @@ refResolver?: { |
@@ -118,32 +118,2 @@ 'use strict' | ||
function transformDefsToComponents (jsonSchema) { | ||
if (typeof jsonSchema === 'object' && jsonSchema !== null) { | ||
// Handle patternProperties, that is not part of OpenAPI definitions | ||
if (jsonSchema.patternProperties) { | ||
jsonSchema.additionalProperties = Object.values(jsonSchema.patternProperties)[0] | ||
delete jsonSchema.patternProperties | ||
} else if (jsonSchema.const !== undefined) { | ||
// OAS 3.1 supports `const` but it is not supported by `swagger-ui` | ||
// https://swagger.io/docs/specification/data-models/keywords/ | ||
jsonSchema.enum = [jsonSchema.const] | ||
delete jsonSchema.const | ||
} | ||
Object.keys(jsonSchema).forEach(function (key) { | ||
if (key === 'properties') { | ||
Object.keys(jsonSchema[key]).forEach(function (prop) { | ||
jsonSchema[key][prop] = transformDefsToComponents(jsonSchema[key][prop]) | ||
}) | ||
} else if (key === '$ref') { | ||
jsonSchema[key] = jsonSchema[key].replace('definitions', 'components/schemas') | ||
} else if (key === '$id' || key === '$schema') { | ||
delete jsonSchema[key] | ||
} else { | ||
jsonSchema[key] = transformDefsToComponents(jsonSchema[key]) | ||
} | ||
}) | ||
} | ||
return jsonSchema | ||
} | ||
function convertExamplesArrayToObject (examples) { | ||
@@ -164,3 +134,3 @@ return examples.reduce((examplesObject, example, index) => { | ||
function plainJsonObjectToOpenapi3 (container, jsonSchema, externalSchemas, securityIgnores = []) { | ||
const obj = transformDefsToComponents(resolveLocalRef(jsonSchema, externalSchemas)) | ||
const obj = convertJsonSchemaToOpenapi3(resolveLocalRef(jsonSchema, externalSchemas)) | ||
let toOpenapiProp | ||
@@ -297,3 +267,3 @@ switch (container) { | ||
function resolveBodyParams (body, schema, consumes, ref) { | ||
const resolved = transformDefsToComponents(ref.resolve(schema)) | ||
const resolved = convertJsonSchemaToOpenapi3(ref.resolve(schema)) | ||
if ((Array.isArray(consumes) && consumes.length === 0) || consumes === undefined) { | ||
@@ -319,3 +289,3 @@ consumes = ['application/json'] | ||
const schemasPath = '#/components/schemas/' | ||
let resolved = transformDefsToComponents(ref.resolve(schema)) | ||
let resolved = convertJsonSchemaToOpenapi3(ref.resolve(schema)) | ||
@@ -346,3 +316,3 @@ // if the resolved definition is in global schema | ||
const rawJsonSchema = fastifyResponseJson[statusCode] | ||
const resolved = transformDefsToComponents(ref.resolve(rawJsonSchema)) | ||
const resolved = convertJsonSchemaToOpenapi3(ref.resolve(rawJsonSchema)) | ||
@@ -473,20 +443,78 @@ /** | ||
function prepareOpenapiSchemas (schemas, ref) { | ||
return Object.entries(schemas) | ||
.reduce((res, [name, schema]) => { | ||
const _ = { ...schema } | ||
const resolved = transformDefsToComponents(ref.resolve(_, { externalSchemas: [schemas] })) | ||
function convertJsonSchemaToOpenapi3 (jsonSchema) { | ||
if (typeof jsonSchema !== 'object' || jsonSchema === null) { | ||
return jsonSchema | ||
} | ||
// Swagger doesn't accept $id on /definitions schemas. | ||
// The $ids are needed by Ref() to check the URI so we need | ||
// to remove them at the end of the process | ||
// definitions are added by resolve but they are replace by components.schemas | ||
delete resolved.$id | ||
delete resolved.definitions | ||
if (Array.isArray(jsonSchema)) { | ||
return jsonSchema.map(convertJsonSchemaToOpenapi3) | ||
} | ||
res[name] = resolved | ||
return res | ||
}, {}) | ||
const openapiSchema = { ...jsonSchema } | ||
for (const key of Object.keys(openapiSchema)) { | ||
const value = openapiSchema[key] | ||
if (key === '$id' || key === '$schema' || key === 'definitions') { | ||
// TODO: this breaks references to the definition properties | ||
delete openapiSchema[key] | ||
continue | ||
} | ||
if (key === '$ref') { | ||
openapiSchema.$ref = value.replace('definitions', 'components/schemas') | ||
continue | ||
} | ||
if (key === 'const') { | ||
// OAS 3.1 supports `const` but it is not supported by `swagger-ui` | ||
// https://swagger.io/docs/specification/data-models/keywords/ | ||
// TODO: check if enum property already exists | ||
// TODO: this breaks references to the const property | ||
openapiSchema.enum = [openapiSchema.const] | ||
delete openapiSchema.const | ||
continue | ||
} | ||
if (key === 'patternProperties') { | ||
// TODO: check if additionalProperties property already exists | ||
// TODO: this breaks references to the additionalProperties properties | ||
// TODO: patternProperties actually allowed in the openapi schema, but should | ||
// always start with "x-" prefix | ||
openapiSchema.additionalProperties = Object.values(openapiSchema.patternProperties)[0] | ||
delete openapiSchema.patternProperties | ||
continue | ||
} | ||
if (key === 'properties') { | ||
openapiSchema[key] = {} | ||
for (const propertyName of Object.keys(value)) { | ||
const propertyJsonSchema = value[propertyName] | ||
const propertyOpenapiSchema = convertJsonSchemaToOpenapi3(propertyJsonSchema) | ||
openapiSchema[key][propertyName] = propertyOpenapiSchema | ||
} | ||
continue | ||
} | ||
openapiSchema[key] = convertJsonSchemaToOpenapi3(value) | ||
} | ||
return openapiSchema | ||
} | ||
function prepareOpenapiSchemas (jsonSchemas, ref) { | ||
const openapiSchemas = {} | ||
for (const schemaName of Object.keys(jsonSchemas)) { | ||
const jsonSchema = { ...jsonSchemas[schemaName] } | ||
const resolvedJsonSchema = ref.resolve(jsonSchema, { externalSchemas: [jsonSchemas] }) | ||
const openapiSchema = convertJsonSchemaToOpenapi3(resolvedJsonSchema) | ||
resolveSchemaExamplesRecursive(openapiSchema) | ||
openapiSchemas[schemaName] = openapiSchema | ||
} | ||
return openapiSchemas | ||
} | ||
module.exports = { | ||
@@ -493,0 +521,0 @@ prepareDefaultOptions, |
@@ -24,3 +24,3 @@ # Migration | ||
| uiConfig | {} | Configuration options for [Swagger UI](https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/configuration.md). Must be literal values, see [#5710](https://github.com/swagger-api/swagger-ui/issues/5710).| | ||
| uiHooks | {} | Additional hooks for the documentation's routes. You can provide the `onRequest` and `preHandler` hooks with the same [route's options](https://fastify.dev/docs/latest/Routes/#options) interface.| | ||
| uiHooks | {} | Additional hooks for the documentation's routes. You can provide the `onRequest` and `preHandler` hooks with the same [route's options](https://fastify.dev/docs/latest/Reference/Routes/#options) interface.| | ||
@@ -27,0 +27,0 @@ The `baseDir` option is new and is only needed if external spec files should be |
{ | ||
"name": "@fastify/swagger", | ||
"version": "8.14.0", | ||
"version": "8.15.0", | ||
"description": "Serve Swagger/OpenAPI documentation for Fastify, supporting dynamic generation", | ||
@@ -47,3 +47,3 @@ "main": "index.js", | ||
"fastify": "^4.0.0", | ||
"fluent-json-schema": "^4.0.0", | ||
"fluent-json-schema": "^5.0.0", | ||
"joi": "^17.6.0", | ||
@@ -54,3 +54,3 @@ "joi-to-json": "^4.0.0", | ||
"tap": "^16.2.0", | ||
"tsd": "^0.30.1" | ||
"tsd": "^0.31.0" | ||
}, | ||
@@ -57,0 +57,0 @@ "dependencies": { |
@@ -11,3 +11,3 @@ # @fastify/swagger | ||
Following plugins serve swagger/openapi front-ends based on the swagger definitions generated by this plugin: | ||
Following plugins serve Swagger/OpenAPI front-ends based on the swagger definitions generated by this plugin: | ||
@@ -21,2 +21,3 @@ - [@fastify/swagger-ui](https://github.com/fastify/fastify-swagger-ui) | ||
## Install | ||
``` | ||
@@ -44,4 +45,5 @@ npm i @fastify/swagger | ||
## Usage | ||
Add it to your project with `register`, pass it some options, call the `swagger` API, and you are done! | ||
Add it to your project with `register`, pass it some options, call the `swagger` API, and you are done! Below an example of how to configure the OpenAPI v3 specification with Fastify Swagger: | ||
```js | ||
@@ -51,3 +53,4 @@ const fastify = require('fastify')() | ||
await fastify.register(require('@fastify/swagger'), { | ||
swagger: { | ||
openapi: { | ||
openapi: '3.0.0', | ||
info: { | ||
@@ -58,10 +61,8 @@ title: 'Test swagger', | ||
}, | ||
externalDocs: { | ||
url: 'https://swagger.io', | ||
description: 'Find more info here' | ||
}, | ||
host: 'localhost', | ||
schemes: ['http'], | ||
consumes: ['application/json'], | ||
produces: ['application/json'], | ||
servers: [ | ||
{ | ||
url: 'http://localhost:3000', | ||
description: 'Development server' | ||
} | ||
], | ||
tags: [ | ||
@@ -71,20 +72,14 @@ { name: 'user', description: 'User related end-points' }, | ||
], | ||
definitions: { | ||
User: { | ||
type: 'object', | ||
required: ['id', 'email'], | ||
properties: { | ||
id: { type: 'string', format: 'uuid' }, | ||
firstName: { type: 'string' }, | ||
lastName: { type: 'string' }, | ||
email: {type: 'string', format: 'email' } | ||
components: { | ||
securitySchemes: { | ||
apiKey: { | ||
type: 'apiKey', | ||
name: 'apiKey', | ||
in: 'header' | ||
} | ||
} | ||
}, | ||
securityDefinitions: { | ||
apiKey: { | ||
type: 'apiKey', | ||
name: 'apiKey', | ||
in: 'header' | ||
} | ||
externalDocs: { | ||
url: 'https://swagger.io', | ||
description: 'Find more info here' | ||
} | ||
@@ -99,2 +94,3 @@ } | ||
summary: 'qwerty', | ||
security: [{ apiKey: [] }], | ||
params: { | ||
@@ -136,10 +132,5 @@ type: 'object', | ||
} | ||
}, | ||
security: [ | ||
{ | ||
"apiKey": [] | ||
} | ||
] | ||
} | ||
} | ||
}, (req, reply) => {}) | ||
}, (req, reply) => { }) | ||
@@ -149,2 +140,19 @@ await fastify.ready() | ||
``` | ||
<a name="usage.fastify.autoload"></a> | ||
### With `@fastify/autoload` | ||
You need to register `@fastify/swagger` before registering routes. | ||
```js | ||
const fastify = require('fastify')() | ||
const fastify = fastify() | ||
await fastify.register(require('@fastify/swagger')) | ||
fastify.register(require("@fastify/autoload"), { | ||
dir: path.join(__dirname, 'routes') | ||
}) | ||
await fastify.ready() | ||
fastify.swagger() | ||
``` | ||
<a name="api"></a> | ||
@@ -151,0 +159,0 @@ ## API |
@@ -22,3 +22,3 @@ 'use strict' | ||
fastify.register(async (instance) => { | ||
instance.addSchema({ $id: 'Order', type: 'object', properties: { id: { type: 'integer' } } }) | ||
instance.addSchema({ $id: 'Order', type: 'object', properties: { id: { type: 'integer', examples: [25] } } }) | ||
instance.post('/', { schema: { body: { $ref: 'Order#' }, response: { 200: { $ref: 'Order#' } } } }, () => {}) | ||
@@ -32,2 +32,3 @@ }) | ||
t.match(Object.keys(openapiObject.components.schemas), ['Order']) | ||
t.equal(openapiObject.components.schemas.Order.properties.id.example, 25) | ||
@@ -72,3 +73,3 @@ await Swagger.validate(openapiObject) | ||
fastify.register(async (instance) => { | ||
instance.addSchema({ $id: 'OrderItem', type: 'object', properties: { id: { type: 'integer' } } }) | ||
instance.addSchema({ $id: 'OrderItem', type: 'object', properties: { id: { type: 'integer' } }, examples: [{ id: 1 }] }) | ||
instance.addSchema({ $id: 'ProductItem', type: 'object', properties: { id: { type: 'integer' } } }) | ||
@@ -90,2 +91,3 @@ instance.addSchema({ $id: 'Order', type: 'object', properties: { products: { type: 'array', items: { $ref: 'OrderItem' } } } }) | ||
t.equal(schemas.Order.properties.products.items.$ref, '#/components/schemas/OrderItem') | ||
t.match(schemas.OrderItem.example, { id: 1 }) | ||
@@ -100,3 +102,3 @@ await Swagger.validate(openapiObject) | ||
instance.addSchema({ $id: 'schemaA', type: 'object', properties: { id: { type: 'integer' } } }) | ||
instance.addSchema({ $id: 'schemaB', type: 'object', properties: { id: { type: 'string' } } }) | ||
instance.addSchema({ $id: 'schemaB', type: 'object', properties: { id: { type: 'string', examples: ['ABC'] } } }) | ||
instance.addSchema({ $id: 'schemaC', type: 'object', properties: { a: { type: 'array', items: { $ref: 'schemaA' } } } }) | ||
@@ -120,2 +122,3 @@ instance.addSchema({ $id: 'schemaD', type: 'object', properties: { b: { $ref: 'schemaB' }, c: { $ref: 'schemaC' } } }) | ||
t.equal(schemas.schemaD.properties.c.$ref, '#/components/schemas/schemaC') | ||
t.equal(schemas.schemaB.properties.id.example, 'ABC') | ||
@@ -122,0 +125,0 @@ await Swagger.validate(openapiObject) |
@@ -223,4 +223,3 @@ import fastify, { FastifySchema, RouteOptions } from 'fastify'; | ||
route, | ||
swaggerObject, | ||
openapiObject, | ||
...documentObject | ||
}) => { | ||
@@ -230,6 +229,3 @@ schema satisfies FastifySchema; | ||
route satisfies RouteOptions; | ||
swaggerObject satisfies Partial<OpenAPIV2.Document>; | ||
openapiObject satisfies Partial< | ||
OpenAPIV3.Document | OpenAPIV3_1.Document | ||
>; | ||
documentObject satisfies { swaggerObject: Partial<OpenAPIV2.Document> } | { openapiObject: Partial<OpenAPIV3.Document | OpenAPIV3_1.Document> }; | ||
return { schema, url }; | ||
@@ -236,0 +232,0 @@ }, |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
303838
8820
1117