@sap-cloud-sdk/odata-common
Version:
SAP Cloud SDK for JavaScript common functions of OData client generator and OpenAPI clint generator.
213 lines • 7.98 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.detectNewLineSymbol = detectNewLineSymbol;
exports.getResponseBody = getResponseBody;
exports.splitBatchResponse = splitBatchResponse;
exports.splitChangeSetResponse = splitChangeSetResponse;
exports.splitResponse = splitResponse;
exports.parseHttpCode = parseHttpCode;
exports.parseResponseData = parseResponseData;
exports.parseBatchResponse = parseBatchResponse;
exports.isHttpSuccessCode = isHttpSuccessCode;
const util_1 = require("@sap-cloud-sdk/util");
const logger = (0, util_1.createLogger)({
package: 'odata-common',
messageContext: 'batch-response-parser'
});
/**
* Detects the system dependent line break in a string.
* @param str - The string to check for line breaks. Should have at least two lines, otherwise an error will be thrown.
* @returns The system dependent line break
* @internal
*/
function detectNewLineSymbol(str) {
if (str.includes(util_1.webEOL)) {
return util_1.webEOL;
}
if (str.includes(util_1.unixEOL)) {
return util_1.unixEOL;
}
throw new Error('Cannot detect line breaks in the batch response body.');
}
/**
* Get the response body from the string representation of a response.
* @param response - String representation of a response.
* @returns The response body as a one line string.
* @internal
*/
function getResponseBody(response) {
const newLineSymbol = detectNewLineSymbol(response);
const lines = response.split(newLineSymbol);
// A valid response should contain at least three lines, part id, empty line and response body.
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}.`);
}
/**
* Parse the headers in the string representation of a response headers into an object. This will only look at the highest level of headers.
* @param response - String representation of a response.
* @returns The headers as an object.
*/
function parseHeaders(response) {
const newLineSymbol = detectNewLineSymbol(response);
// split on the first empty line
const [responseHeaders] = response.split(newLineSymbol + newLineSymbol);
return responseHeaders.split(newLineSymbol).reduce((headers, line) => {
const [key, value] = line.split(':');
return { ...headers, [key]: value?.trim() };
}, {});
}
/**
* Get the boundary from the content type header value. Throws an error if no boundary can be found.
* @param contentType - Value of the content type header.
* @returns The boundary.
*/
function getBoundary(contentType) {
const boundary = contentType?.match(/.*boundary=.+/)
? (0, util_1.last)(contentType.split('boundary='))
: undefined;
if (!boundary) {
throw new Error('No boundary found.');
}
return boundary;
}
/**
* Split a batch response into an array of sub responses for the retrieve requests and changesets.
* @param response - The raw HTTP response.
* @returns A list of sub responses represented as strings.
* @internal
*/
function splitBatchResponse(response) {
const body = response.data.trim();
if (!body) {
return [];
}
try {
const boundary = getBoundary((0, util_1.pickValueIgnoreCase)(response.headers, 'content-type'));
return splitResponse(body, boundary);
}
catch (err) {
throw new util_1.ErrorWithCause('Could not parse batch response.', err);
}
}
/**
* Split a changeset (sub) response into an array of sub responses.
* @param changeSetResponse - The string representation of a change set response.
* @returns A list of sub responses represented as strings.
* @internal
*/
function splitChangeSetResponse(changeSetResponse) {
const headers = parseHeaders(changeSetResponse);
try {
const boundary = getBoundary((0, util_1.pickValueIgnoreCase)(headers, 'content-type'));
return splitResponse(changeSetResponse, boundary);
}
catch (err) {
throw new util_1.ErrorWithCause('Could not parse change set response.', err);
}
}
/**
* Split a string representation of a response into sub responses given its boundary.
* @param response - The string representation of the response to split.
* @param boundary - The boundary to split by.
* @returns A list of sub responses represented as strings.
* @internal
*/
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.');
}
/**
* Parse the HTTP code of response.
* @param response - String representation of the response.
* @returns The HTTP code.
* @internal
*/
function parseHttpCode(response) {
const group = response.match(/HTTP\/\d\.\d (\d{3}).*?/);
if (group) {
return parseInt(group[1].toString());
}
// The TripPin sample service returns this Unknown Status Code but if the @odata metadata is there we got data.
if (response.match('Unknown Status Code') && response.match('@odata')) {
logger.debug("A batch request returned 'Unknown Status Code' but contained @odata annotation. Successful execution is assumed.");
return 200;
}
throw new Error('Cannot parse http code of the response.');
}
/**
* Get the body from the given response and parse it to JSON.
* @param response - The string representation of a single response.
* @returns The parsed JSON representation of the response body.
* @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 {};
}
/**
* Parse the body and http code of a batch sub response.
* @param response - A batch sub response.
* @returns The parsed response.s
* @internal
*/
function parseResponseData(response) {
return {
body: parseResponseBody(response),
httpCode: parseHttpCode(response)
};
}
/**
* Parse the complete batch HTTP response.
* @param batchResponse - HTTP response of a batch request.
* @returns An array of parsed sub responses of the batch response.
* @internal
*/
function parseBatchResponse(batchResponse) {
return splitBatchResponse(batchResponse).map(response => {
const contentType = (0, util_1.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');
}
/**
* Check if httpCode is in [200,300[ range.
* @param httpCode - Code to be checked
* @returns boolean
* @internal
*/
function isHttpSuccessCode(httpCode) {
return httpCode >= 200 && httpCode < 300;
}
//# sourceMappingURL=batch-response-parser.js.map