import { last , createLogger , pickValueIgnoreCase , ErrorWithCause , webEOL , unixEOL } from '@sap-cloud-sdk/util' ; import { HttpResponse } from '@sap-cloud-sdk/http-client' ; const logger = createLogger ( { package : 'odata-common' , messageContext : 'batch-response-parser' } ) ; param str returns internal export function detectNewLineSymbol ( str ) { if ( str . includes ( webEOL ) ) { return webEOL ; } if ( str . includes ( unixEOL ) ) { return unixEOL ; } throw new Error ( 'Cannot detect line breaks in the batch response body.' ) ; } param response returns internal export function getResponseBody ( response ) { const newLineSymbol = detectNewLineSymbol ( response ) ; const lines = response . split ( newLineSymbol ) ; if ( lines . length >= 3 ) { return lines [ lines . length - 1 ] ; } throw Error ( `Cannot parse batch subrequest response body. Expected at least three lines in the response, got ${ lines . length }.` ) ; } param response returns function parseHeaders ( response ) { const newLineSymbol = detectNewLineSymbol ( response ) ; const [ responseHeaders ] = response . split ( newLineSymbol + newLineSymbol ) ; return responseHeaders . split ( newLineSymbol ) . reduce ( ( headers , line ) => { const [ key , value ] = line . split ( ':' ) ; return { ... headers , [ key ] : value ?. trim ( ) } ; } , { } ) ; } param contentType returns function getBoundary ( contentType ) { const boundary = contentType ?. match ( /.*boundary=.+/ ) ? last ( contentType . split ( 'boundary=' ) ) : undefined ; if ( ! boundary ) { throw new Error ( 'No boundary found.' ) ; } return boundary ; } param response returns internal export function splitBatchResponse ( response ) { const body = response . data . trim ( ) ; if ( ! body ) { return [ ] ; } try { const boundary = getBoundary ( pickValueIgnoreCase ( response . headers , 'content-type' ) ) ; return splitResponse ( body , boundary ) ; } catch ( err ) { throw new ErrorWithCause ( 'Could not parse batch response.' , err ) ; } } param changeSetResponse returns internal export function splitChangeSetResponse ( changeSetResponse ) { const headers = parseHeaders ( changeSetResponse ) ; try { const boundary = getBoundary ( pickValueIgnoreCase ( headers , 'content-type' ) ) ; return splitResponse ( changeSetResponse , boundary ) ; } catch ( err ) { throw new ErrorWithCause ( 'Could not parse change set response.' , err ) ; } } param response param boundary returns internal export function splitResponse ( response , boundary ) { const newLineSymbol = detectNewLineSymbol ( response ) ; const parts = response . split ( `--${ boundary }` ) . map ( part => { const trimmedPart = part . trim ( ) ; return trimmedPart . includes ( '204 No Content' ) || ( trimmedPart . includes ( '200 OK' ) && trimmedPart . includes ( 'Content-Length: 0' ) ) ? `${ trimmedPart }${ newLineSymbol }` : trimmedPart ; } ) ; if ( parts . length >= 3 ) { return parts . slice ( 1 , parts . length - 1 ) ; } throw new Error ( 'Could not parse batch response body. Expected at least two response boundaries.' ) ; } param response returns internal export function parseHttpCode ( response ) { const group = response . match ( /HTTP\/\d\.\d (\d{3}).*?/ ) ; if ( group ) { return parseInt ( group [ 1 ] . toString ( ) ) ; } throw new Error ( 'Cannot parse http code of the response.' ) ; } param response returns internal function parseResponseBody ( response ) { const responseBody = getResponseBody ( response ) ; if ( responseBody ) { try { return JSON . parse ( responseBody ) ; } catch ( err ) { logger . error ( `Could not parse response body. Invalid JSON. Original Error: ${ err }` ) ; } } return { } ; } param response returns internal export function parseResponseData ( response ) { return { body : parseResponseBody ( response ) , httpCode : parseHttpCode ( response ) } ; } param batchResponse returns internal export function parseBatchResponse ( batchResponse ) { return splitBatchResponse ( batchResponse ) . map ( response => { const contentType = pickValueIgnoreCase ( parseHeaders ( response ) , 'content-type' ) ; if ( isChangeSetContentType ( contentType ) ) { return splitChangeSetResponse ( response ) . map ( subResponse => parseResponseData ( subResponse ) ) ; } if ( isRetrieveOrErrorContentType ( contentType ) ) { return parseResponseData ( response ) ; } throw Error ( `Cannot parse batch response. Unknown subresponse 'Content-Type' header '${ contentType }'.` ) ; } ) ; } function isChangeSetContentType ( contentType ) { return contentType ?. trim ( ) . startsWith ( 'multipart/mixed' ) ; } function isRetrieveOrErrorContentType ( contentType ) { return contentType ?. trim ( ) . startsWith ( 'application/http' ) ; } param httpCode returns internal export function isHttpSuccessCode ( httpCode ) { return httpCode >= 200 && httpCode < 300 ; } internal export interface ResponseData { body : Record < string , any > ; httpCode : number ; }