import { unixEOL , ErrorWithCause , mergeIgnoreCase , pickIgnoreCase , pickNonNullish , propertyExists , removeLeadingSlashes , removeSlashes , removeTrailingSlashes } from '@sap-cloud-sdk/util' ; import { buildHeadersForDestination , Destination , sanitizeDestination } from '@sap-cloud-sdk/connectivity' ; import { HttpResponse , executeHttpRequestWithOrigin , mergeOptionsWithPriority } from '@sap-cloud-sdk/http-client' ; import { filterCustomRequestConfig , OriginOptions } from '@sap-cloud-sdk/http-client/internal' ; import { ODataRequestConfig } from './odata-request-config' ; import { isWithETag } from './odata-request-traits' ; typeparam internal export class ODataRequest < RequestConfigT extends ODataRequestConfig > { param config param _destination constructor ( public config , private _destination ? ) { } set destination ( dest ) { this . _destination = dest && sanitizeDestination ( dest ) ; } get destination ( ) : Destination | undefined { return this . _destination ; } returns url ( ) { return `${ removeTrailingSlashes ( this . resourceUrl ( ) ) }${ this . config . appendedPaths . join ( '' ) }${ this . query ( ) }` ; } param includeServicePath param includeQueryParameters returns relativeUrl ( includeServicePath = true , includeQueryParameters = true ) { const query = includeQueryParameters ? this . query ( ) : '' ; return `${ removeTrailingSlashes ( this . relativeResourceUrl ( includeServicePath ) ) }${ this . config . appendedPaths . join ( '' ) }${ query }` ; } returns needsAuthentication ( ) { return ( ! ! this . destination && this . destination . authentication !== 'NoAuthentication' ) ; } returns serviceUrl ( ) { if ( ! this . destination ) { throw Error ( 'The destination is undefined.' ) ; } const systemUrl = this . destination . url ; const servicePath = typeof this . config . customServicePath === 'undefined' ? this . config . defaultServicePath : this . config . customServicePath ; return `${ removeTrailingSlashes ( systemUrl ) }/${ removeSlashes ( servicePath ) }` ; } returns relativeServiceUrl ( ) { const servicePath = typeof this . config . customServicePath === 'undefined' ? this . config . defaultServicePath : this . config . customServicePath ; return `${ removeSlashes ( servicePath ) }` ; } returns resourceUrl ( ) { return `${ removeTrailingSlashes ( this . serviceUrl ( ) ) }/${ this . config . resourcePath ( ) }` ; } param includeServicePath returns relativeResourceUrl ( includeServicePath = true ) { const baseUrl = includeServicePath ? removeTrailingSlashes ( this . relativeServiceUrl ( ) ) : '' ; const url = `${ baseUrl }/${ this . config . resourcePath ( ) }` ; return removeLeadingSlashes ( url ) ; } returns query ( ) { const parameters = mergeOptionsWithPriority ( this . queryParameters ( ) ) ; if ( parameters ) { const query = Object . entries ( parameters ) . map ( ( [ key , value ] ) => `${ key }=${ value }` ) . join ( '&' ) ; return query . length ? `?${ query }` : '' ; } return '' ; } returns async headers ( ) { try { if ( ! this . destination ) { throw Error ( 'The destination is undefined.' ) ; } const destinationRelatedHeaders = await buildHeadersForDestination ( this . destination , this . config . customHeaders ) ; return { custom : this . customHeaders ( ) , destination : destinationRelatedHeaders , requestConfig : { ... this . defaultHeaders ( ) , ... this . eTagHeaders ( ) } } ; } catch ( error ) { throw new ErrorWithCause ( 'Constructing headers for OData request failed!' , error ) ; } } returns customHeaders ( ) { return this . config . customHeaders ; } returns defaultHeaders ( ) { const additionalHeaders = this . getAdditionalHeadersForKeys ( ... Object . keys ( this . config . defaultHeaders ) ) ; return mergeIgnoreCase ( pickNonNullish ( this . config . defaultHeaders ) , additionalHeaders ) ; } returns eTagHeaders ( ) { const additionalIfMatchHeader = this . getAdditionalHeadersForKeys ( 'if-match' ) ; if ( Object . keys ( additionalIfMatchHeader ) . length ) { return additionalIfMatchHeader ; } const eTag = isWithETag ( this . config ) ? this . config . versionIdentifierIgnored ? '*' : this . config . eTag : undefined ; return pickNonNullish ( { 'if-match' : eTag } ) ; } returns async execute ( ) { const destination = this . destination ; if ( ! destination ) { throw Error ( 'The destination cannot be undefined.' ) ; } return executeHttpRequestWithOrigin ( destination , { ... filterCustomRequestConfig ( this . config . customRequestConfiguration ) , headers : await this . headers ( ) , params : this . queryParameters ( ) , url : this . relativeUrl ( true , false ) , method : this . config . method , data : this . config . payload } , { fetchCsrfToken : this . config . fetchCsrfToken } ) . catch ( error => { throw constructError ( error , this . config . method , this . serviceUrl ( ) ) ; } ) ; } private getAdditionalHeadersForKeys ( ... keys ) { const destinationHeaders = pickIgnoreCase ( this . destination ?. headers , ... keys ) ; const customHeaders = pickIgnoreCase ( this . customHeaders ( ) , ... keys ) ; return mergeIgnoreCase ( destinationHeaders , customHeaders ) ; } private queryParameters ( ) { return { custom : this . config . customQueryParameters , destination : this . destination ?. queryParameters , requestConfig : this . config . queryParameters ( ) } ; } } function constructError ( error , requestMethod , url ) { if ( error . isAxiosError ) { const defaultMessage = `${ requestMethod } request to ${ url } failed!` ; const s4SpecificMessage = propertyExists ( error , 'response' , 'data' , 'error' ) ? messageFromS4ErrorResponse ( error ) : '' ; const message = [ defaultMessage , s4SpecificMessage ] . join ( ' ' ) ; return new ErrorWithCause ( message , error ) ; } return error ; } function messageFromS4ErrorResponse ( error ) { return `${ propertyExists ( error . response . data . error , 'message' , 'value' ) ? error . response . data . error . message . value : '' }${ unixEOL }${ JSON . stringify ( error . response . data . error ) }` ; }