@loopback/openapi-v3
Version:
Decorators that annotate LoopBack artifacts with OpenAPI v3 metadata and utilities that transform LoopBack metadata to OpenAPI v3 specifications
153 lines (140 loc) • 4.62 kB
text/typescript
// Copyright IBM Corp. and LoopBack contributors 2019,2020. 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 {config, extensionPoint, extensions, Getter} from '@loopback/core';
import debugModule from 'debug';
import * as _ from 'lodash';
import {inspect} from 'util';
import {
DEFAULT_OPENAPI_SPEC_INFO,
OpenApiSpec,
SecuritySchemeObject,
} from '../types';
import {OASEnhancerBindings} from './keys';
import {OASEnhancer} from './types';
const jsonmergepatch = require('json-merge-patch');
const debug = debugModule('loopback:openapi:spec-enhancer');
/**
* Options for the OpenAPI Spec enhancer extension point
*/
export interface OASEnhancerServiceOptions {
// no-op
}
/**
* An extension point for OpenAPI Spec enhancement
* This service is used for enhancing an OpenAPI spec by loading and applying one or more
* registered enhancers.
*
* A typical use of it would be generating the OpenAPI spec for the endpoints on a server
* in the `@loopback/rest` module.
*/
(OASEnhancerBindings.OAS_ENHANCER_EXTENSION_POINT_NAME)
export class OASEnhancerService {
constructor(
/**
* Inject a getter function to fetch spec enhancers
*/
()
private getEnhancers: Getter<OASEnhancer[]>,
/**
* An extension point should be able to receive its options via dependency
* injection.
*/
()
public readonly options?: OASEnhancerServiceOptions,
) {}
private _spec: OpenApiSpec = {
openapi: '3.0.0',
info: {
...DEFAULT_OPENAPI_SPEC_INFO,
},
paths: {},
};
/**
* Getter for `_spec`
*/
get spec(): OpenApiSpec {
return this._spec;
}
/**
* Setter for `_spec`
*/
set spec(value: OpenApiSpec) {
this._spec = value;
}
/**
* Find an enhancer by its name
* @param name The name of the enhancer you want to find
*/
async getEnhancerByName<T extends OASEnhancer = OASEnhancer>(
name: string,
): Promise<T | undefined> {
// Get the latest list of enhancers
const enhancers = await this.getEnhancers();
return enhancers.find(e => e.name === name) as T | undefined;
}
/**
* Apply a given enhancer's merge function. Return the latest _spec.
* @param name The name of the enhancer you want to apply
*/
async applyEnhancerByName(name: string): Promise<OpenApiSpec> {
const enhancer = await this.getEnhancerByName(name);
if (enhancer) this._spec = await enhancer.modifySpec(this._spec);
return this._spec;
}
/**
* Generate OpenAPI spec by applying ALL registered enhancers
* TBD: load enhancers by group names
*/
async applyAllEnhancers(options = {}): Promise<OpenApiSpec> {
const enhancers = await this.getEnhancers();
if (_.isEmpty(enhancers)) return this._spec;
for (const e of enhancers) {
this._spec = await e.modifySpec(this._spec);
}
debug(`Spec enhancer service, generated spec: ${inspect(this._spec)}`);
return this._spec;
}
}
/**
* The default merge function to patch the current OpenAPI spec.
* It leverages module `json-merge-patch`'s merge API to merge two json objects.
* It returns a new merged object without modifying the original one.
*
* A list of merging rules can be found in test file:
* https://github.com/pierreinglebert/json-merge-patch/blob/master/test/lib/merge.js
*
* @param currentSpec The original spec
* @param patchSpec The patch spec to be merged into the original spec
* @returns A new specification object created by merging the original ones.
*/
export function mergeOpenAPISpec<
C extends Partial<OpenApiSpec>,
P extends Partial<OpenApiSpec>,
>(currentSpec: C, patchSpec: P): C & P {
const mergedSpec = jsonmergepatch.merge(currentSpec, patchSpec);
return mergedSpec;
}
/**
* Security scheme merge helper function to patch the current OpenAPI spec.
* It provides a direct route to add a security schema to the specs components.
* It returns a new merged object without modifying the original one.
*
* @param currentSpec The original spec
* @param schemeName The name of the security scheme to be added
* @param schemeSpec The security scheme spec body to be added,
*/
export function mergeSecuritySchemeToSpec(
spec: OpenApiSpec,
schemeName: string,
schemeSpec: SecuritySchemeObject,
): OpenApiSpec {
const patchSpec = {
components: {
securitySchemes: {[schemeName]: schemeSpec},
},
};
const mergedSpec = mergeOpenAPISpec(spec, patchSpec);
return mergedSpec;
}