Skip to content

Commit c1d710b

Browse files
committed
feat(rest): add local UI for api explorer
1 parent bf10d3a commit c1d710b

File tree

11 files changed

+187
-27
lines changed

11 files changed

+187
-27
lines changed

packages/explorer/index.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
// This file is licensed under the MIT License.
44
// License text available at https://opensource.org/licenses/MIT
55

6-
export * from './dist8';
6+
export * from './dist';

packages/explorer/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
// This file is licensed under the MIT License.
44
// License text available at https://opensource.org/licenses/MIT
55

6-
module.exports = require('@loopback/dist-util').loadDist(__dirname);
6+
module.exports = require('./dist');

packages/explorer/package.json

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,33 +6,33 @@
66
"node": ">=8.9"
77
},
88
"scripts": {
9-
"build:all-dist": "npm run build:dist8 && npm run build:dist10",
109
"build:apidocs": "lb-apidocs",
11-
"build": "lb-tsc",
12-
"build:dist8": "lb-tsc es2017",
13-
"build:dist10": "lb-tsc es2018",
10+
"build": "lb-tsc es2017 --outDir dist --copy-resources",
1411
"clean": "lb-clean loopback-explorer*.tgz dist* package api-docs",
1512
"pretest": "npm run build",
16-
"integration": "lb-mocha \"DIST/test/integration/**/*.js\"",
17-
"test": "lb-mocha \"DIST/test/unit/**/*.js\" \"DIST/test/integration/**/*.js\"",
18-
"unit": "lb-mocha \"DIST/test/unit/**/*.js\"",
13+
"integration": "lb-mocha \"dist/test/integration/**/*.js\"",
14+
"test": "lb-mocha \"dist/test/unit/**/*.js\" \"dist/test/integration/**/*.js\"",
15+
"unit": "lb-mocha \"dist/test/unit/**/*.js\"",
1916
"verify": "npm pack && tar xf loopback-explorer*.tgz && tree package && npm run clean"
2017
},
2118
"author": "IBM",
2219
"copyright.owner": "IBM Corp.",
2320
"license": "MIT",
2421
"dependencies": {
22+
"@loopback/context": "^1.0.0",
23+
"@loopback/core": "^1.0.0",
24+
"@loopback/dist-util": "^0.3.6",
25+
"@loopback/rest": "^1.1.0",
2526
"@types/express": "^4.16.0",
2627
"ejs": "^2.6.1",
2728
"serve-static": "^1.13.2",
28-
"swagger-ui-dist": "^3.18.2"
29+
"swagger-ui-dist": "^3.19.0"
2930
},
3031
"devDependencies": {
31-
"@loopback/build": "^0.7.1",
32-
"@loopback/dist-util": "^0.3.6",
33-
"@loopback/testlab": "^0.11.5",
32+
"@loopback/build": "^1.0.0",
33+
"@loopback/testlab": "^1.0.0",
3434
"@types/ejs": "^2.6.0",
35-
"@types/node": "^10.1.1",
35+
"@types/node": "^10.10.1",
3636
"@types/serve-static": "^1.13.2",
3737
"express": "^4.16.3"
3838
},
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright IBM Corp. 2018. All Rights Reserved.
2+
// Node module: @loopback/explorer
3+
// This file is licensed under the MIT License.
4+
// License text available at https://opensource.org/licenses/MIT
5+
6+
import {inject} from '@loopback/context';
7+
import {Application, Component, CoreBindings} from '@loopback/core';
8+
import {RestServer} from '@loopback/rest';
9+
import {ApiExplorerUIOptions, apiExplorerUI} from './explorer';
10+
import {ExplorerBindings} from './keys';
11+
12+
/**
13+
* We have a few options:
14+
*
15+
* 1. The Explorer component contributes an express middleware so that REST
16+
* servers can mount the UI
17+
*
18+
* 2. The Explorer component contributes a route to REST servers
19+
*
20+
* 3. The Explorer component proactively mount itself with the RestApplication
21+
*/
22+
export class ExplorerComponent implements Component {
23+
constructor(
24+
@inject(CoreBindings.APPLICATION_INSTANCE) private application: Application,
25+
@inject(ExplorerBindings.CONFIG, {optional: true})
26+
private config: ApiExplorerUIOptions,
27+
) {
28+
this.init();
29+
}
30+
31+
init() {
32+
// FIXME: We should be able to receive the servers via injection
33+
const restServerBindings = this.application.find(
34+
binding =>
35+
binding.key.startsWith(CoreBindings.SERVERS) &&
36+
binding.valueConstructor === RestServer,
37+
);
38+
for (const binding of restServerBindings) {
39+
const restServer = this.application.getSync<RestServer>(binding.key);
40+
restServer.bind(ExplorerBindings.MIDDLEWARE).to({
41+
path: this.config.path || '/explorer',
42+
handler: apiExplorerUI(this.config),
43+
});
44+
}
45+
}
46+
}

