Skip to content

Commit 8b2c6a3

Browse files
committedFeb 14, 2024
feat: add swaggerUiEnabled option to control activation of Swagger UI
1 parent de2d695 commit 8b2c6a3

File tree

2 files changed

+95
-75
lines changed

2 files changed

+95
-75
lines changed
 

‎lib/interfaces/swagger-custom-options.interface.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { SwaggerUiOptions } from './swagger-ui-options.interface';
2-
import { SwaggerDocumentOptions } from './swagger-document-options.interface';
32
import { OpenAPIObject } from './open-api-spec.interface';
43

54
export interface SwaggerCustomOptions {
65
useGlobalPrefix?: boolean;
6+
swaggerUiEnabled?: boolean;
77
explorer?: boolean;
88
swaggerOptions?: SwaggerUiOptions;
99
customCss?: string;
@@ -22,5 +22,9 @@ export interface SwaggerCustomOptions {
2222
urls?: Record<'url' | 'name', string>[];
2323
jsonDocumentUrl?: string;
2424
yamlDocumentUrl?: string;
25-
patchDocumentOnRequest?: <TRequest = any, TResponse = any> (req: TRequest, res: TResponse, document: OpenAPIObject) => OpenAPIObject;
25+
patchDocumentOnRequest?: <TRequest = any, TResponse = any>(
26+
req: TRequest,
27+
res: TResponse,
28+
document: OpenAPIObject
29+
) => OpenAPIObject;
2630
}

‎lib/swagger-module.ts

+89-73
Original file line numberDiff line numberDiff line change
@@ -85,48 +85,72 @@ export class SwaggerModule {
8585
httpAdapter: HttpServer,
8686
documentOrFactory: OpenAPIObject | (() => OpenAPIObject),
8787
options: {
88+
swaggerUiEnabled: boolean;
8889
jsonDocumentUrl: string;
8990
yamlDocumentUrl: string;
9091
swaggerOptions: SwaggerCustomOptions;
9192
}
9293
) {
9394
let document: OpenAPIObject;
9495

95-
const lazyBuildDocument = () => {
96-
return typeof documentOrFactory === 'function'
97-
? documentOrFactory()
98-
: documentOrFactory;
96+
const getBuiltDocument = () => {
97+
if (!document) {
98+
document =
99+
typeof documentOrFactory === 'function'
100+
? documentOrFactory()
101+
: documentOrFactory;
102+
}
103+
return document;
99104
};
100105

106+
if (options.swaggerUiEnabled) {
107+
this.serveSwaggerUi(
108+
finalPath,
109+
urlLastSubdirectory,
110+
httpAdapter,
111+
getBuiltDocument,
112+
options.swaggerOptions
113+
);
114+
}
115+
this.serveDefinitions(httpAdapter, getBuiltDocument, options);
116+
}
117+
118+
private static serveSwaggerUi(
119+
finalPath: string,
120+
urlLastSubdirectory: string,
121+
httpAdapter: HttpServer,
122+
getBuiltDocument: () => OpenAPIObject,
123+
swaggerOptions: SwaggerCustomOptions
124+
) {
101125
const baseUrlForSwaggerUI = normalizeRelPath(`./${urlLastSubdirectory}/`);
102126

103-
let html: string;
104-
let swaggerInitJS: string;
127+
let swaggerUiHtml: string;
128+
let swaggerUiInitJS: string;
105129

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

111-
if (!document) {
112-
document = lazyBuildDocument();
113-
}
114-
115-
if (options.swaggerOptions.patchDocumentOnRequest) {
116-
const documentToSerialize =
117-
options.swaggerOptions.patchDocumentOnRequest(req, res, document);
136+
if (swaggerOptions.patchDocumentOnRequest) {
137+
const documentToSerialize = swaggerOptions.patchDocumentOnRequest(
138+
req,
139+
res,
140+
document
141+
);
118142
const swaggerInitJsPerRequest = buildSwaggerInitJS(
119143
documentToSerialize,
120-
options.swaggerOptions
144+
swaggerOptions
121145
);
122146
return res.send(swaggerInitJsPerRequest);
123147
}
124148

125-
if (!swaggerInitJS) {
126-
swaggerInitJS = buildSwaggerInitJS(document, options.swaggerOptions);
149+
if (!swaggerUiInitJS) {
150+
swaggerUiInitJS = buildSwaggerInitJS(document, swaggerOptions);
127151
}
128152

129-
res.send(swaggerInitJS);
153+
res.send(swaggerUiInitJS);
130154
}
131155
);
132156

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

145-
if (!document) {
146-
document = lazyBuildDocument();
147-
}
148-
149-
if (options.swaggerOptions.patchDocumentOnRequest) {
150-
const documentToSerialize =
151-
options.swaggerOptions.patchDocumentOnRequest(req, res, document);
170+
if (swaggerOptions.patchDocumentOnRequest) {
171+
const documentToSerialize = swaggerOptions.patchDocumentOnRequest(
172+
req,
173+
res,
174+
document
175+
);
152176
const swaggerInitJsPerRequest = buildSwaggerInitJS(
153177
documentToSerialize,
154-
options.swaggerOptions
178+
swaggerOptions
155179
);
156180
return res.send(swaggerInitJsPerRequest);
157181
}
158182

159-
if (!swaggerInitJS) {
160-
swaggerInitJS = buildSwaggerInitJS(
161-
document,
162-
options.swaggerOptions
163-
);
183+
if (!swaggerUiInitJS) {
184+
swaggerUiInitJS = buildSwaggerInitJS(document, swaggerOptions);
164185
}
165186

166-
res.send(swaggerInitJS);
187+
res.send(swaggerUiInitJS);
167188
}
168189
);
169190
} catch (err) {
@@ -173,40 +194,26 @@ export class SwaggerModule {
173194
*/
174195
}
175196

176-
httpAdapter.get(finalPath, (req, res) => {
197+
httpAdapter.get(finalPath, (_, res) => {
177198
res.type('text/html');
178199

179-
if (!document) {
180-
document = lazyBuildDocument();
181-
}
182-
183-
if (!html) {
184-
html = buildSwaggerHTML(
185-
baseUrlForSwaggerUI,
186-
options.swaggerOptions
187-
);
200+
if (!swaggerUiHtml) {
201+
swaggerUiHtml = buildSwaggerHTML(baseUrlForSwaggerUI, swaggerOptions);
188202
}
189203

190-
res.send(html);
204+
res.send(swaggerUiHtml);
191205
});
192206

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

198-
if (!document) {
199-
document = lazyBuildDocument();
200-
}
201-
202-
if (!html) {
203-
html = buildSwaggerHTML(
204-
baseUrlForSwaggerUI,
205-
options.swaggerOptions
206-
);
212+
if (!swaggerUiHtml) {
213+
swaggerUiHtml = buildSwaggerHTML(baseUrlForSwaggerUI, swaggerOptions);
207214
}
208215

209-
res.send(html);
216+
res.send(swaggerUiHtml);
210217
});
211218
} catch (err) {
212219
/**
@@ -216,13 +223,20 @@ export class SwaggerModule {
216223
* We can simply ignore that error here.
217224
*/
218225
}
226+
}
219227

228+
private static serveDefinitions(
229+
httpAdapter: HttpServer,
230+
getBuiltDocument: () => OpenAPIObject,
231+
options: {
232+
jsonDocumentUrl: string;
233+
yamlDocumentUrl: string;
234+
swaggerOptions: SwaggerCustomOptions;
235+
}
236+
) {
220237
httpAdapter.get(normalizeRelPath(options.jsonDocumentUrl), (req, res) => {
221238
res.type('application/json');
222-
223-
if (!document) {
224-
document = lazyBuildDocument();
225-
}
239+
const document = getBuiltDocument();
226240

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

234248
httpAdapter.get(normalizeRelPath(options.yamlDocumentUrl), (req, res) => {
235249
res.type('text/yaml');
236-
237-
if (!document) {
238-
document = lazyBuildDocument();
239-
}
250+
const document = getBuiltDocument();
240251

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

290+
const swaggerUiEnabled = options?.swaggerUiEnabled ?? true;
291+
279292
const httpAdapter = app.getHttpAdapter();
280293

281294
SwaggerModule.serveDocuments(
@@ -284,24 +297,27 @@ export class SwaggerModule {
284297
httpAdapter,
285298
documentOrFactory,
286299
{
300+
swaggerUiEnabled,
287301
jsonDocumentUrl: finalJSONDocumentPath,
288302
yamlDocumentUrl: finalYAMLDocumentPath,
289303
swaggerOptions: options || {}
290304
}
291305
);
292306

293-
SwaggerModule.serveStatic(finalPath, app, options?.customSwaggerUiPath);
294-
/**
295-
* Covers assets fetched through a relative path when Swagger url ends with a slash '/'.
296-
* @see https://github.com/nestjs/swagger/issues/1976
297-
*/
298-
const serveStaticSlashEndingPath = `${finalPath}/${urlLastSubdirectory}`;
299-
/**
300-
* serveStaticSlashEndingPath === finalPath when path === '' || path === '/'
301-
* in that case we don't need to serve swagger assets on extra sub path
302-
*/
303-
if (serveStaticSlashEndingPath !== finalPath) {
304-
SwaggerModule.serveStatic(serveStaticSlashEndingPath, app);
307+
if (swaggerUiEnabled) {
308+
SwaggerModule.serveStatic(finalPath, app, options?.customSwaggerUiPath);
309+
/**
310+
* Covers assets fetched through a relative path when Swagger url ends with a slash '/'.
311+
* @see https://github.com/nestjs/swagger/issues/1976
312+
*/
313+
const serveStaticSlashEndingPath = `${finalPath}/${urlLastSubdirectory}`;
314+
/**
315+
* serveStaticSlashEndingPath === finalPath when path === '' || path === '/'
316+
* in that case we don't need to serve swagger assets on extra sub path
317+
*/
318+
if (serveStaticSlashEndingPath !== finalPath) {
319+
SwaggerModule.serveStatic(serveStaticSlashEndingPath, app);
320+
}
305321
}
306322
}
307323
}

0 commit comments

Comments
 (0)
Please sign in to comment.