Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

Commit

Permalink
feat($http): support custom params serializers
Browse files Browse the repository at this point in the history
  • Loading branch information
pkozlowski-opensource committed Mar 30, 2015
1 parent 73f3515 commit 7c87cc5
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 27 deletions.
8 changes: 6 additions & 2 deletions src/AngularPublic.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,9 @@
$$AsyncCallbackProvider,
$WindowProvider,
$$jqLiteProvider,
$$CookieReaderProvider
$$CookieReaderProvider,
$$DefaultHttpParamSerializerProvider,
$$JqueryParamSerializerProvider
*/


Expand Down Expand Up @@ -243,7 +245,9 @@ function publishExternalAPI(angular) {
$$asyncCallback: $$AsyncCallbackProvider,
$$jqLite: $$jqLiteProvider,
$$HashMap: $$HashMapProvider,
$$cookieReader: $$CookieReaderProvider
$$cookieReader: $$CookieReaderProvider,
$$defaultHttpParamSerializer: $$DefaultHttpParamSerializerProvider,
$$jqueryParamSerializer: $$JqueryParamSerializerProvider
});
}
]);
Expand Down
92 changes: 68 additions & 24 deletions src/ng/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,57 @@ var JSON_ENDS = {
};
var JSON_PROTECTION_PREFIX = /^\)\]\}',?\n/;

function paramSerializerFactory(arraySuffix) {

function serializeValue(v) {
if (isObject(v)) {
return isDate(v) ? v.toISOString() : toJson(v);
}
return v;
}

return function paramSerializer(params) {
if (!params) return '';
var parts = [];
forEachSorted(params, function(value, key) {
if (value === null || isUndefined(value)) return;
if (isArray(value)) {
forEach(value, function(v) {
parts.push(encodeUriQuery(key) + (arraySuffix ? '[]' : '') + '=' + encodeUriQuery(serializeValue(v)));
});
} else {
parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(value)));
}
});

return parts.length > 0 ? parts.join('&') : '';
};
}

/**
* @ngdoc provider
* @name $$DefaultHttpParamSerializerProvider
* @description
*
* */
function $$DefaultHttpParamSerializerProvider() {
this.$get = function() {
return paramSerializerFactory(false);
};
}

/**
* @ngdoc provider
* @name $$JqueryParamSerializerProvider
* @description
*
* */
function $$JqueryParamSerializerProvider() {
this.$get = function() {
return paramSerializerFactory(true);
};
}

