Skip to content

Commit b0c6aa2

Browse files
fix($urlMatcherFactory): add Type.$normalize function
- Type.$normalize can be passed either a decoded object, or an encoded object (string). If the parameter is already decoded (checked via .is()), then it is returned. Else the parameter is decoded and returned.
1 parent 5797c2e commit b0c6aa2

File tree

2 files changed

+37
-19
lines changed

2 files changed

+37
-19
lines changed

Diff for: src/urlMatcherFactory.js

+29-13
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,11 @@ Type.prototype.pattern = /.*/;
480480

481481
Type.prototype.toString = function() { return "{Type:" + this.name + "}"; };
482482

483+
/** Given an encoded string, or a decoded object, returns a decoded object */
484+
Type.prototype.$normalize = function(val) {
485+
return this.is(val) ? val : this.decode(val);
486+
};
487+
483488
/*
484489
* Wraps an existing custom Type as an array of Type, depending on 'mode'.
485490
* e.g.:
@@ -493,7 +498,6 @@ Type.prototype.toString = function() { return "{Type:" + this.name + "}"; };
493498
Type.prototype.$asArray = function(mode, isSearch) {
494499
if (!mode) return this;
495500
if (mode === "auto" && !isSearch) throw new Error("'auto' array mode is for query parameters only");
496-
return new ArrayType(this, mode);
497501

498502
function ArrayType(type, mode) {
499503
function bindTo(type, callbackName) {
@@ -542,8 +546,12 @@ Type.prototype.$asArray = function(mode, isSearch) {
542546
this.is = arrayHandler(bindTo(type, 'is'), true);
543547
this.equals = arrayEqualsHandler(bindTo(type, 'equals'));
544548
this.pattern = type.pattern;
549+
this.$normalize = arrayHandler(bindTo(type, '$normalize'));
550+
this.name = type.name;
545551
this.$arrayMode = mode;
546552
}
553+
554+
return new ArrayType(this, mode);
547555
};
548556

549557

@@ -571,7 +579,7 @@ function $UrlMatcherFactory() {
571579
string: {
572580
encode: valToString,
573581
decode: valFromString,
574-
is: regexpMatches,
582+
is: function(val) { return typeof val === "string"},
575583
pattern: /[^/]*/
576584
},
577585
int: {
@@ -945,7 +953,10 @@ function $UrlMatcherFactory() {
945953
*/
946954
function $$getDefaultValue() {
947955
if (!injector) throw new Error("Injectable functions cannot be called at configuration time");
948-
return injector.invoke(config.$$fn);
956+
var defaultValue = injector.invoke(config.$$fn);
957+
if (defaultValue !== null && defaultValue !== undefined && !self.type.is(defaultValue))
958+
throw new Error("Default value (" + defaultValue + ") for parameter '" + self.id + "' is not an instance of Type (" + self.type.name + ")");
959+
return defaultValue;
949960
}
950961

951962
/**
@@ -959,7 +970,7 @@ function $UrlMatcherFactory() {
959970
return replacement.length ? replacement[0] : value;
960971
}
961972
value = $replace(value);
962-
return isDefined(value) ? self.type.decode(value) : $$getDefaultValue();
973+
return !isDefined(value) ? $$getDefaultValue() : self.type.$normalize(value);
963974
}
964975

965976
function toString() { return "{Param:" + id + " " + type + " squash: '" + squash + "' optional: " + isOptional + "}"; }
@@ -1015,15 +1026,20 @@ function $UrlMatcherFactory() {
10151026
return equal;
10161027
},
10171028
$$validates: function $$validate(paramValues) {
1018-
var result = true, isOptional, val, param, self = this;
1019-
1020-
forEach(this.$$keys(), function(key) {
1021-
param = self[key];
1022-
val = paramValues[key];
1023-
isOptional = !val && param.isOptional;
1024-
result = result && (isOptional || !!param.type.is(val));
1025-
});
1026-
return result;
1029+
var keys = this.$$keys(), i, param, rawVal, normalized, encoded;
1030+
for (i = 0; i < keys.length; i++) {
1031+
param = this[keys[i]];
1032+
rawVal = paramValues[keys[i]];
1033+
if ((rawVal === undefined || rawVal === null) && param.isOptional)
1034+
break; // There was no parameter value, but the param is optional
1035+
normalized = param.type.$normalize(rawVal);
1036+
if (!param.type.is(normalized))
1037+
return false; // The value was not of the correct Type, and could not be decoded to the correct Type
1038+
encoded = param.type.encode(normalized);
1039+
if (angular.isString(encoded) && !param.type.pattern.exec(encoded))
1040+
return false; // The value was of the correct type, but when encoded, did not match the Type's regexp
1041+
}
1042+
return true;
10271043
},
10281044
$$parent: undefined
10291045
};

Diff for: test/urlMatcherFactorySpec.js

+8-6
Original file line numberDiff line numberDiff line change
@@ -407,15 +407,17 @@ describe("urlMatcherFactoryProvider", function () {
407407
var m;
408408
beforeEach(module('ui.router.util', function($urlMatcherFactoryProvider) {
409409
$urlMatcherFactoryProvider.type("myType", {}, function() {
410-
return { decode: function() { return 'decoded'; }
411-
};
410+
return {
411+
decode: function() { return { status: 'decoded' }; },
412+
is: angular.isObject
413+
};
412414
});
413415
m = new UrlMatcher("/test?{foo:myType}");
414416
}));
415417

416418
it("should handle arrays properly with config-time custom type definitions", inject(function ($stateParams) {
417-
expect(m.exec("/test", {foo: '1'})).toEqual({ foo: 'decoded' });
418-
expect(m.exec("/test", {foo: ['1', '2']})).toEqual({ foo: ['decoded', 'decoded'] });
419+
expect(m.exec("/test", {foo: '1'})).toEqual({ foo: { status: 'decoded' } });
420+
expect(m.exec("/test", {foo: ['1', '2']})).toEqual({ foo: [ { status: 'decoded' }, { status: 'decoded' }] });
419421
}));
420422
});
421423
});
@@ -662,7 +664,7 @@ describe("urlMatcherFactory", function () {
662664

663665
it("should populate query params", function() {
664666
var defaults = { order: "name", limit: 25, page: 1 };
665-
var m = new UrlMatcher('/foo?order&limit&page', {
667+
var m = new UrlMatcher('/foo?order&{limit:int}&{page:int}', {
666668
params: defaults
667669
});
668670
expect(m.exec("/foo")).toEqual(defaults);
@@ -687,7 +689,7 @@ describe("urlMatcherFactory", function () {
687689
});
688690

689691
it("should allow injectable functions", inject(function($stateParams) {
690-
var m = new UrlMatcher('/users/:user', {
692+
var m = new UrlMatcher('/users/{user:json}', {
691693
params: {
692694
user: function($stateParams) {
693695
return $stateParams.user;

0 commit comments

Comments
 (0)