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

Commit 5306136

Browse files
ruprictpetebacondarwin
authored andcommitted
feat($resource): collapse empty suffix parameters correctly
Previously only repeated `/` delimiters were collapsed into a single `/`. Now, the sequence `/.` at the end of the template, i.e. only followed by a sequence of word characters, is collapsed into a single `.`. This makes it easier to support suffixes on resource URLs. For example, given a resource template of `/some/path/:id.:format`, if the `:id` is `""` but format `"json"` then the URL is now `/some/path.json`, rather than `/some/path/.json`. BREAKING CHANGE: A `/` followed by a `.`, in the last segment of the URL template is now collapsed into a single `.` delimiter. For example: `users/.json` will become `users.json`. If your server relied upon this sequence then it will no longer work. In this case you can now escape the `/.` sequence with `/\.`
1 parent c32a859 commit 5306136

File tree

2 files changed

+169
-13
lines changed

2 files changed

+169
-13
lines changed

src/ngResource/resource.js

+14-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@
3434
* `http://example.com:8080/api`), you'll need to escape the colon character before the port
3535
* number, like this: `$resource('http://example.com\\:8080/api')`.
3636
*
37+
* If you are using a url with a suffix, just add the suffix, like this:
38+
* `$resource('http://example.com/resource.json')` or `$resource('http://example.com/:id.json')
39+
* or even `$resource('http://example.com/resource/:resource_id.:format')`
40+
* If the parameter before the suffix is empty, :resource_id in this case, then the `/.` will be
41+
* collapsed down to a single `.`. If you need this sequence to appear and not collapse then you
42+
* can escape it with `/\.`.
43+
*
3744
* @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in
3845
* `actions` methods. If any of the parameter value is a function, it will be executed every time
3946
* when a param value needs to be obtained for a request (unless the param was overridden).
@@ -356,7 +363,13 @@ angular.module('ngResource', ['ng']).
356363
});
357364

358365
// strip trailing slashes and set the url
359-
config.url = url.replace(/\/+$/, '');
366+
url = url.replace(/\/+$/, '');
367+
// then replace collapse `/.` if found in the last URL path segment before the query
368+
// E.g. `http://url.com/id./format?q=x` becomes `http://url.com/id.format?q=x`
369+
url = url.replace(/\/\.(?=\w+($|\?))/, '.');
370+
// replace escaped `/\.` with `/.`
371+
config.url = url.replace(/\/\\\./, '/.');
372+
360373

