import reflect , { mergeDecorator } from "@plumier/reflect" ; import { safeToString } from "./converter" ; import { pipe , VisitorExtension , VisitorInvocation , VisitorInvocationImpl } from "./invocation" ; import { ArrayNode , ObjectNode , PrimitiveNode , SuperNode } from "./transformer" ; import { Class } from './types' ; interface ResultMessages { path : string ; messages : string [ ] ; } interface Result < T = any > { value : T ; issues ? : ResultMessages [ ] ; } interface ParentInfo { value : any ; type : Class ; decorators : any [ ] ; } interface VisitorOption { path : string ; extension : VisitorExtension [ ] ; decorators : any [ ] ; guessArrayElement : boolean ; parent ? : ParentInfo ; } namespace Result { export function create ( value ) { return { value } ; } export function error ( value , path , message ) { return { value , issues : [ { path , messages : Array . isArray ( message ) ? message : [ message ] } ] } ; } } function getPath ( path , name ) { return path . length === 0 ? name : `${ path }.${ name }` ; } const visitorMap = { "Primitive" : primitiveVisitor , "Array" : arrayVisitor , "Object" : objectVisitor , } ; function unableToConvert ( value , type ) { return `Unable to convert "${ safeToString ( value ) }" into ${ type }` ; } function primitiveVisitor ( value , ast , opt ) { const result = ast . converter ( value ) ; if ( result === undefined ) return Result . error ( value , opt . path , unableToConvert ( value , ast . type . name ) ) ; else return Result . create ( result ) ; } function arrayVisitor ( value , ast , opt ) { const newValues = opt . guessArrayElement && ! Array . isArray ( value ) ? [ value ] : value ; if ( ! Array . isArray ( newValues ) ) return Result . error ( value , opt . path , unableToConvert ( value , `Array<${ ast . type . name }>` ) ) ; const result = [ ] ; const errors = [ ] ; for ( let i = 0 ; i < newValues . length ; i ++ ) { const val = newValues [ i ] ; const option = { path : getPath ( opt . path , i . toString ( ) ) , extension : opt . extension , decorators : opt . decorators , parent : opt . parent , guessArrayElement : opt . guessArrayElement } ; const elValue = pipeline ( val , ast . element as SuperNode , option ) ; result . push ( elValue . value ) ; if ( elValue . issues ) errors . push ( ... elValue . issues ) ; } return { value : result , issues : errors . length === 0 ? undefined : errors } ; } function objectVisitor ( value , ast , opt ) { if ( typeof value === "number" || typeof value === "string" || typeof value === "boolean" ) return Result . error ( value , opt . path , unableToConvert ( value , ast . type . name ) ) ; const instance = Object . create ( ast . type . prototype ) ; const meta = reflect ( ast . type ) ; const errors = [ ] ; const props = Object . keys ( value ) . reduce ( ( prev , x ) => ( { ... prev , [ x ] : true } ) , { } as any ) ; for ( const property of meta . properties ) { const node = ast . properties . get ( property . name ) as SuperNode ; const option = { path : getPath ( opt . path , property . name ) , extension : opt . extension , decorators : property . decorators , parent : { value , type : ast . type , decorators : opt . decorators } , guessArrayElement : opt . guessArrayElement } ; const propValue = pipeline ( value [ property . name ] , node , option ) ; if ( propValue . issues ) errors . push ( ... propValue . issues ) ; if ( ! props [ property . name ] ) continue ; instance [ property . name ] = propValue . value ; } return { value : instance , issues : errors . length === 0 ? undefined : errors } ; } function visitor ( value , ast , opt ) { if ( value === undefined || value === null ) return { value } ; if ( value . constructor === ast . type || ast . type === Object ) return { value : ast . kind === "Array" && opt . guessArrayElement && ! Array . isArray ( value ) ? [ value ] : value } ; return visitorMap [ ast . kind ] ( value , ast as any , opt ) ; } function pipeline ( value , ast , opt ) { const mainVis = ( i ) => visitor ( i . value , i . ast , opt ) ; const last = new VisitorInvocationImpl ( mainVis , { decorators : opt . decorators , path : opt . path , ast : ast , value , parent : opt . parent } ) ; const visitors = pipe ( opt . extension , last ) ; return visitors . proceed ( ) ; } export { pipeline , ResultMessages , Result , ParentInfo } ;