packages/explorer/src/explorer.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ export type ApiExplorerUIOptions = {
3030
* Options for serve-static middleware
3131
*/
3232
serveStaticOptions?: serveStatic.ServeStaticOptions;
33+
34+
/**
35+
* Path for the explorer UI
36+
*/
37+
path?: string;
3338
};
3439

3540
/**

packages/explorer/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@
44
// License text available at https://opensource.org/licenses/MIT
55

66
export * from './explorer';
7+
export * from './keys';
8+
export * from './explorer.component';

packages/explorer/src/keys.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright IBM Corp. 2018. All Rights Reserved.
2+
// Node module: @loopback/explorer
3+
// This file is licensed under the MIT License.
4+
// License text available at https://opensource.org/licenses/MIT
5+
6+
import {BindingKey} from '@loopback/context';
7+
import {ApiExplorerUIOptions} from './explorer';
8+
import {Middleware} from '@loopback/rest';
9+
10+
/**
11+
* Binding keys used by this component.
12+
*/
13+
export namespace ExplorerBindings {
14+
export const MIDDLEWARE = BindingKey.create<Middleware>(
15+
'middleware.explorer',
16+
);
17+
18+
export const CONFIG = BindingKey.create<ApiExplorerUIOptions | undefined>(
19+
'explorer.config',
20+
);
21+
}

packages/explorer/test/integration/explorer.integration.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
// License text available at https://opensource.org/licenses/MIT
55

66
import {createClientForHandler} from '@loopback/testlab';
7-
import {apiExplorerUI} from '../../src/explorer';
7+
import {apiExplorerUI} from '../../';
88
import * as express from 'express';
99
import * as path from 'path';
1010

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright IBM Corp. 2018. All Rights Reserved.
2+
// Node module: @loopback/explorer
3+
// This file is licensed under the MIT License.
4+
// License text available at https://opensource.org/licenses/MIT
5+
import {createRestAppClient} from '@loopback/testlab';
6+
import {RestServerConfig, RestApplication} from '@loopback/rest';
7+
import {ExplorerComponent, ExplorerBindings} from '../..';
8+
9+
describe('API Explorer for REST Server', () => {
10+
let restApp: RestApplication;
11+
12+
before(async () => {
13+
restApp = await givenRestApp({
14+
rest: {port: 0},
15+
});
16+
});
17+
18+
after(async () => {
19+
await restApp.stop();
20+
});
21+
22+
it('exposes "GET /explorer"', () => {
23+
const test = createRestAppClient(restApp);
24+
return test
25+
.get('/explorer')
26+
.expect(200, /\<title\>LoopBack API Explorer<\/title\>/)
27+
.expect('content-type', /text\/html.*/);
28+
});
29+
});
30+
31+
async function givenRestApp(options?: {rest: RestServerConfig}) {
32+
const app = new RestApplication(options);
33+
app.bind(ExplorerBindings.CONFIG).to({});
34+
// FIXME: Can we mount the ExplorerComponent to a given server?
35+
app.component(ExplorerComponent);
36+
await app.start();
37+
return app;
38+
}

packages/rest/src/keys.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {HttpProtocol} from '@loopback/http-server';
3232
import * as https from 'https';
3333
import {ErrorWriterOptions} from 'strong-error-handler';
3434
import {RestRouter} from './router';
35+
import {Application} from 'express';
3536

3637
/**
3738
* RestServer-specific bindings
@@ -73,6 +74,10 @@ export namespace RestBindings {
7374
*/
7475
export const ROUTER = BindingKey.create<RestRouter>('rest.router');
7576

77+
/**
78+
* Binding key for express app
79+
*/
80+
export const EXPRESS_APP = BindingKey.create<Application>('rest.express.app');
7681
/**
7782
* Binding key for setting and injecting Reject action's error handling
7883
* options.

0 commit comments

Comments
 (0)