import Pather from "path" ; import { ArrayUtil } from "../utils/ArrayUtil" ; import { StringUtil } from "../utils/StringUtil" ; import { IController } from "../structures/IController" ; import { ParamCategory } from "../structures/ParamCategory" ; import { equal } from "tstl/ranges/module" ; type IModule = { [ key ] : any ; } ; export namespace ReflectAnalyzer { export async function analyze ( unique , file ) { const module = await import ( file ) ; const ret = [ ] ; for ( const tuple of Object . entries ( module ) ) { if ( unique . has ( tuple [ 1 ] ) ) continue ; else unique . add ( tuple [ 1 ] ) ; const controller = _Analyze_controller ( file , ... tuple ) ; if ( controller !== null ) ret . push ( controller ) ; } return ret ; } function _Analyze_controller ( file , name , creator ) { if ( ! ( creator instanceof Function && creator . constructor instanceof Function ) ) return null ; else if ( ArrayUtil . has ( Reflect . getMetadataKeys ( creator ) , "path" , "host" , "scope:options" ) === false ) return null ; const meta = { file , name , path : Reflect . getMetadata ( "path" , creator ) , functions : [ ] } ; for ( const tuple of _Get_prototype_entries ( creator ) ) { const child = _Analyze_function ( creator . prototype , meta , ... tuple ) ; if ( child !== null ) meta . functions . push ( child ) ; } return meta ; } function _Get_prototype_entries ( creator ) { const tuple = Object . entries ( creator . prototype ) ; const parent = Object . getPrototypeOf ( creator ) ; if ( parent . prototype !== undefined ) tuple . push ( ... _Get_prototype_entries ( parent ) ) ; return tuple ; } function _Analyze_function ( classProto , controller , name , proto ) { if ( ! ( proto instanceof Function ) ) return null ; else if ( ArrayUtil . has ( Reflect . getMetadataKeys ( proto ) , "path" , "method" ) === false ) return null ; const meta = { name , method : METHODS [ Reflect . getMetadata ( "method" , proto ) ] , path : Reflect . getMetadata ( "path" , proto ) , parameters : [ ] , encrypted : Reflect . hasMetadata ( "__interceptors__" , proto ) && Reflect . getMetadata ( "__interceptors__" , proto ) [ 0 ] ?. constructor ?. name === "EncryptedRouteInterceptor" } ; const nestParameters = Reflect . getMetadata ( "__routeArguments__" , classProto . constructor , name ) ; if ( nestParameters === undefined ) meta . parameters = [ ] ; else { for ( const tuple of Object . entries ( nestParameters ) ) { const child = _Analyze_parameter ( ... tuple ) ; if ( child !== null ) meta . parameters . push ( child ) ; } meta . parameters = meta . parameters . sort ( ( x , y ) => x . index - y . index ) ; } const funcPathArguments = StringUtil . betweens ( Pather . join ( controller . path , meta . path ) . split ( "\\" ) . join ( "/" ) , ":" , "/" ) . sort ( ) ; const paramPathArguments = meta . parameters . filter ( param => param . category === "param" ) . map ( param => param . field ! ) . sort ( ) ; if ( equal ( funcPathArguments , paramPathArguments ) === false ) throw new Error ( `Error on ${ controller . name }.${ name }(): binded arguments in the "path" between function's decorator and parameters' decorators are different (function: [${ funcPathArguments . join ( ", " ) }], parameters: [${ paramPathArguments . join ( ", " ) }])` ) ; return meta ; } const METHODS = [ "GET" , "POST" , "PUT" , "DELETE" , "PATCH" ] ; function _Analyze_parameter ( key , param ) { const symbol = key . split ( ":" ) [ 0 ] ; if ( symbol . indexOf ( "__custom" ) !== - 1 ) return _Analyze_custom_parameter ( param ) ; const typeIndex = Number ( symbol [ 0 ] ) ; if ( isNaN ( typeIndex ) === true ) return null ; const type = NEST_PARAMETER_TYPES [ typeIndex ] ; if ( type === undefined ) return null ; return { name : key , category : type , index : param . index , field : param . data , encrypted : false } ; } function _Analyze_custom_parameter ( param ) { if ( param . factory === undefined ) return null ; else if ( param . factory . name === "EncryptedBody" || param . factory . name === "PlainBody" ) { return { category : "body" , index : param . index , name : param . name , field : param . data , encrypted : param . factory . name === "EncryptedBody" } ; } else if ( param . factory . name === "TypedParam" ) return { name : param . name , category : "param" , index : param . index , field : param . data , encrypted : false } ; else return null ; } type NestParameters = { [ key ] : INestParam ; } ; interface INestParam { name : string ; index : number ; factory ? : Function ; data : string | undefined ; } const NEST_PARAMETER_TYPES = [ undefined , undefined , undefined , "body" , "query" , "param" , undefined , undefined , undefined , undefined , undefined , undefined ] as const ; }