Skip to content

Commit

Permalink
perf: added possibility to lazy initialize the document
Browse files Browse the repository at this point in the history
  • Loading branch information
H4ad committed Apr 15, 2023
1 parent 5313c38 commit c1f7931
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 34 deletions.
11 changes: 11 additions & 0 deletions lib/interfaces/swagger-custom-options.interface.ts
Original file line number Diff line number Diff line change
@@ -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<OpenAPIObject, 'paths'>;
customCss?: string;
customCssUrl?: string | string[];
customJs?: string | string[];
Expand Down
122 changes: 88 additions & 34 deletions lib/swagger-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,22 @@ export class SwaggerModule {
public static createDocument(
app: INestApplication,
config: Omit<OpenAPIObject, 'paths'>,
options: SwaggerDocumentOptions = {}
options: SwaggerDocumentOptions = {},
): OpenAPIObject {
const swaggerScanner = new SwaggerScanner();
const document = swaggerScanner.scanApplication(app, options);

document.components = assignTwoLevelsDeep(
{},
config.components,
document.components
document.components,
);

return {
openapi: '3.0.0',
paths: {},
...config,
...document
...document,
};
}

Expand All @@ -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);
}
},
);

/**
Expand All @@ -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) {
/**
Expand All @@ -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) {
/**
Expand All @@ -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)
Expand All @@ -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);
Expand All @@ -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);
}

}
}

0 comments on commit c1f7931

Please sign in to comment.