import { Observable , of } from 'rxjs' ; import { catchError , map , switchMap } from 'rxjs/operators' ; import { IResourceAction , IResourceActionInner , IResourceResponse , ResourceActionReturnType , ResourceQueryMappingMethod , ResourceRequestBodyType , ResourceRequestMethod } from './Declarations' ; import { ResourceGlobalConfig } from './ResourceGlobalConfig' ; import { ResourceHelper } from './ResourceHelper' ; import { ResourceHandler } from './ResourceHandler' ; import { ResourceModule } from './ResourceModule' ; import { Optional , Type } from '@angular/core' ; export class Resource { protected get requestHandler ( ) : ResourceHandler { if ( ! this . _requestHandler ) { if ( ! ResourceModule . injector ) { throw new Error ( 'ResourceModule.injector is missing' ) ; } this . _requestHandler = ResourceModule . injector . get ( ResourceHandler as Type < ResourceHandler > ) ; if ( ! this . _requestHandler ) { throw new Error ( 'ResourceHandler provider is missing' ) ; } } return this . _requestHandler ; } private $url : string | null = null ; private $pathPrefix : string | null = null ; private $path : string | null = null ; private $headers : { } | null = null ; private $body : { } | null = null ; private $params : { } | null = null ; private $query : { } | null = null ; private _requestHandler : ResourceHandler | null = null ; constructor ( @ Optional ( ) requestHandler ? ) { if ( requestHandler ) { this . _requestHandler = requestHandler ; } ( this . constructor as any ) . instance = this ; } /**
     * Used to get url
     */ $getUrl ( actionOptions = { } ) { return this . $url || actionOptions . url || ResourceGlobalConfig . url || '' ; } $setUrl ( url ) { this . $url = url ; } /**
     * Used to get path prefix
     */ $getPathPrefix ( actionOptions = { } ) { return this . $pathPrefix || actionOptions . pathPrefix || ResourceGlobalConfig . pathPrefix || '' ; } $setPathPrefix ( path ) { this . $pathPrefix = path ; } /**
     * Used to get path
     */ $getPath ( actionOptions = { } ) { return this . $path || actionOptions . path || ResourceGlobalConfig . path || '' ; } $setPath ( path ) { this . $path = path ; } /**
     * Get headers.
     */ $getHeaders ( actionOptions = { } ) { return this . $headers || actionOptions . headers || ResourceGlobalConfig . headers || { } ; } $setHeaders ( headers ) { this . $headers = headers ; } /**
     * Get body
     */ $getBody ( actionOptions = { } ) { return this . $body || actionOptions . body || ResourceGlobalConfig . body || { } ; } $setBody ( body ) { this . $body = body ; } /**
     * Get path params
     */ $getParams ( actionOptions = { } ) { return this . $params || actionOptions . params || ResourceGlobalConfig . params || { } ; } $setParams ( params ) { this . $params = params ; } /**
     * Get query params
     */ $getQuery ( actionOptions = { } ) { return this . $query || actionOptions . query || ResourceGlobalConfig . query || { } ; } $setQuery ( query ) { this . $query = query ; } /**
     * Used to filter received data.
     * Is applied on each element of array or object
     */ $filter ( data , options = { } ) { return true ; } /**
     * Used to map received data
     * Is applied on each element of array or object
     */ $map ( data , options = { } ) { return data ; } /**
     * Used to create result object
     * Is applied on each element of array or object
     */ $resultFactory ( data , options = { } ) { return data == null ? { } : data ; } $restAction ( options ) { this . $_setResourceActionInnerDefaults ( options ) ; this . $_setResourceActionOptionDefaults ( options ) ; if ( ! options . actionOptions ) { throw new Error ( 'Action options are not set' ) ; } const actionOptions = options . actionOptions ; if ( ! actionOptions . resultFactory ) { throw new Error ( 'Action options resultFactory is not set' ) ; } if ( ! options . actionAttributes ) { throw new Error ( 'Action attributes is not set' ) ; } if ( actionOptions . mutateBody || options . isModel ) { options . returnData = options . actionAttributes . body ; } if ( actionOptions . returnAs === ResourceActionReturnType . Resource ) { options . returnData = actionOptions . expectJsonArray ? [ ] : actionOptions . resultFactory . call ( this , null , options ) ; } if ( this . $_canSetInternalData ( options ) ) { ResourceHelper . defineReturnDataPropertiesResolvedAbort ( options . returnData ) ; } options . mainObservable = this . $_createMainObservable ( options ) ; if ( this . $_canSetInternalData ( options ) ) { ResourceHelper . defineReturnDataPropertiesPromise ( options . returnData , this . $_createPromise ( options ) ) ; } switch ( actionOptions . returnAs ) { case ResourceActionReturnType . Observable : return options . mainObservable ; case ResourceActionReturnType . Promise : return this . $_createPromise ( options ) ; default : return options . returnData ; } } /**
     * Converts observable to promise and ads abort method
     */ protected $_createPromise ( options ) { if ( ! options . promise ) { options . promise = new Promise < any > ( ( resolve , reject ) => { if ( ! options . mainObservable ) { reject ( new Error ( '$_createPromise options.mainObservable missing' ) ) ; return ; } options . subscription = options . mainObservable . subscribe ( resolve , error => { reject ( error ) ; options . subscription = null ; } , ( ) => { options . subscription = null ; } ) ; ResourceHelper . createNewAbortMethod ( options ) ; } ) ; } return options . promise ; } /**
     * Creates main request observable
     */ protected $_createMainObservable ( options ) { const requestPreparationPromise = this . $_setResolvedOptions ( options ) . then ( ( o ) => this . $_createRequestOptions ( o ) ) ; return of ( requestPreparationPromise ) . pipe ( switchMap ( ( oPromise ) => { return oPromise . then ( ( o ) => { if ( ! o . requestOptions ) { throw new Error ( 'IResourceActionInner miss request options' ) ; } const handlerResp = this . requestHandler . handle ( o . requestOptions ) ; if ( o . returnData && this . $_canSetInternalData ( options ) ) { o . returnData . $abort = handlerResp . abort ; } if ( handlerResp . observable ) { return handlerResp . observable as any ; } return handlerResp . promise ; } ) ; } ) , switchMap ( s => s instanceof Observable ? s : of ( s ) ) , map ( ( resp ) => this . $handleSuccessResponse ( options , resp ) ) , catchError ( ( resp ) => { throw this . $handleErrorResponse ( options , resp ) ; } ) ) ; } /**
     * Success response handler
     */ protected $handleSuccessResponse ( options , resp ) { let body = resp . body ; if ( Array . isArray ( body ) ) { body = this . $prepareSuccessResponseBodyArray ( body , options ) ; } else { body = this . $prepareSuccessResponseBodyObject ( body , options ) ; } if ( this . $_canSetInternalData ( options ) ) { options . returnData . $resolved = true ; } if ( options . actionOptions && options . actionOptions . asResourceResponse ) { resp . body = body ; body = resp ; } if ( options . actionAttributes && options . actionAttributes . onSuccess ) { options . actionAttributes . onSuccess ( body ) ; } return body ; } /**
     * Prepare success response body as array
     */ protected $prepareSuccessResponseBodyArray ( body , options ) { if ( ! options . actionOptions ) { throw new Error ( '$prepareSuccessResponseBodyArray options.actionOptions missing' ) ; } const actionOptions = options . actionOptions ; body = body . filter ( ( item ) => { return actionOptions . filter ? actionOptions . filter . call ( this , item , options ) : true ; } ) . map ( ( item ) => { if ( actionOptions . map ) { item = actionOptions . map . call ( this , item , options ) ; } return actionOptions . resultFactory ? actionOptions . resultFactory . call ( this , item , options ) : item ; } ) ; if ( options . returnData ) { Array . prototype . push . apply ( options . returnData , body ) ; body = options . returnData ; } return body ; } /**
     * Prepare success response body as object
     */ protected $prepareSuccessResponseBodyObject ( body , options ) { if ( ! options . actionOptions ) { throw new Error ( '$prepareSuccessResponseBodyObject options.actionOptions missing' ) ; } const actionOptions = options . actionOptions ; if ( actionOptions . filter && ! actionOptions . filter . call ( this , body , options ) ) { return null ; } if ( actionOptions . map ) { body = actionOptions . map . call ( this , body , options ) ; } let newBody = options . returnData ; if ( newBody ) { if ( typeof newBody . $setData === 'function' ) { newBody . $setData ( body ) ; } else { Object . assign ( newBody , body ) ; } } else { newBody = actionOptions . resultFactory ? actionOptions . resultFactory . call ( this , body , options ) : body ; } body = newBody ; if ( body . $resource ) { body . $resolved = true ; body . $promise = options . mainObservable ; body . $abort = ( ) => true ; } return body ; } /**
     * Handle error
     */ protected $handleErrorResponse ( options , resp ) { if ( options . returnData && this . $_canSetInternalData ( options ) ) { options . returnData . $resolved = true ; } if ( options . actionAttributes && options . actionAttributes . onError ) { options . actionAttributes . onError ( resp ) ; } throw resp ; } /**
     * Sets request options url
     */ protected $setRequestOptionsUrl ( options ) { const requestOptions = ResourceHelper . getRequestOptionsOrThrow ( options ) ; const resolvedOptions = ResourceHelper . getResolvedOptionsOrThrow ( options ) ; const actionAttributes = ResourceHelper . getActionAttributesOrThrow ( options ) ; if ( ! requestOptions . url ) { requestOptions . url = ( resolvedOptions . url || '' ) + ( resolvedOptions . pathPrefix || '' ) + ( resolvedOptions . path || '' ) ; } options . usedInPath = { } ; ResourceHelper . setRequestOptionsUrlParams ( requestOptions , resolvedOptions , actionAttributes , options . usedInPath ) ; requestOptions . url = requestOptions . url . replace ( /\/\/+/g , '/' ) ; if ( requestOptions . url . startsWith ( 'http' ) ) { requestOptions . url = requestOptions . url . replace ( ':/' , '://' ) ; } if ( options . actionOptions && options . actionOptions . removeTrailingSlash ) { while ( requestOptions . url [ requestOptions . url . length - 1 ] === '/' ) { requestOptions . url = requestOptions . url . substr ( 0 , requestOptions . url . length - 1 ) ; } } } protected $setRequestOptionsBody ( options ) { const actionOptions = ResourceHelper . getActionOptionsOrThrow ( options ) ; const actionAttributes = ResourceHelper . getActionAttributesOrThrow ( options ) ; const requestOptions = ResourceHelper . getRequestOptionsOrThrow ( options ) ; let body = actionAttributes . body ; if ( ! body ) { return ; } const realBodyType = ResourceHelper . getRealTypeOf ( body ) ; let bodyOk = realBodyType === actionOptions . requestBodyType ; if ( ! bodyOk && realBodyType === ResourceRequestBodyType . JSON && actionOptions . requestBodyType === ResourceRequestBodyType . FORM_DATA ) { body = ResourceHelper . createRequestOptionsFormDataBody ( body , actionOptions ) ; bodyOk = true ; } if ( ! bodyOk ) { throw new Error ( 'Can not convert body' ) ; } if ( ! ( body instanceof FormData ) ) { if ( actionOptions . rootNode ) { const newBody = { } ; newBody [ actionOptions . rootNode ] = body ; body = newBody ; } if ( ( actionOptions . requestBodyType === ResourceRequestBodyType . NONE || ( actionOptions . requestBodyType === ResourceRequestBodyType . JSON && typeof body === 'object' && Object . keys ( body ) . length === 0 ) ) && ! actionOptions . keepEmptyBody ) { return ; } } requestOptions . body = body ; } protected $setRequestOptionsQuery ( options ) { const actionAttributes = ResourceHelper . getActionAttributesOrThrow ( options ) ; const resolvedOptions = ResourceHelper . getResolvedOptionsOrThrow ( options ) ; const requestOptions = ResourceHelper . getRequestOptionsOrThrow ( options ) ; const actionOptions = ResourceHelper . getActionOptionsOrThrow ( options ) ; options . usedInPath = options . usedInPath || { } ; let oq = actionAttributes . query || { } ; if ( resolvedOptions . query ) { oq = { ... resolvedOptions . query , ... oq } ; } if ( oq ) { requestOptions . query = { } ; Object . keys ( oq ) . forEach ( ( key ) => { if ( oq . hasOwnProperty ( key ) && ! options . usedInPath ! [ key ] ) { this . $appendQueryParams ( requestOptions . query as any , key , oq [ key ] , options . queryMappingMethod ) ; } } ) ; } if ( actionOptions . addTimestamp ) { requestOptions . query = requestOptions . query || { } ; this . $appendQueryParams ( requestOptions . query , actionOptions . addTimestamp as string , Date . now ( ) . toString ( 10 ) , options . queryMappingMethod ) ; } } protected $appendQueryParams ( query , key , value , queryMappingMethod ? ) { ResourceHelper . appendQueryParams ( query , key , value , queryMappingMethod ) ; } protected $_setResourceActionInnerDefaults ( options ) { const actionOptions = ResourceHelper . getActionOptionsOrThrow ( options ) ; const actionAttributes = ResourceHelper . getActionAttributesOrThrow ( options ) ; if ( ! actionOptions . method ) { actionOptions . method = ResourceRequestMethod . Get ; } if ( actionAttributes . body ) { if ( ! actionOptions . requestBodyType ) { actionOptions . requestBodyType = ResourceHelper . getRealTypeOf ( actionAttributes . body ) ; } if ( actionOptions . requestBodyType === ResourceRequestBodyType . JSON && typeof actionAttributes . body === 'object' && ! Array . isArray ( actionAttributes . body ) ) { if ( ! actionAttributes . params ) { actionAttributes . params = actionAttributes . body ; } options . isModel = ! ! actionAttributes . body . $resource ; } } actionAttributes . params = actionAttributes . params || { } ; if ( ! actionAttributes . query && actionOptions . method === ResourceRequestMethod . Get ) { actionAttributes . query = actionAttributes . params ; } options . queryMappingMethod = actionOptions . queryMappingMethod || ResourceGlobalConfig . queryMappingMethod ; } protected $_setResourceActionOptionDefaults ( options ) { const actionOptions = ResourceHelper . getActionOptionsOrThrow ( options ) ; if ( ResourceHelper . isNullOrUndefined ( actionOptions . filter ) ) { actionOptions . filter = this . $filter ; } if ( ResourceHelper . isNullOrUndefined ( actionOptions . map ) ) { actionOptions . map = this . $map ; } if ( ResourceHelper . isNullOrUndefined ( actionOptions . resultFactory ) ) { actionOptions . resultFactory = this . $resultFactory ; } if ( ResourceHelper . isNullOrUndefined ( actionOptions . removeTrailingSlash ) ) { actionOptions . removeTrailingSlash = ResourceGlobalConfig . removeTrailingSlash ; } if ( ResourceHelper . isNullOrUndefined ( actionOptions . withCredentials ) ) { actionOptions . withCredentials = ResourceGlobalConfig . withCredentials ; } if ( ResourceHelper . isNullOrUndefined ( actionOptions . returnAs ) ) { actionOptions . returnAs = ResourceGlobalConfig . returnAs ; } if ( ResourceHelper . isNullOrUndefined ( actionOptions . responseBodyType ) ) { actionOptions . responseBodyType = ResourceGlobalConfig . responseBodyType ; } if ( ResourceHelper . isNullOrUndefined ( actionOptions . lean ) ) { actionOptions . lean = ! ! ResourceGlobalConfig . lean ; if ( actionOptions . mutateBody && actionOptions . returnAs === ResourceActionReturnType . Resource && ResourceHelper . isNullOrUndefined ( actionOptions . lean ) ) { actionOptions . lean = true ; } } if ( ResourceHelper . isNullOrUndefined ( actionOptions . addTimestamp ) ) { actionOptions . addTimestamp = ResourceGlobalConfig . addTimestamp ; if ( actionOptions . addTimestamp && typeof actionOptions . addTimestamp !== 'string' ) { actionOptions . addTimestamp = 'ts' ; } } } protected $_setResolvedOptions ( options ) { return Promise . all ( [ this . $getUrl ( options . actionOptions ) , this . $getPathPrefix ( options . actionOptions ) , this . $getPath ( options . actionOptions ) , this . $getHeaders ( options . actionOptions ) , this . $getBody ( options . actionOptions ) , this . $getParams ( options . actionOptions ) , this . $getQuery ( options . actionOptions ) ] ) . then ( ( [ url , pathPrefix , path , headers , body , params , query ] ) => { options . resolvedOptions = { url , pathPrefix , path , headers , body , params , query } ; return options ; } ) ; } protected $_createRequestOptions ( options ) { const actionOptions = ResourceHelper . getActionOptionsOrThrow ( options ) ; const resolvedOptions = ResourceHelper . getResolvedOptionsOrThrow ( options ) ; options . requestOptions = { } ; options . requestOptions . method = actionOptions . method ; options . requestOptions . headers = resolvedOptions . headers ; options . requestOptions . withCredentials = actionOptions . withCredentials ; options . requestOptions . responseBodyType = actionOptions . responseBodyType ; options . requestOptions . requestBodyType = actionOptions . requestBodyType ; this . $setRequestOptionsUrl ( options ) ; this . $setRequestOptionsBody ( options ) ; this . $setRequestOptionsQuery ( options ) ; return options ; } protected $_canSetInternalData ( options ) { const actionOptions = ResourceHelper . getActionOptionsOrThrow ( options ) ; return ! ! ( options . returnData && ( ! actionOptions . lean || options . isModel ) ) ; } }