Skip to content

Commit 138a083

Browse files
parse batch response if batch request fails. #62
1 parent d88cf0d commit 138a083

File tree

13 files changed

+252
-33
lines changed

13 files changed

+252
-33
lines changed

DynamicsWebApi.njsproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@
5757
<Compile Include="lib\helpers\ErrorHelper.js">
5858
<SubType>Code</SubType>
5959
</Compile>
60+
<Compile Include="lib\polyfills\Array-es6.js">
61+
<SubType>Code</SubType>
62+
</Compile>
6063
<Compile Include="lib\requests\helpers\parseResponse.js">
6164
<SubType>Code</SubType>
6265
</Compile>

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2016,6 +2016,7 @@ the config option "formatted" will enable developers to retrieve all information
20162016
- [X] Batch requests. `Implemented in v.1.5.0`
20172017
- [X] TypeScript declaration files `d.ts` `Added in v.1.5.3`.
20182018
- [X] Implement `Content-ID` header to reference a request in a Change Set in a batch operation `Added in v.1.5.6`.
2019+
- [X] Change Tracking `Added in v.1.5.11`.
20192020
- [ ] Upload DynamicsWebApi declaration files to DefinitelyTyped repository.
20202021

20212022
Many more features to come!

lib/helpers/ErrorHelper.js

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,13 @@ var ErrorHelper = {
6363
}
6464
},
6565

