import { Injectable , IPlugin , Application , RouteItem , Container , InjectApp , InjectConfig , } from '@tiejs/common' ; import { validateOrReject , ValidationError } from 'class-validator' ; import { plainToClass } from 'class-transformer' ; import { parse } from 'cookie' ; import bodyParser , { Options } from 'body-parser' ; import isPromise from 'is-promise' ; import { Request , Response , NextFunction } from 'express' ; import { Exception } from '@tiejs/exception' ; import { paramTypes } from './constant' ; import { paramStore } from './stores/paramStore' ; import { RouteBuilder } from './RouteBuilder' ; import { formatError } from './utils/formatError' ; interface Arg { value : any ; shouldValidate : boolean ; } @ Injectable ( ) export class ControllerPlugin implements IPlugin { private routes : RouteItem [ ] = [ ] ; constructor ( @ InjectApp ( ) private app , @ InjectConfig ( 'body' ) private body ) { this . app . use ( bodyParser . urlencoded ( { extended : false } ) as any ) ; this . app . use ( bodyParser . json ( this . body || { } ) as any ) ; } async appDidReady ( app ) { const routeBuilder = Container . get ( RouteBuilder ) ; this . routes = routeBuilder . buildRoutes ( ) ; app . routerStore = this . routes ; } async middlewareDidReady ( app ) { for ( const route of this . routes ) { const { method , path , instance , fn , target , propertyKey , view } = route ; app [ method ] ( path , async ( req , res , next ) => { const args = getArgs ( req , res , target , propertyKey ) ; const validationErrors = await getValidationErrors ( args ) ; if ( validationErrors . length ) { return next ( new Exception ( { type : 'ValidationError' , code : 'ValidationError' , message : 'Argument Validation Error' , origin : validationErrors , } ) ) ; } try { let result = fn . apply ( instance , args . map ( i => i . value ) ) ; result = isPromise ( result ) ? await result : result ; if ( view ) return res . render ( view , result ) ; if ( typeof result === 'object' ) return res . json ( result ) ; res . send ( result ) ; } catch ( error ) { const { status , body } = formatError ( error ) ; res . status ( status ) ; res . send ( body ) ; } } ) ; } /** For next.js */ if ( app . config . enableNextJS ) { app . all ( '*' , async ( req , res , next ) => { if ( req . path === '/graphql' ) { return next ( ) ; } const nextApp = app . config . nextApp ; const handle = nextApp . getRequestHandler ( ) ; return handle ( req , res ) ; } ) ; } } } function getValue ( req , res , paramType , paramName ) { const ValueMaps = { [ paramTypes . Query ] : ( ) => ( paramName ? req . query [ paramName ] : { ... req . query } ) , [ paramTypes . Body ] : ( ) => ( paramName ? req . body [ paramName ] : req . body ) , [ paramTypes . Params ] : ( ) => ( paramName ? req . params [ paramName ] : req . params ) , [ paramTypes . Cookie ] : ( ) => { const cookies = parse ( req . headers . cookie || '' ) || { } ; return paramName ? cookies [ paramName ] : cookies ; } , [ paramTypes . Method ] : ( ) => req . method , [ paramTypes . Req ] : ( ) => req , [ paramTypes . Res ] : ( ) => res , [ paramTypes . Headers ] : ( ) => req . headers , } ; return ( ValueMaps as any ) [ paramType ] ( ) ; } function getArgs ( req , res , target , propertyKey ) { const args = [ ] ; const ParamTypes = paramStore . getParamTypes ( target , propertyKey ) ; for ( const type of Object . values ( paramTypes ) ) { const paramMetadata = paramStore . get ( { paramType : type , controllerClass : target , method : propertyKey , } ) ; if ( paramMetadata ) { for ( const paramName of Object . keys ( paramMetadata ) ) { let shouldValidate = false ; let value = getValue ( req , res , type , paramName ) ; const index = paramMetadata [ paramName ] ; const paramType = ParamTypes [ index ] ; const builtinTypes = [ paramTypes . Req , paramTypes . Res , paramTypes . Method , paramTypes . Next ] ; try { if ( ! builtinTypes . includes ( type ) ) { shouldValidate = true ; value = plainToClass ( paramType , value ) ; } } catch { } args [ paramMetadata [ paramName ] ] = { value , shouldValidate , } ; } } } return args ; } async function getValidationErrors ( args ) { let validationErrors = [ ] ; for ( const arg of args ) { try { if ( arg . shouldValidate ) { await validateOrReject ( arg ) ; } } catch ( e ) { const errors = e as any [ ] ; if ( errors && errors . length ) { validationErrors = [ ... validationErrors , ... errors ] ; } } } return validationErrors ; }