diff --git a/e2e/express.e2e-spec.ts b/e2e/express.e2e-spec.ts index 64c844141..0b3f827f7 100644 --- a/e2e/express.e2e-spec.ts +++ b/e2e/express.e2e-spec.ts @@ -163,6 +163,95 @@ describe('Express Swagger', () => { }); }); + describe('disabled Swagger Documents(JSON, YAML) but served Swagger UI', () => { + const SWAGGER_RELATIVE_URL = '/apidoc'; + + beforeEach(async () => { + const swaggerDocument = SwaggerModule.createDocument( + app, + builder.build() + ); + SwaggerModule.setup(SWAGGER_RELATIVE_URL, app, swaggerDocument, { + documentsEnabled: false + }); + + await app.init(); + }); + + afterEach(async () => { + await app.close(); + }); + + it('should not serve the JSON definition file', async () => { + const response = await request(app.getHttpServer()).get( + `${SWAGGER_RELATIVE_URL}-json` + ); + + expect(response.status).toEqual(404); + }); + + it('should not serve the YAML definition file', async () => { + const response = await request(app.getHttpServer()).get( + `${SWAGGER_RELATIVE_URL}-yaml` + ); + + expect(response.status).toEqual(404); + }); + + it.each([SWAGGER_RELATIVE_URL, `${SWAGGER_RELATIVE_URL}/`])( + 'should serve Swagger UI at "%s"', + async (url) => { + const response = await request(app.getHttpServer()).get(url); + expect(response.status).toEqual(200); + } + ); + }); + + describe('disabled Both Swagger UI AND Swagger Documents(JSON, YAML)', () => { + const SWAGGER_RELATIVE_URL = '/apidoc'; + + beforeEach(async () => { + const swaggerDocument = SwaggerModule.createDocument( + app, + builder.build() + ); + SwaggerModule.setup(SWAGGER_RELATIVE_URL, app, swaggerDocument, { + swaggerUiEnabled: false, + documentsEnabled: false + }); + + await app.init(); + }); + + afterEach(async () => { + await app.close(); + }); + + it('should not serve the JSON definition file', async () => { + const response = await request(app.getHttpServer()).get( + `${SWAGGER_RELATIVE_URL}-json` + ); + + expect(response.status).toEqual(404); + }); + + it('should not serve the YAML definition file', async () => { + const response = await request(app.getHttpServer()).get( + `${SWAGGER_RELATIVE_URL}-yaml` + ); + + expect(response.status).toEqual(404); + }); + + it.each([SWAGGER_RELATIVE_URL, `${SWAGGER_RELATIVE_URL}/`])( + 'should not serve Swagger UI at "%s"', + async (url) => { + const response = await request(app.getHttpServer()).get(url); + expect(response.status).toEqual(404); + } + ); + }); + describe('custom documents endpoints', () => { const JSON_CUSTOM_URL = '/apidoc-json'; const YAML_CUSTOM_URL = '/apidoc-yaml'; diff --git a/e2e/fastify.e2e-spec.ts b/e2e/fastify.e2e-spec.ts index fabb58ab6..0cf577f99 100644 --- a/e2e/fastify.e2e-spec.ts +++ b/e2e/fastify.e2e-spec.ts @@ -3,11 +3,11 @@ import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify'; +import * as path from 'path'; import * as request from 'supertest'; import * as SwaggerParser from 'swagger-parser'; import { DocumentBuilder, SwaggerModule } from '../lib'; import { ApplicationModule } from './src/app.module'; -import * as path from 'path'; describe('Fastify Swagger', () => { let app: NestFastifyApplication; @@ -84,11 +84,11 @@ describe('Fastify Swagger', () => { beforeEach(async () => { const swaggerDocument = SwaggerModule.createDocument( app, - builder.build(), + builder.build() ); SwaggerModule.setup(SWAGGER_RELATIVE_URL, app, swaggerDocument, { // to showcase that in new implementation u can use custom swagger-ui path. Useful when using e.g. webpack - customSwaggerUiPath: path.resolve(`./node_modules/swagger-ui-dist`), + customSwaggerUiPath: path.resolve(`./node_modules/swagger-ui-dist`) }); await app.init(); @@ -117,6 +117,93 @@ describe('Fastify Swagger', () => { }); }); + describe('disabled Swagger Documents(JSON, YAML) but served Swagger UI', () => { + const SWAGGER_RELATIVE_URL = '/apidoc'; + + beforeEach(async () => { + const swaggerDocument = SwaggerModule.createDocument( + app, + builder.build() + ); + SwaggerModule.setup(SWAGGER_RELATIVE_URL, app, swaggerDocument, { + documentsEnabled: false + }); + + await app.init(); + await app.getHttpAdapter().getInstance().ready(); + }); + + afterEach(async () => { + await app.close(); + }); + + it('should not serve the JSON definition file', async () => { + const response = await request(app.getHttpServer()).get( + `${SWAGGER_RELATIVE_URL}-json` + ); + expect(response.status).toEqual(404); + }); + + it('should not serve the YAML definition file', async () => { + const response = await request(app.getHttpServer()).get( + `${SWAGGER_RELATIVE_URL}-yaml` + ); + expect(response.status).toEqual(404); + }); + + it.each([SWAGGER_RELATIVE_URL, `${SWAGGER_RELATIVE_URL}/`])( + 'should serve Swagger UI at "%s"', + async (url) => { + const response = await request(app.getHttpServer()).get(url); + expect(response.status).toEqual(200); + } + ); + }); + + describe('disabled Both Swagger UI AND Swagger Documents(JSON, YAML)', () => { + const SWAGGER_RELATIVE_URL = '/apidoc'; + + beforeEach(async () => { + const swaggerDocument = SwaggerModule.createDocument( + app, + builder.build() + ); + SwaggerModule.setup(SWAGGER_RELATIVE_URL, app, swaggerDocument, { + swaggerUiEnabled: false, + documentsEnabled: false + }); + + await app.init(); + await app.getHttpAdapter().getInstance().ready(); + }); + + afterEach(async () => { + await app.close(); + }); + + it('should not serve the JSON definition file', async () => { + const response = await request(app.getHttpServer()).get( + `${SWAGGER_RELATIVE_URL}-json` + ); + expect(response.status).toEqual(404); + }); + + it('should not serve the YAML definition file', async () => { + const response = await request(app.getHttpServer()).get( + `${SWAGGER_RELATIVE_URL}-yaml` + ); + expect(response.status).toEqual(404); + }); + + it.each([SWAGGER_RELATIVE_URL, `${SWAGGER_RELATIVE_URL}/`])( + 'should not serve Swagger UI at "%s"', + async (url) => { + const response = await request(app.getHttpServer()).get(url); + expect(response.status).toEqual(404); + } + ); + }); + describe('custom documents endpoints', () => { const JSON_CUSTOM_URL = '/apidoc-json'; const YAML_CUSTOM_URL = '/apidoc-yaml'; @@ -158,7 +245,7 @@ describe('Fastify Swagger', () => { `${JSON_CUSTOM_URL}?description=My%20custom%20description` ); - expect(response.body.info.description).toBe("My custom description"); + expect(response.body.info.description).toBe('My custom description'); }); it('yaml document should be server in the custom url', async () => { @@ -172,7 +259,7 @@ describe('Fastify Swagger', () => { const response = await request(app.getHttpServer()).get( `${YAML_CUSTOM_URL}?description=My%20custom%20description` ); - expect(response.text).toContain("My custom description"); + expect(response.text).toContain('My custom description'); }); }); @@ -310,7 +397,7 @@ describe('Fastify Swagger', () => { it('should patch the OpenAPI document', async function () { const response: Response = await request(app.getHttpServer()).get( - "/custom/swagger-ui-init.js?description=Custom%20Swagger%20description%20passed%20by%20query%20param" + '/custom/swagger-ui-init.js?description=Custom%20Swagger%20description%20passed%20by%20query%20param' ); expect(response.text).toContain( `"description": "Custom Swagger description passed by query param"` @@ -330,24 +417,30 @@ describe('Fastify Swagger', () => { ); SwaggerModule.setup('/:tenantId/', app, swaggerDocument, { - patchDocumentOnRequest (req, res, document) { + patchDocumentOnRequest( + req, + res, + document + ) { return { ...document, info: { description: `${req.params.tenantId}'s API documentation` } - } + }; } }); await app.init(); await app.getHttpAdapter().getInstance().ready(); - const response: Response = await request(app.getHttpServer()).get('/tenant-1/swagger-ui-init.js'); + const response: Response = await request(app.getHttpServer()).get( + '/tenant-1/swagger-ui-init.js' + ); await app.close(); expect(response.text).toContain("tenant-1's API documentation"); - }) + }); afterEach(async () => { await app.close(); diff --git a/lib/interfaces/swagger-custom-options.interface.ts b/lib/interfaces/swagger-custom-options.interface.ts index aa3c15c49..53f0cc476 100644 --- a/lib/interfaces/swagger-custom-options.interface.ts +++ b/lib/interfaces/swagger-custom-options.interface.ts @@ -1,5 +1,5 @@ -import { SwaggerUiOptions } from './swagger-ui-options.interface'; import { OpenAPIObject } from './open-api-spec.interface'; +import { SwaggerUiOptions } from './swagger-ui-options.interface'; export interface SwaggerCustomOptions { /** @@ -10,12 +10,20 @@ export interface SwaggerCustomOptions { useGlobalPrefix?: boolean; /** - * If `false`, only API definitions (JSON and YAML) will be served (on `/{path}-json` and `/{path}-yaml`). - * This is particularly useful if you are already hosting a Swagger UI somewhere else and just want to serve API definitions. + * If `false`, the Swagger UI will not be served. Only API definitions (JSON and YAML) + * will be accessible (on `/{path}-json` and `/{path}-yaml`). + * To fully disable both the Swagger UI and API definitions, use `documentsEnabled: false`. * Default: `true`. */ swaggerUiEnabled?: boolean; + /** + * If `false`, both the Swagger UI and API definitions (JSON and YAML) will be disabled. + * Use this option when you want to completely hide all Swagger-related endpoints. + * Default: `true`. + */ + documentsEnabled?: boolean; + /** * Url point the API definition to load in Swagger UI. */ @@ -103,5 +111,4 @@ export interface SwaggerCustomOptions { * @deprecated This property has no effect. */ urls?: Record<'url' | 'name', string>[]; - } diff --git a/lib/swagger-module.ts b/lib/swagger-module.ts index 12ee6422a..0dff344e3 100644 --- a/lib/swagger-module.ts +++ b/lib/swagger-module.ts @@ -86,6 +86,7 @@ export class SwaggerModule { documentOrFactory: OpenAPIObject | (() => OpenAPIObject), options: { swaggerUiEnabled: boolean; + documentsEnabled: boolean; jsonDocumentUrl: string; yamlDocumentUrl: string; swaggerOptions: SwaggerCustomOptions; @@ -112,7 +113,11 @@ export class SwaggerModule { options.swaggerOptions ); } - this.serveDefinitions(httpAdapter, getBuiltDocument, options); + + // Skip registering JSON/YAML endpoints if documentsEnabled is false + if (options.documentsEnabled) { + this.serveDefinitions(httpAdapter, getBuiltDocument, options); + } } protected static serveSwaggerUi( @@ -283,6 +288,7 @@ export class SwaggerModule { : `${finalPath}-yaml`; const swaggerUiEnabled = options?.swaggerUiEnabled ?? true; + const documentsEnabled = options?.documentsEnabled ?? true; const httpAdapter = app.getHttpAdapter(); @@ -293,6 +299,7 @@ export class SwaggerModule { documentOrFactory, { swaggerUiEnabled, + documentsEnabled, jsonDocumentUrl: finalJSONDocumentPath, yamlDocumentUrl: finalYAMLDocumentPath, swaggerOptions: options || {}