66-
stringOrArrayParameterCheck: function(parameter, functionName, parameterName) {
66+
stringOrArrayParameterCheck: function (parameter, functionName, parameterName) {
6767
if (parameter.constructor !== Array && typeof parameter != "string") {
6868
throwParameterError(functionName, parameterName, "String or Array");
6969
}
7070
},
7171

72-
numberParameterCheck : function (parameter, functionName, parameterName) {
72+
numberParameterCheck: function (parameter, functionName, parameterName) {
7373
///<summary>
7474
/// Private function used to check whether required parameters are null or undefined
7575
///</summary>
@@ -141,7 +141,7 @@ var ErrorHelper = {
141141
var alternateKeys = parameter.split(',');
142142

143143
if (alternateKeys.length) {
144-
for (var i = 0; i < alternateKeys.length; i++){
144+
for (var i = 0; i < alternateKeys.length; i++) {
145145
alternateKeys[i] = alternateKeys[i].trim().replace('"', "'");
146146
/^[\w\d\_]+\=('[^\'\r\n]+'|\d+)$/i.exec(alternateKeys[i])[0];
147147
}
@@ -180,6 +180,22 @@ var ErrorHelper = {
180180
if (!isBatch) {
181181
throw new Error("Batch operation has not been started. Please call a DynamicsWebApi.startBatch() function prior to calling DynamicsWebApi.executeBatch() to perform a batch request correctly.");
182182
}
183+
},
184+
185+
handleHttpError: function (parsedError, parameters) {
186+
var error = new Error();
187+
188+
Object.keys(parsedError).forEach(k => {
189+
error[k] = parsedError[k];
190+
});
191+
192+
if (parameters) {
193+
Object.keys(parameters).forEach(k => {
194+
error[k] = parameters[k];
195+
});
196+
}
197+
198+
return error;
183199
}
184200
};
185201

lib/polyfills/Array-es6.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Array.isArray = function (arg) {
2+
return Object.prototype.toString.call(arg) === '[object Array]';
3+
};

lib/requests/helpers/parseResponse.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
var DWA = require('../../dwa');
22
var Utility = require('../../utilities/Utility');
3+
var ErrorHelper = require('../../helpers/ErrorHelper');
34
var dateReviver = require('./dateReviver');
45

56
//string es6 polyfill
@@ -140,6 +141,11 @@ function parseBatchResponse(response, parseParams, requestNumber) {
140141
result = result.concat(parseBatchResponse(batchToProcess, parseParams, requestNumber));
141142
}
142143
else {
144+
//check http status
145+
var httpStatusReg = /HTTP\/?\s*[\d.]*\s+(\d{3})\s+([\w\s]*)$/gm.exec(batchResponse);
146+
var httpStatus = parseInt(httpStatusReg[1]);
147+
var httpStatusMessage = httpStatusReg[2].trim();
148+
143149
var responseData = batchResponse.substring(batchResponse.indexOf("{"), batchResponse.lastIndexOf("}") + 1);
144150

145151
if (!responseData) {
@@ -168,7 +174,18 @@ function parseBatchResponse(response, parseParams, requestNumber) {
168174
}
169175
}
170176
else {
171-
result.push(parseData(JSON.parse(responseData, dateReviver), parseParams[requestNumber]));
177+
var parsedResponse = parseData(JSON.parse(responseData, dateReviver), parseParams[requestNumber]);
178+
179+
if (httpStatus >= 400) {
180+
result.push(ErrorHelper.handleHttpError(parsedResponse, {
181+
status: httpStatus,
182+
statusText: httpStatusMessage,
183+
statusMessage: httpStatusMessage
184+
}));
185+
}
186+
else {
187+
result.push(parsedResponse);
188+
}
172189
}
173190
}
174191

lib/requests/http.js

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
var https = require('https');
33
var url = require('url');
44
var parseResponse = require('./helpers/parseResponse');
5+
var ErrorHelper = require('./../helpers/ErrorHelper');
56

67
/**
78
* Sends a request to given URL with given parameters
@@ -32,7 +33,7 @@ var httpRequest = function (options) {
3233
}
3334

3435
var parsedUrl = url.parse(uri);
35-
var protocol = parsedUrl.protocol.replace(':','');
36+
var protocol = parsedUrl.protocol.replace(':', '');
3637
var protocolInterface = protocol === 'http' ? http : https;
3738

3839
var internalOptions = {
@@ -90,11 +91,17 @@ var httpRequest = function (options) {
9091
default: // All other statuses are error cases.
9192
var crmError;
9293
try {
93-
var errorParsed = JSON.parse(rawData);
94+
var errorParsed = parseResponse(rawData, res.headers, responseParams);
95+
96+
if (Array.isArray(errorParsed)) {
97+
errorCallback(errorParsed);
98+
break;
99+
}
94100

95101
crmError = errorParsed.hasOwnProperty('error') && errorParsed.error
96102
? errorParsed.error
97103
: { message: errorParsed.Message };
104+
98105
} catch (e) {
99106
if (rawData.length > 0) {
100107
crmError = { message: rawData };
@@ -103,13 +110,9 @@ var httpRequest = function (options) {
103110
crmError = { message: "Unexpected Error" };
104111
}
105112
}
106-
var error = new Error();
107-
Object.keys(crmError).forEach(k => {
108-
error[k] = crmError[k];
109-
});
110-
error.status = res.statusCode;
111-
error.statusText = request.statusText;
112-
errorCallback(error);
113+
114+
errorCallback(ErrorHelper.handleHttpError(crmError, { status: res.statusCode, statusText: request.statusText, statusMessage: res.statusMessage }));
115+
113116
break;
114117
}
115118

@@ -135,4 +138,4 @@ var httpRequest = function (options) {
135138
request.end();
136139
};
137140

138-
module.exports = httpRequest;
141+
module.exports = httpRequest;

lib/requests/xhr.js

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
var parseResponse = require('./helpers/parseResponse');
22
var parseResponseHeaders = require('./helpers/parseResponseHeaders');
3+
var ErrorHelper = require('../helpers/ErrorHelper');
4+
5+
if (!Array.isArray) {
6+
require("../polyfills/Array-es6");
7+
}
38

49
/**
510
* Sends a request to given URL with given parameters
@@ -45,7 +50,14 @@ var xhrRequest = function (options) {
4550
default: // All other statuses are error cases.
4651
var error;
4752
try {
48-
error = JSON.parse(request.response).error;
53+
var errorParsed = parseResponse(request.responseText, parseResponseHeaders(request.getAllResponseHeaders()), responseParams);
54+
55+
if (Array.isArray(errorParsed)) {
56+
errorCallback(errorParsed);
57+
break;
58+
}
59+
60+
error = errorParsed.error;
4961
} catch (e) {
5062
if (request.response.length > 0) {
5163
error = { message: request.response };
@@ -54,9 +66,12 @@ var xhrRequest = function (options) {
5466
error = { message: "Unexpected Error" };
5567
}
5668
}
57-
error.status = request.status;
58-
error.statusText = request.statusText;
59-
errorCallback(error);
69+
70+
errorCallback(ErrorHelper.handleHttpError(error, {
71+
status: request.status,
72+
statusText: request.statusText
73+
}));
74+
6075
break;
6176
}
6277

@@ -70,21 +85,21 @@ var xhrRequest = function (options) {
7085
}
7186

7287
request.onerror = function () {
73-
errorCallback({
88+
errorCallback(ErrorHelper.handleHttpError({
7489
status: request.status,
7590
statusText: request.statusText,
7691
message: request.responseText || "Network Error"
77-
});
92+
}));
7893
responseParams.length = 0;
7994
request = null;
8095
};
8196

8297
request.ontimeout = function () {
83-
errorCallback({
98+
errorCallback(ErrorHelper.handleHttpError({
8499
status: request.status,
85100
statusText: request.statusText,
86101
message: request.responseText || "Request Timed Out"
87-
});
102+
}));
88103
responseParams.length = 0;
89104
request = null;
90105
};

tests/callbacks-tests.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4490,6 +4490,64 @@ describe("callbacks -", function () {
44904490
expect(scope.isDone()).to.be.true;
44914491
});
44924492
});
4493+
4494+
describe("update / delete - returns an error", function () {
4495+
var scope;
4496+
var rBody = mocks.data.batchUpdateDelete;
4497+
var rBodys = rBody.split('\n');
4498+
var checkBody = '';
4499+
for (var i = 0; i < rBodys.length; i++) {
4500+
checkBody += rBodys[i];
4501+
}
4502+
before(function () {
4503+
var response = mocks.responses.batchError;
4504+
scope = nock(mocks.webApiUrl + '$batch')
4505+
.filteringRequestBody(function (body) {
4506+
body = body.replace(/dwa_batch_[\d\w]{8}-[\d\w]{4}-[\d\w]{4}-[\d\w]{4}-[\d\w]{12}/g, 'dwa_batch_XXX');
4507+
body = body.replace(/changeset_[\d\w]{8}-[\d\w]{4}-[\d\w]{4}-[\d\w]{4}-[\d\w]{12}/g, 'changeset_XXX');
4508+
var bodys = body.split('\n');
4509+
4510+
var resultBody = '';
4511+
for (var i = 0; i < bodys.length; i++) {
4512+
resultBody += bodys[i];
4513+
}
4514+
return resultBody;
4515+
})
4516+
.post("", checkBody)
4517+
.reply(response.status, response.responseText, response.responseHeaders);
4518+
});
4519+
4520+
after(function () {
4521+
nock.cleanAll();
4522+
});
4523+
4524+
it("returns a correct response", function (done) {
4525+
dynamicsWebApiTest.startBatch();
4526+
4527+
dynamicsWebApiTest.update(mocks.data.testEntityId2, 'records', { firstname: "Test", lastname: "Batch!" });
4528+
dynamicsWebApiTest.deleteRecord(mocks.data.testEntityId2, 'records', null, null, 'firstname');
4529+
4530+
dynamicsWebApiTest.executeBatch(function (object) {
4531+
done(object);
4532+
}, function (object) {
4533+
expect(object.length).to.be.eq(1);
4534+
4535+
expect(object[0].error).to.deep.equal({
4536+
"code": "0x0", "message": "error", "innererror": { "message": "error", "type": "Microsoft.Crm.CrmHttpException", "stacktrace": "stack" }
4537+
});
4538+
4539+
expect(object[0].status).to.equal(400);
4540+
expect(object[0].statusMessage).to.equal("Bad Request");
4541+
expect(object[0].statusText).to.equal("Bad Request");
4542+
4543+
done();
4544+
});
4545+
});
4546+
4547+
it("all requests have been made", function () {
4548+
expect(scope.isDone()).to.be.true;
4549+
});
4550+
});
44934551
});
44944552

44954553
describe("dynamicsWebApi.constructor -", function () {

tests/main-tests.js

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3560,7 +3560,7 @@ describe("promises -", function () {
35603560
"If-Match": "*"
35613561
}
35623562
})
3563-
.put(mocks.responses.entityDefinitionsIdUrl + '/Attributes(' + mocks.data.testEntityId2 + ')', mocks.data.testAttributeDefinition)
3563+
.put(mocks.responses.entityDefinitionsIdUrl + '/Attributes(' + mocks.data.testEntityId2 + ')', mocks.data.testAttributeDefinition)
35643564
.reply(response.status, response.responseText, response.responseHeaders);
35653565
});
35663566

@@ -4868,6 +4868,65 @@ describe("promises -", function () {
48684868
});
48694869
});
48704870

4871+
describe("update / delete - returns an error", function () {
4872+
var scope;
4873+
var rBody = mocks.data.batchUpdateDelete;
4874+
var rBodys = rBody.split('\n');
4875+
var checkBody = '';
4876+
for (var i = 0; i < rBodys.length; i++) {
4877+
checkBody += rBodys[i];
4878+
}
4879+
before(function () {
4880+
var response = mocks.responses.batchError;
4881+
scope = nock(mocks.webApiUrl + '$batch')
4882+
.filteringRequestBody(function (body) {
4883+
body = body.replace(/dwa_batch_[\d\w]{8}-[\d\w]{4}-[\d\w]{4}-[\d\w]{4}-[\d\w]{12}/g, 'dwa_batch_XXX');
4884+
body = body.replace(/changeset_[\d\w]{8}-[\d\w]{4}-[\d\w]{4}-[\d\w]{4}-[\d\w]{12}/g, 'changeset_XXX');
4885+
var bodys = body.split('\n');
4886+
4887+
var resultBody = '';
4888+
for (var i = 0; i < bodys.length; i++) {
4889+
resultBody += bodys[i];
4890+
}
4891+
return resultBody;
4892+
})
4893+
.post("", checkBody)
4894+
.reply(response.status, response.responseText, response.responseHeaders);
4895+
});
4896+
4897+
after(function () {
4898+
nock.cleanAll();
4899+
});
4900+
4901+
it("returns a correct response", function (done) {
4902+
dynamicsWebApiTest.startBatch();
4903+
4904+
dynamicsWebApiTest.update(mocks.data.testEntityId2, 'records', { firstname: "Test", lastname: "Batch!" });
4905+
dynamicsWebApiTest.deleteRecord(mocks.data.testEntityId2, 'records', 'firstname');
4906+
4907+
dynamicsWebApiTest.executeBatch()
4908+
.then(function (object) {
4909+
done(object);
4910+
}).catch(function (object) {
4911+
expect(object.length).to.be.eq(1);
4912+
4913+
expect(object[0].error).to.deep.equal({
4914+
"code": "0x0", "message": "error", "innererror": { "message": "error", "type": "Microsoft.Crm.CrmHttpException", "stacktrace": "stack" }
4915+
});
4916+
4917+
expect(object[0].status).to.equal(400);
4918+
expect(object[0].statusMessage).to.equal("Bad Request");
4919+
expect(object[0].statusText).to.equal("Bad Request");
4920+
4921+
done();
4922+
});
4923+
});
4924+
4925+
it("all requests have been made", function () {
4926+
expect(scope.isDone()).to.be.true;
4927+
});
4928+
});
4929+
48714930
describe("create / create with Content-ID", function () {
48724931
var scope;
48734932
var rBody = mocks.data.batchCreateContentID;
@@ -4956,7 +5015,7 @@ describe("promises -", function () {
49565015
dynamicsWebApiTest.startBatch();
49575016

49585017
dynamicsWebApiTest.createRequest({ collection: 'records', entity: { firstname: "Test", lastname: "Batch!" }, contentId: '1' });
4959-
dynamicsWebApiTest.createRequest({ collection: 'tests', entity: { firstname: "Test1", lastname: "Batch!", "[email protected]": "$1" }});
5018+
dynamicsWebApiTest.createRequest({ collection: 'tests', entity: { firstname: "Test1", lastname: "Batch!", "[email protected]": "$1" } });
49605019

49615020
dynamicsWebApiTest.executeBatch()
49625021
.then(function (object) {
@@ -5481,7 +5540,7 @@ describe("promises -", function () {
54815540
}
54825541
})
54835542
.get(mocks.responses.collectionUrl)
5484-
.query({ '$select': 'name'})
5543+
.query({ '$select': 'name' })
54855544
.reply(response.status, response.responseText, response.responseHeaders);
54865545
});
54875546

0 commit comments

Comments
 (0)