Skip to content

Commit

Permalink
openapi-framework: Adding support for operations (kogosoftwarellc#336)
Browse files Browse the repository at this point in the history
  • Loading branch information
jsdevel committed Feb 6, 2019
1 parent db057fe commit ee6af89
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 40 deletions.
110 changes: 72 additions & 38 deletions packages/openapi-framework/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export default class OpenAPIFramework implements IOpenAPIFramework {
private errorTransformer;
private externalSchemas;
private originalApiDoc;
private operations;
private paths;
private pathsIgnore;
private pathSecurity;
Expand All @@ -94,7 +95,6 @@ export default class OpenAPIFramework implements IOpenAPIFramework {
{ name: 'externalSchemas', type: 'object' },
{ name: 'featureType', required: true },
{ name: 'name', required: true },
{ name: 'paths', required: true },
{ name: 'pathSecurity', class: Array, className: 'Array' },
{ name: 'securityHandlers', type: 'object' }
].forEach(arg => {
Expand Down Expand Up @@ -123,6 +123,14 @@ export default class OpenAPIFramework implements IOpenAPIFramework {
}
});

if (!args.paths && !args.operations) {
throw new Error(
`${
this.loggingPrefix
}args.paths and args.operations must not both be empty`
);
}

this.enableObjectCoercion = !!args.enableObjectCoercion;
this.originalApiDoc = handleYaml(handleFilePath(args.apiDoc));
this.apiDoc = copy(this.originalApiDoc);
Expand All @@ -143,6 +151,7 @@ export default class OpenAPIFramework implements IOpenAPIFramework {
this.dependencies = args.dependencies;
this.errorTransformer = args.errorTransformer;
this.externalSchemas = args.externalSchemas;
this.operations = args.operations;
this.paths = args.paths;
this.pathsIgnore = args.pathsIgnore;
this.pathSecurity = Array.isArray(args.pathSecurity)
Expand Down Expand Up @@ -186,47 +195,72 @@ export default class OpenAPIFramework implements IOpenAPIFramework {
})
: null;

const paths = [].concat(this.paths);
let paths = [];
let routes = [];
paths.forEach(pathItem => {
if (byString(pathItem)) {
pathItem = toAbsolutePath(pathItem);
if (!byDirectory(pathItem)) {
throw new Error(
`${
this.loggingPrefix
}args.paths contained a value that was not a path to a directory`
);
}
routes = routes.concat(
fsRoutes(pathItem, {
glob: this.routesGlob,
indexFileRegExp: this.routesIndexFileRegExp
})
.filter(fsRoutesItem => {
return this.pathsIgnore
? !this.pathsIgnore.test(fsRoutesItem.route)
: true;
})
.map(fsRoutesItem => {
return {
path: fsRoutesItem.route,
module: require(fsRoutesItem.path)
};

if (this.paths) {
paths = [].concat(this.paths);
paths.forEach(pathItem => {
if (byString(pathItem)) {
pathItem = toAbsolutePath(pathItem);
if (!byDirectory(pathItem)) {
throw new Error(
`${
this.loggingPrefix
}args.paths contained a value that was not a path to a directory`
);
}
routes = routes.concat(
fsRoutes(pathItem, {
glob: this.routesGlob,
indexFileRegExp: this.routesIndexFileRegExp
})
);
} else {
if (!pathItem.path || !pathItem.module) {
throw new Error(
`${
this.loggingPrefix
}args.paths must consist of strings or valid route specifications`
.filter(fsRoutesItem => {
return this.pathsIgnore
? !this.pathsIgnore.test(fsRoutesItem.route)
: true;
})
.map(fsRoutesItem => {
return {
path: fsRoutesItem.route,
module: require(fsRoutesItem.path)
};
})
);
} else {
if (!pathItem.path || !pathItem.module) {
throw new Error(
`${
this.loggingPrefix
}args.paths must consist of strings or valid route specifications`
);
}
routes.push(pathItem);
}
routes.push(pathItem);
}
});
routes = routes.sort(byRoute);
});
routes = routes.sort(byRoute);
}

if (this.operations) {
const apiDocPaths = this.apiDoc.paths;
Object.keys(apiDocPaths).forEach(apiDocPathUrl => {
const apiDocPath = apiDocPaths[apiDocPathUrl];
Object.keys(apiDocPath)
.filter(byMethods)
.forEach(method => {
const methodDoc = apiDocPath[method];
const operationId = methodDoc.operationId;
if (operationId && operationId in this.operations) {
routes.push({
path: apiDocPathUrl,
module: {
[method]: this.operations[operationId]
}
});
}
});
});
}

// Check for duplicate routes
const dups = routes.filter((v, i, o) => {
Expand Down
3 changes: 2 additions & 1 deletion packages/openapi-framework/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ interface OpenAPIFrameworkArgs {
errorTransformer?: OpenAPIErrorTransformer;
externalSchemas?: { string: IJsonSchema };
pathSecurity?: PathSecurityTuple[];
paths: string | OpenAPIFrameworkPathObject[];
operations?: string | { [operationId: string]: (...arg: any[]) => any };
paths?: string | OpenAPIFrameworkPathObject[];
pathsIgnore?: RegExp;
routesGlob?: string;
routesIndexFileRegExp?: RegExp;
Expand Down
14 changes: 13 additions & 1 deletion packages/openapi-framework/test/constructor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ describe('OpenapiFramework', () => {
});
});

['apiDoc', 'featureType', 'name', 'paths'].forEach(spec => {
['apiDoc', 'featureType', 'name'].forEach(spec => {
describe(`when options.${spec} is missing`, () => {
beforeEach(() => {
delete options[spec];
Expand All @@ -54,6 +54,18 @@ describe('OpenapiFramework', () => {
});
});

describe('when options.paths and options.operations are missing', () => {
beforeEach(() => {
delete options.paths;
});

it('should throw', () => {
expect(() => {
new OpenapiFramework(options);
}).to.throw(`args.paths and args.operations must not both be empty`);
});
});

[
['errorTransformer', 'function'],
['externalSchemas', 'object'],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
swagger: '2.0'
info:
title: sample api doc
version: '3'
paths:
/foo:
get:
operationId: getFoo
responses:
default:
description: return foo
schema: {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = function getFoo() {
return;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/* tslint:disable:no-unused-expression */
import { expect } from 'chai';
import OpenapiFramework from '../../../';
const path = require('path');

describe(path.basename(__dirname), () => {
let framework: OpenapiFramework;

beforeEach(() => {
framework = new OpenapiFramework({
apiDoc: path.resolve(__dirname, 'apiDoc.yml'),
featureType: 'middleware',
name: 'some-framework',
operations: {
getFoo: require('./operations/foo')
}
});
});

it('should work', () => {
framework.initialize({
visitOperation(ctx) {
expect(ctx.features.responseValidator).to.not.be.undefined;
expect(ctx.features.requestValidator).to.be.undefined;
expect(ctx.features.coercer).to.be.undefined;
expect(ctx.features.defaultSetter).to.be.undefined;
expect(ctx.features.securityHandler).to.be.undefined;
},
visitApi(ctx) {
const apiDoc = ctx.getApiDoc();
expect(apiDoc.paths['/foo']).to.eql({
get: {
operationId: 'getFoo',
responses: {
default: {
description: 'return foo',
schema: {}
}
}
},
parameters: []
});
}
});
});
});

0 comments on commit ee6af89

Please sign in to comment.