Skip to content

Commit

Permalink
feat(swagger): add documentsEnabled option to disable JSON/YAML
Browse files Browse the repository at this point in the history
endpoints
  • Loading branch information
mag123c committed Nov 24, 2024
1 parent 9496e92 commit 9752fcb
Show file tree
Hide file tree
Showing 4 changed files with 211 additions and 15 deletions.
89 changes: 89 additions & 0 deletions e2e/express.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
113 changes: 103 additions & 10 deletions e2e/fastify.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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';
Expand Down Expand Up @@ -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 () => {
Expand All @@ -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');
});
});

Expand Down Expand Up @@ -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"`
Expand All @@ -330,24 +417,30 @@ describe('Fastify Swagger', () => {
);

SwaggerModule.setup('/:tenantId/', app, swaggerDocument, {
patchDocumentOnRequest<ExpressRequest, ExpressResponse> (req, res, document) {
patchDocumentOnRequest<ExpressRequest, ExpressResponse>(
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();
Expand Down
15 changes: 11 additions & 4 deletions lib/interfaces/swagger-custom-options.interface.ts
Original file line number Diff line number Diff line change
@@ -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 {
/**
Expand All @@ -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.
*/
Expand Down Expand Up @@ -103,5 +111,4 @@ export interface SwaggerCustomOptions {
* @deprecated This property has no effect.
*/
urls?: Record<'url' | 'name', string>[];

}
9 changes: 8 additions & 1 deletion lib/swagger-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ export class SwaggerModule {
documentOrFactory: OpenAPIObject | (() => OpenAPIObject),
options: {
swaggerUiEnabled: boolean;
documentsEnabled: boolean;
jsonDocumentUrl: string;
yamlDocumentUrl: string;
swaggerOptions: SwaggerCustomOptions;
Expand All @@ -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(
Expand Down Expand Up @@ -283,6 +288,7 @@ export class SwaggerModule {
: `${finalPath}-yaml`;

const swaggerUiEnabled = options?.swaggerUiEnabled ?? true;
const documentsEnabled = options?.documentsEnabled ?? true;

const httpAdapter = app.getHttpAdapter();

Expand All @@ -293,6 +299,7 @@ export class SwaggerModule {
documentOrFactory,
{
swaggerUiEnabled,
documentsEnabled,
jsonDocumentUrl: finalJSONDocumentPath,
yamlDocumentUrl: finalYAMLDocumentPath,
swaggerOptions: options || {}
Expand Down

0 comments on commit 9752fcb

Please sign in to comment.