-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: openapi spec contributor extension point
- Loading branch information
Showing
17 changed files
with
535 additions
and
15 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
23 changes: 23 additions & 0 deletions
23
packages/openapi-v3/src/__tests__/unit/enhancers/fixtures/application.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
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); | ||
} | ||
} |
29 changes: 29 additions & 0 deletions
29
packages/openapi-v3/src/__tests__/unit/enhancers/fixtures/info.spec.extension.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
// 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 {bind} from '@loopback/core'; | ||
import debugModule from 'debug'; | ||
import {inspect} from 'util'; | ||
import {defaultMergeFn} from '../../../..'; | ||
import {asSpecEnhancer, OASEnhancer} from '../../../../enhancers/types'; | ||
import {OpenApiSpec} from '../../../../types'; | ||
const debug = debugModule('loopback:openapi:spec-enhancer'); | ||
|
||
/** | ||
* A spec enhancer to add OpenAPI info spec | ||
*/ | ||
@bind(asSpecEnhancer) | ||
export class InfoSpecEnhancer implements OASEnhancer { | ||
name = 'info'; | ||
|
||
modifySpec(spec: OpenApiSpec): OpenApiSpec { | ||
const InfoPatchSpec = { | ||
info: {title: 'LoopBack Test Application', version: '1.0.1'}, | ||
}; | ||
const mergedSpec = defaultMergeFn(spec, InfoPatchSpec); | ||
debug(`security spec extension, merged spec: ${inspect(mergedSpec)}`); | ||
return mergedSpec; | ||
} | ||
} |
44 changes: 44 additions & 0 deletions
44
packages/openapi-v3/src/__tests__/unit/enhancers/fixtures/security.spec.extension.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
// 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 {bind} from '@loopback/core'; | ||
import debugModule from 'debug'; | ||
import {inspect} from 'util'; | ||
import { | ||
defaultMergeFn, | ||
ReferenceObject, | ||
SecuritySchemeObject, | ||
} from '../../../..'; | ||
import {asSpecEnhancer, OASEnhancer} from '../../../../enhancers/types'; | ||
import {OpenApiSpec} from '../../../../types'; | ||
const debug = debugModule('loopback:openapi:spec-enhancer'); | ||
|
||
export type SecuritySchemeObjects = { | ||
[securityScheme: string]: SecuritySchemeObject | ReferenceObject; | ||
}; | ||
|
||
export const SECURITY_SCHEME_SPEC: SecuritySchemeObjects = { | ||
bearerAuth: { | ||
type: 'http', | ||
scheme: 'bearer', | ||
bearerFormat: 'JWT', | ||
}, | ||
}; | ||
|
||
/** | ||
* A spec enhancer to add bearer token OpenAPI security entry to | ||
* `spec.component.securitySchemes` | ||
*/ | ||
@bind(asSpecEnhancer) | ||
export class SecuritySpecEnhancer implements OASEnhancer { | ||
name = 'security'; | ||
|
||
modifySpec(spec: OpenApiSpec): OpenApiSpec { | ||
const patchSpec = {components: {securitySchemes: SECURITY_SCHEME_SPEC}}; | ||
const mergedSpec = defaultMergeFn(spec, patchSpec); | ||
debug(`security spec extension, merged spec: ${inspect(mergedSpec)}`); | ||
return mergedSpec; | ||
} | ||
} |
100 changes: 100 additions & 0 deletions
100
packages/openapi-v3/src/__tests__/unit/enhancers/spec-enhancer.extensions.unit.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
// 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 {expect} from '@loopback/testlab'; | ||
import {OASEnhancerService} from '../../..'; | ||
import {SpecServiceApplication} from './fixtures/application'; | ||
|
||
describe('spec-enhancer-extension-point', () => { | ||
let app: SpecServiceApplication; | ||
let specService: OASEnhancerService; | ||
|
||
beforeEach(givenAppWithSpecComponent); | ||
beforeEach(findSpecService); | ||
|
||
it('setter - can set spec', async () => { | ||
const EXPECTED_SPEC = { | ||
openapi: '3.0.0', | ||
info: {title: 'LoopBack Application', version: '1.0.0'}, | ||
// setter adds paths spec based on the default spec | ||
paths: getSamplePathSpec(), | ||
}; | ||
const PATHS_SPEC = getSamplePathSpec(); | ||
specService.spec = {...specService.spec, paths: PATHS_SPEC}; | ||
expect(specService.spec).to.eql(EXPECTED_SPEC); | ||
}); | ||
|
||
it('generateSpec - loads and create spec for ALL registered extensions', async () => { | ||
const EXPECTED_SPEC = { | ||
openapi: '3.0.0', | ||
// info object is updated by the info enhancer | ||
info: {title: 'LoopBack Test Application', version: '1.0.1'}, | ||
paths: {}, | ||
// the security scheme entry is added by the security enhancer | ||
components: { | ||
securitySchemes: { | ||
bearerAuth: { | ||
type: 'http', | ||
scheme: 'bearer', | ||
bearerFormat: 'JWT', | ||
}, | ||
}, | ||
}, | ||
}; | ||
const specFromService = await specService.generateSpec(); | ||
expect(specFromService).to.eql(EXPECTED_SPEC); | ||
}); | ||
|
||
it('getEnhancerByName - returns the enhancer with a given name', async () => { | ||
const enhancer = await specService.getEnhancerByName('info'); | ||
expect(enhancer).to.not.be.undefined(); | ||
expect(enhancer?.name).to.equal('info'); | ||
}); | ||
|
||
it('applyEnhancerByName - returns the merged spec after applying a given enhancer', async () => { | ||
const EXPECTED_SPEC = { | ||
openapi: '3.0.0', | ||
// info object is updated by the info enhancer | ||
info: {title: 'LoopBack Test Application', version: '1.0.1'}, | ||
paths: {}, | ||
}; | ||
const mergedSpec = await specService.applyEnhancerByName('info'); | ||
expect(mergedSpec).to.eql(EXPECTED_SPEC); | ||
}); | ||
|
||
function givenAppWithSpecComponent() { | ||
app = new SpecServiceApplication(); | ||
} | ||
|
||
async function findSpecService() { | ||
specService = await app.getSpecService(); | ||
} | ||
|
||
function getSamplePathSpec() { | ||
return { | ||
'/pets': { | ||
get: { | ||
description: | ||
'Returns all pets from the system that the user has access to', | ||
responses: { | ||
'200': { | ||
description: 'A list of pets.', | ||
content: { | ||
'application/json': { | ||
schema: { | ||
type: 'array', | ||
items: { | ||
$ref: '#/components/schemas/pet', | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}; | ||
} | ||
}); |
Oops, something went wrong.