diff --git a/src/.eslintrc.json b/src/.eslintrc.json index 4cf6b0f95e00..0725085afefc 100644 --- a/src/.eslintrc.json +++ b/src/.eslintrc.json @@ -17,7 +17,7 @@ "toString": false, "minErrConfig": false, "errorHandlingConfig": false, - "isValidObjectMaxDepth": false, + "isValidNumberForMinErrConfig": false, "ngMinErr": false, "_angular": false, "angularModule": false, diff --git a/src/Angular.js b/src/Angular.js index 48c31ea1a0b9..65cb0c7044d0 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -12,7 +12,7 @@ toString, minErrConfig, errorHandlingConfig, - isValidObjectMaxDepth, + isValidNumberForMinErrConfig, ngMinErr, angularModule, uid, @@ -129,7 +129,8 @@ var VALIDITY_STATE_PROPERTY = 'validity'; var hasOwnProperty = Object.prototype.hasOwnProperty; var minErrConfig = { - objectMaxDepth: 5 + objectMaxDepth: 5, + urlMaxLength: 2000 }; /** @@ -143,6 +144,8 @@ var minErrConfig = { * current configuration if used as a getter. The following options are supported: * * - **objectMaxDepth**: The maximum depth to which objects are traversed when stringified for error messages. + * - **urlMaxLength**: The maximum length of the URL reference in the error messages. + * if the url exceeds the max length it will be truncated, so it will lack of information. * * Omitted or undefined options will leave the corresponding configuration values unchanged. * @@ -152,11 +155,18 @@ var minErrConfig = { * * `objectMaxDepth` **{Number}** - The max depth for stringifying objects. Setting to a * non-positive or non-numeric value, removes the max depth limit. * Default: 5 + * + * * `urlMaxLength` **{Number}** - The max length of the URL reference in the error messages. Setting to a + * non-positive or non-numeric value, removes the url max length limit. + * Default: 2000 */ function errorHandlingConfig(config) { if (isObject(config)) { if (isDefined(config.objectMaxDepth)) { - minErrConfig.objectMaxDepth = isValidObjectMaxDepth(config.objectMaxDepth) ? config.objectMaxDepth : NaN; + minErrConfig.objectMaxDepth = isValidNumberForMinErrConfig(config.objectMaxDepth) ? config.objectMaxDepth : NaN; + } + if (isDefined(config.urlMaxLength)) { + minErrConfig.urlMaxLength = isValidNumberForMinErrConfig(config.urlMaxLength) ? config.urlMaxLength : NaN; } } else { return minErrConfig; @@ -165,11 +175,11 @@ function errorHandlingConfig(config) { /** * @private - * @param {Number} maxDepth + * @param {Number} num * @return {boolean} */ -function isValidObjectMaxDepth(maxDepth) { - return isNumber(maxDepth) && maxDepth > 0; +function isValidNumberForMinErrConfig(num) { + return isNumber(num) && num > 0; } /** @@ -897,7 +907,7 @@ function arrayRemove(array, value) { function copy(source, destination, maxDepth) { var stackSource = []; var stackDest = []; - maxDepth = isValidObjectMaxDepth(maxDepth) ? maxDepth : NaN; + maxDepth = isValidNumberForMinErrConfig(maxDepth) ? maxDepth : NaN; if (destination) { if (isTypedArray(destination) || isArrayBuffer(destination)) { diff --git a/src/minErr.js b/src/minErr.js index c188f7725d6d..7f8ae7c79b75 100644 --- a/src/minErr.js +++ b/src/minErr.js @@ -51,13 +51,19 @@ function minErr(module, ErrorConstructor) { return match; }); - message += '\nhttp://errors.angularjs.org/"NG_VERSION_FULL"/' + + var url = 'http://errors.angularjs.org/"NG_VERSION_FULL"/' + (module ? module + '/' : '') + code; for (i = 0, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') { - message += paramPrefix + 'p' + i + '=' + encodeURIComponent(templateArgs[i]); + url += paramPrefix + 'p' + i + '=' + encodeURIComponent(templateArgs[i]); + + if (url.length > minErrConfig.urlMaxLength) { + url = url.substr(0, minErrConfig.urlMaxLength - 3) + '...'; + break; + } } + message += '\n' + url; return new ErrorConstructor(message); }; } diff --git a/src/stringify.js b/src/stringify.js index c33aabb48c88..7e1e2374dea0 100644 --- a/src/stringify.js +++ b/src/stringify.js @@ -8,7 +8,7 @@ function serializeObject(obj, maxDepth) { // There is no direct way to stringify object until reaching a specific depth // and a very deep object can cause a performance issue, so we copy the object // based on this specific depth and then stringify it. - if (isValidObjectMaxDepth(maxDepth)) { + if (isValidNumberForMinErrConfig(maxDepth)) { obj = copy(obj, null, maxDepth); } return JSON.stringify(obj, function(key, val) { diff --git a/test/AngularSpec.js b/test/AngularSpec.js index 9aaa2f5b8f83..7ec8bf72cf59 100644 --- a/test/AngularSpec.js +++ b/test/AngularSpec.js @@ -7,6 +7,8 @@ Float32Array, Float64Array, */ describe('angular', function() { var element, document; + var originalObjectMaxDepthInErrorMessage = minErrConfig.objectMaxDepth; + var originalUrlMaxLengthInErrorMessage = minErrConfig.urlMaxLength; beforeEach(function() { document = window.document; @@ -14,6 +16,58 @@ describe('angular', function() { afterEach(function() { dealoc(element); + minErrConfig.objectMaxDepth = originalObjectMaxDepthInErrorMessage; + minErrConfig.urlMaxLength = originalUrlMaxLengthInErrorMessage; + }); + + describe('errorHandlingConfig', function() { + it('should get default objectMaxDepth', function() { + expect(errorHandlingConfig().objectMaxDepth).toBe(5); + }); + + it('should set objectMaxDepth only', function() { + errorHandlingConfig({objectMaxDepth: 3}); + expect(errorHandlingConfig()).toEqual({ + objectMaxDepth: 3, + urlMaxLength: originalUrlMaxLengthInErrorMessage + }); + }); + + it('should not change objectMaxDepth when undefined is supplied', function() { + errorHandlingConfig({objectMaxDepth: undefined}); + expect(errorHandlingConfig().objectMaxDepth).toBe(originalObjectMaxDepthInErrorMessage); + }); + + they('should set objectMaxDepth to NAN when $prop is supplied', + [NaN, null, true, false, -1, 0], function(maxDepth) { + errorHandlingConfig({objectMaxDepth: maxDepth}); + expect(errorHandlingConfig().objectMaxDepth).toBeNaN(); + } + ); + + it('should get default urlMaxLength', function() { + expect(errorHandlingConfig().urlMaxLength).toBe(2000); + }); + + it('should set urlMaxLength only', function() { + errorHandlingConfig({urlMaxLength: 500}); + expect(errorHandlingConfig()).toEqual({ + 'objectMaxDepth': originalObjectMaxDepthInErrorMessage, + 'urlMaxLength': 500 + }); + }); + + it('should not change urlMaxLength when undefined is supplied', function() { + errorHandlingConfig({urlMaxLength: undefined}); + expect(errorHandlingConfig().urlMaxLength).toBe(originalUrlMaxLengthInErrorMessage); + }); + + they('should set urlMaxLength to NAN when $prop is supplied', + [NaN, null, true, false, -1, 0], function(maxLength) { + errorHandlingConfig({urlMaxLength: maxLength}); + expect(errorHandlingConfig().urlMaxLength).toBeNaN(); + } + ); }); describe('case', function() { diff --git a/test/minErrSpec.js b/test/minErrSpec.js index cc38354e8cbc..68b1e82fcc89 100644 --- a/test/minErrSpec.js +++ b/test/minErrSpec.js @@ -10,10 +10,21 @@ describe('minErr', function() { testError = minErr('test'); var originalObjectMaxDepthInErrorMessage = minErrConfig.objectMaxDepth; + var originalUrlMaxLengthInErrorMessage = minErrConfig.urlMaxLength; afterEach(function() { minErrConfig.objectMaxDepth = originalObjectMaxDepthInErrorMessage; + minErrConfig.urlMaxLength = originalUrlMaxLengthInErrorMessage; }); + function extractUrlFromErrorMessage(message) { + var match = message.match(/http[\s\S]*\?p0=/); + var urlStartAt = message.indexOf(match[0]); + if (urlStartAt < 0) { + throw new Error('Could not find url'); + } + return message.slice(urlStartAt); + } + it('should return an Error factory', function() { var myError = testError('test', 'Oops'); expect(myError instanceof Error).toBe(true); @@ -78,32 +89,28 @@ describe('minErr', function() { var myError = testError('26', 'a when objectMaxDepth is default=5 is {0}', a); expect(myError.message).toMatch(/a when objectMaxDepth is default=5 is {"b":{"c":{"d":{"e":{"f":"..."}}}}}/); - expect(errorHandlingConfig().objectMaxDepth).toBe(5); + errorHandlingConfig({objectMaxDepth: 1}); myError = testError('26', 'a when objectMaxDepth is set to 1 is {0}', a); expect(myError.message).toMatch(/a when objectMaxDepth is set to 1 is {"b":"..."}/); - expect(errorHandlingConfig().objectMaxDepth).toBe(1); errorHandlingConfig({objectMaxDepth: 2}); myError = testError('26', 'a when objectMaxDepth is set to 2 is {0}', a); expect(myError.message).toMatch(/a when objectMaxDepth is set to 2 is {"b":{"c":"..."}}/); - expect(errorHandlingConfig().objectMaxDepth).toBe(2); errorHandlingConfig({objectMaxDepth: undefined}); myError = testError('26', 'a when objectMaxDepth is set to undefined is {0}', a); expect(myError.message).toMatch(/a when objectMaxDepth is set to undefined is {"b":{"c":"..."}}/); - expect(errorHandlingConfig().objectMaxDepth).toBe(2); }); they('should handle arguments that are objects and ignore max depth when objectMaxDepth = $prop', [NaN, null, true, false, -1, 0], function(maxDepth) { - var a = {b: {c: {d: 1}}}; + var a = {b: {c: {d: {e: {f: {g: 1}}}}}}; errorHandlingConfig({objectMaxDepth: maxDepth}); var myError = testError('26', 'a is {0}', a); - expect(myError.message).toMatch(/a is {"b":{"c":{"d":1}}}/); - expect(errorHandlingConfig().objectMaxDepth).toBeNaN(); + expect(myError.message).toMatch(/a is {"b":{"c":{"d":{"e":{"f":{"g":1}}}}}}/); } ); @@ -143,4 +150,31 @@ describe('minErr', function() { expect(testError('acode', 'aproblem', 'a', 'b', 'value with space').message) .toMatch(/^[\s\S]*\?p0=a&p1=b&p2=value%20with%20space$/); }); + + it('should slice error reference URL in the message if it exceeds url max length', function() { + var a = new Array(3000).join('a'); + var myError = testError('26', 'a is {0}', a); + var url = extractUrlFromErrorMessage(myError.message); + expect(url.length).toBe(2000); + + errorHandlingConfig({urlMaxLength: 500}); + myError = testError('26', 'a is {0}', a); + url = extractUrlFromErrorMessage(myError.message); + expect(url.length).toBe(500); + + errorHandlingConfig({urlMaxLength: undefined}); + myError = testError('26', 'a is {0}', a); + url = extractUrlFromErrorMessage(myError.message); + expect(url.length).toBe(500); + }); + + they('should ignore url max length when urlMaxLength = $prop', + [NaN, null, true, false, -1, 0], function(maxLength) { + var a = new Array(3000).join('a'); + errorHandlingConfig({urlMaxLength: maxLength}); + var myError = testError('26', 'a is {0}', a); + var url = extractUrlFromErrorMessage(myError.message); + expect(url.length).toBeGreaterThan(3000); + } + ); });