Skip to content

Commit

Permalink
refactor(rest): make getApiSpec() async
Browse files Browse the repository at this point in the history
BREAKING CHANGE: Api specifications are now emitted as a Promise instead
of a value object.  Calls to getApiSpec function must switch from
the old style to new style as follows:

1. Old style

```ts
function() {
  // ...
  const spec = restApp.restServer.getApiSpec();
  // ...
}
```

2. New style

```ts
async function() {
  // ...
  const spec = await restApp.restServer.getApiSpec();
  // ...
}
```
  • Loading branch information
dougal83 authored and raymondfeng committed Mar 5, 2020
1 parent 8b6b3a3 commit fe3df1b
Show file tree
Hide file tree
Showing 6 changed files with 39 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ describe('CoffeeShopApplication', () => {
let apiSpec: OpenApiSpec;

before(async () => {
apiSpec = app.lbApp.restServer.getApiSpec();
apiSpec = await app.lbApp.restServer.getApiSpec();
});

it('has the same properties in both the LB3 and LB4 specs', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ describe('booter-lb3app', () => {
});

context('generated OpenAPI spec', () => {
it('uses different request-body schema for "create" operation', () => {
const spec = app.restServer.getApiSpec();
it('uses different request-body schema for "create" operation', async () => {
const spec = await app.restServer.getApiSpec();
const createOp: OperationObject = spec.paths['/api/CoffeeShops'].post;
expect(createOp.requestBody).to.containDeep({
content: {
Expand All @@ -66,8 +66,8 @@ describe('booter-lb3app', () => {
});
});

it('includes the target model as a property of the source model in a relation', () => {
const spec = app.restServer.getApiSpec();
it('includes the target model as a property of the source model in a relation', async () => {
const spec = await app.restServer.getApiSpec();
const schemas = (spec.components ?? {}).schemas ?? {};

expect(schemas.CoffeeShop)
Expand Down Expand Up @@ -118,8 +118,8 @@ describe('booter-lb3app', () => {
}
});

it('includes LoopBack 3 endpoints with `/api` base in OpenApiSpec', () => {
const apiSpec = app.restServer.getApiSpec();
it('includes LoopBack 3 endpoints with `/api` base in OpenApiSpec', async () => {
const apiSpec = await app.restServer.getApiSpec();
const paths = Object.keys(apiSpec.paths);
expect(paths).to.containDeep([
'/api/CoffeeShops/{id}',
Expand Down Expand Up @@ -219,8 +219,8 @@ describe('booter-lb3app', () => {
}));
});

it('does apply the spec modification', () => {
const spec = app.restServer.getApiSpec();
it('does apply the spec modification', async () => {
const spec = await app.restServer.getApiSpec();
const createOp: OperationObject = spec.paths['/api/CoffeeShops'].post;
expect(createOp.summary).to.eql('just a very simple modification');
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ describe('CrudRestController for a simple Product model', () => {
// a new test suite that will configure a PK with a different name
// and type, e.g. `pk: string` instead of `id: number`.
it('uses correct schema for the id parameter', async () => {
const spec = app.restServer.getApiSpec();
const spec = await app.restServer.getApiSpec();
const findByIdOp = spec.paths['/products/{id}'].get;
expect(findByIdOp).to.containDeep({
parameters: [
Expand Down Expand Up @@ -210,7 +210,7 @@ describe('CrudRestController for a simple Product model', () => {
// a new test suite that will configure a PK with a different name
// and type, e.g. `pk: string` instead of `id: number`.
it('uses correct schema for the id parameter', async () => {
const spec = app.restServer.getApiSpec();
const spec = await app.restServer.getApiSpec();
const findByIdOp = spec.paths['/products/{id}'].patch;
expect(findByIdOp).to.containDeep({
parameters: [
Expand Down Expand Up @@ -245,7 +245,7 @@ describe('CrudRestController for a simple Product model', () => {
// a new test suite that will configure a PK with a different name
// and type, e.g. `pk: string` instead of `id: number`.
it('uses correct schema for the id parameter', async () => {
const spec = app.restServer.getApiSpec();
const spec = await app.restServer.getApiSpec();
const findByIdOp = spec.paths['/products/{id}']['patch'];
expect(findByIdOp).to.containDeep({
parameters: [
Expand Down Expand Up @@ -278,7 +278,7 @@ describe('CrudRestController for a simple Product model', () => {
// a new test suite that will configure a PK with a different name
// and type, e.g. `pk: string` instead of `id: number`.
it('uses correct schema for the id parameter', async () => {
const spec = app.restServer.getApiSpec();
const spec = await app.restServer.getApiSpec();
const findByIdOp = spec.paths['/products/{id}']['delete'];
expect(findByIdOp).to.containDeep({
parameters: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ describe('RestApplication (integration)', () => {
'x-foo': 'bar',
});

const spec = restApp.restServer.getApiSpec();
const spec = await restApp.restServer.getApiSpec();
expect(spec).to.deepEqual({
openapi: '3.0.0',
info: {
Expand Down Expand Up @@ -231,7 +231,7 @@ describe('RestApplication (integration)', () => {
restApp.mountExpressRouter('/dogs', router, spec);
await client.get('/dogs/hello').expect(200, 'Hello dogs!');

const openApiSpec = restApp.restServer.getApiSpec();
const openApiSpec = await restApp.restServer.getApiSpec();
expect(openApiSpec.paths).to.deepEqual({
'/dogs/hello': {
get: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ describe('RestServer.getApiSpec()', () => {
beforeEach(givenApplication);

it('comes with a valid default spec', async () => {
await validateApiSpec(server.getApiSpec());
await validateApiSpec(await server.getApiSpec());
});

it('honours API defined via app.api()', () => {
it('honours API defined via app.api()', async () => {
server.api({
openapi: '3.0.0',
info: {
Expand All @@ -36,7 +36,7 @@ describe('RestServer.getApiSpec()', () => {
'x-foo': 'bar',
});

const spec = server.getApiSpec();
const spec = await server.getApiSpec();
expect(spec).to.deepEqual({
openapi: '3.0.0',
info: {
Expand Down Expand Up @@ -81,11 +81,11 @@ describe('RestServer.getApiSpec()', () => {
});
});

it('returns routes registered via app.route(route)', () => {
it('returns routes registered via app.route(route)', async () => {
function greet() {}
server.route('get', '/greet', {responses: {}}, greet);

const spec = server.getApiSpec();
const spec = await server.getApiSpec();
expect(spec.paths).to.eql({
'/greet': {
get: {
Expand All @@ -95,7 +95,7 @@ describe('RestServer.getApiSpec()', () => {
});
});

it('ignores routes marked as "x-visibility" via app.route(route)', () => {
it('ignores routes marked as "x-visibility" via app.route(route)', async () => {
function greet() {}
function meet() {}
server.route(
Expand All @@ -105,7 +105,7 @@ describe('RestServer.getApiSpec()', () => {
greet,
);
server.route('get', '/meet', {responses: {}, spec: {}}, meet);
const spec = server.getApiSpec();
const spec = await server.getApiSpec();
expect(spec.paths).to.eql({
'/meet': {
get: {
Expand All @@ -116,7 +116,7 @@ describe('RestServer.getApiSpec()', () => {
});
});

it('returns routes registered via app.route(..., Controller, method)', () => {
it('returns routes registered via app.route(..., Controller, method)', async () => {
class MyController {
greet() {}
}
Expand All @@ -130,7 +130,7 @@ describe('RestServer.getApiSpec()', () => {
'greet',
);

const spec = server.getApiSpec();
const spec = await server.getApiSpec();
expect(spec.paths).to.eql({
'/greet': {
get: {
Expand All @@ -143,7 +143,7 @@ describe('RestServer.getApiSpec()', () => {
});
});

it('ignores routes marked as "x-visibility" via app.route(..., Controller, method)', () => {
it('ignores routes marked as "x-visibility" via app.route(..., Controller, method)', async () => {
class GreetController {
greet() {}
}
Expand All @@ -170,7 +170,7 @@ describe('RestServer.getApiSpec()', () => {
'meet',
);

const spec = server.getApiSpec();
const spec = await server.getApiSpec();
expect(spec.paths).to.eql({
'/meet': {
get: {
Expand All @@ -183,14 +183,14 @@ describe('RestServer.getApiSpec()', () => {
});
});

it('honors tags in the operation spec', () => {
it('honors tags in the operation spec', async () => {
class MyController {
@get('/greet', {responses: {'200': {description: ''}}, tags: ['MyTag']})
greet() {}
}
app.controller(MyController);

const spec = server.getApiSpec();
const spec = await server.getApiSpec();
expect(spec.paths).to.eql({
'/greet': {
get: {
Expand All @@ -204,7 +204,7 @@ describe('RestServer.getApiSpec()', () => {
});
});

it('emits all media types for request body', () => {
it('emits all media types for request body', async () => {
const expectedOpSpec = anOperationSpec()
.withRequestBody({
description: 'Any object value.',
Expand Down Expand Up @@ -238,18 +238,18 @@ describe('RestServer.getApiSpec()', () => {
}
app.controller(MyController);

const spec = server.getApiSpec();
const spec = await server.getApiSpec();
expect(spec.paths['/show-body'].post).to.containDeep(expectedOpSpec);
});

it('returns routes registered via app.controller()', () => {
it('returns routes registered via app.controller()', async () => {
class MyController {
@get('/greet')
greet() {}
}
app.controller(MyController);

const spec = server.getApiSpec();
const spec = await server.getApiSpec();
expect(spec.paths).to.eql({
'/greet': {
get: {
Expand All @@ -265,7 +265,7 @@ describe('RestServer.getApiSpec()', () => {
});
});

it('returns definitions inferred via app.controller()', () => {
it('returns definitions inferred via app.controller()', async () => {
@model()
class MyModel {
@property()
Expand All @@ -277,7 +277,7 @@ describe('RestServer.getApiSpec()', () => {
}
app.controller(MyController);

const spec = server.getApiSpec();
const spec = await server.getApiSpec();
expect(spec.components && spec.components.schemas).to.deepEqual({
MyModel: {
title: 'MyModel',
Expand All @@ -291,7 +291,7 @@ describe('RestServer.getApiSpec()', () => {
});
});

it('preserves routes specified in app.api()', () => {
it('preserves routes specified in app.api()', async () => {
function status() {}
server.api(
anOpenApiSpec()
Expand All @@ -305,7 +305,7 @@ describe('RestServer.getApiSpec()', () => {
function greet() {}
server.route('get', '/greet', {responses: {}}, greet);

const spec = server.getApiSpec();
const spec = await server.getApiSpec();
expect(spec.paths).to.eql({
'/greet': {
get: {
Expand Down
6 changes: 3 additions & 3 deletions packages/rest/src/rest.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,7 @@ export class RestServer extends Context implements Server, HttpServerLike {
);

specForm = specForm ?? {version: '3.0.0', format: 'json'};
const specObj = this.getApiSpec(requestContext);
const specObj = await this.getApiSpec(requestContext);

if (specForm.format === 'json') {
const spec = JSON.stringify(specObj, null, 2);
Expand Down Expand Up @@ -777,8 +777,8 @@ export class RestServer extends Context implements Server, HttpServerLike {
* @param requestContext - Optional context to update the `servers` list
* in the returned spec
*/
getApiSpec(requestContext?: RequestContext): OpenApiSpec {
let spec = this.getSync<OpenApiSpec>(RestBindings.API_SPEC);
async getApiSpec(requestContext?: RequestContext): Promise<OpenApiSpec> {
let spec = await this.get<OpenApiSpec>(RestBindings.API_SPEC);
const defs = this.httpHandler.getApiDefinitions();

// Apply deep clone to prevent getApiSpec() callers from
Expand Down

0 comments on commit fe3df1b

Please sign in to comment.