Skip to content

Commit

Permalink
feat: openapi spec contributor extension point
Browse files Browse the repository at this point in the history
  • Loading branch information
jannyHou committed Dec 19, 2019
1 parent 58c331c commit 6d60027
Show file tree
Hide file tree
Showing 19 changed files with 698 additions and 15 deletions.
153 changes: 153 additions & 0 deletions docs/site/Extending-OpenAPI-specification.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
---
lang: en
title: 'Extending OpenAPI Specification'
keywords: LoopBack 4.0, LoopBack 4
sidebar: lb4_sidebar
permalink: /doc/en/lb4/Extending-OpenAPI-specification.html
---

## OpenAPI Specification Enhancer

The APIs in a LoopBack `RestApplication` are described by the
[OpenAPI Specification (short for OAS)](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md).
An application's OAS is mainly generated from
[controllers](https://loopback.io/doc/en/lb4/Controllers.html) and their
members' metadata. Besides this, we would also like to contribute specifications
from other places. Therefore, an extension point `OASEnhancerService` is created
to allow registered extensions to provide their OAS fragments and modify a rest
application's specification.

_Read about the extension point/extension pattern in
[documentation](Extension-point-and-extensions.md)_

## Adding a New OAS Enhancer

Interface `OASEnhancer` is created in `@loopback/openapi-v3` to describe the
specification enhancers. A typical OAS enhancer class should have a string type
`name` field and a function `modifySpec()` to modify the current specification.

For example, to modify the `info` field of an OAS, you can create an
`InfoSpecEnhancer` that implements interface `OASEnhancer` as follows:

```ts
import {bind} from '@loopback/core';
import {
mergeOpenAPISpec,
asSpecEnhancer,
OASEnhancer,
OpenApiSpec,
} from '@loopback/openapi-v3';

/**
* A spec enhancer to add OpenAPI info spec
*/
@bind(asSpecEnhancer)
export class InfoSpecEnhancer implements OASEnhancer {
// give your enhancer a proper name
name = 'info';

// takes in the current spec, modifies it, and returns a new one
modifySpec(spec: OpenApiSpec): OpenApiSpec {
const InfoPatchSpec = {
info: {title: 'LoopBack Test Application', version: '1.0.1'},
};
// the example calls a default helper function to merge the fragment spec.
const mergedSpec = mergeOpenAPISpec(spec, InfoPatchSpec);
return mergedSpec;
}
}
```

- The class is decorated with a binding template `asSpecEnhancer`.
- The enhancer has a name as `info`. Name can be used to retrieve a certain
enhancer (explained in the
[extension point section](#oas-enhancer-service-as-extension-point)).
- The enhancer changes the current specification's `info` object in function
`modifySpec`.
- It calls [`mergeOpenAPISpec`](#default-merge-function) to merge the
specification fragment into the current spec.

### Default Merge Function

Since `modifySpec` has full access to the current spec, it can perform any
operation: merge, delete, or more complicated changes. This is totally
determined by the extension contributor.

To apply the basic merging, we provide a default helper function called
`mergeOpenAPISpec` that leverages
[`json-merge-patch`](https://github.com/pierreinglebert/json-merge-patch) to
merge two json objects. You can find its usage in the
[previous section](#adding-a-new-oas-enhancer)

### Registering an Enhancer

After decorating your enhancer properly with `@bind(asSpecEnhancer)`, you can
bind it to your application as follows:

```ts
import {createBindingFromClass} from '@loopback/core';
import {InfoSpecEnhancer} from './enhancers/infoSpecEnhancer';

class MyApplication extends RestApplication {
constructor() {
super();
this.add(createBindingFromClass(InfoSpecEnhancer));
}
}
```

## OAS Enhancer Service as Extension Point

The OAS enhancer extension point is created in package `@loopback/openapi-v3`.
It organizes the registered OAS enhancers, and provides APIs to either apply one
enhancer by name, or apply all enhancers automatically.

### Registering an Enhancer Service

You can bind the OAS enhancer extension point to your app via key
`OAS_ENHANCER_SERVICE`:

```ts
import {RestApplication} from '@loopback/rest';
import {OASEnhancerService, OAS_ENHANCER_SERVICE} from '@loopback/openapi-v3';

class MyApplication extends RestApplication {
constructor() {
super();
this.add(
createBindingFromClass(OASEnhancerService, {
key: OAS_ENHANCER_SERVICE,
}),
);
}

// define a function to return a spec service by the same key
getSpecService() {
return this.get(OAS_ENHANCER_SERVICE);
}
}
```

### Applying Registered Enhancers

To automatically apply all the registered enhancers, call `applyAllEnhancers`:

```ts
await app.getSpecService.applyAllEnhancers();
```

_In the future we will support applying enhancers by a custom sequence. The
sequence will be determined by a combination of group names and the alphabetical
order._

To retrieve an enhancer by name, call `getEnhancerByName`:

```ts
await app.getSpecService.getEnhancerByName('info');
```

To apply an enhancer by name, call `applyEnhancerByName`:

```ts
await app.getSpecService.applyEnhancerByName('info');
```
4 changes: 4 additions & 0 deletions docs/site/sidebars/lb4_sidebar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,10 @@ children:
url: Extension-life-cycle.html
output: 'web, pdf'

- title: 'Extending OpenAPI specification'
url: Extending-OpenAPI-specification.html
output: 'web, pdf'

- title: 'Testing your extension'
url: Testing-your-extension.html
output: 'web, pdf'
Expand Down
159 changes: 159 additions & 0 deletions packages/openapi-v3/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions packages/openapi-v3/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@
"node": ">=8.9"
},
"dependencies": {
"@loopback/context": "^1.25.0",
"@loopback/repository-json-schema": "^1.11.3",
"@loopback/core": "^1.12.0",
"debug": "^4.1.1",
"lodash": "^4.17.15",
"openapi3-ts": "^1.3.0"
"openapi3-ts": "^1.3.0",
"json-merge-patch": "^0.2.3"
},
"devDependencies": {
"@loopback/build": "^3.0.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright IBM Corp. 2019. All Rights Reserved.
// Node module: @loopback/openapi-v3
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {Application, createBindingFromClass} from '@loopback/core';
import {OASEnhancerService, OAS_ENHANCER_SERVICE} from '../../../..';
import {InfoSpecEnhancer} from './info.spec.extension';
import {SecuritySpecEnhancer} from './security.spec.extension';

export class SpecServiceApplication extends Application {
constructor() {
super();
this.add(
createBindingFromClass(OASEnhancerService, {
key: OAS_ENHANCER_SERVICE,
}),
);
this.add(createBindingFromClass(SecuritySpecEnhancer));
this.add(createBindingFromClass(InfoSpecEnhancer));
}

async main() {}

getSpecService() {
return this.get(OAS_ENHANCER_SERVICE);
}
}
Loading

0 comments on commit 6d60027

Please sign in to comment.