diff --git a/packages/rest-explorer/src/rest-explorer.controller.ts b/packages/rest-explorer/src/rest-explorer.controller.ts index 19d52dbf5f99..962469123309 100644 --- a/packages/rest-explorer/src/rest-explorer.controller.ts +++ b/packages/rest-explorer/src/rest-explorer.controller.ts @@ -21,6 +21,7 @@ const template = fs.readFileSync(indexHtml, 'utf-8'); const templateFn = ejs.compile(template); export class ExplorerController { + private serverRootPath: string; private openApiSpecUrl: string; constructor( @@ -29,11 +30,12 @@ export class ExplorerController { @inject(RestBindings.Http.REQUEST) private request: Request, @inject(RestBindings.Http.RESPONSE) private response: Response, ) { + this.serverRootPath = this.getServerRootPath(restConfig); this.openApiSpecUrl = this.getOpenApiSpecUrl(restConfig); } indexRedirect() { - this.response.redirect(301, this.request.url + '/'); + this.response.redirect(301, this.serverRootPath + this.request.url + '/'); } index() { @@ -48,13 +50,24 @@ export class ExplorerController { .send(homePage); } + private getServerRootPath(restConfig: RestServerConfig): string { + const openApiConfig = restConfig.openApiSpec || {}; + const servers = openApiConfig.servers || []; + let url = servers[0] ? servers[0].url : ''; + // trim trailing '/' so we can safely append rooted paths to it + if (url.endsWith('/')) { + url = url.substring(0, url.length - 1); + } + return url; + } + private getOpenApiSpecUrl(restConfig: RestServerConfig): string { const openApiConfig = restConfig.openApiSpec || {}; const endpointMapping = openApiConfig.endpointMapping || {}; const endpoint = Object.keys(endpointMapping).find(k => isOpenApiV3Json(endpointMapping[k]), ); - return endpoint || '/openapi.json'; + return this.serverRootPath + (endpoint || '/openapi.json'); } } diff --git a/packages/rest-explorer/test/acceptance/rest-explorer.acceptance.ts b/packages/rest-explorer/test/acceptance/rest-explorer.acceptance.ts index b8159fa2d15f..8e0c0f360ac2 100644 --- a/packages/rest-explorer/test/acceptance/rest-explorer.acceptance.ts +++ b/packages/rest-explorer/test/acceptance/rest-explorer.acceptance.ts @@ -71,13 +71,6 @@ describe('API Explorer (acceptance)', () => { const body = response.body; expect(body).to.match(/^\s*url: '\/apispec',\s*$/m); }); - - async function givenAppWithCustomRestConfig(config: RestServerConfig) { - app = givenRestApplication(config); - app.component(RestExplorerComponent); - await app.start(); - request = createRestAppClient(app); - } }); context('with custom RestExplorerConfig', () => { @@ -109,8 +102,53 @@ describe('API Explorer (acceptance)', () => { } }); + context('with custom server root', async () => { + beforeEach(async () => { + await givenAppWithCustomRestConfig({ + openApiSpec: { + servers: [{url: '/external/proxy/root'}], + }, + }); + }); + + it('prepends server root path to OpenAPI path', async () => { + const response = await request.get('/explorer/').expect(200); + const body = response.body; + expect(body).to.match( + /^\s*url: '\/external\/proxy\/root\/openapi.json',\s*$/m, + ); + }); + + it('prepends server root path to /explorer redirect', async () => { + await request + .get('/explorer') + .expect(301) + .expect('location', '/external/proxy/root/explorer/'); + }); + + it('handles server root with trailing slash', async () => { + await givenAppWithCustomRestConfig({ + openApiSpec: { + servers: [{url: '/external/proxy/root/'}], + }, + }); + const response = await request.get('/explorer/').expect(200); + const body = response.body; + expect(body).to.match( + /^\s*url: '\/external\/proxy\/root\/openapi.json',\s*$/m, + ); + }); + }); + function givenRestApplication(config?: RestServerConfig) { const rest = Object.assign({}, givenHttpServerConfig(), config); return new RestApplication({rest}); } + + async function givenAppWithCustomRestConfig(config: RestServerConfig) { + app = givenRestApplication(config); + app.component(RestExplorerComponent); + await app.start(); + request = createRestAppClient(app); + } });