From bf10d3aa6aa79b6c107322462d081d0f210d7a0a Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Wed, 29 Aug 2018 16:23:50 -0700 Subject: [PATCH 1/2] feat(explorer): add a package for local API Explorer UI --- packages/explorer/.npmrc | 1 + packages/explorer/LICENSE | 25 +++++++ packages/explorer/README.md | 27 ++++++++ packages/explorer/docs.json | 7 ++ packages/explorer/index.d.ts | 6 ++ packages/explorer/index.js | 6 ++ packages/explorer/index.ts | 8 +++ packages/explorer/package.json | 59 +++++++++++++++++ packages/explorer/src/explorer.ts | 64 ++++++++++++++++++ packages/explorer/src/index.html.ejs | 64 ++++++++++++++++++ packages/explorer/src/index.ts | 6 ++ .../test/integration/explorer.integration.ts | 57 ++++++++++++++++ .../explorer/test/integration/test.html.ejs | 65 +++++++++++++++++++ packages/explorer/tsconfig.build.json | 8 +++ 14 files changed, 403 insertions(+) create mode 100644 packages/explorer/.npmrc create mode 100644 packages/explorer/LICENSE create mode 100644 packages/explorer/README.md create mode 100644 packages/explorer/docs.json create mode 100644 packages/explorer/index.d.ts create mode 100644 packages/explorer/index.js create mode 100644 packages/explorer/index.ts create mode 100644 packages/explorer/package.json create mode 100644 packages/explorer/src/explorer.ts create mode 100644 packages/explorer/src/index.html.ejs create mode 100644 packages/explorer/src/index.ts create mode 100644 packages/explorer/test/integration/explorer.integration.ts create mode 100644 packages/explorer/test/integration/test.html.ejs create mode 100644 packages/explorer/tsconfig.build.json diff --git a/packages/explorer/.npmrc b/packages/explorer/.npmrc new file mode 100644 index 000000000000..43c97e719a5a --- /dev/null +++ b/packages/explorer/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/packages/explorer/LICENSE b/packages/explorer/LICENSE new file mode 100644 index 000000000000..3d49daf70323 --- /dev/null +++ b/packages/explorer/LICENSE @@ -0,0 +1,25 @@ +Copyright (c) IBM Corp. 2018. All Rights Reserved. +Node module: @loopback/explorer +This project is licensed under the MIT License, full text below. + +-------- + +MIT license + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/packages/explorer/README.md b/packages/explorer/README.md new file mode 100644 index 000000000000..86337fb86cad --- /dev/null +++ b/packages/explorer/README.md @@ -0,0 +1,27 @@ +# @loopback/explorer + +This module contains a handler to serve API Explorer + +## Installation + +```sh +npm install --save @loopback/explorer +``` + +## Contributions + +- [Guidelines](https://github.com/strongloop/loopback-next/blob/master/docs/CONTRIBUTING.md) +- [Join the team](https://github.com/strongloop/loopback-next/issues/110) + +## Tests + +Run `npm test` from the root folder. + +## Contributors + +See +[all contributors](https://github.com/strongloop/loopback-next/graphs/contributors). + +## License + +MIT diff --git a/packages/explorer/docs.json b/packages/explorer/docs.json new file mode 100644 index 000000000000..81a57a650369 --- /dev/null +++ b/packages/explorer/docs.json @@ -0,0 +1,7 @@ +{ + "content": [ + "index.ts", + "src/explorer.ts" + ], + "codeSectionDepth": 4 +} diff --git a/packages/explorer/index.d.ts b/packages/explorer/index.d.ts new file mode 100644 index 000000000000..880ff9799b52 --- /dev/null +++ b/packages/explorer/index.d.ts @@ -0,0 +1,6 @@ +// Copyright IBM Corp. 2018. All Rights Reserved. +// Node module: @loopback/explorer +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +export * from './dist8'; diff --git a/packages/explorer/index.js b/packages/explorer/index.js new file mode 100644 index 000000000000..512820ec0cdb --- /dev/null +++ b/packages/explorer/index.js @@ -0,0 +1,6 @@ +// Copyright IBM Corp. 2018. All Rights Reserved. +// Node module: @loopback/explorer +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +module.exports = require('@loopback/dist-util').loadDist(__dirname); diff --git a/packages/explorer/index.ts b/packages/explorer/index.ts new file mode 100644 index 000000000000..3a66043229fa --- /dev/null +++ b/packages/explorer/index.ts @@ -0,0 +1,8 @@ +// Copyright IBM Corp. 2018. All Rights Reserved. +// Node module: @loopback/explorer +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +// DO NOT EDIT THIS FILE +// Add any additional (re)exports to src/index.ts instead. +export * from './src'; diff --git a/packages/explorer/package.json b/packages/explorer/package.json new file mode 100644 index 000000000000..710f6fb1c27c --- /dev/null +++ b/packages/explorer/package.json @@ -0,0 +1,59 @@ +{ + "name": "@loopback/explorer", + "version": "0.1.0", + "description": "LoopBack's API Explorer", + "engines": { + "node": ">=8.9" + }, + "scripts": { + "build:all-dist": "npm run build:dist8 && npm run build:dist10", + "build:apidocs": "lb-apidocs", + "build": "lb-tsc", + "build:dist8": "lb-tsc es2017", + "build:dist10": "lb-tsc es2018", + "clean": "lb-clean loopback-explorer*.tgz dist* package api-docs", + "pretest": "npm run build", + "integration": "lb-mocha \"DIST/test/integration/**/*.js\"", + "test": "lb-mocha \"DIST/test/unit/**/*.js\" \"DIST/test/integration/**/*.js\"", + "unit": "lb-mocha \"DIST/test/unit/**/*.js\"", + "verify": "npm pack && tar xf loopback-explorer*.tgz && tree package && npm run clean" + }, + "author": "IBM", + "copyright.owner": "IBM Corp.", + "license": "MIT", + "dependencies": { + "@types/express": "^4.16.0", + "ejs": "^2.6.1", + "serve-static": "^1.13.2", + "swagger-ui-dist": "^3.18.2" + }, + "devDependencies": { + "@loopback/build": "^0.7.1", + "@loopback/dist-util": "^0.3.6", + "@loopback/testlab": "^0.11.5", + "@types/ejs": "^2.6.0", + "@types/node": "^10.1.1", + "@types/serve-static": "^1.13.2", + "express": "^4.16.3" + }, + "keywords": [ + "LoopBack", + "Explorer", + "Swagger" + ], + "files": [ + "README.md", + "index.js", + "index.d.ts", + "dist*/src", + "dist*/index*", + "src" + ], + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "https://github.com/strongloop/loopback-next.git" + } +} diff --git a/packages/explorer/src/explorer.ts b/packages/explorer/src/explorer.ts new file mode 100644 index 000000000000..eb63a5bc46d2 --- /dev/null +++ b/packages/explorer/src/explorer.ts @@ -0,0 +1,64 @@ +// Copyright IBM Corp. 2018. All Rights Reserved. +// Node module: @loopback/explorer +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +const swaggerUI = require('swagger-ui-dist'); + +import * as path from 'path'; +import * as fs from 'fs'; + +import {Handler} from 'express'; +import * as serveStatic from 'serve-static'; +import * as ejs from 'ejs'; + +/** + * Options to configure API Explorer UI + */ +export type ApiExplorerUIOptions = { + /** + * URL to the OpenAPI spec + */ + openApiSpecUrl?: string; + + /** + * Custom EJS template for index.html + */ + indexHtmlTemplate?: string; + + /** + * Options for serve-static middleware + */ + serveStaticOptions?: serveStatic.ServeStaticOptions; +}; + +/** + * Mount the API Explorer UI (swagger-ui) to the given express router + * @param options + */ +export function apiExplorerUI(options: ApiExplorerUIOptions = {}): Handler { + const openApiSpecUrl = options.openApiSpecUrl || '/openapi.json'; + const indexHtml = + options.indexHtmlTemplate || path.resolve(__dirname, './index.html.ejs'); + const template = fs.readFileSync(indexHtml, 'utf-8'); + const templateFn = ejs.compile(template); + const uiHandler = serveStatic( + swaggerUI.getAbsoluteFSPath(), + options.serveStaticOptions, + ); + + return (req, res, next) => { + if (req.path === '/' || req.path === '/index.html') { + const data = { + openApiSpecUrl, + }; + const homePage = templateFn(data); + res + .status(200) + .contentType('text/html') + .send(homePage); + } else { + uiHandler(req, res, next); + } + }; +} diff --git a/packages/explorer/src/index.html.ejs b/packages/explorer/src/index.html.ejs new file mode 100644 index 000000000000..28ceaf371838 --- /dev/null +++ b/packages/explorer/src/index.html.ejs @@ -0,0 +1,64 @@ + + + + + + + LoopBack API Explorer + + + + + + + +
+ + + + + + + diff --git a/packages/explorer/src/index.ts b/packages/explorer/src/index.ts new file mode 100644 index 000000000000..b5570c86dc1a --- /dev/null +++ b/packages/explorer/src/index.ts @@ -0,0 +1,6 @@ +// Copyright IBM Corp. 2018. All Rights Reserved. +// Node module: @loopback/explorer +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +export * from './explorer'; diff --git a/packages/explorer/test/integration/explorer.integration.ts b/packages/explorer/test/integration/explorer.integration.ts new file mode 100644 index 000000000000..665db4019e45 --- /dev/null +++ b/packages/explorer/test/integration/explorer.integration.ts @@ -0,0 +1,57 @@ +// Copyright IBM Corp. 2018. All Rights Reserved. +// Node module: @loopback/explorer +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {createClientForHandler} from '@loopback/testlab'; +import {apiExplorerUI} from '../../src/explorer'; +import * as express from 'express'; +import * as path from 'path'; + +describe('API Explorer UI', () => { + let app: express.Application; + let router: express.Router; + + beforeEach(() => { + app = express(); + router = express.Router(); + app.use('/api-explorer', router); + }); + + it('mounts API explorer UI', () => { + router.use(apiExplorerUI()); + const test = createClientForHandler(app); + test + .get('/api-explorer') + .expect('content-type', 'text/html') + .expect(200, 'LoopBack API Explorer'); + + test.get('/api-explorer/swagger-ui-bundle.js').expect(200); + }); + + it('accepts API explorer UI options - openApiSpecUrl', () => { + router.use( + apiExplorerUI({ + openApiSpecUrl: 'https://localhost:8080/openapi.json', + }), + ); + const test = createClientForHandler(app); + test + .get('/api-explorer') + .expect('content-type', 'text/html') + .expect(200, 'https://localhost:8080/openapi.json'); + }); + + it('accepts API explorer UI options - indexHtmlTemplate', () => { + router.use( + apiExplorerUI({ + indexHtmlTemplate: path.resolve(__dirname, 'test.html.ejs'), + }), + ); + const test = createClientForHandler(app); + test + .get('/api-explorer') + .expect('content-type', 'text/html') + .expect(200, 'Test API Explorer'); + }); +}); diff --git a/packages/explorer/test/integration/test.html.ejs b/packages/explorer/test/integration/test.html.ejs new file mode 100644 index 000000000000..e2b271528f41 --- /dev/null +++ b/packages/explorer/test/integration/test.html.ejs @@ -0,0 +1,65 @@ + + + + + + + Test API Explorer + + + + + + + +
+ + + + + + + diff --git a/packages/explorer/tsconfig.build.json b/packages/explorer/tsconfig.build.json new file mode 100644 index 000000000000..3ffcd508d23e --- /dev/null +++ b/packages/explorer/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "$schema": "http://json.schemastore.org/tsconfig", + "extends": "../build/config/tsconfig.common.json", + "compilerOptions": { + "rootDir": "." + }, + "include": ["index.ts", "src", "test"] +} From c1d710bddffeeba2d22fb4a0d4115015c688890e Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Fri, 31 Aug 2018 14:37:38 -0700 Subject: [PATCH 2/2] feat(rest): add local UI for api explorer --- packages/explorer/index.d.ts | 2 +- packages/explorer/index.js | 2 +- packages/explorer/package.json | 24 +++---- packages/explorer/src/explorer.component.ts | 46 +++++++++++++ packages/explorer/src/explorer.ts | 5 ++ packages/explorer/src/index.ts | 2 + packages/explorer/src/keys.ts | 21 ++++++ .../test/integration/explorer.integration.ts | 2 +- .../test/integration/rest.integration.ts | 38 +++++++++++ packages/rest/src/keys.ts | 5 ++ packages/rest/src/rest.server.ts | 67 +++++++++++++++---- 11 files changed, 187 insertions(+), 27 deletions(-) create mode 100644 packages/explorer/src/explorer.component.ts create mode 100644 packages/explorer/src/keys.ts create mode 100644 packages/explorer/test/integration/rest.integration.ts diff --git a/packages/explorer/index.d.ts b/packages/explorer/index.d.ts index 880ff9799b52..26661fd7a05f 100644 --- a/packages/explorer/index.d.ts +++ b/packages/explorer/index.d.ts @@ -3,4 +3,4 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -export * from './dist8'; +export * from './dist'; diff --git a/packages/explorer/index.js b/packages/explorer/index.js index 512820ec0cdb..81d3ba10abdd 100644 --- a/packages/explorer/index.js +++ b/packages/explorer/index.js @@ -3,4 +3,4 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -module.exports = require('@loopback/dist-util').loadDist(__dirname); +module.exports = require('./dist'); diff --git a/packages/explorer/package.json b/packages/explorer/package.json index 710f6fb1c27c..92a41038a16f 100644 --- a/packages/explorer/package.json +++ b/packages/explorer/package.json @@ -6,33 +6,33 @@ "node": ">=8.9" }, "scripts": { - "build:all-dist": "npm run build:dist8 && npm run build:dist10", "build:apidocs": "lb-apidocs", - "build": "lb-tsc", - "build:dist8": "lb-tsc es2017", - "build:dist10": "lb-tsc es2018", + "build": "lb-tsc es2017 --outDir dist --copy-resources", "clean": "lb-clean loopback-explorer*.tgz dist* package api-docs", "pretest": "npm run build", - "integration": "lb-mocha \"DIST/test/integration/**/*.js\"", - "test": "lb-mocha \"DIST/test/unit/**/*.js\" \"DIST/test/integration/**/*.js\"", - "unit": "lb-mocha \"DIST/test/unit/**/*.js\"", + "integration": "lb-mocha \"dist/test/integration/**/*.js\"", + "test": "lb-mocha \"dist/test/unit/**/*.js\" \"dist/test/integration/**/*.js\"", + "unit": "lb-mocha \"dist/test/unit/**/*.js\"", "verify": "npm pack && tar xf loopback-explorer*.tgz && tree package && npm run clean" }, "author": "IBM", "copyright.owner": "IBM Corp.", "license": "MIT", "dependencies": { + "@loopback/context": "^1.0.0", + "@loopback/core": "^1.0.0", + "@loopback/dist-util": "^0.3.6", + "@loopback/rest": "^1.1.0", "@types/express": "^4.16.0", "ejs": "^2.6.1", "serve-static": "^1.13.2", - "swagger-ui-dist": "^3.18.2" + "swagger-ui-dist": "^3.19.0" }, "devDependencies": { - "@loopback/build": "^0.7.1", - "@loopback/dist-util": "^0.3.6", - "@loopback/testlab": "^0.11.5", + "@loopback/build": "^1.0.0", + "@loopback/testlab": "^1.0.0", "@types/ejs": "^2.6.0", - "@types/node": "^10.1.1", + "@types/node": "^10.10.1", "@types/serve-static": "^1.13.2", "express": "^4.16.3" }, diff --git a/packages/explorer/src/explorer.component.ts b/packages/explorer/src/explorer.component.ts new file mode 100644 index 000000000000..f5ddc87f80c2 --- /dev/null +++ b/packages/explorer/src/explorer.component.ts @@ -0,0 +1,46 @@ +// Copyright IBM Corp. 2018. All Rights Reserved. +// Node module: @loopback/explorer +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {inject} from '@loopback/context'; +import {Application, Component, CoreBindings} from '@loopback/core'; +import {RestServer} from '@loopback/rest'; +import {ApiExplorerUIOptions, apiExplorerUI} from './explorer'; +import {ExplorerBindings} from './keys'; + +/** + * We have a few options: + * + * 1. The Explorer component contributes an express middleware so that REST + * servers can mount the UI + * + * 2. The Explorer component contributes a route to REST servers + * + * 3. The Explorer component proactively mount itself with the RestApplication + */ +export class ExplorerComponent implements Component { + constructor( + @inject(CoreBindings.APPLICATION_INSTANCE) private application: Application, + @inject(ExplorerBindings.CONFIG, {optional: true}) + private config: ApiExplorerUIOptions, + ) { + this.init(); + } + + init() { + // FIXME: We should be able to receive the servers via injection + const restServerBindings = this.application.find( + binding => + binding.key.startsWith(CoreBindings.SERVERS) && + binding.valueConstructor === RestServer, + ); + for (const binding of restServerBindings) { + const restServer = this.application.getSync(binding.key); + restServer.bind(ExplorerBindings.MIDDLEWARE).to({ + path: this.config.path || '/explorer', + handler: apiExplorerUI(this.config), + }); + } + } +} diff --git a/packages/explorer/src/explorer.ts b/packages/explorer/src/explorer.ts index eb63a5bc46d2..8acf284ebec8 100644 --- a/packages/explorer/src/explorer.ts +++ b/packages/explorer/src/explorer.ts @@ -30,6 +30,11 @@ export type ApiExplorerUIOptions = { * Options for serve-static middleware */ serveStaticOptions?: serveStatic.ServeStaticOptions; + + /** + * Path for the explorer UI + */ + path?: string; }; /** diff --git a/packages/explorer/src/index.ts b/packages/explorer/src/index.ts index b5570c86dc1a..845bef34e20c 100644 --- a/packages/explorer/src/index.ts +++ b/packages/explorer/src/index.ts @@ -4,3 +4,5 @@ // License text available at https://opensource.org/licenses/MIT export * from './explorer'; +export * from './keys'; +export * from './explorer.component'; diff --git a/packages/explorer/src/keys.ts b/packages/explorer/src/keys.ts new file mode 100644 index 000000000000..475288957764 --- /dev/null +++ b/packages/explorer/src/keys.ts @@ -0,0 +1,21 @@ +// Copyright IBM Corp. 2018. All Rights Reserved. +// Node module: @loopback/explorer +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {BindingKey} from '@loopback/context'; +import {ApiExplorerUIOptions} from './explorer'; +import {Middleware} from '@loopback/rest'; + +/** + * Binding keys used by this component. + */ +export namespace ExplorerBindings { + export const MIDDLEWARE = BindingKey.create( + 'middleware.explorer', + ); + + export const CONFIG = BindingKey.create( + 'explorer.config', + ); +} diff --git a/packages/explorer/test/integration/explorer.integration.ts b/packages/explorer/test/integration/explorer.integration.ts index 665db4019e45..c300544aa5ea 100644 --- a/packages/explorer/test/integration/explorer.integration.ts +++ b/packages/explorer/test/integration/explorer.integration.ts @@ -4,7 +4,7 @@ // License text available at https://opensource.org/licenses/MIT import {createClientForHandler} from '@loopback/testlab'; -import {apiExplorerUI} from '../../src/explorer'; +import {apiExplorerUI} from '../../'; import * as express from 'express'; import * as path from 'path'; diff --git a/packages/explorer/test/integration/rest.integration.ts b/packages/explorer/test/integration/rest.integration.ts new file mode 100644 index 000000000000..1fecb49700f1 --- /dev/null +++ b/packages/explorer/test/integration/rest.integration.ts @@ -0,0 +1,38 @@ +// Copyright IBM Corp. 2018. All Rights Reserved. +// Node module: @loopback/explorer +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT +import {createRestAppClient} from '@loopback/testlab'; +import {RestServerConfig, RestApplication} from '@loopback/rest'; +import {ExplorerComponent, ExplorerBindings} from '../..'; + +describe('API Explorer for REST Server', () => { + let restApp: RestApplication; + + before(async () => { + restApp = await givenRestApp({ + rest: {port: 0}, + }); + }); + + after(async () => { + await restApp.stop(); + }); + + it('exposes "GET /explorer"', () => { + const test = createRestAppClient(restApp); + return test + .get('/explorer') + .expect(200, /\LoopBack API Explorer<\/title\>/) + .expect('content-type', /text\/html.*/); + }); +}); + +async function givenRestApp(options?: {rest: RestServerConfig}) { + const app = new RestApplication(options); + app.bind(ExplorerBindings.CONFIG).to({}); + // FIXME: Can we mount the ExplorerComponent to a given server? + app.component(ExplorerComponent); + await app.start(); + return app; +} diff --git a/packages/rest/src/keys.ts b/packages/rest/src/keys.ts index 688fbc3bb710..0ee85448b20d 100644 --- a/packages/rest/src/keys.ts +++ b/packages/rest/src/keys.ts @@ -32,6 +32,7 @@ import {HttpProtocol} from '@loopback/http-server'; import * as https from 'https'; import {ErrorWriterOptions} from 'strong-error-handler'; import {RestRouter} from './router'; +import {Application} from 'express'; /** * RestServer-specific bindings @@ -73,6 +74,10 @@ export namespace RestBindings { */ export const ROUTER = BindingKey.create('rest.router'); + /** + * Binding key for express app + */ + export const EXPRESS_APP = BindingKey.create('rest.express.app'); /** * Binding key for setting and injecting Reject action's error handling * options. diff --git a/packages/rest/src/rest.server.ts b/packages/rest/src/rest.server.ts index 3e698daad76d..894fa9e52d10 100644 --- a/packages/rest/src/rest.server.ts +++ b/packages/rest/src/rest.server.ts @@ -192,6 +192,7 @@ export class RestServer extends Context implements Server, HttpServerLike { protected _setupRequestHandler() { this._expressApp = express(); + this.bind(RestBindings.EXPRESS_APP).to(this._expressApp); // Disable express' built-in query parser, we parse queries ourselves // Note that when disabled, express sets query to an empty object, @@ -203,17 +204,8 @@ export class RestServer extends Context implements Server, HttpServerLike { this.requestHandler = this._expressApp; - // Allow CORS support for all endpoints so that users - // can test with online SwaggerUI instance - const corsOptions = this.config.cors || { - origin: '*', - methods: 'GET,HEAD,PUT,PATCH,POST,DELETE', - preflightContinue: false, - optionsSuccessStatus: 204, - maxAge: 86400, - credentials: true, - }; - this._expressApp.use(cors(corsOptions)); + // Enable CORS + this._setupCORS(); // Set up endpoints for OpenAPI spec/ui this._setupOpenApiSpecEndpoints(); @@ -232,7 +224,25 @@ export class RestServer extends Context implements Server, HttpServerLike { } /** - * Mount /openapi.json, /openapi.yaml for specs and /swagger-ui, /explorer + * Set up CORS middleware + */ + protected _setupCORS() { + // Allow CORS support for all endpoints so that users + // can test with online SwaggerUI instance + const corsOptions = this.config.cors || { + origin: '*', + methods: 'GET,HEAD,PUT,PATCH,POST,DELETE', + preflightContinue: false, + optionsSuccessStatus: 204, + maxAge: 86400, + credentials: true, + }; + // Set up CORS + this._expressApp.use(cors(corsOptions)); + } + + /** + * Mount /openapi.json, /openapi.yaml for specs and /swagger-ui, /api-explorer * to redirect to externally hosted API explorer */ protected _setupOpenApiSpecEndpoints() { @@ -259,6 +269,20 @@ export class RestServer extends Context implements Server, HttpServerLike { return this.httpHandler.handleRequest(request, response); } + protected _registerMiddleware() { + for (const b of this.find('middleware.*')) { + const middleware = this.getSync(b.key); + if (!middleware.method) { + this._expressApp.use(middleware.path || '/', middleware.handler); + } else { + this._expressApp[middleware.method]( + middleware.path || '/', + middleware.handler, + ); + } + } + } + protected _setupHandlerIfNeeded() { // TODO(bajtos) support hot-reloading of controllers // after the app started. The idea is to rebuild the HttpHandler @@ -273,6 +297,8 @@ export class RestServer extends Context implements Server, HttpServerLike { const routingTable = new RoutingTable(router); this._httpHandler = new HttpHandler(this, routingTable); + this._registerMiddleware(); + for (const b of this.find('controllers.*')) { const controllerName = b.key.replace(/^controllers\./, ''); const ctor = b.valueConstructor; @@ -844,3 +870,20 @@ export interface RestServerOptions { * @interface RestServerConfig */ export type RestServerConfig = RestServerOptions & HttpServerOptions; + +/** + * Middleware registration entry + */ +export interface Middleware { + method?: + | 'all' + | 'get' + | 'post' + | 'put' + | 'delete' + | 'patch' + | 'options' + | 'head'; + path?: PathParams; + handler: express.RequestHandler; +}