Skip to content

Commit

Permalink
Merge pull request #200 from cdimascio/param-parser
Browse files Browse the repository at this point in the history
clean up req/param parser
  • Loading branch information
cdimascio committed Dec 29, 2019
2 parents c847379 + 4876980 commit edbbcaf
Show file tree
Hide file tree
Showing 13 changed files with 511 additions and 437 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ dist
secrets.zip
jest
junk
/a_reference
22 changes: 19 additions & 3 deletions src/framework/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@ import ajv = require('ajv');
import { Request, Response, NextFunction } from 'express';
export { OpenAPIFrameworkArgs };

export type BodySchema =
| OpenAPIV3.ReferenceObject
| OpenAPIV3.SchemaObject
| {};

export interface ParametersSchema {
query: object;
headers: object;
params: object;
cookies: object;
}

export interface ValidationSchema extends ParametersSchema {
body: BodySchema;
}

export interface OpenAPIFrameworkInit {
apiDoc: OpenAPIV3.Document;
basePaths: string[];
Expand Down Expand Up @@ -41,7 +57,7 @@ export interface OpenApiValidatorOpts {
unknownFormats?: true | string[] | 'ignore';
multerOpts?: {};
$refParser?: {
mode: 'bundle' | 'dereference',
mode: 'bundle' | 'dereference';
};
}

Expand Down Expand Up @@ -159,7 +175,7 @@ export namespace OpenAPIV3 {
export type ArraySchemaObjectType = 'array';
export type SchemaObject = ArraySchemaObject | NonArraySchemaObject;

interface ArraySchemaObject extends BaseSchemaObject {
export interface ArraySchemaObject extends BaseSchemaObject {
type: ArraySchemaObjectType;
items: ReferenceObject | SchemaObject;
}
Expand Down Expand Up @@ -363,7 +379,7 @@ interface OpenAPIFrameworkArgs {
apiDoc: OpenAPIV3.Document | string;
validateApiDoc?: boolean;
$refParser?: {
mode: 'bundle' | 'dereference',
mode: 'bundle' | 'dereference';
};
}

Expand Down
103 changes: 50 additions & 53 deletions src/middlewares/openapi.request.validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,44 +9,36 @@ import {
import ono from 'ono';
import { NextFunction, RequestHandler, Response } from 'express';
import {
ValidationSchema,
OpenAPIV3,
OpenApiRequest,
RequestValidatorOptions,
ValidateRequestOpts,
OpenApiRequestMetadata,
} from '../framework/types';
import { BodySchemaParser } from './parsers/body.parse';
import { ParametersSchemaParser } from './parsers/schema.parse';
import { RequestParameterMutator } from './parsers/req.parameter.mutator';

import {
ParametersSchemaParser,
ParametersSchema,
} from './schemas/parameters.parse';
import { ParametersTransform } from './schemas/parameters.transform';
import { BodySchemaParser } from './schemas/body.parse';

interface DraftProperties extends ParametersSchema {
body?: OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject | {};
}

export interface DraftSchema {
required: string[];
properties: DraftProperties;
}
type OperationObject = OpenAPIV3.OperationObject;
type SchemaObject = OpenAPIV3.SchemaObject;
type SecurityRequirementObject = OpenAPIV3.SecurityRequirementObject;

export class RequestValidator {
private _middlewareCache: { [key: string]: RequestHandler } = {};
private _apiDocs: OpenAPIV3.Document;
private middlewareCache: { [key: string]: RequestHandler } = {};
private apiDoc: OpenAPIV3.Document;
private ajv: Ajv;
private _requestOpts: ValidateRequestOpts = {};
private requestOpts: ValidateRequestOpts = {};

constructor(
apiDocs: OpenAPIV3.Document,
apiDoc: OpenAPIV3.Document,
options: RequestValidatorOptions = {},
) {
this._middlewareCache = {};
this._apiDocs = apiDocs;
this._requestOpts.allowUnknownQueryParameters =
this.middlewareCache = {};
this.apiDoc = apiDoc;
this.requestOpts.allowUnknownQueryParameters =
options.allowUnknownQueryParameters;
this.ajv = createRequestAjv(apiDocs, options);
this.ajv = createRequestAjv(apiDoc, options);
}

public validate(
Expand All @@ -67,8 +59,8 @@ export class RequestValidator {
throw validationError(404, req.path, 'not found');
}

const pathSchema = openapi.schema;
if (!pathSchema) {
const reqSchema = openapi.schema;
if (!reqSchema) {
throw validationError(405, req.path, `${req.method} method not allowed`);
}

Expand All @@ -77,56 +69,61 @@ export class RequestValidator {
const contentTypeKey = contentType.equivalents()[0] ?? 'not_provided';
const key = `${req.method}-${req.originalUrl}-${contentTypeKey}`;

if (!this._middlewareCache[key]) {
const middleware = this.buildMiddleware(path, pathSchema, contentType);
this._middlewareCache[key] = middleware;
if (!this.middlewareCache[key]) {
const middleware = this.buildMiddleware(path, reqSchema, contentType);
this.middlewareCache[key] = middleware;
}
return this._middlewareCache[key](req, res, next);
return this.middlewareCache[key](req, res, next);
}

private buildMiddleware(
path: string,
pathSchema: OpenAPIV3.OperationObject,
reqSchema: OperationObject,
contentType: ContentType,
): RequestHandler {
const paramSchemaParser = new ParametersSchemaParser(this._apiDocs);
const parameters = paramSchemaParser.parse(path, pathSchema.parameters);
const securityQueryParam = Security.queryParam(this._apiDocs, pathSchema);
const bodySchemaParser = new BodySchemaParser(this.ajv, this._apiDocs);
// TODO bodyParser.parse should return OpenAPIV3.SchemaObject instead of BodySchema
const body = <OpenAPIV3.SchemaObject>(
bodySchemaParser.parse(path, pathSchema, contentType)
);
;
const required = body.required ? ['body'] : [];
const apiDoc = this.apiDoc;
const schemaParser = new ParametersSchemaParser(apiDoc);
const bodySchemaParser = new BodySchemaParser(this.ajv, apiDoc);
const parameters = schemaParser.parse(path, reqSchema.parameters);
const securityQueryParam = Security.queryParam(apiDoc, reqSchema);
const body = bodySchemaParser.parse(path, reqSchema, contentType);

const properties: ValidationSchema = { ...parameters, body: body };
const required = (<SchemaObject>body).required ? ['body'] : [];

// $schema: "http://json-schema.org/draft-04/schema#",
const schema = {
required: ['query', 'headers', 'params'].concat(required),
properties: { ...parameters.schema, body: body },
properties,
};

const validator = this.ajv.compile(schema);

return (req: OpenApiRequest, res: Response, next: NextFunction): void => {
const parametersRequest = new ParametersTransform(parameters, schema);
const openapi = <OpenApiRequestMetadata>req.openapi;
const hasPathParams = Object.keys(openapi.pathParams).length > 0;

if (hasPathParams) {
req.params = openapi.pathParams ?? req.params;
}

const mutator = new RequestParameterMutator(apiDoc, path, properties);

parametersRequest.applyExplodedJsonTransform(req);
parametersRequest.applyExplodedJsonArrayTransform(req);
mutator.modifyRequest(req);

if (!this._requestOpts.allowUnknownQueryParameters) {
if (!this.requestOpts.allowUnknownQueryParameters) {
this.rejectUnknownQueryParams(
req.query,
schema.properties.query,
securityQueryParam,
);
}

parametersRequest.applyPathTransform(req);
parametersRequest.applyJsonTransform(req);
parametersRequest.applyJsonArrayTransform(req);

const cookies = req.cookies
? { ...req.cookies, ...req.signedCookies }
? {
...req.cookies,
...req.signedCookies,
}
: undefined;

const valid = validator({ ...req, cookies });
Expand Down Expand Up @@ -165,14 +162,14 @@ export class RequestValidator {
class Security {
public static queryParam(
apiDocs: OpenAPIV3.Document,
schema: OpenAPIV3.OperationObject,
schema: OperationObject,
): string[] {
const hasPathSecurity =
schema.hasOwnProperty('security') && schema.security.length > 0;
const hasRootSecurity =
apiDocs.hasOwnProperty('security') && apiDocs.security.length > 0;

let usedSecuritySchema: OpenAPIV3.SecurityRequirementObject[] = [];
let usedSecuritySchema: SecurityRequirementObject[] = [];
if (hasPathSecurity) {
usedSecuritySchema = schema.security;
} else if (hasRootSecurity) {
Expand All @@ -188,7 +185,7 @@ class Security {
}

private static getSecurityQueryParams(
usedSecuritySchema: OpenAPIV3.SecurityRequirementObject[],
usedSecuritySchema: SecurityRequirementObject[],
securitySchema,
): string[] {
return usedSecuritySchema && securitySchema
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import { Ajv } from 'ajv';
import { ContentType, validationError } from '../util';

import { OpenAPIV3 } from '../../framework/types';

export type BodySchema =
| OpenAPIV3.ReferenceObject
| OpenAPIV3.SchemaObject
| {};
import { OpenAPIV3, BodySchema } from '../../framework/types';

export class BodySchemaParser {
private _apiDoc: OpenAPIV3.Document;
Expand Down Expand Up @@ -35,6 +30,7 @@ export class BodySchemaParser {
}
return {};
}

private toSchema(
path: string,
contentType: ContentType,
Expand Down
Loading

0 comments on commit edbbcaf

Please sign in to comment.