import { TypeAssertion , TypeAssertionMap , TypeAssertionSetValue , ObjectAssertion , AssertionSymlink , SymbolResolverOperators , ResolveSymbolOptions , SymbolResolverContext } from '../types' ; import * as operators from '../operators' ; import { NumberPattern } from '../lib/util' ; function mergeTypeAndSymlink ( ty , link ) { const link2 = { ... link } ; delete ( link2 as any ) . kind ; delete ( link2 as any ) . symlinkTargetName ; delete ( link2 as any ) . memberTree ; return ( { ... ty , ... link2 } as any as TypeAssertion ) ; } function updateSchema ( original , schema , ty , typeName ) { if ( typeName && schema . has ( typeName ) ) { const z = schema . get ( typeName ) as TypeAssertionSetValue ; if ( z . ty === original ) { schema . set ( typeName , { ... z , ty , resolved : true } ) ; } } return ty ; } export function resolveMemberNames ( ty , rootSym , memberTreeSymbols , memberPos ) { const addTypeName = ( mt , typeName , memberSym ) => { if ( typeName ) { return ( { ... mt , typeName : memberPos === 0 ? `${ rootSym }.${ memberTreeSymbols . join ( '.' ) }` : `${ typeName }.${ memberSym }` , } ) ; } else { return mt ; } } ; for ( let i = memberPos ; i < memberTreeSymbols . length ; i ++ ) { const memberSym = memberTreeSymbols [ i ] ; switch ( ty . kind ) { case 'optional' : return resolveMemberNames ( ty . optional , rootSym , memberTreeSymbols , i + 1 ) ; case 'object' : for ( const m of ty . members ) { if ( memberSym === m [ 0 ] ) { return addTypeName ( resolveMemberNames ( m [ 1 ] , rootSym , memberTreeSymbols , i + 1 ) , ty . typeName , memberSym ) ; } } if ( ty . additionalProps ) { for ( const m of ty . additionalProps ) { for ( const k of m [ 0 ] ) { switch ( k ) { case 'number' : if ( NumberPattern . test ( memberSym ) ) { return resolveMemberNames ( m [ 1 ] , rootSym , memberTreeSymbols , i + 1 ) ; } break ; case 'string' : return resolveMemberNames ( m [ 1 ] , rootSym , memberTreeSymbols , i + 1 ) ; default : if ( k . test ( memberSym ) ) { return resolveMemberNames ( m [ 1 ] , rootSym , memberTreeSymbols , i + 1 ) ; } break ; } } } } throw new Error ( `Undefined member name is appeared: ${ memberSym }` ) ; case 'symlink' : if ( ! ty . typeName ) { throw new Error ( `Reference of anonymous type is appeared: ${ memberSym }` ) ; } return ( { ... { kind : 'symlink' , symlinkTargetName : rootSym , name : memberSym , typeName : rootSym , } , ... ( 0 < memberTreeSymbols . length ? { memberTree : memberTreeSymbols , } : { } ) , } ) ; default : throw new Error ( `Unsupported type kind is appeared: (kind:${ ty . kind }).${ memberSym }` ) ; } } return ty ; } export function resolveSymbols ( schema , ty , ctx ) { const ctx2 = { ... ctx , nestLevel : ctx . nestLevel + 1 } ; switch ( ty . kind ) { case 'symlink' : { const x = schema . get ( ty . symlinkTargetName ) ; if ( ! x ) { throw new Error ( `Undefined symbol '${ ty . symlinkTargetName }' is referred.` ) ; } if ( 0 <= ctx . symlinkStack . findIndex ( s => s === ty . symlinkTargetName ) ) { return ty ; } const ty2 = { ... ty } ; let xTy = x . ty ; if ( ty . memberTree && 0 < ty . memberTree . length ) { xTy = { ... resolveMemberNames ( xTy , ty . symlinkTargetName , ty . memberTree , 0 ) , } ; ty2 . typeName = xTy . typeName ; } return ( resolveSymbols ( schema , mergeTypeAndSymlink ( xTy , ty2 ) , { ... ctx2 , symlinkStack : [ ... ctx2 . symlinkStack , ty2 . symlinkTargetName ] } ) ) ; } case 'repeated' : return updateSchema ( ty , schema , { ... ty , repeated : resolveSymbols ( schema , ty . repeated , ctx2 ) , } , ty . typeName ) ; case 'spread' : return updateSchema ( ty , schema , { ... ty , spread : resolveSymbols ( schema , ty . spread , ctx2 ) , } , ty . typeName ) ; case 'sequence' : return updateSchema ( ty , schema , { ... ty , sequence : ty . sequence . map ( x => resolveSymbols ( schema , x , ctx2 ) ) , } , ty . typeName ) ; case 'one-of' : return updateSchema ( ty , schema , { ... ty , oneOf : ty . oneOf . map ( x => resolveSymbols ( schema , x , ctx2 ) ) , } , ty . typeName ) ; case 'optional' : return updateSchema ( ty , schema , { ... ty , optional : resolveSymbols ( schema , ty . optional , ctx2 ) , } , ty . typeName ) ; case 'object' : { if ( 0 < ctx . nestLevel && ty . typeName && 0 <= ctx . symlinkStack . findIndex ( s => s === ty . typeName ) ) { if ( schema . has ( ty . typeName ) ) { const z = schema . get ( ty . typeName ) as TypeAssertionSetValue ; if ( z . resolved ) { return z . ty ; } } } const baseSymlinks = ty . baseTypes ?. filter ( x => x . kind === 'symlink' ) as AssertionSymlink [ ] ; if ( baseSymlinks && baseSymlinks . length > 0 && ! ctx . isDeserialization ) { const exts = baseSymlinks . map ( x => resolveSymbols ( schema , x , ctx2 ) ) . filter ( x => x . kind === 'object' ) ; const d2 = resolveSymbols ( schema , operators . derived ( { ... ty , ... ( ty . baseTypes ? { baseTypes : ty . baseTypes . filter ( x => x . kind !== 'symlink' ) , } : { } ) , } , ... exts ) , ty . typeName ? { ... ctx2 , symlinkStack : [ ... ctx2 . symlinkStack , ty . typeName ] } : ctx2 ) ; return updateSchema ( ty , schema , { ... ty , ... d2 , } , ty . typeName ) ; } else { return updateSchema ( ty , schema , { ... { ... ty , members : ty . members . map ( x => [ x [ 0 ] , resolveSymbols ( schema , x [ 1 ] , ty . typeName ? { ... ctx2 , symlinkStack : [ ... ctx2 . symlinkStack , ty . typeName ] } : ctx2 ) , ... x . slice ( 2 ) , ] as any ) , } , ... ( ty . additionalProps && 0 < ty . additionalProps . length ? { additionalProps : ty . additionalProps . map ( x => [ x [ 0 ] , resolveSymbols ( schema , x [ 1 ] , ty . typeName ? { ... ctx2 , symlinkStack : [ ... ctx2 . symlinkStack , ty . typeName ] } : ctx2 ) , ... x . slice ( 2 ) , ] as any ) , } : { } ) , ... ( ty . baseTypes && 0 < ty . baseTypes . length ? { baseTypes : ctx . isDeserialization ? ty . baseTypes . map ( x => x . kind === 'symlink' ? resolveSymbols ( schema , x , ctx2 ) : x ) . filter ( x => x . kind === 'object' ) as ObjectAssertion [ ] : ty . baseTypes , } : { } ) , } , ty . typeName ) ; } } case 'operator' : if ( ctx2 . operators ) { const ctx3 = ty . typeName ? { ... ctx2 , symlinkStack : [ ... ctx2 . symlinkStack , ty . typeName ] } : ctx2 ; const operands = ty . operands . map ( x => { if ( typeof x === 'object' && x . kind ) { return resolveSymbols ( schema , x , ctx3 ) ; } return x ; } ) ; if ( 0 < operands . filter ( x => x && typeof x === 'object' && ( x . kind === 'symlink' || x . kind === 'operator' ) ) . length ) { throw new Error ( `Unresolved type operator is found: ${ ty . operator }` ) ; } if ( ! ctx2 . operators [ ty . operator ] ) { throw new Error ( `Undefined type operator is found: ${ ty . operator }` ) ; } const ty2 = { ... ty } ; delete ( ty2 as any ) . operator ; delete ( ty2 as any ) . operands ; return updateSchema ( ty , schema , { ... ty2 , ... resolveSymbols ( schema , ctx2 . operators [ ty . operator ] ( ... operands ) , ctx3 ) , } , ty . typeName ) ; } else { return ty ; } default : return ty ; } } const resolverOps = { picked : operators . picked , omit : operators . omit , partial : operators . partial , intersect : operators . intersect , subtract : operators . subtract , } ; export function resolveSchema ( schema , opts ? ) { for ( const ent of schema . entries ( ) ) { const ty = resolveSymbols ( schema , ent [ 1 ] . ty , { ... opts , nestLevel : 0 , symlinkStack : [ ent [ 0 ] ] , operators : resolverOps } ) ; ent [ 1 ] . ty = ty ; } return schema ; }