Skip to content

Commit

Permalink
feat: add swaggerUiEnabled option to control activation of Swagger UI
Browse files Browse the repository at this point in the history
  • Loading branch information
lucas-gregoire committed Feb 14, 2024
1 parent de2d695 commit 8b2c6a3
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 75 deletions.
8 changes: 6 additions & 2 deletions lib/interfaces/swagger-custom-options.interface.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
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;
swaggerUiEnabled?: boolean;
explorer?: boolean;
swaggerOptions?: SwaggerUiOptions;
customCss?: string;
Expand All @@ -22,5 +22,9 @@ export interface SwaggerCustomOptions {
urls?: Record<'url' | 'name', string>[];
jsonDocumentUrl?: string;
yamlDocumentUrl?: string;
patchDocumentOnRequest?: <TRequest = any, TResponse = any> (req: TRequest, res: TResponse, document: OpenAPIObject) => OpenAPIObject;
patchDocumentOnRequest?: <TRequest = any, TResponse = any>(
req: TRequest,
res: TResponse,
document: OpenAPIObject
) => OpenAPIObject;
}
162 changes: 89 additions & 73 deletions lib/swagger-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,48 +85,72 @@ export class SwaggerModule {
httpAdapter: HttpServer,
documentOrFactory: OpenAPIObject | (() => OpenAPIObject),
options: {
swaggerUiEnabled: boolean;
jsonDocumentUrl: string;
yamlDocumentUrl: string;
swaggerOptions: SwaggerCustomOptions;
}
) {
let document: OpenAPIObject;

const lazyBuildDocument = () => {
return typeof documentOrFactory === 'function'
? documentOrFactory()
: documentOrFactory;
const getBuiltDocument = () => {
if (!document) {
document =
typeof documentOrFactory === 'function'
? documentOrFactory()
: documentOrFactory;
}
return document;
};

if (options.swaggerUiEnabled) {
this.serveSwaggerUi(
finalPath,
urlLastSubdirectory,
httpAdapter,
getBuiltDocument,
options.swaggerOptions
);
}
this.serveDefinitions(httpAdapter, getBuiltDocument, options);
}

private static serveSwaggerUi(
finalPath: string,
urlLastSubdirectory: string,
httpAdapter: HttpServer,
getBuiltDocument: () => OpenAPIObject,
swaggerOptions: SwaggerCustomOptions
) {
const baseUrlForSwaggerUI = normalizeRelPath(`./${urlLastSubdirectory}/`);

let html: string;
let swaggerInitJS: string;
let swaggerUiHtml: string;
let swaggerUiInitJS: string;

httpAdapter.get(
normalizeRelPath(`${finalPath}/swagger-ui-init.js`),
(req, res) => {
res.type('application/javascript');
const document = getBuiltDocument();

if (!document) {
document = lazyBuildDocument();
}

if (options.swaggerOptions.patchDocumentOnRequest) {
const documentToSerialize =
options.swaggerOptions.patchDocumentOnRequest(req, res, document);
if (swaggerOptions.patchDocumentOnRequest) {
const documentToSerialize = swaggerOptions.patchDocumentOnRequest(
req,
res,
document
);
const swaggerInitJsPerRequest = buildSwaggerInitJS(
documentToSerialize,
options.swaggerOptions
swaggerOptions
);
return res.send(swaggerInitJsPerRequest);
}

if (!swaggerInitJS) {
swaggerInitJS = buildSwaggerInitJS(document, options.swaggerOptions);
if (!swaggerUiInitJS) {
swaggerUiInitJS = buildSwaggerInitJS(document, swaggerOptions);
}

res.send(swaggerInitJS);
res.send(swaggerUiInitJS);
}
);

Expand All @@ -141,29 +165,26 @@ export class SwaggerModule {
),
(req, res) => {
res.type('application/javascript');
const document = getBuiltDocument();

if (!document) {
document = lazyBuildDocument();
}

if (options.swaggerOptions.patchDocumentOnRequest) {
const documentToSerialize =
options.swaggerOptions.patchDocumentOnRequest(req, res, document);
if (swaggerOptions.patchDocumentOnRequest) {
const documentToSerialize = swaggerOptions.patchDocumentOnRequest(
req,
res,
document
);
const swaggerInitJsPerRequest = buildSwaggerInitJS(
documentToSerialize,
options.swaggerOptions
swaggerOptions
);
return res.send(swaggerInitJsPerRequest);
}

if (!swaggerInitJS) {
swaggerInitJS = buildSwaggerInitJS(
document,
options.swaggerOptions
);
if (!swaggerUiInitJS) {
swaggerUiInitJS = buildSwaggerInitJS(document, swaggerOptions);
}

res.send(swaggerInitJS);
res.send(swaggerUiInitJS);
}
);
} catch (err) {
Expand All @@ -173,40 +194,26 @@ export class SwaggerModule {
*/
}

httpAdapter.get(finalPath, (req, res) => {
httpAdapter.get(finalPath, (_, res) => {
res.type('text/html');

if (!document) {
document = lazyBuildDocument();
}

if (!html) {
html = buildSwaggerHTML(
baseUrlForSwaggerUI,
options.swaggerOptions
);
if (!swaggerUiHtml) {
swaggerUiHtml = buildSwaggerHTML(baseUrlForSwaggerUI, swaggerOptions);
}

res.send(html);
res.send(swaggerUiHtml);
});

// fastify doesn't resolve 'routePath/' -> 'routePath', that's why we handle it manually
try {
httpAdapter.get(normalizeRelPath(`${finalPath}/`), (req, res) => {
httpAdapter.get(normalizeRelPath(`${finalPath}/`), (_, res) => {
res.type('text/html');

if (!document) {
document = lazyBuildDocument();
}

if (!html) {
html = buildSwaggerHTML(
baseUrlForSwaggerUI,
options.swaggerOptions
);
if (!swaggerUiHtml) {
swaggerUiHtml = buildSwaggerHTML(baseUrlForSwaggerUI, swaggerOptions);
}

res.send(html);
res.send(swaggerUiHtml);
});
} catch (err) {
/**
Expand All @@ -216,13 +223,20 @@ export class SwaggerModule {
* We can simply ignore that error here.
*/
}
}

private static serveDefinitions(
httpAdapter: HttpServer,
getBuiltDocument: () => OpenAPIObject,
options: {
jsonDocumentUrl: string;
yamlDocumentUrl: string;
swaggerOptions: SwaggerCustomOptions;
}
) {
httpAdapter.get(normalizeRelPath(options.jsonDocumentUrl), (req, res) => {
res.type('application/json');

if (!document) {
document = lazyBuildDocument();
}
const document = getBuiltDocument();

const documentToSerialize = options.swaggerOptions.patchDocumentOnRequest
? options.swaggerOptions.patchDocumentOnRequest(req, res, document)
Expand All @@ -233,10 +247,7 @@ export class SwaggerModule {

httpAdapter.get(normalizeRelPath(options.yamlDocumentUrl), (req, res) => {
res.type('text/yaml');

if (!document) {
document = lazyBuildDocument();
}
const document = getBuiltDocument();

const documentToSerialize = options.swaggerOptions.patchDocumentOnRequest
? options.swaggerOptions.patchDocumentOnRequest(req, res, document)
Expand Down Expand Up @@ -276,6 +287,8 @@ export class SwaggerModule {
? `${validatedGlobalPrefix}${validatePath(options.yamlDocumentUrl)}`
: `${finalPath}-yaml`;

const swaggerUiEnabled = options?.swaggerUiEnabled ?? true;

const httpAdapter = app.getHttpAdapter();

SwaggerModule.serveDocuments(
Expand All @@ -284,24 +297,27 @@ export class SwaggerModule {
httpAdapter,
documentOrFactory,
{
swaggerUiEnabled,
jsonDocumentUrl: finalJSONDocumentPath,
yamlDocumentUrl: finalYAMLDocumentPath,
swaggerOptions: options || {}
}
);

SwaggerModule.serveStatic(finalPath, app, options?.customSwaggerUiPath);
/**
* Covers assets fetched through a relative path when Swagger url ends with a slash '/'.
* @see https://github.com/nestjs/swagger/issues/1976
*/
const serveStaticSlashEndingPath = `${finalPath}/${urlLastSubdirectory}`;
/**
* serveStaticSlashEndingPath === finalPath when path === '' || path === '/'
* in that case we don't need to serve swagger assets on extra sub path
*/
if (serveStaticSlashEndingPath !== finalPath) {
SwaggerModule.serveStatic(serveStaticSlashEndingPath, app);
if (swaggerUiEnabled) {
SwaggerModule.serveStatic(finalPath, app, options?.customSwaggerUiPath);
/**
* Covers assets fetched through a relative path when Swagger url ends with a slash '/'.
* @see https://github.com/nestjs/swagger/issues/1976
*/
const serveStaticSlashEndingPath = `${finalPath}/${urlLastSubdirectory}`;
/**
* serveStaticSlashEndingPath === finalPath when path === '' || path === '/'
* in that case we don't need to serve swagger assets on extra sub path
*/
if (serveStaticSlashEndingPath !== finalPath) {
SwaggerModule.serveStatic(serveStaticSlashEndingPath, app);
}
}
}
}

0 comments on commit 8b2c6a3

Please sign in to comment.