Skip to content

Commit

Permalink
openapi-framework: Allow injecting custom classes for features (kogos…
Browse files Browse the repository at this point in the history
  • Loading branch information
jsau- authored Jan 28, 2022
1 parent 1722e37 commit 5687716
Show file tree
Hide file tree
Showing 13 changed files with 372 additions and 10 deletions.
29 changes: 24 additions & 5 deletions packages/openapi-framework/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export default class OpenAPIFramework implements IOpenAPIFramework {
private enableObjectCoercion;
private errorTransformer;
private externalSchemas;
private features;
private originalApiDoc;
private operations;
private paths;
Expand Down Expand Up @@ -156,6 +157,7 @@ export default class OpenAPIFramework implements IOpenAPIFramework {
this.dependencies = args.dependencies;
this.errorTransformer = args.errorTransformer;
this.externalSchemas = args.externalSchemas;
this.features = args.features;
this.operations = args.operations;
this.paths = args.paths;
this.pathsIgnore = args.pathsIgnore;
Expand Down Expand Up @@ -435,10 +437,13 @@ export default class OpenAPIFramework implements IOpenAPIFramework {
operationDoc
)
) {
const ResponseValidatorClass =
this.features?.responseValidator || OpenAPIResponseValidator;

// add response validation feature
// it's invalid for a method doc to not have responses, but the post
// validation will pick it up, so this is almost always going to be added.
const responseValidator = new OpenAPIResponseValidator({
const responseValidator = new ResponseValidatorClass({
loggingKey: `${this.name}-response-validation`,
components: this.apiDoc.components,
definitions: this.apiDoc.definitions,
Expand Down Expand Up @@ -483,7 +488,10 @@ export default class OpenAPIFramework implements IOpenAPIFramework {
operationDoc
)
) {
const requestValidator = new OpenAPIRequestValidator({
const RequestValidatorClass =
this.features?.requestValidator || OpenAPIRequestValidator;

const requestValidator = new RequestValidatorClass({
errorTransformer: this.errorTransformer,
logger: this.logger,
parameters: methodParameters,
Expand Down Expand Up @@ -513,7 +521,10 @@ export default class OpenAPIFramework implements IOpenAPIFramework {
operationDoc
)
) {
const coercer = new OpenAPIRequestCoercer({
const CoercerClass =
this.features?.coercer || OpenAPIRequestCoercer;

const coercer = new CoercerClass({
extensionBase: `x-${this.name}-coercion`,
loggingKey: `${this.name}-coercion`,
parameters: methodParameters,
Expand All @@ -535,9 +546,13 @@ export default class OpenAPIFramework implements IOpenAPIFramework {
operationDoc
)
) {
const defaultSetter = new OpenAPIDefaultSetter({
const DefaultSetterClass =
this.features?.defaultSetter || OpenAPIDefaultSetter;

const defaultSetter = new DefaultSetterClass({
parameters: methodParameters,
});

operationContext.features.defaultSetter = defaultSetter;
}
}
Expand All @@ -558,7 +573,11 @@ export default class OpenAPIFramework implements IOpenAPIFramework {

if (securityDefinition) {
pathDoc[methodName].security = securityDefinition;
securityFeature = new OpenAPISecurityHandler({

const SecurityHandlerClass =
this.features?.securityHandler || OpenAPISecurityHandler;

securityFeature = new SecurityHandlerClass({
securityDefinitions: securitySchemes,
securityHandlers: this.securityHandlers,
operationSecurity: securityDefinition,
Expand Down
38 changes: 33 additions & 5 deletions packages/openapi-framework/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
import { IOpenAPIDefaultSetter } from 'openapi-default-setter';
import { IOpenAPIRequestCoercer } from 'openapi-request-coercer';
import { IOpenAPIRequestValidator } from 'openapi-request-validator';
import { IOpenAPIResponseValidator } from 'openapi-response-validator';
import {
IOpenAPIDefaultSetter,
OpenAPIDefaultSetterArgs,
} from 'openapi-default-setter';
import {
IOpenAPIRequestCoercer,
OpenAPIRequestCoercerArgs,
} from 'openapi-request-coercer';
import {
IOpenAPIRequestValidator,
OpenAPIRequestValidatorArgs,
} from 'openapi-request-validator';
import {
IOpenAPIResponseValidator,
OpenAPIResponseValidatorArgs,
} from 'openapi-response-validator';
import {
IOpenAPISecurityHandler,
OpenAPISecurityHandlerArgs,
SecurityHandlers,
} from 'openapi-security-handler';
import { IJsonSchema, OpenAPI, OpenAPIV2, OpenAPIV3 } from 'openapi-types';
import { IJsonSchema, OpenAPIV2, OpenAPIV3 } from 'openapi-types';
import { Logger } from 'ts-log';
import BasePath from './BasePath';
import * as Ajv from 'ajv';
Expand Down Expand Up @@ -87,6 +100,21 @@ interface OpenAPIFrameworkArgs {
dependencies?: { [service: string]: any };
enableObjectCoercion?: boolean;
errorTransformer?: OpenAPIErrorTransformer;
features?: {
coercer?: new (args: OpenAPIRequestCoercerArgs) => IOpenAPIRequestCoercer;
defaultSetter?: new (
args: OpenAPIDefaultSetterArgs
) => IOpenAPIDefaultSetter;
requestValidator?: new (
args: OpenAPIRequestValidatorArgs
) => IOpenAPIRequestValidator;
responseValidator?: new (
args: OpenAPIResponseValidatorArgs
) => IOpenAPIResponseValidator;
securityHandler?: new (
args: OpenAPISecurityHandlerArgs
) => IOpenAPISecurityHandler;
};
externalSchemas?: { [index: string]: IJsonSchema };
pathSecurity?: PathSecurityTuple[];
operations?: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
swagger: '2.0'
info:
title: sample api doc
version: '3'
paths: {}
securityDefinitions:
basic:
type: basic
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {
IOpenAPIRequestCoercer,
OpenAPIRequestCoercerArgs,
} from 'openapi-request-coercer';

export default class CustomCoercer implements IOpenAPIRequestCoercer {
private args;

constructor(args: OpenAPIRequestCoercerArgs) {
this.args = args;
}

coerce(request: any) {
return;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {
IOpenAPIDefaultSetter,
OpenAPIDefaultSetterArgs,
} from 'openapi-default-setter';
import { OpenAPI } from 'openapi-types';

export default class CustomDefaultSetter implements IOpenAPIDefaultSetter {
private args;

constructor(args: OpenAPIDefaultSetterArgs) {
this.args = args;
}

handle(request: OpenAPI.Request) {
return;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {
IOpenAPIRequestValidator,
OpenAPIRequestValidatorArgs,
} from 'openapi-request-validator';

export default class CustomRequestValidator
implements IOpenAPIRequestValidator {
private args;

constructor(args: OpenAPIRequestValidatorArgs) {
this.args = args;
}

validateRequest(request: any) {
return;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {
IOpenAPIResponseValidator,
OpenAPIResponseValidatorArgs,
} from 'openapi-response-validator';

export default class CustomResponseValidator
implements IOpenAPIResponseValidator {
private args;

constructor(args: OpenAPIResponseValidatorArgs) {
this.args = args;
}

validateResponse(statusCode: any, response: any) {
return {
errors: [],
message: 'Hello, world!',
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {
IOpenAPISecurityHandler,
OpenAPISecurityHandlerArgs,
} from 'openapi-security-handler';

export default class CustomSecurityHandler implements IOpenAPISecurityHandler {
private args;

constructor(args: OpenAPISecurityHandlerArgs) {
this.args = args;
}

handle(request: any) {
return new Promise<void>((resolve) => {
resolve();
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
module.exports = {
GET,
};

function GET() {
return;
}

GET.apiDoc = {
parameters: [
{
name: 'name',
in: 'query',
type: 'string',
default: 'elvis',
},
],
responses: {
default: {
description: 'return foo',
schema: {},
},
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/* tslint:disable:no-unused-expression */
import { expect } from 'chai';
import OpenapiFramework from '../../../';
import CustomDefaultSetter from './features/CustomDefaultSetter';
import CustomCoercer from './features/CustomCoercer';
import CustomRequestValidator from './features/CustomRequestValidator';
import CustomResponseValidator from './features/CustomResponseValidator';
import CustomSecurityHandler from './features/CustomSecurityHandler';
const path = require('path');

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

beforeEach(() => {
framework = new OpenapiFramework({
apiDoc: path.resolve(__dirname, 'apiDoc.yml'),
featureType: 'middleware',
features: {
coercer: CustomCoercer,
defaultSetter: CustomDefaultSetter,
requestValidator: CustomRequestValidator,
responseValidator: CustomResponseValidator,
securityHandler: CustomSecurityHandler,
},
name: 'some-framework',
paths: path.resolve(__dirname, './paths'),
pathSecurity: [
[/.+/, [{ basic: [] }]],
[/^awes/, [{ basic: [] }]],
],
securityHandlers: {
basic() {
return true;
},
},
});
});

it('should instantiate custom features', () => {
framework.initialize({
visitOperation(ctx) {
expect(ctx.features.coercer).to.be.instanceof(CustomCoercer);
expect(ctx.features.defaultSetter).to.be.instanceof(
CustomDefaultSetter
);
expect(ctx.features.requestValidator).to.be.instanceof(
CustomRequestValidator
);
expect(ctx.features.responseValidator).to.be.instanceof(
CustomResponseValidator
);
expect(ctx.features.securityHandler).to.be.instanceof(
CustomSecurityHandler
);
},
visitApi(ctx) {
const apiDoc = ctx.getApiDoc();
expect(apiDoc.paths['/foo']).to.eql({
parameters: [],
get: {
parameters: [
{
name: 'name',
in: 'query',
type: 'string',
default: 'elvis',
},
],
responses: {
default: {
description: 'return foo',
schema: {},
},
},
security: [
{
basic: [],
},
],
},
});
},
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
swagger: '2.0'
info:
title: sample api doc
version: '3'
paths: {}
securityDefinitions:
basic:
type: basic
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
module.exports = {
GET,
};

function GET() {
return;
}

GET.apiDoc = {
parameters: [
{
name: 'name',
in: 'query',
type: 'string',
default: 'elvis',
},
],
responses: {
default: {
description: 'return foo',
schema: {},
},
},
};
Loading

0 comments on commit 5687716

Please sign in to comment.