Skip to content

Commit

Permalink
CLS Context is lost after using multer middleware (#695)
Browse files Browse the repository at this point in the history
related issue: expressjs/multer#814
Used the solution described in the above link to fix the issue

Co-authored-by: Alan Wang <alan@tacen.app>
  • Loading branch information
yidongw and Alan Wang committed Jan 25, 2024
1 parent 4b0c989 commit 40716fb
Showing 1 changed file with 49 additions and 42 deletions.
91 changes: 49 additions & 42 deletions src/middlewares/openapi.multipart.ts
Expand Up @@ -20,51 +20,58 @@ export function multipart(
): OpenApiRequestHandler {
const mult = multer(options.multerOpts);
const Ajv = createRequestAjv(apiDoc, { ...options.ajvOpts });
return (req, res, next) => {
return async (req, res, next) => {
// TODO check that format: binary (for upload) else do not use multer.any()
// use multer.none() if no binary parameters exist
if (shouldHandle(Ajv, req)) {
mult.any()(req, res, (err) => {
if (err) {
next(error(req, err));
} else {
// TODO:
// If a form parameter 'file' is defined to take file value, but the user provides a string value instead
// req.files will be empty and req.body.file will be populated with a string
// This will incorrectly PASS validation.
// Instead, we should return a 400 with an invalid type e.g. file expects a file, but found string.
//
// In order to support this, we likely need to inspect the schema directly to find the type.
// For example, if param with type: 'string', format: 'binary' is defined, we expect to see it in
// req.files. If it's not present we should throw a 400
//
// This is a bit complex because the schema may be defined inline (easy) or via a $ref (complex) in which
// case we must follow the $ref to check the type.

if (req.files) {
// to handle single and multiple file upload at the same time, let us this initialize this count variable
// for example { "files": 5 }
const count_by_fieldname = (<Express.Multer.File[]>req.files)
.map((file) => file.fieldname)
.reduce((acc, curr) => {
acc[curr] = (acc[curr] || 0) + 1;
return acc;
}, {});

// add file(s) to body
Object.entries(count_by_fieldname).forEach(
([fieldname, count]: [string, number]) => {
// TODO maybe also check in the api doc if it is a single upload or multiple
const is_multiple = count > 1;
req.body[fieldname] = is_multiple
? new Array(count).fill('')
: '';
},
);
}
next();
}
});
try {
await new Promise<void>((resolve, reject) => {
mult.any()(req, res, (err) => {
if (err) {
reject(error(req, err));
} else {
// TODO:
// If a form parameter 'file' is defined to take file value, but the user provides a string value instead
// req.files will be empty and req.body.file will be populated with a string
// This will incorrectly PASS validation.
// Instead, we should return a 400 with an invalid type e.g. file expects a file, but found string.
//
// In order to support this, we likely need to inspect the schema directly to find the type.
// For example, if param with type: 'string', format: 'binary' is defined, we expect to see it in
// req.files. If it's not present we should throw a 400
//
// This is a bit complex because the schema may be defined inline (easy) or via a $ref (complex) in which
// case we must follow the $ref to check the type.

if (req.files) {
// to handle single and multiple file upload at the same time, let us this initialize this count variable
// for example { "files": 5 }
const count_by_fieldname = (<Express.Multer.File[]>req.files)
.map((file) => file.fieldname)
.reduce((acc, curr) => {
acc[curr] = (acc[curr] || 0) + 1;
return acc;
}, {});

// add file(s) to body
Object.entries(count_by_fieldname).forEach(
([fieldname, count]: [string, number]) => {
// TODO maybe also check in the api doc if it is a single upload or multiple
const is_multiple = count > 1;
req.body[fieldname] = is_multiple
? new Array(count).fill('')
: '';
},
);
}
resolve();
}
});
});
next()
} catch (error) {
next(error);
}
} else {
next();
}
Expand Down

0 comments on commit 40716fb

Please sign in to comment.