Skip to content

Commit

Permalink
feat(): routing platform independence (fastify-swagger uiHooks backwa…
Browse files Browse the repository at this point in the history
…rd compatibility)
  • Loading branch information
flamewow committed Apr 14, 2022
1 parent 082dc0e commit fd40f40
Show file tree
Hide file tree
Showing 11 changed files with 230 additions and 71 deletions.
44 changes: 40 additions & 4 deletions e2e/manual-e2e.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { NestFactory } from '@nestjs/core';
import { ApplicationModule } from './src/app.module';
import { DocumentBuilder, SwaggerModule } from '../lib';
import { FastifyAdapter } from '@nestjs/platform-fastify';
import {
FastifyAdapter,
NestFastifyApplication
} from '@nestjs/platform-fastify';
import { INestApplication, Logger } from '@nestjs/common';
import { ExpressAdapter } from '@nestjs/platform-express';
import {
ExpressAdapter,
NestExpressApplication
} from '@nestjs/platform-express';
import { join } from 'path';

const port = 4001;
const host = 'localhost';
Expand All @@ -12,6 +19,7 @@ const docRelPath = '/api-docs';
const USE_FASTIFY = false;

const adapter = USE_FASTIFY ? new FastifyAdapter() : new ExpressAdapter();
const publicFolderPath = join(__dirname, '../../e2e', 'public');

async function bootstrap() {
const app = await NestFactory.create<INestApplication>(
Expand Down Expand Up @@ -48,17 +56,45 @@ async function bootstrap() {
swaggerOptions: {
persistAuthorization: true,
defaultModelsExpandDepth: -1
}
},
customfavIcon: '/public/favicon.ico',
customCssUrl: '/public/theme.css', // to showcase that in new implementation u can use custom css with fastify
uiHooks: USE_FASTIFY
? {
onRequest: (req: any, res: any, next: any) => {
console.log('FASTIFY HOOK POC 1');
next();
}
}
: undefined
});

SwaggerModule.setup('/swagger-docs', app, document, {
customSiteTitle: 'Demo API - Swagger UI 2',
uiConfig: {
persistAuthorization: true,
defaultModelsExpandDepth: -1
}
},
uiHooks: USE_FASTIFY
? {
onRequest: (req: any, res: any, next: any) => {
console.log('FASTIFY HOOK POC 2');
next();
}
}
: undefined
});

USE_FASTIFY
? (app as NestFastifyApplication).useStaticAssets({
root: publicFolderPath,
prefix: `/public`,
decorateReply: false
})
: (app as NestExpressApplication).useStaticAssets(publicFolderPath, {
prefix: '/public'
});

