import { Injectable , OnDestroy } from '@angular/core' ; import { AlainConfigService , AlainMockConfig } from '@delon/util/config' ; import type { NzSafeAny } from 'ng-zorro-antd/core/types' ; import { MockCachedRule , MockOptions , MockRule } from './interface' ; import { MOCK_DEFULAT_CONFIG } from './mock.config' ; @ Injectable ( { providedIn : 'root' } ) export class MockService implements OnDestroy { private cached : MockCachedRule [ ] = [ ] ; readonly config : AlainMockConfig ; constructor ( cogSrv , options ) { this . config = cogSrv . merge ( 'mock' , MOCK_DEFULAT_CONFIG ) ! ; this . setData ( options ?. data ) ; } /**
     * Reset request data
     *
     * 重新设置请求数据
     */ setData ( data ) { this . applyMock ( data ) ; } private applyMock ( data ) { this . cached = [ ] ; try { this . realApplyMock ( data ) ; } catch ( e ) { this . outputError ( e ) ; } } private realApplyMock ( data ) { if ( ! data ) return ; Object . keys ( data ) . forEach ( ( key ) => { const rules = data [ key ] ; if ( ! rules ) return ; Object . keys ( rules ) . forEach ( ( ruleKey ) => { const value = rules [ ruleKey ] ; if ( ! ( typeof value === 'function' || typeof value === 'object' || typeof value === 'string' ) ) { throw Error ( `mock value of [${ key }-${ ruleKey }] should be function or object or string, but got ${ typeof value }` ) ; } const rule = this . genRule ( ruleKey , value ) ; if ( [ 'GET' , 'POST' , 'PUT' , 'HEAD' , 'DELETE' , 'PATCH' , 'OPTIONS' ] . indexOf ( rule . method ) === - 1 ) { throw Error ( `method of ${ key }-${ ruleKey } is not valid` ) ; } const item = this . cached . find ( w => w . url === rule . url && w . method === rule . method ) ; if ( item ) { item . callback = rule . callback ; } else { this . cached . push ( rule ) ; } } ) ; } ) ; this . cached . sort ( ( a , b ) => ( b . martcher || '' ) . toString ( ) . length - ( a . martcher || '' ) . toString ( ) . length ) ; } private genRule ( key , callback ) { let method = 'GET' ; let url = key ; if ( key . indexOf ( ' ' ) > - 1 ) { const splited = key . split ( ' ' ) ; method = splited [ 0 ] . toLowerCase ( ) ; url = splited [ 1 ] ; } let martcher = null ; let segments = [ ] ; if ( ~ url . indexOf ( ':' ) ) { segments = url ! . split ( '/' ) . filter ( segment => segment . startsWith ( ':' ) ) . map ( v => v . substring ( 1 ) ) ; const reStr = url ! . split ( '/' ) . map ( segment => ( segment . startsWith ( ':' ) ? `([^/]+)` : segment ) ) . join ( '/' ) ; martcher = new RegExp ( `^${ reStr }` , 'i' ) ; } else if ( /(\([^)]+\))/i . test ( url ) ) { martcher = new RegExp ( url , 'i' ) ; } return { url , martcher , segments , callback , method : method . toUpperCase ( ) } ; } private outputError ( error ) { const filePath = error . message . split ( ': ' ) [ 0 ] ; const errors = ( error . stack as string ) . split ( '\n' ) . filter ( line => line . trim ( ) . indexOf ( 'at ' ) !== 0 ) . map ( line => line . replace ( `${ filePath }: ` , '' ) ) ; errors . splice ( 1 , 0 , '' ) ; console . group ( ) ; console . warn ( `==========Failed to parse mock config.==========` ) ; console . log ( errors . join ( '\n' ) ) ; console . groupEnd ( ) ; throw error ; } getRule ( method , url ) { method = ( method || 'GET' ) . toUpperCase ( ) ; const params = { } ; const list = this . cached . filter ( w => w . method === method && ( w . martcher ? w . martcher . test ( url ) : w . url === url ) ) ; if ( list . length === 0 ) return null ; const ret = list . find ( w => w . url === url ) || list [ 0 ] ; if ( ret . martcher ) { const execArr = ret . martcher . exec ( url ) ; execArr ! . slice ( 1 ) . map ( ( value , index ) => { params [ ret . segments [ index ] ] = value ; } ) ; } return { url , method : ret . method , params , callback : ret . callback } ; } clearCache ( ) { this . cached = [ ] ; } get rules ( ) : MockCachedRule [ ] { return this . cached ; } ngOnDestroy ( ) { this . clearCache ( ) ; } }