361374
// set params - delegate param encoding to $http
362375
forEach(params, function(value, key){

test/ngResource/resourceSpec.js

+155-12
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,13 @@ describe("resource", function() {
7676
it('should not ignore leading slashes of undefinend parameters that have non-slash trailing sequence', function() {
7777
var R = $resource('/Path/:a.foo/:b.bar/:c.baz');
7878

79-
$httpBackend.when('GET', '/Path/.foo/.bar/.baz').respond('{}');
80-
$httpBackend.when('GET', '/Path/0.foo/.bar/.baz').respond('{}');
81-
$httpBackend.when('GET', '/Path/false.foo/.bar/.baz').respond('{}');
82-
$httpBackend.when('GET', '/Path/.foo/.bar/.baz').respond('{}');
83-
$httpBackend.when('GET', '/Path/.foo/.bar/.baz').respond('{}');
84-
$httpBackend.when('GET', '/Path/1.foo/.bar/.baz').respond('{}');
85-
$httpBackend.when('GET', '/Path/2.foo/3.bar/.baz').respond('{}');
79+
$httpBackend.when('GET', '/Path/.foo/.bar.baz').respond('{}');
80+
$httpBackend.when('GET', '/Path/0.foo/.bar.baz').respond('{}');
81+
$httpBackend.when('GET', '/Path/false.foo/.bar.baz').respond('{}');
82+
$httpBackend.when('GET', '/Path/.foo/.bar.baz').respond('{}');
83+
$httpBackend.when('GET', '/Path/.foo/.bar.baz').respond('{}');
84+
$httpBackend.when('GET', '/Path/1.foo/.bar.baz').respond('{}');
85+
$httpBackend.when('GET', '/Path/2.foo/3.bar.baz').respond('{}');
8686
$httpBackend.when('GET', '/Path/4.foo/.bar/5.baz').respond('{}');
8787
$httpBackend.when('GET', '/Path/6.foo/7.bar/8.baz').respond('{}');
8888

@@ -692,17 +692,160 @@ describe("resource", function() {
692692
expect(person.id).toEqual(456);
693693
});
694694

695+
describe('suffix parameter', function() {
696+
697+
describe('query', function() {
698+
it('should add a suffix', function() {
699+
$httpBackend.expect('GET', '/users.json').respond([{id: 1, name: 'user1'}]);
700+
var UserService = $resource('/users/:id.json', {id: '@id'});
701+
var user = UserService.query();
702+
$httpBackend.flush();
703+
expect(user).toEqualData([{id: 1, name: 'user1'}]);
704+
});
705+
706+
it('should not require it if not provided', function(){
707+
$httpBackend.expect('GET', '/users.json').respond([{id: 1, name: 'user1'}]);
708+
var UserService = $resource('/users.json');
709+
var user = UserService.query();
710+
$httpBackend.flush();
711+
expect(user).toEqualData([{id: 1, name: 'user1'}]);
712+
});
713+
714+
it('should work when query parameters are supplied', function() {
715+
$httpBackend.expect('GET', '/users.json?red=blue').respond([{id: 1, name: 'user1'}]);
716+
var UserService = $resource('/users/:user_id.json', {user_id: '@id'});
717+
var user = UserService.query({red: 'blue'});
718+
$httpBackend.flush();
719+
expect(user).toEqualData([{id: 1, name: 'user1'}]);
720+
});
721+
722+
it('should work when query parameters are supplied and the format is a resource parameter', function() {
723+
$httpBackend.expect('GET', '/users.json?red=blue').respond([{id: 1, name: 'user1'}]);
724+
var UserService = $resource('/users/:user_id.:format', {user_id: '@id', format: 'json'});
725+
var user = UserService.query({red: 'blue'});
726+
$httpBackend.flush();
727+
expect(user).toEqualData([{id: 1, name: 'user1'}]);
728+
});
729+
730+
it('should work with the action is overriden', function(){
731+
$httpBackend.expect('GET', '/users.json').respond([{id: 1, name: 'user1'}]);
732+
var UserService = $resource('/users/:user_id', {user_id: '@id'}, {
733+
query: {
734+
method: 'GET',
735+
url: '/users/:user_id.json',
736+
isArray: true
737+
}
738+
});
739+
var user = UserService.query();
740+
$httpBackend.flush();
741+
expect(user).toEqualData([ {id: 1, name: 'user1'} ]);
742+
});
743+
});
744+
745+
describe('get', function(){
746+
it('should add them to the id', function() {
747+
$httpBackend.expect('GET', '/users/1.json').respond({id: 1, name: 'user1'});
748+
var UserService = $resource('/users/:user_id.json', {user_id: '@id'});
749+
var user = UserService.get({user_id: 1});
750+
$httpBackend.flush();
751+
expect(user).toEqualData({id: 1, name: 'user1'});
752+
});
753+
754+
it('should work when an id and query parameters are supplied', function() {
755+
$httpBackend.expect('GET', '/users/1.json?red=blue').respond({id: 1, name: 'user1'});
756+
var UserService = $resource('/users/:user_id.json', {user_id: '@id'});
757+
var user = UserService.get({user_id: 1, red: 'blue'});
758+
$httpBackend.flush();
759+
expect(user).toEqualData({id: 1, name: 'user1'});
760+
});
761+
762+
it('should work when the format is a parameter', function() {
763+
$httpBackend.expect('GET', '/users/1.json?red=blue').respond({id: 1, name: 'user1'});
764+
var UserService = $resource('/users/:user_id.:format', {user_id: '@id', format: 'json'});
765+
var user = UserService.get({user_id: 1, red: 'blue'});
766+
$httpBackend.flush();
767+
expect(user).toEqualData({id: 1, name: 'user1'});
768+
});
769+
770+
it('should work with the action is overriden', function(){
771+
$httpBackend.expect('GET', '/users/1.json').respond({id: 1, name: 'user1'});
772+
var UserService = $resource('/users/:user_id', {user_id: '@id'}, {
773+
get: {
774+
method: 'GET',
775+
url: '/users/:user_id.json'
776+
}
777+
});
778+
var user = UserService.get({user_id: 1});
779+
$httpBackend.flush();
780+
expect(user).toEqualData({id: 1, name: 'user1'});
781+
});
782+
});
783+
784+
describe("save", function() {
785+
it('should append the suffix', function() {
786+
$httpBackend.expect('POST', '/users.json', '{"name":"user1"}').respond({id: 123, name: 'user1'});
787+
var UserService = $resource('/users/:user_id.json', {user_id: '@id'});
788+
var user = UserService.save({name: 'user1'}, callback);
789+
expect(user).toEqualData({name: 'user1'});
790+
expect(callback).not.toHaveBeenCalled();
791+
$httpBackend.flush();
792+
expect(user).toEqualData({id: 123, name: 'user1'});
793+
expect(callback).toHaveBeenCalledOnce();
794+
expect(callback.mostRecentCall.args[0]).toEqual(user);
795+
expect(callback.mostRecentCall.args[1]()).toEqual({});
796+
});
797+
798+
it('should append when an id is supplied', function() {
799+
$httpBackend.expect('POST', '/users/123.json', '{"id":123,"name":"newName"}').respond({id: 123, name: 'newName'});
800+
var UserService = $resource('/users/:user_id.json', {user_id: '@id'});
801+
var user = UserService.save({id: 123, name: 'newName'}, callback);
802+
expect(callback).not.toHaveBeenCalled();
803+
$httpBackend.flush();
804+
expect(user).toEqualData({id: 123, name: 'newName'});
805+
expect(callback).toHaveBeenCalledOnce();
806+
expect(callback.mostRecentCall.args[0]).toEqual(user);
807+
expect(callback.mostRecentCall.args[1]()).toEqual({});
808+
});
809+
810+
it('should append when an id is supplied and the format is a parameter', function() {
811+
$httpBackend.expect('POST', '/users/123.json', '{"id":123,"name":"newName"}').respond({id: 123, name: 'newName'});
812+
var UserService = $resource('/users/:user_id.:format', {user_id: '@id', format: 'json'});
813+
var user = UserService.save({id: 123, name: 'newName'}, callback);
814+
expect(callback).not.toHaveBeenCalled();
815+
$httpBackend.flush();
816+
expect(user).toEqualData({id: 123, name: 'newName'});
817+
expect(callback).toHaveBeenCalledOnce();
818+
expect(callback.mostRecentCall.args[0]).toEqual(user);
819+
expect(callback.mostRecentCall.args[1]()).toEqual({});
820+
});
821+
});
822+
823+
describe('escaping /. with /\\.', function() {
824+
it('should work with query()', function() {
825+
$httpBackend.expect('GET', '/users/.json').respond();
826+
$resource('/users/\\.json').query();
827+
});
828+
it('should work with get()', function() {
829+
$httpBackend.expect('GET', '/users/.json').respond();
830+
$resource('/users/\\.json').get();
831+
});
832+
it('should work with save()', function() {
833+
$httpBackend.expect('POST', '/users/.json').respond();
834+
$resource('/users/\\.json').save({});
835+
});
836+
});
837+
});
695838

696839
describe('action-level url override', function() {
697840

698841
it('should support overriding url template with static url', function() {
699842
$httpBackend.expect('GET', '/override-url?type=Customer&typeId=123').respond({id: 'abc'});
700843
var TypeItem = $resource('/:type/:typeId', {type: 'Order'}, {
701-
get: {
702-
method: 'GET',
703-
params: {type: 'Customer'},
704-
url: '/override-url'
705-
}
844+
get: {
845+
method: 'GET',
846+
params: {type: 'Customer'},
847+
url: '/override-url'
848+
}
706849
});
707850
var item = TypeItem.get({typeId: 123});
708851
$httpBackend.flush();

0 commit comments

Comments
 (0)