odata-batch
Version:
a simple lib for send and recieve calls odata batch
142 lines (110 loc) • 4.42 kB
text/typescript
import { flatten } from './utils';
export interface BatchResponseParsed {
code: string;
status: string;
headers: { key: string; value: string; }[];
data: any[];
success: boolean;
}
export interface BatchResponseInterface {
response: BatchResponseParsed[];
}
export interface BatchResponseConstructor {
new(OResponse: { data, headers }, accept: string,): BatchResponseInterface;
}
export function createBatchResponse(
ctor: BatchResponseConstructor,
OResponse: { data, headers },
accept: string,
): BatchResponseInterface {
return new ctor(OResponse, accept);
}
export class BatchResponse implements BatchResponseInterface {
private boundary: string;
private accept: string;
response: BatchResponseParsed[];
constructor({ data, headers }: any, accept: string) {
this.accept = accept;
this.ensureHasAccept();
this.boundary = this.getBoundary(headers);
this.response = this.parseBatch(data);
}
ensureHasAccept() {
if (!this.accept) {
throw new Error('Need accept to know how parse.')
}
}
parseResponse(part: string): BatchResponseParsed {
const responseParts = part.split("\r\n\r\n");
//responseParts[1] are headers for the part
//responseParts[2] is the responses code and headers
//responseParts[3] is data
const httpResponseWithHeaders = responseParts[1].split("\r\n");
const regCodeAndStatus = RegExp("HTTP/1.1 ([0-9]{3}) (.+)");
const headers = httpResponseWithHeaders
.filter((header: string) => !regCodeAndStatus.test(header))
.map((header: string) => {
const headerKeyAndValue = header.match("(.+): (.+)");
if (!headerKeyAndValue) {
return {
key: '',
value: '',
};
}
return {
key: headerKeyAndValue[1],
value: headerKeyAndValue[2]
};
})
const httpCodeAndDesc = httpResponseWithHeaders[0]
.match(regCodeAndStatus);
const code = httpCodeAndDesc && httpCodeAndDesc[1] || '';
const success = code.substring(0, 1) != "4" && code.substring(0, 1) != "5";
const data = this.parseData(responseParts[2]);
return {
code,
status: httpCodeAndDesc && httpCodeAndDesc[2] || '',
headers,
data,
success,
};
}
parseData(sData: string): any {
if (this.accept === 'application/json') {
return JSON.parse(sData);
}
const parts = sData.match(/.*/) || [''];
return parts[0];
}
parseBatch(body: string): BatchResponseParsed[] {
//Split the batch result into its associated parts
const batchParts = body.split(RegExp("--" + this.boundary + "(?:\r\n)?(?:--\r\n)?"));
const parseResponses = batchParts
.filter((batchPart: string) => RegExp("^content-type", "i").test(batchPart))
.map((batchPart: string) => {
// For each batch part, check to see if the part is a changeset.
const boundary = batchPart.match(RegExp("boundary=(.+)", "m"));
if (!boundary) {
return batchPart;
}
const changeSetBoundary = boundary[1];
const changeSetBody = batchPart
.match(RegExp("(--" + changeSetBoundary + "\r\n[^]+--" + changeSetBoundary + ")", "i"));
const changeSetParts = changeSetBody && changeSetBody[1]
.split(RegExp("--" + changeSetBoundary + "(?:\r\n)?(?:--\r\n)?")) || [];
return changeSetParts.filter((p: any) => p);
});
return flatten(parseResponses).map(p => this.parseResponse(p));
}
getBoundary(headers: { [x: string]: any; }): any {
const contentType = headers['content-type'];
const boundaryMatch = contentType.match(/boundary=([^;]+)/);
this.ensureHasBoundary(boundaryMatch);
return boundaryMatch[1];
}
ensureHasBoundary(boundaryMatch: RegExpExecArray): void {
if (!boundaryMatch) {
throw new Error('Bad content-type header, no multipart boundary');
}
}
}