Skip to content

Commit

Permalink
feat(): routing platform independence (initial implementation)
Browse files Browse the repository at this point in the history
  • Loading branch information
flamewow committed Apr 11, 2022
1 parent 5d5f3fb commit b666b3e
Show file tree
Hide file tree
Showing 7 changed files with 16,451 additions and 390 deletions.
59 changes: 59 additions & 0 deletions e2e/manual-e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import 'source-map-support/register';

import { NestFactory } from '@nestjs/core';
import { ApplicationModule } from './src/app.module';
import { DocumentBuilder, SwaggerModule } from '../lib';
import { FastifyAdapter } from '@nestjs/platform-fastify';
import { INestApplication } from '@nestjs/common';
import { ExpressAdapter } from '@nestjs/platform-express';

const port = 4001;
const host = '0.0.0.0';
const docRelPath = '/api-docs';

async function bootstrap() {
const app = await NestFactory.create<INestApplication>(
ApplicationModule,
new FastifyAdapter()
// new ExpressAdapter()
);

const swaggerSettings = new DocumentBuilder()
.setTitle('Cats example')
.setDescription('The cats API description')
.setVersion('1.0')
.addTag('cats')
.addBasicAuth()
.addBearerAuth()
.addOAuth2()
.addApiKey()
.addApiKey({ type: 'apiKey' }, 'key1')
.addApiKey({ type: 'apiKey' }, 'key2')
.addCookieAuth()
.addSecurityRequirements('bearer')
.addSecurityRequirements({ basic: [], cookie: [] })
.build();

const document = SwaggerModule.createDocument(app, swaggerSettings, {
deepScanRoutes: true,
ignoreGlobalPrefix: false,
extraModels: [] // add DTOs that are not explicitly registered here (like PaginatedDto, etc)
});

SwaggerModule.setup(docRelPath, app, document, {
customSiteTitle: 'Demo API - Swagger UI',
swaggerOptions: {
persistAuthorization: true,
defaultModelsExpandDepth: -1
}
});

return app.listen(port, host);
}

const baseUrl = `http://${host}:${port}`;
const startMessage = `Server started at ${baseUrl}; AsyncApi at ${
baseUrl + docRelPath
};`;

bootstrap().then(() => console.log(startMessage));
129 changes: 62 additions & 67 deletions lib/swagger-module.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import { INestApplication } from '@nestjs/common';
import { loadPackage } from '@nestjs/common/utils/load-package.util';
import {
ExpressSwaggerCustomOptions,
FastifySwaggerCustomOptions,
OpenAPIObject,
SwaggerCustomOptions,
SwaggerDocumentOptions
Expand All @@ -11,6 +8,10 @@ import { SwaggerScanner } from './swagger-scanner';
import { assignTwoLevelsDeep } from './utils/assign-two-levels-deep';
import { getGlobalPrefix } from './utils/get-global-prefix';
import { validatePath } from './utils/validate-path.util';
import * as jsyaml from 'js-yaml';
import swaggerUi from './swagger-ui';
import * as fs from 'fs';
import * as pathNode from 'path';

export class SwaggerModule {
public static createDocument(
Expand Down Expand Up @@ -48,74 +49,68 @@ export class SwaggerModule {
? `${globalPrefix}${validatePath(path)}`
: path
);
if (httpAdapter && httpAdapter.getType() === 'fastify') {
return this.setupFastify(
finalPath,
httpAdapter,
document,
options as FastifySwaggerCustomOptions
);
}
return this.setupExpress(
finalPath,
app,
document,
options as ExpressSwaggerCustomOptions
);
}

private static setupExpress(
path: string,
app: INestApplication,
document: OpenAPIObject,
options?: ExpressSwaggerCustomOptions
) {
const httpAdapter = app.getHttpAdapter();
const swaggerUi = loadPackage('swagger-ui-express', 'SwaggerModule', () =>
require('swagger-ui-express')
);
const swaggerHtml = swaggerUi.generateHTML(document, options);
app.use(path, swaggerUi.serveFiles(document, options));
console.log(finalPath);
// httpAdapter.get('/test', (req, res) => {
// return 'test OK!';
// });

httpAdapter.get(path, (req, res) => res.send(swaggerHtml));
httpAdapter.get(path + '-json', (req, res) => res.json(document));
}
const yamlDocument = jsyaml.dump(document);
const jsonDocument = JSON.stringify(document);
const html = swaggerUi.generateHTML(finalPath, document, options);

private static setupFastify(
path: string,
httpServer: any,
document: OpenAPIObject,
options?: FastifySwaggerCustomOptions
) {
// Workaround for older versions of the @nestjs/platform-fastify package
// where "isParserRegistered" getter is not defined.
const hasParserGetterDefined = (
Object.getPrototypeOf(httpServer) as Object
).hasOwnProperty('isParserRegistered');
if (hasParserGetterDefined && !httpServer.isParserRegistered) {
httpServer.registerParserMiddleware();
}
const swaggerUIBasePath = swaggerUi.getSwaggerUiAbsoluteFSPath();
const swaggerUIAssetsNames = [
'swagger-ui.css',
'swagger-ui-bundle.js',
'swagger-ui-standalone-preset.js'
// 'favicon-32x32.png',
// 'favicon-16x16.png'
];

const ext2type = {
js: 'application/javascript',
css: 'text/css'
};
/*
* in oder to avoid using static serving libraries (they are routing platform dependant)
* swaggerUI assets are served manually
*/
swaggerUIAssetsNames.forEach((assetName) => {
const assetFullUrl = `${finalPath}/${assetName}`;
const [, assetExtension] = assetName.split('.');
const assetPath = pathNode.join(swaggerUIBasePath, assetName);

httpAdapter.get(assetFullUrl, async (req, res) => {
fs.readFile(assetPath, 'utf8', function (err, data) {
if (err) {
throw err;
}

res.type(ext2type[assetExtension]);
res.send(data);
});
});
});

httpAdapter.get(`${finalPath}/swagger-ui-init.js`, (req, res) => {
res.type('js');
res.send(swaggerUi.getInitJs(document, options));
});

httpAdapter.get(finalPath, (req, res) => {
res.type('html');
res.send(html);
});

httpAdapter.get(`${finalPath}-json`, (req, res) => {
res.type('json');
res.send(jsonDocument);
});

httpServer.register(async (httpServer: any) => {
httpServer.register(
loadPackage('fastify-swagger', 'SwaggerModule', () =>
require('fastify-swagger')
),
{
swagger: document,
exposeRoute: true,
routePrefix: path,
mode: 'static',
specification: {
document
},
uiConfig: options?.uiConfig,
initOAuth: options?.initOAuth,
staticCSP: options?.staticCSP,
transformStaticCSP: options?.transformStaticCSP,
uiHooks: options?.uiHooks
}
);
httpAdapter.get(`${finalPath}-yaml`, (req, res) => {
res.type('yaml');
res.send(yamlDocument);
});
}
}

0 comments on commit b666b3e

Please sign in to comment.