From df6a5a9264868bebc39d5cb329d06685ac190d46 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 10 Jan 2023 15:06:04 +0100 Subject: [PATCH] backend-app-api: refactor RestrictedIndexedRouter -> DefaultRootHttpRouter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: blam Co-authored-by: Johan Haals Signed-off-by: Patrik Oldsberg --- packages/backend-app-api/api-report.md | 16 +++++++ ....test.ts => DefaultRootHttpRouter.test.ts} | 14 ++++-- ...exedRouter.ts => DefaultRootHttpRouter.ts} | 43 +++++++++++++++++-- .../implementations/rootHttpRouter/index.ts | 12 ++++-- .../rootHttpRouter/rootHttpRouterFactory.ts | 8 ++-- 5 files changed, 77 insertions(+), 16 deletions(-) rename packages/backend-app-api/src/services/implementations/rootHttpRouter/{RestrictedIndexedRouter.test.ts => DefaultRootHttpRouter.test.ts} (76%) rename packages/backend-app-api/src/services/implementations/rootHttpRouter/{RestrictedIndexedRouter.ts => DefaultRootHttpRouter.ts} (62%) diff --git a/packages/backend-app-api/api-report.md b/packages/backend-app-api/api-report.md index a8f077b760..b1356e93e3 100644 --- a/packages/backend-app-api/api-report.md +++ b/packages/backend-app-api/api-report.md @@ -12,6 +12,7 @@ import { CorsOptions } from 'cors'; import { ErrorRequestHandler } from 'express'; import { Express as Express_2 } from 'express'; import { ExtensionPoint } from '@backstage/backend-plugin-api'; +import { Handler } from 'express'; import { HelmetOptions } from 'helmet'; import * as http from 'http'; import { HttpRouterService } from '@backstage/backend-plugin-api'; @@ -79,6 +80,21 @@ export const databaseFactory: ( options?: undefined, ) => ServiceFactory; +// @public +export class DefaultRootHttpRouter implements RootHttpRouterService { + // (undocumented) + static create(options?: DefaultRootHttpRouterOptions): DefaultRootHttpRouter; + // (undocumented) + handler(): Handler; + // (undocumented) + use(path: string, handler: Handler): void; +} + +// @public +export interface DefaultRootHttpRouterOptions { + indexPath?: string | false; +} + // @public (undocumented) export const discoveryFactory: ( options?: undefined, diff --git a/packages/backend-app-api/src/services/implementations/rootHttpRouter/RestrictedIndexedRouter.test.ts b/packages/backend-app-api/src/services/implementations/rootHttpRouter/DefaultRootHttpRouter.test.ts similarity index 76% rename from packages/backend-app-api/src/services/implementations/rootHttpRouter/RestrictedIndexedRouter.test.ts rename to packages/backend-app-api/src/services/implementations/rootHttpRouter/DefaultRootHttpRouter.test.ts index b72e87f77b..b72b30e02e 100644 --- a/packages/backend-app-api/src/services/implementations/rootHttpRouter/RestrictedIndexedRouter.test.ts +++ b/packages/backend-app-api/src/services/implementations/rootHttpRouter/DefaultRootHttpRouter.test.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -import { RestrictedIndexedRouter } from './RestrictedIndexedRouter'; +import { DefaultRootHttpRouter } from './DefaultRootHttpRouter'; -describe('RestrictedIndexedRouter', () => { +describe('DefaultRootHttpRouter', () => { it.each([ [['/b'], '/a'], [['/a'], '/aa/b'], @@ -25,7 +25,7 @@ describe('RestrictedIndexedRouter', () => { [['/b/a'], '/a'], [['/a'], '/aa'], ])(`with existing paths %s, adds %s without conflict`, (existing, added) => { - const router = new RestrictedIndexedRouter(false); + const router = DefaultRootHttpRouter.create(); for (const path of existing) { router.use(path, () => {}); } @@ -39,7 +39,7 @@ describe('RestrictedIndexedRouter', () => { ])( `find conflict when existing paths %s, adds %s`, (existing, added, conflict) => { - const router = new RestrictedIndexedRouter(false); + const router = DefaultRootHttpRouter.create(); for (const path of existing) { router.use(path, () => {}); } @@ -48,4 +48,10 @@ describe('RestrictedIndexedRouter', () => { ); }, ); + + it('should not be possible to supply an empty indexPath', () => { + expect(() => DefaultRootHttpRouter.create({ indexPath: '' })).toThrow( + 'indexPath option may not be an empty string', + ); + }); }); diff --git a/packages/backend-app-api/src/services/implementations/rootHttpRouter/RestrictedIndexedRouter.ts b/packages/backend-app-api/src/services/implementations/rootHttpRouter/DefaultRootHttpRouter.ts similarity index 62% rename from packages/backend-app-api/src/services/implementations/rootHttpRouter/RestrictedIndexedRouter.ts rename to packages/backend-app-api/src/services/implementations/rootHttpRouter/DefaultRootHttpRouter.ts index 961277f34d..2acbb3833a 100644 --- a/packages/backend-app-api/src/services/implementations/rootHttpRouter/RestrictedIndexedRouter.ts +++ b/packages/backend-app-api/src/services/implementations/rootHttpRouter/DefaultRootHttpRouter.ts @@ -21,18 +21,53 @@ function normalizePath(path: string): string { return path.replace(/\/*$/, '/'); } -export class RestrictedIndexedRouter implements RootHttpRouterService { - #indexPath?: false | string; +/** + * Options for the {@link DefaultRootHttpRouter} class. + * + * @public + */ +export interface DefaultRootHttpRouterOptions { + /** + * The path to forward all unmatched requests to. Defaults to '/api/app' if + * not given. Disables index path behavior if false is given. + */ + indexPath?: string | false; +} + +/** + * The default implementation of the {@link @backstage/backend-plugin-api#RootHttpRouterService} interface for + * {@link @backstage/backend-plugin-api#coreServices.rootHttpRouter}. + * + * @public + */ +export class DefaultRootHttpRouter implements RootHttpRouterService { + #indexPath?: string; #router = Router(); #namedRoutes = Router(); #indexRouter = Router(); #existingPaths = new Array(); - constructor(indexPath?: false | string) { + static create(options?: DefaultRootHttpRouterOptions) { + let indexPath; + if (options?.indexPath === false) { + indexPath = undefined; + } else if (options?.indexPath === undefined) { + indexPath = '/api/app'; + } else if (options?.indexPath === '') { + throw new Error('indexPath option may not be an empty string'); + } else { + indexPath = options.indexPath; + } + return new DefaultRootHttpRouter(indexPath); + } + + private constructor(indexPath?: string) { this.#indexPath = indexPath; this.#router.use(this.#namedRoutes); - this.#router.use(this.#indexRouter); + if (this.#indexPath) { + this.#router.use(this.#indexRouter); + } } use(path: string, handler: Handler) { diff --git a/packages/backend-app-api/src/services/implementations/rootHttpRouter/index.ts b/packages/backend-app-api/src/services/implementations/rootHttpRouter/index.ts index 1dfd72273d..e24662df05 100644 --- a/packages/backend-app-api/src/services/implementations/rootHttpRouter/index.ts +++ b/packages/backend-app-api/src/services/implementations/rootHttpRouter/index.ts @@ -14,8 +14,12 @@ * limitations under the License. */ -export { rootHttpRouterFactory } from './rootHttpRouterFactory'; -export type { - RootHttpRouterFactoryOptions, - RootHttpRouterConfigureOptions, +export { + rootHttpRouterFactory, + type RootHttpRouterFactoryOptions, + type RootHttpRouterConfigureOptions, } from './rootHttpRouterFactory'; +export { + DefaultRootHttpRouter, + type DefaultRootHttpRouterOptions, +} from './DefaultRootHttpRouter'; diff --git a/packages/backend-app-api/src/services/implementations/rootHttpRouter/rootHttpRouterFactory.ts b/packages/backend-app-api/src/services/implementations/rootHttpRouter/rootHttpRouterFactory.ts index 45cb826b4d..58f8cc2db1 100644 --- a/packages/backend-app-api/src/services/implementations/rootHttpRouter/rootHttpRouterFactory.ts +++ b/packages/backend-app-api/src/services/implementations/rootHttpRouter/rootHttpRouterFactory.ts @@ -27,7 +27,7 @@ import { MiddlewareFactory, readHttpServerOptions, } from '../../../http'; -import { RestrictedIndexedRouter } from './RestrictedIndexedRouter'; +import { DefaultRootHttpRouter } from './DefaultRootHttpRouter'; /** * @public @@ -46,7 +46,8 @@ export interface RootHttpRouterConfigureOptions { */ export type RootHttpRouterFactoryOptions = { /** - * The path to forward all unmatched requests to. Defaults to '/api/app' + * The path to forward all unmatched requests to. Defaults to '/api/app' if + * not given. Disables index path behavior if false is given. */ indexPath?: string | false; @@ -82,11 +83,10 @@ export const rootHttpRouterFactory = createServiceFactory({ configure = defaultConfigure, }: RootHttpRouterFactoryOptions = {}, ) { - const router = new RestrictedIndexedRouter(indexPath ?? '/api/app'); const logger = rootLogger.child({ service: 'rootHttpRouter' }); - const app = express(); + const router = DefaultRootHttpRouter.create({ indexPath }); const middleware = MiddlewareFactory.create({ config, logger }); configure({