diff --git a/lib/interfaces/swagger-custom-options.interface.ts b/lib/interfaces/swagger-custom-options.interface.ts index f9fc32285..56d4ea138 100644 --- a/lib/interfaces/swagger-custom-options.interface.ts +++ b/lib/interfaces/swagger-custom-options.interface.ts @@ -1,9 +1,20 @@ import { SwaggerUiOptions } from './swagger-ui-options.interface'; +import { SwaggerDocumentOptions } from './swagger-document-options.interface'; +import { OpenAPIObject } from './open-api-spec.interface'; export interface SwaggerCustomOptions { useGlobalPrefix?: boolean; explorer?: boolean; swaggerOptions?: SwaggerUiOptions; + /** + * @note Only use when you want to use lazy initialization, if you create the document with `createDocument` you don't need to use this option + */ + documentOptions?: SwaggerDocumentOptions; + /** + * @note Only use when you want to use lazy initialization, if you create the document with `createDocument` you don't need to use this option + * @required when you want to use lazy initialization + */ + openApiConfig?: Omit; customCss?: string; customCssUrl?: string | string[]; customJs?: string | string[]; diff --git a/lib/swagger-module.ts b/lib/swagger-module.ts index 54cc7126f..37a8c646d 100644 --- a/lib/swagger-module.ts +++ b/lib/swagger-module.ts @@ -24,7 +24,7 @@ export class SwaggerModule { public static createDocument( app: INestApplication, config: Omit, - options: SwaggerDocumentOptions = {} + options: SwaggerDocumentOptions = {}, ): OpenAPIObject { const swaggerScanner = new SwaggerScanner(); const document = swaggerScanner.scanApplication(app, options); @@ -32,14 +32,14 @@ export class SwaggerModule { document.components = assignTwoLevelsDeep( {}, config.components, - document.components + document.components, ); return { openapi: '3.0.0', paths: {}, ...config, - ...document + ...document, }; } @@ -62,24 +62,49 @@ export class SwaggerModule { } private static serveDocuments( + app: INestApplication, finalPath: string, urlLastSubdirectory: string, httpAdapter: HttpServer, - swaggerInitJS: string, + document: OpenAPIObject | undefined, options: { - html: string; - yamlDocument: string; - jsonDocument: string; jsonDocumentUrl: string; yamlDocumentUrl: string; - } + swaggerOptions: SwaggerCustomOptions, + }, ) { + if (!document && !options.swaggerOptions.openApiConfig) { + throw new Error( + 'If you want to use lazy initialization for the document, you must provide the "openApiConfig" option.', + ); + } + + const lazyBuildDocument = () => { + return SwaggerModule.createDocument( + app, + options.swaggerOptions.openApiConfig!, + options.swaggerOptions.documentOptions, + ); + } + + const baseUrlForSwaggerUI = normalizeRelPath(`./${urlLastSubdirectory}/`); + + let html: string; + let swaggerInitJS: string; + httpAdapter.get( normalizeRelPath(`${finalPath}/swagger-ui-init.js`), (req, res) => { res.type('application/javascript'); + + if (!document) + document = lazyBuildDocument(); + + if (!swaggerInitJS) + swaggerInitJS = buildSwaggerInitJS(document, options); + res.send(swaggerInitJS); - } + }, ); /** @@ -89,12 +114,19 @@ export class SwaggerModule { try { httpAdapter.get( normalizeRelPath( - `${finalPath}/${urlLastSubdirectory}/swagger-ui-init.js` + `${finalPath}/${urlLastSubdirectory}/swagger-ui-init.js`, ), (req, res) => { res.type('application/javascript'); + + if (!document) + document = lazyBuildDocument(); + + if (!swaggerInitJS) + swaggerInitJS = buildSwaggerInitJS(document, options); + res.send(swaggerInitJS); - } + }, ); } catch (err) { /** @@ -105,14 +137,28 @@ export class SwaggerModule { httpAdapter.get(finalPath, (req, res) => { res.type('text/html'); - res.send(options.html); + + if (!document) + document = lazyBuildDocument(); + + if (!html) + html = buildSwaggerHTML(baseUrlForSwaggerUI, document, options); + + res.send(html); }); // fastify doesn't resolve 'routePath/' -> 'routePath', that's why we handle it manually try { httpAdapter.get(normalizeRelPath(`${finalPath}/`), (req, res) => { res.type('text/html'); - res.send(options.html); + + if (!document) + document = lazyBuildDocument(); + + if (!html) + html = buildSwaggerHTML(baseUrlForSwaggerUI, document, options); + + res.send(html); }); } catch (err) { /** @@ -123,33 +169,46 @@ export class SwaggerModule { */ } + let yamlDocument: string; + let jsonDocument: string; + httpAdapter.get(normalizeRelPath(options.jsonDocumentUrl), (req, res) => { res.type('application/json'); - res.send(options.jsonDocument); + + if (!document) + document = lazyBuildDocument(); + + if (!yamlDocument) jsonDocument = JSON.stringify(document); + + res.send(jsonDocument); }); httpAdapter.get(normalizeRelPath(options.yamlDocumentUrl), (req, res) => { res.type('text/yaml'); - res.send(options.yamlDocument); + + if (!document) + document = lazyBuildDocument(); + + if (!yamlDocument) + yamlDocument = jsyaml.dump(document); + + res.send(yamlDocument); }); } public static setup( path: string, app: INestApplication, - document: OpenAPIObject, - options?: SwaggerCustomOptions + document?: OpenAPIObject, + options?: SwaggerCustomOptions, ) { const globalPrefix = getGlobalPrefix(app); const finalPath = validatePath( options?.useGlobalPrefix && validateGlobalPrefix(globalPrefix) ? `${globalPrefix}${validatePath(path)}` - : path + : path, ); - const urlLastSubdirectory = finalPath.split('/').slice(-1).pop(); - - const yamlDocument = jsyaml.dump(document, { skipInvalid: true }); - const jsonDocument = JSON.stringify(document); + const urlLastSubdirectory = finalPath.split('/').slice(-1).pop() || ''; const validatedGlobalPrefix = options?.useGlobalPrefix && validateGlobalPrefix(globalPrefix) @@ -164,24 +223,19 @@ export class SwaggerModule { ? `${validatedGlobalPrefix}${validatePath(options.yamlDocumentUrl)}` : `${finalPath}-yaml`; - const baseUrlForSwaggerUI = normalizeRelPath(`./${urlLastSubdirectory}/`); - - const html = buildSwaggerHTML(baseUrlForSwaggerUI, document, options); - const swaggerInitJS = buildSwaggerInitJS(document, options); const httpAdapter = app.getHttpAdapter(); SwaggerModule.serveDocuments( + app, finalPath, urlLastSubdirectory, httpAdapter, - swaggerInitJS, + document, { - html, - yamlDocument, - jsonDocument, jsonDocumentUrl: finalJSONDocumentPath, - yamlDocumentUrl: finalYAMLDocumentPath - } + yamlDocumentUrl: finalYAMLDocumentPath, + swaggerOptions: options || {}, + }, ); SwaggerModule.serveStatic(finalPath, app); @@ -194,8 +248,8 @@ export class SwaggerModule { * serveStaticSlashEndingPath === finalPath when path === '' || path === '/' * in that case we don't need to serve swagger assets on extra sub path */ - if (serveStaticSlashEndingPath !== finalPath) { + if (serveStaticSlashEndingPath !== finalPath) SwaggerModule.serveStatic(serveStaticSlashEndingPath, app); - } + } }