import { IResourceAction , IResourceActionInner , IResourceResponse , ResourceQueryMappingMethod , ResourceRequestBodyType , ResourceRequestMethod } from './Declarations' ; import { ResourceGlobalConfig } from './ResourceGlobalConfig' ; import { ResourceHelper } from './ResourceHelper' ; import { ResourceHandler } from './ResourceHandler' ; export class Resource { private $url : string = null ; private $pathPrefix : string = null ; private $path : string = null ; private $headers : any = null ; private $body : any = null ; private $params : any = null ; private $query : any = null ; constructor ( protected requestHandler ) { ( this . constructor as any ) . instance = this ; } param { IResourceAction } actionOptions return { string | Promise < string > } $getUrl ( actionOptions = { } ) { return this . $url || actionOptions . url || ResourceGlobalConfig . url || '' ; } $setUrl ( url ) { this . $url = url ; } param { IResourceAction } actionOptions return { string | Promise < string > } $getPathPrefix ( actionOptions = { } ) { return this . $pathPrefix || actionOptions . pathPrefix || ResourceGlobalConfig . pathPrefix || '' ; } $setPathPrefix ( path ) { this . $pathPrefix = path ; } param { IResourceAction } actionOptions return { string | Promise < string > } $getPath ( actionOptions = { } ) { return this . $path || actionOptions . path || ResourceGlobalConfig . path || '' ; } $setPath ( path ) { this . $path = path ; } param { IResourceAction } actionOptions return { any | Promise < any > } $getHeaders ( actionOptions = { } ) { return this . $headers || actionOptions . headers || ResourceGlobalConfig . headers || { } ; } $setHeaders ( headers ) { this . $headers = headers ; } param { IResourceAction } actionOptions return { any | Promise < any > } $getBody ( actionOptions = { } ) { return this . $body || actionOptions . body || ResourceGlobalConfig . body || { } ; } $setBody ( body ) { this . $body = body ; } param { IResourceAction } actionOptions return { any | Promise < any > } $getParams ( actionOptions = { } ) { return this . $params || actionOptions . params || ResourceGlobalConfig . params || { } ; } $setParams ( params ) { this . $params = params ; } param { IResourceAction } actionOptions return { any | Promise < any > } $getQuery ( actionOptions = { } ) { return this . $query || actionOptions . query || ResourceGlobalConfig . query || { } ; } $setQuery ( query ) { this . $query = query ; } param data param { IResourceActionInner } options return { boolean } $filter ( data , options = { } ) { return true ; } param data param { IResourceActionInner } options return { any } $map ( data , options = { } ) { return data ; } param data param { IResourceActionInner } options return { any } $resultFactory ( data , options = { } ) { return data || { } ; } $restAction ( options ) { this . $_setResourceActionInnerDefaults ( options ) ; this . $_setResourceActionOptionDefaults ( options ) ; const actionOptions = options . actionOptions ; if ( actionOptions . mutateBody || options . isModel ) { options . returnData = options . actionAttributes . body ; } if ( ! actionOptions . asPromise ) { options . returnData = actionOptions . expectJsonArray ? [ ] : actionOptions . resultFactory . call ( this , null , options ) ; } if ( this . $_canSetInternalData ( options ) ) { Object . defineProperty ( options . returnData , '$resolved' , { enumerable : false , configurable : true , writable : true , value : false } ) ; Object . defineProperty ( options . returnData , '$abort' , { enumerable : false , configurable : true , writable : true , value : ( ) => { } } ) ; } options . mainPromise = this . $_setResolvedOptions ( options ) . then ( ( o ) => this . $_createRequestOptions ( o ) ) . then ( ( o ) => { const handlerResp = this . requestHandler . handle ( o . requestOptions ) ; if ( o . returnData && this . $_canSetInternalData ( options ) ) { o . returnData . $abort = handlerResp . abort ; } return handlerResp . promise ; } ) . then ( ( resp ) => this . $handleSuccessResponse ( options , resp ) ) . catch ( ( resp ) => this . $handleErrorResponse ( options , resp ) ) ; if ( this . $_canSetInternalData ( options ) ) { Object . defineProperty ( options . returnData , '$promise' , { enumerable : false , configurable : true , writable : true , value : options . mainPromise } ) ; } return actionOptions . asPromise ? options . mainPromise : options . returnData ; } protected $handleSuccessResponse ( options , resp ) { let body = resp . body ; const actionOptions = options . actionOptions ; if ( Array . isArray ( body ) ) { body = body . filter ( ( item ) => actionOptions . filter . call ( this , item , options ) ) . map ( ( item ) => { item = actionOptions . map . call ( this , item , options ) ; return actionOptions . resultFactory . call ( this , item , options ) ; } ) ; if ( options . returnData ) { Array . prototype . push . apply ( options . returnData , body ) ; body = options . returnData ; } } else { if ( actionOptions . filter . call ( this , body , options ) ) { 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 . call ( this , body , options ) ; } body = newBody ; if ( body . $resource ) { body . $resolved = true ; body . $promise = options . mainPromise ; body . $abort = ( ) => true ; } } else { body = null ; } } if ( this . $_canSetInternalData ( options ) ) { options . returnData . $resolved = true ; } if ( options . actionOptions . asResourceResponse ) { resp . body = body ; body = resp ; } if ( options . actionAttributes . onSuccess ) { options . actionAttributes . onSuccess ( body ) ; } return body ; } protected $handleErrorResponse ( options , resp ) { if ( options . returnData && this . $_canSetInternalData ( options ) ) { options . returnData . $resolved = true ; } if ( options . actionAttributes . onError ) { options . actionAttributes . onError ( resp ) ; } throw resp ; } protected $setRequestOptionsUrl ( options ) { const ro = options . requestOptions ; if ( ! ro . url ) { ro . url = options . resolvedOptions . url + options . resolvedOptions . pathPrefix + options . resolvedOptions . path ; } options . usedInPath = { } ; const params = ResourceHelper . defaults ( options . actionAttributes . params , options . resolvedOptions . params ) ; const pathParams = ro . url . match ( /{([^}]*)}/g ) || [ ] ; for ( let i = 0 ; i < pathParams . length ; i ++ ) { const pathParam = pathParams [ i ] ; let pathKey = pathParam . substr ( 1 , pathParam . length - 2 ) ; const isMandatory = pathKey [ 0 ] === '!' ; if ( isMandatory ) { pathKey = pathKey . substr ( 1 ) ; } const onlyPathParam = pathKey [ 0 ] === ':' ; if ( onlyPathParam ) { pathKey = pathKey . substr ( 1 ) ; } if ( options . actionAttributes . query && options . actionAttributes . query === options . actionAttributes . params ) { options . usedInPath [ pathKey ] = true ; } const value = params [ pathKey ] ; if ( onlyPathParam ) { delete params [ pathKey ] ; } if ( ResourceHelper . isNullOrUndefined ( value ) ) { if ( isMandatory ) { const consoleMsg = `Mandatory ${ pathParam } path parameter is missing` ; console . warn ( consoleMsg ) ; throw new Error ( consoleMsg ) ; } ro . url = ro . url . substr ( 0 , ro . url . indexOf ( pathParam ) ) ; break ; } ro . url = ro . url . replace ( pathParam , value ) ; } ro . url = ro . url . replace ( /\/\/+/g , '/' ) ; if ( ro . url . startsWith ( 'http' ) ) { ro . url = ro . url . replace ( ':/' , '://' ) ; } if ( options . actionOptions . removeTrailingSlash ) { while ( ro . url [ ro . url . length - 1 ] === '/' ) { ro . url = ro . url . substr ( 0 , ro . url . length - 1 ) ; } } } protected $setRequestOptionsBody ( options ) { let body = options . actionAttributes . body ; if ( ! body ) { return ; } const realBodyType = ResourceHelper . getRealTypeOf ( body ) ; let bodyOk = realBodyType === options . actionOptions . requestBodyType ; if ( ! bodyOk ) { if ( realBodyType === ResourceRequestBodyType . JSON ) { if ( options . actionOptions . requestBodyType === ResourceRequestBodyType . FORM_DATA ) { const newBody = new FormData ( ) ; Object . keys ( body ) . forEach ( ( key ) => { const value = body [ key ] ; if ( body . hasOwnProperty ( key ) && typeof value !== 'function' ) { const isArrayOfFiles = value instanceof Array && value . reduce ( ( acc , elem ) => acc && elem instanceof File , true ) ; if ( isArrayOfFiles ) { value . forEach ( ( f , index ) => { newBody . append ( `${ key }[${ index }]` , f , ( f as File ) . name ) ; } ) ; } else if ( value instanceof File ) { newBody . append ( key , value , ( value as File ) . name ) ; } else if ( ! options . actionOptions . rootNode ) { newBody . append ( key , value ) ; } } } ) ; if ( options . actionOptions . rootNode ) { newBody . append ( options . actionOptions . rootNode , JSON . stringify ( body ) ) ; } body = newBody ; bodyOk = true ; } } } if ( ! bodyOk ) { throw new Error ( 'Can not convert body' ) ; } if ( ! ( body instanceof FormData ) ) { if ( options . actionOptions . rootNode ) { const newBody = { } ; newBody [ options . actionOptions . rootNode ] = body ; body = newBody ; } if ( ( options . actionOptions . requestBodyType === ResourceRequestBodyType . NONE || ( options . actionOptions . requestBodyType === ResourceRequestBodyType . JSON && typeof body === 'object' && Object . keys ( body ) . length === 0 ) ) && ! options . actionOptions . keepEmptyBody ) { return ; } } options . requestOptions . body = body ; } protected $setRequestOptionsQuery ( options ) { let oq = options . actionAttributes . query || { } ; if ( options . resolvedOptions . query ) { oq = { ... options . resolvedOptions . query , ... oq } ; } if ( oq ) { options . requestOptions . query = { } ; Object . keys ( oq ) . forEach ( ( key ) => { if ( oq . hasOwnProperty ( key ) && ! options . usedInPath [ key ] ) { this . $appendQueryParams ( options . requestOptions . query , key , oq [ key ] , options . queryMappingMethod ) ; } } ) ; } if ( options . actionOptions . addTimestamp ) { options . requestOptions . query = options . requestOptions . query || { } ; this . $appendQueryParams ( options . requestOptions . query , options . actionOptions . addTimestamp as string , Date . now ( ) . toString ( 10 ) , options . queryMappingMethod ) ; } } protected $appendQueryParams ( query , key , value , queryMappingMethod ) { if ( value instanceof Date ) { query [ key ] = value . toISOString ( ) ; return ; } if ( typeof value === 'object' ) { switch ( queryMappingMethod ) { case ResourceQueryMappingMethod . Plain : if ( Array . isArray ( value ) ) { query [ key ] = value . join ( ',' ) ; } else { if ( value && typeof value === 'object' ) { if ( value instanceof Date ) { value = value . toISOString ( ) ; } else { value = JSON . stringify ( value ) ; } } query [ key ] = value ; } return ; case ResourceQueryMappingMethod . Bracket : for ( const k in value ) { if ( value . hasOwnProperty ( k ) ) { this . $appendQueryParams ( query , `${ key }[${ k }]` , value [ k ] , queryMappingMethod ) ; } } return ; case ResourceQueryMappingMethod . JQueryParamsBracket : for ( const k in value ) { if ( value . hasOwnProperty ( k ) ) { let path = `${ key }[${ k }]` ; if ( Array . isArray ( value ) && typeof value [ < any > k ] !== 'object' ) { path = `${ key }[]` ; } this . $appendQueryParams ( query , path , value [ k ] , queryMappingMethod ) ; } } return ; } } query [ key ] = value ; } protected $_setResourceActionInnerDefaults ( options ) { const actionOptions = options . actionOptions ; if ( ! actionOptions . method ) { actionOptions . method = ResourceRequestMethod . Get ; } const actionAttributes = options . actionAttributes ; 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 = options . actionOptions ; 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 . asPromise ) ) { actionOptions . asPromise = ResourceGlobalConfig . asPromise ; } if ( ResourceHelper . isNullOrUndefined ( actionOptions . asResourceResponse ) ) { actionOptions . asResourceResponse = ResourceGlobalConfig . asResourceResponse ; } if ( ResourceHelper . isNullOrUndefined ( actionOptions . responseBodyType ) ) { actionOptions . responseBodyType = ResourceGlobalConfig . responseBodyType ; } if ( ResourceHelper . isNullOrUndefined ( actionOptions . lean ) ) { actionOptions . lean = ResourceGlobalConfig . lean ; if ( actionOptions . mutateBody && ! actionOptions . asPromise && 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 ( ( resolvedMain ) => { options . resolvedOptions = { } ; const r = options . resolvedOptions ; [ r . url , r . pathPrefix , r . path , r . headers , r . body , r . params , r . query ] = resolvedMain ; return options ; } ) ; } protected $_createRequestOptions ( options ) { options . requestOptions = { } ; options . requestOptions . method = options . actionOptions . method ; options . requestOptions . headers = options . resolvedOptions . headers ; options . requestOptions . withCredentials = options . actionOptions . withCredentials ; options . requestOptions . responseBodyType = options . actionOptions . responseBodyType ; options . requestOptions . requestBodyType = options . actionOptions . requestBodyType ; this . $setRequestOptionsUrl ( options ) ; this . $setRequestOptionsBody ( options ) ; this . $setRequestOptionsQuery ( options ) ; return options ; } protected $_canSetInternalData ( options ) { return options . returnData && ( ! options . actionOptions . lean || options . isModel ) ; } }