await app.listen(port, host);
const baseUrl = `http://${host}:${port}`;
const startMessage = `Server started at ${baseUrl}; SwaggerUI at ${
Expand Down
Binary file added e2e/public/favicon.ico
Binary file not shown.
Binary file added e2e/public/logo.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 24 additions & 0 deletions e2e/public/theme.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
.swagger-ui .btn.authorize {
line-height: 1;
display: inline;
color: #336E7B;
border-color: #336E7B;
}
.swagger-ui .btn.authorize svg {
fill: #ef0505;
}


.swagger-ui body {
margin: 0;
background: #fafafa
}

img[alt="Swagger UI"] {
display: block;
-moz-box-sizing: border-box;
box-sizing: border-box;
content: url('/public/logo.png');
max-width: 100%;
max-height: 100%;
}
127 changes: 127 additions & 0 deletions lib/backward-compatilibity-layer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import {
FastifySwaggerCustomOptions,
SwaggerCustomOptions,
SwaggerCustomOptionsLegacy
} from './interfaces';
import { HttpServer } from '@nestjs/common/interfaces/http/http-server.interface';

export interface FastifyExtra {
initOAuth?: Record<string, any>;
staticCSP?: boolean | string | Record<string, string | string[]>;
transformStaticCSP?: (header: string) => string;
uiHooks?: {
onRequest?: Function;
preHandler?: Function;
};
}

export interface ProcessSwaggerOptionsOutput {
customOptions: SwaggerCustomOptions;
extra: FastifyExtra;
}

export function processSwaggerOptions(
options: SwaggerCustomOptionsLegacy = {}
): ProcessSwaggerOptionsOutput {
const unifiedOptions: SwaggerCustomOptions = options;
const fastifyOptions: FastifySwaggerCustomOptions = options;

const customOptions: SwaggerCustomOptions = {
useGlobalPrefix: unifiedOptions.useGlobalPrefix,
explorer: unifiedOptions.explorer,
customCss: unifiedOptions.customCss,
customCssUrl: unifiedOptions.customCssUrl,
customJs: unifiedOptions.customJs,
customfavIcon: unifiedOptions.customfavIcon,
swaggerUrl: unifiedOptions.swaggerUrl,
customSiteTitle: unifiedOptions.customSiteTitle,
validatorUrl: unifiedOptions.validatorUrl,
url: unifiedOptions.url,
urls: unifiedOptions.urls,
initOAuth: unifiedOptions.initOAuth,

swaggerOptions: unifiedOptions.swaggerOptions || fastifyOptions.uiConfig
};

const extra = {
uiHooks: fastifyOptions.uiHooks
};

return { customOptions, extra };
}

export function serveDocumentsFastify(
finalPath: string,
httpAdapter: HttpServer,
swaggerInitJS: string,
yamlDocument: string,
jsonDocument: string,
html: string,
fastifyExtras: FastifyExtra
) {
const httpServer = httpAdapter as any;

// 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();
}

httpServer.register(async (fastifyApp: any) => {
const hooks = Object.create(null);

if (fastifyExtras.uiHooks) {
const additionalHooks = ['onRequest', 'preHandler'];
for (const hook of additionalHooks) {
hooks[hook] = fastifyExtras.uiHooks[hook];
}
}

fastifyApp.route({
url: finalPath,
method: 'GET',
schema: { hide: true },
...hooks,
handler: (req: any, reply: any) => {
reply.type('text/html');
reply.send(html);
}
});

fastifyApp.route({
url: `${finalPath}/swagger-ui-init.js`,
method: 'GET',
schema: { hide: true },
...hooks,
handler: (req: any, reply: any) => {
reply.type('application/javascript');
reply.send(swaggerInitJS);
}
});

fastifyApp.route({
url: `${finalPath}-json`,
method: 'GET',
schema: { hide: true },
...hooks,
handler: (req: any, reply: any) => {
reply.type('text/json');
reply.send(jsonDocument);
}
});

fastifyApp.route({
url: `${finalPath}-yaml`,
method: 'GET',
schema: { hide: true },
...hooks,
handler: (req: any, reply: any) => {
reply.type('text/yaml');
reply.send(yamlDocument);
}
});
});
}
4 changes: 2 additions & 2 deletions lib/interfaces/swagger-custom-options-legacy.instace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ export interface FastifySwaggerCustomOptions
queryConfigEnabled: boolean;
}>;
initOAuth?: Record<string, any>;
staticCSP?: boolean | string | Record<string, string | string[]>;
transformStaticCSP?: (header: string) => string;
staticCSP?: boolean | string | Record<string, string | string[]>; // not supported
transformStaticCSP?: (header: string) => string; // not supported
uiHooks?: {
onRequest?: Function;
preHandler?: Function;
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 @@ -11,4 +11,5 @@ export interface SwaggerCustomOptions {
validatorUrl?: string;
url?: string;
urls?: Record<'url' | 'name', string>[];
initOAuth?: Record<string, any>; // https://swagger.io/docs/open-source-tools/swagger-ui/usage/oauth2/
}
45 changes: 33 additions & 12 deletions lib/swagger-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ import {
} from './swagger-ui';
import { NestFastifyApplication } from '@nestjs/platform-fastify';
import { NestExpressApplication } from '@nestjs/platform-express';
import { processSwaggerOptions } from './utils/backward-compatilibity-layer';
import {
processSwaggerOptions,
serveDocumentsFastify
} from './backward-compatilibity-layer';
import { HttpServer } from '@nestjs/common/interfaces/http/http-server.interface';

export class SwaggerModule {
public static createDocument(
Expand Down Expand Up @@ -60,14 +64,12 @@ export class SwaggerModule {

private static serveDocuments(
finalPath: string,
app: INestApplication,
httpAdapter: HttpServer,
swaggerInitJS: string,
yamlDocument: string,
jsonDocument: string,
html: string
) {
const httpAdapter = app.getHttpAdapter();

httpAdapter.get(`${finalPath}/swagger-ui-init.js`, (req, res) => {
res.type('application/javascript');
res.send(swaggerInitJS);
Expand Down Expand Up @@ -117,14 +119,33 @@ export class SwaggerModule {
const html = buildSwaggerHTML(finalPath, document, customOptions);
const swaggerInitJS = buildSwaggerInitJS(document, customOptions);

SwaggerModule.serveDocuments(
finalPath,
app,
swaggerInitJS,
yamlDocument,
jsonDocument,
html
);
const httpAdapter = app.getHttpAdapter();

// START: fastify backward compatibility layer
const IS_FASTIFY = httpAdapter && httpAdapter.getType() === 'fastify';

if (IS_FASTIFY) {
serveDocumentsFastify(
finalPath,
httpAdapter,
swaggerInitJS,
yamlDocument,
jsonDocument,
html,
extra
);
} else {
SwaggerModule.serveDocuments(
finalPath,
httpAdapter,
swaggerInitJS,
yamlDocument,
jsonDocument,
html
);
}
// END: fastify backward compatibility layer

SwaggerModule.serveStatic(finalPath, app);
}
}
4 changes: 2 additions & 2 deletions lib/swagger-ui/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,8 @@ window.onload = function() {
}
let ui = SwaggerUIBundle(swaggerOptions)
if (customOptions.oauth) {
ui.initOAuth(customOptions.oauth)
if (customOptions.initOAuth) {
ui.initOAuth(customOptions.initOAuth)
}
if (customOptions.authAction) {
Expand Down
2 changes: 1 addition & 1 deletion lib/swagger-ui/swagger-ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function buildSwaggerInitJS(
* Stores absolute path to swagger-ui assets
*/
export const swaggerAssetsAbsoluteFSPath = swaggerUi.getAbsoluteFSPath();

console.log(swaggerAssetsAbsoluteFSPath);
/**
* Used to build swagger-ui custom html
*/
Expand Down
50 changes: 0 additions & 50 deletions lib/utils/backward-compatilibity-layer.ts

This file was deleted.

0 comments on commit fd40f40

Please sign in to comment.