import { OpenApiContext } from '../framework/openapi.context' ; import { createRequestAjv } from '../framework/ajv' ; import { OpenAPIV3 , OpenApiRequest , OpenApiRequestHandler , ValidationError , BadRequest , InternalServerError , HttpError , MultipartOpts , } from '../framework/types' ; import { MulterError } from 'multer' ; const multer = require ( 'multer' ) ; export function multipart ( apiDoc , options ) { const mult = multer ( options . multerOpts ) ; const Ajv = createRequestAjv ( apiDoc , { ... options . ajvOpts } ) ; return ( req , res , next ) => { if ( shouldHandle ( Ajv , req ) ) { mult . any ( ) ( req , res , ( err ) => { if ( err ) { next ( error ( req , err ) ) ; } else { if ( req . files ) { const count_by_fieldname = ( < Express . Multer . File [ ] > req . files ) . map ( ( file ) => file . fieldname ) . reduce ( ( acc , curr ) => { acc [ curr ] = ( acc [ curr ] || 0 ) + 1 ; return acc ; } , { } ) ; Object . entries ( count_by_fieldname ) . forEach ( ( [ fieldname , count ] ) => { const is_multiple = count > 1 ; req . body [ fieldname ] = is_multiple ? new Array ( count ) . fill ( '' ) : '' ; } ) ; } next ( ) ; } } ) ; } else { next ( ) ; } } ; } function shouldHandle ( Ajv , req ) { const reqContentType = req . headers [ 'content-type' ] ; if ( isMultipart ( req ) && reqContentType ?. includes ( 'multipart/form-data' ) ) { return true ; } const bodyRef = ( < any > req ?. openapi ) ?. schema ?. $ref ; const requestBody = bodyRef ? Ajv . getSchema ( bodyRef ) : ( < any > req ?. openapi ) ?. schema ?. requestBody ; const bodyContent = requestBody ?. content ; if ( ! bodyContent ) return false ; const content = < { [ media ] : OpenAPIV3 . MediaTypeObject ; } > bodyContent ; const contentTypes = Object . entries ( content ) ; for ( const [ contentType , mediaType ] of contentTypes ) { if ( ! contentType . includes ( reqContentType ) ) continue ; const mediaTypeSchema = < any > mediaType ?. schema ; const schema = mediaTypeSchema ?. $ref ? Ajv . getSchema ( mediaTypeSchema . $ref ) : mediaTypeSchema ; const format = schema ?. format ; if ( format === 'binary' ) { return true ; } } } function isMultipart ( req ) { return ( < any > req ?. openapi ) ?. schema ?. requestBody ?. content ?. [ 'multipart/form-data' ] ; } function error ( req , err ) { if ( err instanceof multer . MulterError ) { const multerError = < MulterError > err ; const payload_too_big = /LIMIT_(FILE|PART)_(SIZE|COUNT)/ . test ( multerError . code ) ; const unexpected = /LIMIT_UNEXPECTED_FILE/ . test ( multerError . code ) ; const status = payload_too_big ? 413 : ! unexpected ? 400 : 500 ; return HttpError . create ( { status : status , path : req . path , message : err . message , } ) ; } else { const missingField = /Multipart: Boundary not found/i . test ( err . message ?? '' ) ; if ( missingField ) { return new BadRequest ( { path : req . path , message : 'multipart file(s) required' , } ) ; } else { return new InternalServerError ( { path : req . path , message : err . message } ) ; } } }