Skip to content

Commit

Permalink
fixes 332 unknown formats with req validation
Browse files Browse the repository at this point in the history
  • Loading branch information
carmine.dimacsio committed Jul 12, 2020
1 parent afdfd81 commit edeff91
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 20 deletions.
5 changes: 5 additions & 0 deletions src/framework/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ export type SecurityHandlers = {
) => boolean | Promise<boolean>;
};

export interface MultipartOpts extends ajv.Options
{
multerOpts: any;
}

export interface RequestValidatorOptions
extends ajv.Options,
ValidateRequestOpts {}
Expand Down
44 changes: 28 additions & 16 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import * as _uniq from 'lodash.uniq';
import * as middlewares from './middlewares';
import { Application, Response, NextFunction, Router } from 'express';
import { OpenApiContext } from './framework/openapi.context';
import { OpenApiSpecLoader, Spec, RouteMetadata } from './framework/openapi.spec.loader';
import {
OpenApiSpecLoader,
Spec,
RouteMetadata,
} from './framework/openapi.spec.loader';
import {
OpenApiValidatorOpts,
ValidateRequestOpts,
Expand All @@ -17,7 +21,7 @@ import { deprecationWarning } from './middlewares/util';
import * as path from 'path';
import { BasePath } from './framework/base.path';
import { defaultResolver } from './resolvers';
import { OperationHandlerOptions } from './framework/types'
import { OperationHandlerOptions } from './framework/types';

export {
InternalServerError,
Expand All @@ -30,7 +34,7 @@ export {
Forbidden,
} from './framework/types';

export * as resolvers from './resolvers'
export * as resolvers from './resolvers';

export class OpenApiValidator {
private readonly options: OpenApiValidatorOpts;
Expand All @@ -47,21 +51,21 @@ export class OpenApiValidator {
if (options.fileUploader == null) options.fileUploader = {};
if (options.$refParser == null) options.$refParser = { mode: 'bundle' };
if (options.validateFormats == null) options.validateFormats = 'fast';

if (typeof options.operationHandlers === 'string') {
/**
/**
* Internally, we want to convert this to a value typed OperationHandlerOptions.
* In this way, we can treat the value as such when we go to install (rather than
* re-interpreting it over and over).
*/
options.operationHandlers = {
basePath: options.operationHandlers,
resolver: defaultResolver
}
resolver: defaultResolver,
};
} else if (typeof options.operationHandlers !== 'object') {
// This covers cases where operationHandlers is null, undefined or false.
options.operationHandlers = false
}
options.operationHandlers = false;
}

if (options.validateResponses === true) {
options.validateResponses = {
Expand Down Expand Up @@ -187,7 +191,13 @@ export class OpenApiValidator {
app: Application | Router,
context: OpenApiContext,
): void {
app.use(middlewares.multipart(context, this.options.fileUploader));
xcontext;
app.use(
middlewares.multipart(context, {
multerOpts: this.options.fileUploader,
unknownFormats: this.options.unknownFormats,
}),
);
}

private installSecurityMiddleware(
Expand Down Expand Up @@ -265,13 +275,13 @@ export class OpenApiValidator {
/**
* This if-statement is here to "narrow" the type of options.operationHanlders
* to OperationHandlerOptions (down from string | false | OperationHandlerOptions)
* At this point of execution it _should_ be impossible for this to NOT be the correct
* At this point of execution it _should_ be impossible for this to NOT be the correct
* type as we re-assign during construction to verify this.
*/
if (this.isOperationHandlerOptions(this.options.operationHandlers)) {
const { basePath, resolver } = this.options.operationHandlers
const { basePath, resolver } = this.options.operationHandlers;
app[method.toLowerCase()](expressRoute, resolver(basePath, route));
}
}
}
}

Expand Down Expand Up @@ -342,11 +352,13 @@ export class OpenApiValidator {
}
}

private isOperationHandlerOptions(value: false | string | OperationHandlerOptions): value is OperationHandlerOptions {
private isOperationHandlerOptions(
value: false | string | OperationHandlerOptions,
): value is OperationHandlerOptions {
if ((value as OperationHandlerOptions).resolver) {
return true
return true;
} else {
return false
return false;
}
}
}
10 changes: 6 additions & 4 deletions src/middlewares/openapi.multipart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import {
OpenApiRequestHandler,
ValidationError,
BadRequest,
RequestEntityToLarge,
InternalServerError,
HttpError,
MultipartOpts,
} from '../framework/types';
import { MulterError } from 'multer';
import ajv = require('ajv');
Expand All @@ -17,10 +17,12 @@ const multer = require('multer');

export function multipart(
context: OpenApiContext,
multerOpts: {},
options: MultipartOpts,
): OpenApiRequestHandler {
const mult = multer(multerOpts);
const Ajv = createRequestAjv(context.apiDoc, {});
const mult = multer(options.multerOpts);
const Ajv = createRequestAjv(context.apiDoc, {
unknownFormats: options.unknownFormats,
});
return (req, res, next) => {
// TODO check that format: binary (for upload) else do not use multer.any()
// use multer.none() if no binary parameters exist
Expand Down
35 changes: 35 additions & 0 deletions test/resources/unknown.formats.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
openapi: 3.0.1
info:
title: Formats
version: 1.0.0
servers:
- url: /v1
paths:
/persons:
post:
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Person'
responses:
200:
description: Invalid ID supplied


components:
schemas:
Person:
required:
- id
type: object
properties:
id:
type: integer
format: int64
name:
type: string
hypertext:
type: string
format: hypertext
example: "<p>Hello. I am..."
39 changes: 39 additions & 0 deletions test/unknown.formats.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import * as path from 'path';
import * as request from 'supertest';
import { createApp } from './common/app';
import * as packageJson from '../package.json';

describe(packageJson.name, () => {
let app = null;

before(async () => {
const apiSpec = path.join('test', 'resources', 'unknown.formats.yaml');
app = await createApp(
{
apiSpec,
unknownFormats: ['hypertext'],
},
3005,
(app) => {
app.post(`${app.basePath}/persons`, (req, res) =>
res.json({
...req.body,
}),
);
},
true,
);
});

after(() => app.server.close());

it('should return 200 for valid request with unknown format', async () =>
request(app)
.post(`${app.basePath}/persons`)
.send({
id: 10,
name: 'henry',
hypertext: '<p>hello</p>',
})
.expect(200));
});

0 comments on commit edeff91

Please sign in to comment.