Skip to content

Commit

Permalink
feat(@nestjs/swagger): allow to patch document using a custom function
Browse files Browse the repository at this point in the history
Define `patchDocument` option to patch the OpenAPI document
based on the request, response and generated OpenAPI document.

#2502
  • Loading branch information
botflux committed Jun 30, 2023
1 parent 7674a11 commit f49f5b2
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 3 deletions.
71 changes: 69 additions & 2 deletions e2e/express.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,14 @@ describe('Express Swagger', () => {
);
SwaggerModule.setup('api', app, swaggerDocument, {
jsonDocumentUrl: JSON_CUSTOM_URL,
yamlDocumentUrl: YAML_CUSTOM_URL
yamlDocumentUrl: YAML_CUSTOM_URL,
patchDocument: (req, res, document) => ({
...document,
info: {
...document.info,
description: (req as Record<string, any>).query.description
}
})
});

await app.init();
Expand All @@ -130,12 +137,24 @@ describe('Express Swagger', () => {
expect(Object.keys(response.body).length).toBeGreaterThan(0);
});

it('patched JSON document should be served', async () => {
const response = await request(app.getHttpServer()).get(`${JSON_CUSTOM_URL}?description=My%20custom%20description`);

expect(response.body.info.description).toBe("My custom description")
})

it('yaml document should be server in the custom url', async () => {
const response = await request(app.getHttpServer()).get(YAML_CUSTOM_URL);

expect(response.status).toEqual(200);
expect(response.text.length).toBeGreaterThan(0);
});

it('patched YAML document should be served', async () => {
const response = await request(app.getHttpServer()).get(`${YAML_CUSTOM_URL}?description=My%20custom%20description`);

expect(response.text).toContain("My custom description")
})
});

describe('custom documents endpoints with global prefix', () => {
Expand Down Expand Up @@ -209,7 +228,15 @@ describe('Express Swagger', () => {
customJsStr: CUSTOM_JS_STR,
customfavIcon: CUSTOM_FAVICON,
customSiteTitle: CUSTOM_SITE_TITLE,
customCssUrl: CUSTOM_CSS_URL
customCssUrl: CUSTOM_CSS_URL,
patchDocument<ExpressRequest, ExpressResponse> (req, res, document) {
return {
...document,
info: {
description: req.query.description
}
}
}
});

await app.init();
Expand Down Expand Up @@ -249,6 +276,46 @@ describe('Express Swagger', () => {
);
});

it('should patch the OpenAPI document', async () => {
const response: Response = await request(app.getHttpServer()).get('/swagger-ui-init.js?description=Custom%20Swagger%20description%20passed%20by%20query%20param')
expect(response.text).toContain(
`"description": "Custom Swagger description passed by query param"`
)
})

it('should patch the OpenAPI document based on path param of the swagger prefix', async () => {
const app = await NestFactory.create<NestExpressApplication>(
ApplicationModule,
new ExpressAdapter(),
{ logger: false }
);

app.setGlobalPrefix("/:customer/")

const swaggerDocument = SwaggerModule.createDocument(
app,
builder.build()
);

SwaggerModule.setup('/:customer/', app, swaggerDocument, {
patchDocument<ExpressRequest, ExpressResponse> (req, res, document) {
return {
...document,
info: {
description: `${req.params.customer}'s API documentation`
}
}
}
});

await app.init();

const response: Response = await request(app.getHttpServer()).get('/customer-1/swagger-ui-init.js')

await app.close()
expect(response.text).toContain("customer-1's API documentation")
})

afterEach(async () => {
await app.close();
});
Expand Down
50 changes: 49 additions & 1 deletion e2e/fastify.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,14 @@ describe('Fastify Swagger', () => {
customJsStr: CUSTOM_JS_STR,
customfavIcon: CUSTOM_FAVICON,
customSiteTitle: CUSTOM_SITE_TITLE,
customCssUrl: CUSTOM_CSS_URL
customCssUrl: CUSTOM_CSS_URL,
patchDocument: (req, res, document) => ({
...document,
info: {
...document.info,
description: (req as Record<string, any>).query.description
}
})
});

await app.init();
Expand Down Expand Up @@ -267,6 +274,47 @@ 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"
)
expect(response.text).toContain(
`"description": "Custom Swagger description passed by query param"`
)
})

it('should patch the OpenAPI document based on path param of the swagger prefix', async () => {
const app = await NestFactory.create<NestFastifyApplication>(
ApplicationModule,
new FastifyAdapter(),
{ logger: false }
);

const swaggerDocument = SwaggerModule.createDocument(
app,
builder.build()
);

SwaggerModule.setup('/:tenantId/', app, swaggerDocument, {
patchDocument<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')

await app.close()
expect(response.text).toContain("tenant-1's API documentation")
})

afterEach(async () => {
await app.close();
});
Expand Down
10 changes: 10 additions & 0 deletions e2e/manual-e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,16 @@ async function bootstrap() {
}
});

SwaggerModule.setup("/:tenantId/api-docs", app, document, {
patchDocument: (req, res, document1) => ({
...document1,
info: {
...document1.info,
title: `${(req as Record<string, any>).params.tenantId}'s API document`
}
})
})

USE_FASTIFY
? (app as NestFastifyApplication).useStaticAssets({
root: publicFolderPath,
Expand Down
1 change: 1 addition & 0 deletions lib/interfaces/swagger-custom-options.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ export interface SwaggerCustomOptions {
urls?: Record<'url' | 'name', string>[];
jsonDocumentUrl?: string;
yamlDocumentUrl?: string;
patchDocument?: <TRequest = any, TResponse = any> (req: TRequest, res: TResponse, document: OpenAPIObject) => OpenAPIObject;
}
20 changes: 20 additions & 0 deletions lib/swagger-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ export class SwaggerModule {

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

if (options.swaggerOptions.patchDocument) {
document = options.swaggerOptions.patchDocument(req, res, document)
}
}

if (!swaggerInitJS) {
Expand All @@ -126,6 +130,10 @@ export class SwaggerModule {

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

if (options.swaggerOptions.patchDocument) {
document = options.swaggerOptions.patchDocument(req, res, document);
}
}

if (!swaggerInitJS) {
Expand All @@ -150,6 +158,10 @@ export class SwaggerModule {

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

if (options.swaggerOptions.patchDocument) {
document = options.swaggerOptions.patchDocument(req, res, document)
}
}

if (!html) {
Expand Down Expand Up @@ -199,6 +211,10 @@ export class SwaggerModule {

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

if (options.swaggerOptions.patchDocument) {
document = options.swaggerOptions.patchDocument(req, res, document)
}
}

if (!jsonDocument) {
Expand All @@ -213,6 +229,10 @@ export class SwaggerModule {

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

if (options.swaggerOptions.patchDocument) {
document = options.swaggerOptions.patchDocument(req, res, document)
}
}

if (!yamlDocument) {
Expand Down

0 comments on commit f49f5b2

Please sign in to comment.