Skip to content

Commit 9e660a3

Browse files
authored
add type definitions (#72)
* add type definitions * add types
1 parent 5c7ed8e commit 9e660a3

16 files changed

+466
-71
lines changed

package-lock.json

Lines changed: 12 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"@types/express": "^4.17.0",
3939
"@types/mocha": "^5.2.7",
4040
"@types/morgan": "^1.7.36",
41+
"@types/multer": "^1.3.10",
4142
"@types/node": "^11.13.18",
4243
"@types/supertest": "^2.0.8",
4344
"body-parser": "^1.19.0",
@@ -55,6 +56,6 @@
5556
"supertest": "^4.0.2",
5657
"ts-node": "^8.3.0",
5758
"tsc": "^1.20150623.0",
58-
"typescript": "^3.5.3"
59+
"typescript": "^3.6.4"
5960
}
6061
}

src/framework/openapi.context.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import { OpenApiSpecLoader } from './openapi.spec.loader';
22
import { OpenAPIFrameworkArgs } from './index';
3+
import { OpenAPIV3 } from './types';
34

45
export class OpenApiContext {
56
// TODO cleanup structure (group related functionality)
6-
expressRouteMap = {};
7-
openApiRouteMap = {};
8-
routes = [];
9-
apiDoc;
7+
public readonly expressRouteMap = {};
8+
public readonly openApiRouteMap = {};
9+
public readonly routes = [];
10+
public readonly apiDoc: OpenAPIV3.Document;
1011
private basePaths: Set<string>;
12+
1113
constructor(opts: OpenAPIFrameworkArgs) {
1214
const openApiRouteDiscovery = new OpenApiSpecLoader(opts);
1315
const { apiDoc, basePaths, routes } = openApiRouteDiscovery.load();

src/framework/types.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Request } from 'express';
1+
import { Request, Response, NextFunction } from 'express';
22
import { Logger } from 'ts-log';
33
import BasePath from './base.path';
44
export {
@@ -381,6 +381,12 @@ export interface OpenApiRequest extends Request {
381381
openapi;
382382
}
383383

384+
export type OpenApiRequestHandler = (
385+
req: OpenApiRequest,
386+
res: Response,
387+
next: NextFunction,
388+
) => any;
389+
384390
export interface IJsonSchema {
385391
id?: string;
386392
$schema?: string;
@@ -423,6 +429,17 @@ export interface IJsonSchema {
423429
not?: IJsonSchema;
424430
}
425431

432+
export interface ValidationError {
433+
message?: string;
434+
status: number;
435+
errors: ValidationErrorItem[];
436+
}
437+
438+
export interface ValidationErrorItem {
439+
path: string;
440+
message: string;
441+
error_code?: string;
442+
}
426443
/* istanbul ignore next */
427444
export class ConsoleDebugAdapterLogger implements Logger {
428445
/**

src/framework/util.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,18 @@ import * as fs from 'fs';
44
import * as jsYaml from 'js-yaml';
55
import * as path from 'path';
66

7-
export function assertRegExpAndSecurity(framework, tuple) {
7+
export function assertRegExpAndSecurity(framework, tuple): void {
88
if (!Array.isArray(tuple)) {
99
throw new Error(
1010
`${framework.name}args.pathSecurity expects an array of tuples`,
1111
);
1212
} else if (!(tuple[0] instanceof RegExp)) {
1313
throw new Error(
14-
`${
15-
framework.name
16-
}args.pathSecurity tuples expect the first argument to be a RegExp`,
14+
`${framework.name}args.pathSecurity tuples expect the first argument to be a RegExp`,
1715
);
1816
} else if (!Array.isArray(tuple[1])) {
1917
throw new Error(
20-
`${
21-
framework.name
22-
}args.pathSecurity tuples expect the second argument to be a security Array`,
18+
`${framework.name}args.pathSecurity tuples expect the second argument to be a security Array`,
2319
);
2420
}
2521
}

src/index.ts

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
11
import ono from 'ono';
22
import * as _ from 'lodash';
3-
import { Application, Request } from 'express';
3+
import {
4+
Application,
5+
Request,
6+
Response,
7+
NextFunction,
8+
RequestHandler,
9+
} from 'express';
410
import { OpenApiContext } from './framework/openapi.context';
5-
import { OpenAPIV3, OpenApiRequest } from './framework/types';
11+
import {
12+
OpenAPIV3,
13+
OpenApiRequest,
14+
OpenApiRequestHandler,
15+
} from './framework/types';
616
import * as middlewares from './middlewares';
717

818
export type SecurityHandlers = {
@@ -52,13 +62,22 @@ export class OpenApiValidator {
5262

5363
// install param on routes with paths
5464
for (const p of _.uniq(pathParams)) {
55-
app.param(p, (req: OpenApiRequest, res, next, value, name) => {
56-
if (req.openapi.pathParams) {
57-
// override path params
58-
req.params[name] = req.openapi.pathParams[name] || req.params[name];
59-
}
60-
next();
61-
});
65+
app.param(
66+
p,
67+
(
68+
req: OpenApiRequest,
69+
res: Response,
70+
next: NextFunction,
71+
value: any,
72+
name: string,
73+
) => {
74+
if (req.openapi.pathParams) {
75+
// override path params
76+
req.params[name] = req.openapi.pathParams[name] || req.params[name];
77+
}
78+
next();
79+
},
80+
);
6281
}
6382

6483
const { coerceTypes, unknownFormats } = this.options;
@@ -73,9 +92,8 @@ export class OpenApiValidator {
7392
},
7493
);
7594

76-
const requestValidatorMw = (req, res, next) => {
77-
return requestValidator.validate(req, res, next);
78-
};
95+
const requestValidatorMw: OpenApiRequestHandler = (req, res, next) =>
96+
requestValidator.validate(req, res, next);
7997

8098
const responseValidator = new middlewares.ResponseValidator(
8199
this.context.apiDoc,

src/middlewares/ajv/index.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,29 @@
11
import * as Ajv from 'ajv';
22
import * as draftSchema from 'ajv/lib/refs/json-schema-draft-04.json';
33
import { formats } from './formats';
4+
import { OpenAPIV3 } from '../../framework/types';
45

56
const TYPE_JSON = 'application/json';
67

7-
export function createRequestAjv(openApiSpec, options: any = {}) {
8+
export function createRequestAjv(
9+
openApiSpec: OpenAPIV3.Document,
10+
options: any = {},
11+
): Ajv.Ajv {
812
return createAjv(openApiSpec, options);
913
}
10-
export function createResponseAjv(openApiSpec, options: any = {}) {
14+
15+
export function createResponseAjv(
16+
openApiSpec: OpenAPIV3.Document,
17+
options: any = {},
18+
): Ajv.Ajv {
1119
return createAjv(openApiSpec, options, false);
1220
}
13-
function createAjv(openApiSpec, options: any = {}, request: boolean = true) {
21+
22+
function createAjv(
23+
openApiSpec: OpenAPIV3.Document,
24+
options: any = {},
25+
request = true,
26+
): Ajv.Ajv {
1427
const ajv = new Ajv({
1528
...options,
1629
schemaId: 'auto',

src/middlewares/openapi.metadata.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import * as pathToRegexp from 'path-to-regexp';
22
import * as _ from 'lodash';
33
import { OpenApiContext } from '../framework/openapi.context';
4+
import { OpenApiRequest, OpenApiRequestHandler } from '../framework/types';
45

5-
export function applyOpenApiMetadata(openApiContext: OpenApiContext) {
6-
return (req, res, next) => {
6+
export function applyOpenApiMetadata(
7+
openApiContext: OpenApiContext,
8+
): OpenApiRequestHandler {
9+
return (req, res, next): any => {
710
const matched = lookupRoute(req);
811

912
if (matched) {
@@ -20,7 +23,7 @@ export function applyOpenApiMetadata(openApiContext: OpenApiContext) {
2023
-next();
2124
};
2225

23-
function lookupRoute(req) {
26+
function lookupRoute(req: OpenApiRequest) {
2427
const path = req.path;
2528
const method = req.method;
2629
const routeEntries = Object.entries(openApiContext.expressRouteMap);

src/middlewares/openapi.multipart.ts

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
import { OpenApiContext } from '../framework/openapi.context';
22
import { validationError } from './util';
3-
import * as multer from 'multer';
3+
import { Request } from 'express';
4+
import { OpenApiRequest, OpenApiRequestHandler } from '../framework/types';
5+
const multer = require('multer');
46

5-
export function multipart(openApiContext: OpenApiContext, multerOpts: {} = {}) {
7+
export function multipart(
8+
OpenApiContext: OpenApiContext,
9+
multerOpts: {} = {},
10+
): OpenApiRequestHandler {
611
const mult = multer(multerOpts);
712
return (req, res, next) => {
813
if (isMultipart(req) && isValidContentType(req)) {
@@ -13,20 +18,22 @@ export function multipart(openApiContext: OpenApiContext, multerOpts: {} = {}) {
1318
// TODO:
1419
// If a form parameter 'file' is defined to take file value, but the user provides a string value instead
1520
// req.files will be empty and req.body.file will be populated with a string
16-
// This will incorrectly PASS validation.
21+
// This will incorrectly PASS validation.
1722
// Instead, we should return a 400 with an invalid type e.g. file expects a file, but found string.
18-
//
19-
// In order to support this, we likely need to inspect the schema directly to find the type.
23+
//
24+
// In order to support this, we likely need to inspect the schema directly to find the type.
2025
// For example, if param with type: 'string', format: 'binary' is defined, we expect to see it in
2126
// req.files. If it's not present we should throw a 400
22-
//
27+
//
2328
// This is a bit complex because the schema may be defined inline (easy) or via a $ref (complex) in which
2429
// case we must follow the $ref to check the type.
2530
if (req.files) {
2631
// add files to body
27-
req.files.forEach(f => {
28-
req.body[f.fieldname] = '';
29-
});
32+
(<Express.Multer.File[]>req.files).forEach(
33+
(f: Express.Multer.File) => {
34+
req.body[f.fieldname] = '';
35+
},
36+
);
3037
}
3138
next();
3239
}
@@ -37,12 +44,12 @@ export function multipart(openApiContext: OpenApiContext, multerOpts: {} = {}) {
3744
};
3845
}
3946

40-
function isValidContentType(req) {
47+
function isValidContentType(req: Request): boolean {
4148
const contentType = req.headers['content-type'];
4249
return !contentType || contentType.includes('multipart/form-data');
4350
}
4451

45-
function isMultipart(req) {
52+
function isMultipart(req: OpenApiRequest): boolean {
4653
return (
4754
req.openapi &&
4855
req.openapi.schema &&
@@ -52,10 +59,9 @@ function isMultipart(req) {
5259
);
5360
}
5461

55-
function error(req, err) {
62+
function error(req: OpenApiRequest, err: Error) {
5663
if (err instanceof multer.MulterError) {
5764
// TODO is special handling for MulterErrors needed
58-
console.error(err);
5965
return validationError(500, req.path, err.message);
6066
} else {
6167
// HACK
@@ -66,7 +72,6 @@ function error(req, err) {
6672
if (missingField) {
6773
return validationError(400, req.path, 'multipart file(s) required');
6874
} else {
69-
console.error(err);
7075
return validationError(500, req.path, err.message);
7176
}
7277
}

src/middlewares/openapi.request.validator.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,24 @@ import {
55
ajvErrorsToValidatorError,
66
} from './util';
77
import ono from 'ono';
8+
import { NextFunction, Response } from 'express';
9+
import { OpenAPIV3, OpenApiRequest } from '../framework/types';
10+
import { Ajv } from 'ajv';
811

912
const TYPE_JSON = 'application/json';
1013

1114
export class RequestValidator {
1215
private _middlewareCache;
13-
private _apiDocs;
14-
private ajv;
16+
private _apiDocs: OpenAPIV3.Document;
17+
private ajv: Ajv;
1518

16-
constructor(apiDocs, options = {}) {
19+
constructor(apiDocs: OpenAPIV3.Document, options = {}) {
1720
this._middlewareCache = {};
1821
this._apiDocs = apiDocs;
1922
this.ajv = createRequestAjv(apiDocs, options);
2023
}
2124

22-
validate(req, res, next) {
25+
public validate(req: OpenApiRequest, res: Response, next: NextFunction): void {
2326
if (!req.openapi) {
2427
// this path was not found in open api and
2528
// this path is not defined under an openapi base path

0 commit comments

Comments
 (0)