Skip to content

Commit 5e6cfd7

Browse files
refactoring; pre-compiling regular expressions to improve performance
1 parent b80df9c commit 5e6cfd7

File tree

8 files changed

+700
-634
lines changed

8 files changed

+700
-634
lines changed
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import { DynamicsWebApiError, ErrorHelper } from "../../helpers/ErrorHelper";
2+
import {
3+
BATCH_RESPONSE_HEADERS_REGEX,
4+
LINE_ENDING_REGEX,
5+
HTTP_STATUS_REGEX,
6+
TEXT_REGEX,
7+
CONTENT_TYPE_PLAIN_REGEX,
8+
ODATA_ENTITYID_REGEX,
9+
extractUuidFromUrl,
10+
} from "../../helpers/Regex";
11+
import { handleJsonResponse, handlePlainResponse } from "./parseResponse";
12+
13+
//partially taken from http://olingo.apache.org/doc/javascript/apidoc/batch.js.html
14+
function parseBatchHeaders(text: string): any {
15+
const ctx = { position: 0 };
16+
const headers: Record<string, string> = {};
17+
let parts: RegExpExecArray | null;
18+
let line: string | null;
19+
let pos: number;
20+
21+
do {
22+
pos = ctx.position;
23+
line = readLine(text, ctx);
24+
if (!line) break; //if the line is empty, then it is the end of the headers
25+
parts = BATCH_RESPONSE_HEADERS_REGEX.exec(line);
26+
if (parts !== null) {
27+
headers[parts[1].toLowerCase()] = parts[2];
28+
} else {
29+
// Whatever was found is not a header, so reset the context position.
30+
ctx.position = pos;
31+
}
32+
} while (line && parts);
33+
34+
return headers;
35+
}
36+
37+
//partially taken from http://olingo.apache.org/doc/javascript/apidoc/batch.js.html
38+
function readLine(text: string, ctx: { position: number }): string | null {
39+
return readTo(text, ctx, LINE_ENDING_REGEX);
40+
}
41+
42+
//partially taken from http://olingo.apache.org/doc/javascript/apidoc/batch.js.html
43+
function readTo(text: string, ctx: { position: number }, searchRegTerm: RegExp): string | null {
44+
const start = ctx.position || 0;
45+
const slicedText = text.slice(start);
46+
const match = searchRegTerm.exec(slicedText);
47+
if (!match) {
48+
return null;
49+
}
50+
const end = start + match.index;
51+
ctx.position = end + match[0].length;
52+
return text.substring(start, end);
53+
}
54+
55+
//partially taken from https://github.com/emiltholin/google-api-batch-utils
56+
function getHttpStatus(response: string) {
57+
const parts = HTTP_STATUS_REGEX.exec(response);
58+
//todo: add error handler for httpStatus and httpStatusMessage; remove "!" operator
59+
return { httpStatusString: parts![0], httpStatus: parseInt(parts![1]), httpStatusMessage: parts![2].trim() };
60+
}
61+
62+
function getPlainContent(response: string) {
63+
// Reset the lastIndex property to ensure correct matching
64+
HTTP_STATUS_REGEX.lastIndex = 0;
65+
66+
const textReg = TEXT_REGEX.exec(response.trim());
67+
return textReg?.length ? textReg[0] : undefined;
68+
}
69+
70+
function handlePlainContent(batchResponse: string, parseParams: any, requestNumber: number): any {
71+
const plainContent = getPlainContent(batchResponse);
72+
return handlePlainResponse(plainContent);
73+
}
74+
75+
function handleEmptyContent(batchResponse: string, parseParams: any, requestNumber: number): any {
76+
if (parseParams?.[requestNumber]?.valueIfEmpty !== undefined) {
77+
return parseParams[requestNumber].valueIfEmpty;
78+
} else {
79+
const entityUrl = ODATA_ENTITYID_REGEX.exec(batchResponse);
80+
return extractUuidFromUrl(entityUrl?.[0]) ?? undefined;
81+
}
82+
}
83+
84+
function processBatchPart(batchResponse: string, parseParams: any, requestNumber: number): any {
85+
const { httpStatusString, httpStatus, httpStatusMessage } = getHttpStatus(batchResponse);
86+
const responseData = batchResponse.substring(batchResponse.indexOf("{"), batchResponse.lastIndexOf("}") + 1);
87+
88+
//if the batch part does not contain a json response, parse it as plain or empty content
89+
if (!responseData) {
90+
if (CONTENT_TYPE_PLAIN_REGEX.test(batchResponse)) {
91+
return handlePlainContent(batchResponse, parseParams, requestNumber);
92+
}
93+
94+
return handleEmptyContent(batchResponse, parseParams, requestNumber);
95+
}
96+
97+
//parse json data
98+
const parsedResponse = handleJsonResponse(responseData, parseParams, requestNumber);
99+
100+
if (httpStatus < 400) {
101+
return parsedResponse;
102+
}
103+
104+
//handle error
105+
const responseHeaders = parseBatchHeaders(
106+
batchResponse.substring(batchResponse.indexOf(httpStatusString) + httpStatusString.length + 1, batchResponse.indexOf("{"))
107+
);
108+
109+
return ErrorHelper.handleHttpError(parsedResponse, {
110+
status: httpStatus,
111+
statusText: httpStatusMessage,
112+
statusMessage: httpStatusMessage,
113+
headers: responseHeaders,
114+
});
115+
}
116+
117+
/**
118+
*
119+
* @param {string} response - response that needs to be parsed
120+
* @param {Array} parseParams - parameters for parsing the response
121+
* @param {Number} [requestNumber] - number of the request
122+
* @returns {any} parsed batch response
123+
*/
124+
export function parseBatchResponse(response: string, parseParams: any, requestNumber: number = 0): (string | undefined | DynamicsWebApiError | Number)[] {
125+
// Not the same delimiter in the response as we specify ourselves in the request,
126+
// so we have to extract it.
127+
const delimiter = response.substring(0, response.search(LINE_ENDING_REGEX));
128+
const batchResponseParts = response.split(delimiter);
129+
// The first part will always be an empty string. Just remove it.
130+
batchResponseParts.shift();
131+
// The last part will be the "--". Just remove it.
132+
batchResponseParts.pop();
133+
134+
let result: (string | undefined | DynamicsWebApiError | Number)[] = [];
135+
for (let part of batchResponseParts) {
136+
if (part.indexOf("--changesetresponse_") === -1) {
137+
result.push(processBatchPart(part, parseParams, requestNumber++));
138+
continue;
139+
}
140+
141+
part = part.trim();
142+
const batchToProcess = part.substring(part.search(LINE_ENDING_REGEX) + 1).trim();
143+
result = result.concat(parseBatchResponse(batchToProcess, parseParams, requestNumber++));
144+
}
145+
146+
return result;
147+
}

0 commit comments

Comments
 (0)