Skip to content

Commit 85aad91

Browse files
committed
Coverted all throw validationError(...) to throw new <Name>Error(...).
1 parent 53d9715 commit 85aad91

7 files changed

+143
-80
lines changed

src/middlewares/openapi.multipart.ts

+15-6
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { OpenApiContext } from '../framework/openapi.context';
22
import { createRequestAjv } from '../framework/ajv';
3-
import { validationError } from './util';
43
import {
54
OpenAPIV3,
65
OpenApiRequest,
76
OpenApiRequestHandler,
87
ValidationError,
8+
BadRequest,
9+
RequestEntityToLarge,
10+
InternalServerError,
911
} from '../framework/types';
1012
import { MulterError } from 'multer';
1113
import ajv = require('ajv');
@@ -22,7 +24,7 @@ export function multipart(
2224
// TODO check that format: binary (for upload) else do not use multer.any()
2325
// use multer.none() if no binary parameters exist
2426
if (shouldHandle(Ajv, req)) {
25-
mult.any()(req, res, err => {
27+
mult.any()(req, res, (err) => {
2628
if (err) {
2729
next(error(req, err));
2830
} else {
@@ -43,7 +45,7 @@ export function multipart(
4345
// to handle single and multiple file upload at the same time, let us this initialize this count variable
4446
// for example { "files": 5 }
4547
const count_by_fieldname = (<Express.Multer.File[]>req.files)
46-
.map(file => file.fieldname)
48+
.map((file) => file.fieldname)
4749
.reduce((acc, curr) => {
4850
acc[curr] = (acc[curr] || 0) + 1;
4951
return acc;
@@ -115,17 +117,24 @@ function error(req: OpenApiRequest, err: Error): ValidationError {
115117
);
116118
const unexpected = /LIMIT_UNEXPECTED_FILE/.test(multerError.code);
117119
const status = payload_too_big ? 413 : !unexpected ? 400 : 500;
118-
return validationError(status, req.path, err.message);
120+
return payload_too_big
121+
? new RequestEntityToLarge({ path: req.path, message: err.message })
122+
: !unexpected
123+
? new BadRequest({ path: req.path, message: err.message })
124+
: new InternalServerError({ path: req.path, message: err.message });
119125
} else {
120126
// HACK
121127
// TODO improve multer error handling
122128
const missingField = /Multipart: Boundary not found/i.test(
123129
err.message ?? '',
124130
);
125131
if (missingField) {
126-
return validationError(400, req.path, 'multipart file(s) required');
132+
return new BadRequest({
133+
path: req.path,
134+
message: 'multipart file(s) required',
135+
});
127136
} else {
128-
return validationError(500, req.path, err.message);
137+
return new InternalServerError({ path: req.path, message: err.message });
129138
}
130139
}
131140
}

src/middlewares/openapi.request.validator.ts

+41-22
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@ import { Ajv } from 'ajv';
22
import { createRequestAjv } from '../framework/ajv';
33
import {
44
ContentType,
5-
validationError,
65
ajvErrorsToValidatorError,
76
augmentAjvErrors,
87
} from './util';
9-
import ono from 'ono';
108
import { NextFunction, RequestHandler, Response } from 'express';
119
import {
1210
ValidationSchema,
@@ -15,6 +13,9 @@ import {
1513
RequestValidatorOptions,
1614
ValidateRequestOpts,
1715
OpenApiRequestMetadata,
16+
NotFound,
17+
MethodNotAllowed,
18+
BadRequest,
1819
} from '../framework/types';
1920
import { BodySchemaParser } from './parsers/body.parse';
2021
import { ParametersSchemaParser } from './parsers/schema.parse';
@@ -59,12 +60,18 @@ export class RequestValidator {
5960
const openapi = <OpenApiRequestMetadata>req.openapi;
6061
const path = openapi.expressRoute;
6162
if (!path) {
62-
throw validationError(404, req.path, 'not found');
63+
throw new NotFound({
64+
path: req.path,
65+
message: 'not found',
66+
});
6367
}
6468

6569
const reqSchema = openapi.schema;
6670
if (!reqSchema) {
67-
throw validationError(405, req.path, `${req.method} method not allowed`);
71+
throw new MethodNotAllowed({
72+
path: req.path,
73+
message: `${req.method} method not allowed`,
74+
});
6875
}
6976

7077
// cache middleware by combining method, path, and contentType
@@ -93,9 +100,13 @@ export class RequestValidator {
93100
const body = bodySchemaParser.parse(path, reqSchema, contentType);
94101

95102
const isBodyBinary = body?.['format'] === 'binary';
96-
const properties: ValidationSchema = { ...parameters, body: isBodyBinary ? {} : body };
103+
const properties: ValidationSchema = {
104+
...parameters,
105+
body: isBodyBinary ? {} : body,
106+
};
97107
// TODO throw 400 if missing a required binary body
98-
const required = (<SchemaObject>body).required && !isBodyBinary ? ['body'] : [];
108+
const required =
109+
(<SchemaObject>body).required && !isBodyBinary ? ['body'] : [];
99110
// $schema: "http://json-schema.org/draft-04/schema#",
100111
const schema = {
101112
required: ['query', 'headers', 'params'].concat(required),
@@ -112,7 +123,12 @@ export class RequestValidator {
112123
req.params = openapi.pathParams ?? req.params;
113124
}
114125

115-
const mutator = new RequestParameterMutator(this.ajv, apiDoc, path, properties);
126+
const mutator = new RequestParameterMutator(
127+
this.ajv,
128+
apiDoc,
129+
path,
130+
properties,
131+
);
116132

117133
mutator.modifyRequest(req);
118134

@@ -138,33 +154,36 @@ export class RequestValidator {
138154
const errors = augmentAjvErrors([...(validator.errors ?? [])]);
139155
const err = ajvErrorsToValidatorError(400, errors);
140156
const message = this.ajv.errorsText(errors, { dataVar: 'request' });
141-
throw ono(err, message);
157+
const error: BadRequest = new BadRequest({
158+
path: req.path,
159+
message: message,
160+
});
161+
error.errors = err.errors;
162+
throw error;
142163
}
143164
};
144165
}
145166

146167
private processQueryParam(query, schema, whiteList: string[] = []) {
147168
if (!schema.properties) return;
148169
const knownQueryParams = new Set(Object.keys(schema.properties));
149-
whiteList.forEach(item => knownQueryParams.add(item));
170+
whiteList.forEach((item) => knownQueryParams.add(item));
150171
const queryParams = Object.keys(query);
151172
const allowedEmpty = schema.allowEmptyValue;
152173
for (const q of queryParams) {
153174
if (
154175
!this.requestOpts.allowUnknownQueryParameters &&
155176
!knownQueryParams.has(q)
156177
) {
157-
throw validationError(
158-
400,
159-
`.query.${q}`,
160-
`Unknown query parameter '${q}'`,
161-
);
178+
throw new BadRequest({
179+
path: `.query.${q}`,
180+
message: `Unknown query parameter '${q}'`,
181+
});
162182
} else if (!allowedEmpty?.has(q) && (query[q] === '' || null)) {
163-
throw validationError(
164-
400,
165-
`.query.${q}`,
166-
`Empty value found for query parameter '${q}'`,
167-
);
183+
throw new BadRequest({
184+
path: `.query.${q}`,
185+
message: `Empty value found for query parameter '${q}'`,
186+
});
168187
}
169188
}
170189
}
@@ -201,12 +220,12 @@ class Security {
201220
): string[] {
202221
return usedSecuritySchema && securitySchema
203222
? usedSecuritySchema
204-
.filter(obj => Object.entries(obj).length !== 0)
205-
.map(sec => {
223+
.filter((obj) => Object.entries(obj).length !== 0)
224+
.map((sec) => {
206225
const securityKey = Object.keys(sec)[0];
207226
return <SecuritySchemeObject>securitySchema[securityKey];
208227
})
209-
.filter(sec => sec?.type === 'apiKey' && sec?.in == 'query')
228+
.filter((sec) => sec?.type === 'apiKey' && sec?.in == 'query')
210229
.map((sec: ApiKeySecurityScheme) => sec.name)
211230
: [];
212231
}

src/middlewares/openapi.response.validator.ts

+34-26
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import ono from 'ono';
21
import { RequestHandler } from 'express';
32
import * as ajv from 'ajv';
43
import mung from '../framework/modded.express.mung';
@@ -7,17 +6,16 @@ import {
76
augmentAjvErrors,
87
ContentType,
98
ajvErrorsToValidatorError,
10-
validationError,
119
} from './util';
1210
import {
1311
OpenAPIV3,
1412
OpenApiRequest,
1513
OpenApiRequestMetadata,
14+
InternalServerError,
15+
ValidationError,
1616
} from '../framework/types';
1717
import * as mediaTypeParser from 'media-typer';
1818
import * as contentTypeParser from 'content-type';
19-
import { type } from 'os';
20-
import { createCipher } from 'crypto';
2119

2220
interface ValidateResult {
2321
validators: { [key: string]: ajv.ValidateFunction };
@@ -114,23 +112,26 @@ export class ResponseValidator {
114112
} else if (validators.default) {
115113
svalidator = validators.default;
116114
} else {
117-
throw validationError(
118-
500,
119-
path,
120-
`no schema defined for status code '${status}' in the openapi spec`,
121-
);
115+
throw new InternalServerError({
116+
path: path,
117+
message: `no schema defined for status code '${status}' in the openapi spec`,
118+
});
122119
}
123120

124121
validator = svalidator[contentType];
125122

126-
if (!validator) { // wildcard support
127-
for (const validatorContentType of Object.keys(svalidator).sort().reverse()) {
123+
if (!validator) {
124+
// wildcard support
125+
for (const validatorContentType of Object.keys(svalidator)
126+
.sort()
127+
.reverse()) {
128128
if (validatorContentType === '*/*') {
129129
validator = svalidator[validatorContentType];
130130
break;
131131
}
132132

133-
if (RegExp(/^[a-z]+\/\*$/).test(validatorContentType)) { // wildcard of type application/*
133+
if (RegExp(/^[a-z]+\/\*$/).test(validatorContentType)) {
134+
// wildcard of type application/*
134135
const [type] = validatorContentType.split('/', 1);
135136

136137
if (new RegExp(`^${type}\/.+$`).test(contentType)) {
@@ -149,20 +150,23 @@ export class ResponseValidator {
149150
// assume valid
150151
return;
151152
}
152-
153+
153154
if (!body) {
154-
throw validationError(500, '.response', 'response body required.');
155+
throw new InternalServerError({
156+
path: '.response',
157+
message: 'response body required.',
158+
});
155159
}
156-
160+
157161
// CHECK If Content-Type is validatable
158162
try {
159-
if (!this.canValidateContentType(contentType)) {
160-
console.warn('Cannot validate content type', contentType);
161-
// assume valid
162-
return;
163-
}
163+
if (!this.canValidateContentType(contentType)) {
164+
console.warn('Cannot validate content type', contentType);
165+
// assume valid
166+
return;
167+
}
164168
} catch (e) {
165-
// Do nothing. Move on and validate response
169+
// Do nothing. Move on and validate response
166170
}
167171

168172
const valid = validator({
@@ -174,7 +178,11 @@ export class ResponseValidator {
174178
const message = this.ajv.errorsText(errors, {
175179
dataVar: '', // responses
176180
});
177-
throw ono(ajvErrorsToValidatorError(500, errors), message);
181+
throw new InternalServerError({
182+
path: path,
183+
errors: ajvErrorsToValidatorError(500, errors).errors,
184+
message: message,
185+
});
178186
}
179187
}
180188

@@ -209,7 +217,8 @@ export class ResponseValidator {
209217
// Handle wildcards
210218
if (
211219
response.content[contentType].schema &&
212-
(contentType === '*/*' || new RegExp(/^[a-z]+\/\*$/).test(contentType))
220+
(contentType === '*/*' ||
221+
new RegExp(/^[a-z]+\/\*$/).test(contentType))
213222
) {
214223
types.push(contentType);
215224
}
@@ -254,7 +263,7 @@ export class ResponseValidator {
254263
for (const [code, contentTypeSchemas] of Object.entries(responseSchemas)) {
255264
for (const contentType of Object.keys(contentTypeSchemas)) {
256265
const schema = contentTypeSchemas[contentType];
257-
266+
258267
validators[code] = {
259268
...validators[code],
260269
[contentType]: this.ajv.compile(<object>schema),
@@ -275,8 +284,7 @@ export class ResponseValidator {
275284
const mediaTypeParsed = mediaTypeParser.parse(contentTypeParsed.type);
276285

277286
return (
278-
mediaTypeParsed.subtype === 'json' ||
279-
mediaTypeParsed.suffix === 'json'
287+
mediaTypeParsed.subtype === 'json' || mediaTypeParsed.suffix === 'json'
280288
);
281289
}
282290
}

0 commit comments

Comments
 (0)