function defaultHttpResponseTransform(data, headers) {
if (isString(data)) {
// Strip json vulnerability protection prefix and trim whitespace
Expand Down Expand Up @@ -174,7 +225,9 @@ function $HttpProvider() {
},

xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN'
xsrfHeaderName: 'X-XSRF-TOKEN',

paramSerializer: '$$defaultHttpParamSerializer'
};

var useApplyAsync = false;
Expand Down Expand Up @@ -225,6 +278,12 @@ function $HttpProvider() {

var defaultCache = $cacheFactory('$http');

/**
* Make sure that default param serializer is exposed as a function
*/
defaults.paramSerializer = isString(defaults.paramSerializer) ?
$injector.get(defaults.paramSerializer) : defaults.paramSerializer;

/**
* Interceptors stored in reverse order. Inner interceptors before outer interceptors.
* The reversal is needed so that we can build up the interception chain around the
Expand Down Expand Up @@ -764,11 +823,14 @@ function $HttpProvider() {
var config = extend({
method: 'get',
transformRequest: defaults.transformRequest,
transformResponse: defaults.transformResponse
transformResponse: defaults.transformResponse,
paramSerializer: defaults.paramSerializer
}, requestConfig);

config.headers = mergeHeaders(requestConfig);
config.method = uppercase(config.method);
config.paramSerializer = isString(config.paramSerializer) ?
$injector.get(config.paramSerializer) : config.paramSerializer;

var serverRequest = function(config) {
var headers = config.headers;
Expand Down Expand Up @@ -1032,7 +1094,7 @@ function $HttpProvider() {
cache,
cachedResp,
reqHeaders = config.headers,
url = buildUrl(config.url, config.params);
url = buildUrl(config.url, config.paramSerializer(config.params));

$http.pendingRequests.push(config);
promise.then(removePendingReq, removePendingReq);
Expand Down Expand Up @@ -1139,27 +1201,9 @@ function $HttpProvider() {
}


function buildUrl(url, params) {
if (!params) return url;
var parts = [];
forEachSorted(params, function(value, key) {
if (value === null || isUndefined(value)) return;
if (!isArray(value)) value = [value];

forEach(value, function(v) {
if (isObject(v)) {
if (isDate(v)) {
v = v.toISOString();
} else {
v = toJson(v);
}
}
parts.push(encodeUriQuery(key) + '=' +
encodeUriQuery(v));
});
});
if (parts.length > 0) {
url += ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&');
function buildUrl(url, serializedParams) {
if (serializedParams.length > 0) {
url += ((url.indexOf('?') == -1) ? '?' : '&') + serializedParams;
}
return url;
}
Expand Down
67 changes: 66 additions & 1 deletion test/ng/httpSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
describe('$http', function() {

var callback, mockedCookies;
var customParamSerializer = function(params) {
return Object.keys(params).join('_');
};

beforeEach(function() {
callback = jasmine.createSpy('done');
Expand All @@ -14,6 +17,9 @@ describe('$http', function() {
});
});

beforeEach(module({
customParamSerializer: customParamSerializer
}));
beforeEach(module(function($exceptionHandlerProvider) {
$exceptionHandlerProvider.mode('log');
}));
Expand Down Expand Up @@ -354,6 +360,20 @@ describe('$http', function() {
$httpBackend.expect('GET', '/url?date=2014-07-15T17:30:00.000Z').respond('');
$http({url: '/url', params: {date:new Date('2014-07-15T17:30:00.000Z')}, method: 'GET'});
});


describe('custom params serialization', function() {

it('should allow specifying custom paramSerializer as function', function() {
$httpBackend.expect('GET', '/url?foo_bar').respond('');
$http({url: '/url', params: {foo: 'fooVal', bar: 'barVal'}, paramSerializer: customParamSerializer});
});

it('should allow specifying custom paramSerializer as function from DI', function() {
$httpBackend.expect('GET', '/url?foo_bar').respond('');
$http({url: '/url', params: {foo: 'fooVal', bar: 'barVal'}, paramSerializer: 'customParamSerializer'});
});
});
});


Expand Down Expand Up @@ -1788,11 +1808,16 @@ describe('$http', function() {
$httpBackend.flush();
});

it('should have separate opbjects for defaults PUT and POST', function() {
it('should have separate objects for defaults PUT and POST', function() {
expect($http.defaults.headers.post).not.toBe($http.defaults.headers.put);
expect($http.defaults.headers.post).not.toBe($http.defaults.headers.patch);
expect($http.defaults.headers.put).not.toBe($http.defaults.headers.patch);
});

it('should expose default param serializer at runtime', function() {
var paramSerializer = $http.defaults.paramSerializer;
expect(paramSerializer({foo: 'foo', bar: ['bar', 'baz']})).toEqual('bar=bar&bar=baz&foo=foo');
});
});
});

Expand Down Expand Up @@ -1929,3 +1954,43 @@ describe('$http with $applyAsync', function() {
expect(log).toEqual(['response 1', 'response 2', 'response 3']);
});
});

describe('$http param serializers', function() {

var defSer, jqrSer;
beforeEach(inject(function($$defaultHttpParamSerializer, $$jqueryParamSerializer) {
defSer = $$defaultHttpParamSerializer;
jqrSer = $$jqueryParamSerializer;
}));

describe('common functionality', function() {

it('should return empty string for null or undefined params', function() {
expect(defSer(undefined)).toEqual('');
expect(jqrSer(undefined)).toEqual('');
expect(defSer(null)).toEqual('');
expect(jqrSer(null)).toEqual('');
});

it('should serialize objects', function() {
expect(defSer({foo: 'foov', bar: 'barv'})).toEqual('bar=barv&foo=foov');
expect(jqrSer({foo: 'foov', bar: 'barv'})).toEqual('bar=barv&foo=foov');
});

});

describe('default array serialization', function() {

it('should serialize arrays by repeating param name', function() {
expect(defSer({a: 'b', foo: ['bar', 'baz']})).toEqual('a=b&foo=bar&foo=baz');
});
});

describe('jquery array serialization', function() {

it('should serialize arrays by repeating param name with [] suffix', function() {
expect(jqrSer({a: 'b', foo: ['bar', 'baz']})).toEqual('a=b&foo[]=bar&foo[]=baz');
});
});

});

0 comments on commit 7c87cc5

Please sign in to comment.