Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions modules/angular2/src/http/base_request_options.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import {CONST_EXPR, CONST, isPresent} from 'angular2/src/facade/lang';
import {CONST_EXPR, CONST, isPresent, isString} from 'angular2/src/facade/lang';
import {Headers} from './headers';
import {RequestModesOpts, RequestMethods, RequestCacheOpts, RequestCredentialsOpts} from './enums';
import {IRequestOptions} from './interfaces';
import {Injectable} from 'angular2/di';
import {URLSearchParams} from './url_search_params';

/**
* Creates a request options object similar to the `RequestInit` description
Expand Down Expand Up @@ -33,14 +34,19 @@ export class RequestOptions implements IRequestOptions {
credentials: RequestCredentialsOpts;
cache: RequestCacheOpts;
url: string;
constructor({method, headers, body, mode, credentials, cache, url}: IRequestOptions = {}) {
search: URLSearchParams;
constructor({method, headers, body, mode, credentials, cache, url, search}:
IRequestOptions = {}) {
this.method = isPresent(method) ? method : null;
this.headers = isPresent(headers) ? headers : null;
this.body = isPresent(body) ? body : null;
this.mode = isPresent(mode) ? mode : null;
this.credentials = isPresent(credentials) ? credentials : null;
this.cache = isPresent(cache) ? cache : null;
this.url = isPresent(url) ? url : null;
this.search = isPresent(search) ? (isString(search) ? new URLSearchParams(<string>(search)) :
<URLSearchParams>(search)) :
null;
}

/**
Expand All @@ -56,7 +62,11 @@ export class RequestOptions implements IRequestOptions {
credentials: isPresent(options) && isPresent(options.credentials) ? options.credentials :
this.credentials,
cache: isPresent(options) && isPresent(options.cache) ? options.cache : this.cache,
url: isPresent(options) && isPresent(options.url) ? options.url : this.url
url: isPresent(options) && isPresent(options.url) ? options.url : this.url,
search: isPresent(options) && isPresent(options.search) ?
(isString(options.search) ? new URLSearchParams(<string>(options.search)) :
(<URLSearchParams>(options.search)).clone()) :
this.search
});
}
}
Expand Down
1 change: 1 addition & 0 deletions modules/angular2/src/http/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ function mergeOptions(defaultOpts, providedOpts, method, url): RequestOptions {
newOptions = newOptions.merge(new RequestOptions({
method: providedOpts.method,
url: providedOpts.url,
search: providedOpts.search,
headers: providedOpts.headers,
body: providedOpts.body,
mode: providedOpts.mode,
Expand Down
5 changes: 5 additions & 0 deletions modules/angular2/src/http/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import {Headers} from './headers';
import {BaseException} from 'angular2/src/facade/lang';
import {EventEmitter} from 'angular2/src/facade/async';
import {Request} from './static_request';
import {URLSearchParamsUnionFixer, URLSearchParams} from './url_search_params';

// Work around Dartanalyzer problem :(
const URLSearchParams_UnionFixer = URLSearchParamsUnionFixer;

/**
* Abstract class from which real backends are derived.
Expand Down Expand Up @@ -41,6 +45,7 @@ export class Connection {
export interface IRequestOptions {
url?: string;
method?: RequestMethods;
search?: string | URLSearchParams;
headers?: Headers;
// TODO: Support Blob, ArrayBuffer, JSON, URLSearchParams, FormData
body?: string;
Expand Down
15 changes: 14 additions & 1 deletion modules/angular2/src/http/static_request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import {
RegExpWrapper,
CONST_EXPR,
isPresent,
isJsObject
isJsObject,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trailing comma

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm surprised enforce-format and lint didn't complain about this

StringWrapper
} from 'angular2/src/facade/lang';

// TODO(jeffbcross): properly implement body accessors
Expand Down Expand Up @@ -39,7 +40,19 @@ export class Request {
cache: RequestCacheOpts;
constructor(requestOptions: RequestOptions) {
// TODO: assert that url is present
let url = requestOptions.url;
this.url = requestOptions.url;
if (isPresent(requestOptions.search)) {
let search = requestOptions.search.toString();
if (search.length > 0) {
let prefix = '?';
if (StringWrapper.contains(this.url, '?')) {
prefix = (this.url[this.url.length - 1] == '&') ? '' : '&';
}
// TODO: just delete search-query-looking string in url?
this.url = url + prefix + search;
}
}
this._body = requestOptions.body;
this.method = requestOptions.method;
// TODO(jeffbcross): implement behavior
Expand Down
104 changes: 90 additions & 14 deletions modules/angular2/src/http/url_search_params.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {isPresent, isBlank, StringWrapper} from 'angular2/src/facade/lang';
import {CONST_EXPR, isPresent, isBlank, StringWrapper} from 'angular2/src/facade/lang';
import {
Map,
MapWrapper,
Expand All @@ -7,28 +7,42 @@ import {
isListLikeIterable
} from 'angular2/src/facade/collection';

function paramParser(rawParams: string): Map<string, List<string>> {
function paramParser(rawParams: string = ''): Map<string, List<string>> {
var map: Map<string, List<string>> = new Map();
var params: List<string> = StringWrapper.split(rawParams, new RegExp('&'));
ListWrapper.forEach(params, (param: string) => {
var split: List<string> = StringWrapper.split(param, new RegExp('='));
var key = ListWrapper.get(split, 0);
var val = ListWrapper.get(split, 1);
var list = isPresent(map.get(key)) ? map.get(key) : [];
list.push(val);
map.set(key, list);
});
if (rawParams.length > 0) {
var params: List<string> = StringWrapper.split(rawParams, new RegExp('&'));
ListWrapper.forEach(params, (param: string) => {
var split: List<string> = StringWrapper.split(param, new RegExp('='));
var key = ListWrapper.get(split, 0);
var val = ListWrapper.get(split, 1);
var list = isPresent(map.get(key)) ? map.get(key) : [];
list.push(val);
map.set(key, list);
});
}
return map;
}

// TODO(caitp): This really should not be needed. Issue with ts2dart.
export const URLSearchParamsUnionFixer: string = CONST_EXPR("UnionFixer");

/**
* Map-like representation of url search parameters, based on
* [URLSearchParams](https://url.spec.whatwg.org/#urlsearchparams) in the url living standard.
*
* [URLSearchParams](https://url.spec.whatwg.org/#urlsearchparams) in the url living standard,
* with several extensions for merging URLSearchParams objects:
* - setAll()
* - appendAll()
* - replaceAll()
*/
export class URLSearchParams {
paramsMap: Map<string, List<string>>;
constructor(public rawParams: string) { this.paramsMap = paramParser(rawParams); }
constructor(public rawParams: string = '') { this.paramsMap = paramParser(rawParams); }

clone(): URLSearchParams {
var clone = new URLSearchParams();
clone.appendAll(this);
return clone;
}

has(param: string): boolean { return this.paramsMap.has(param); }

Expand All @@ -46,13 +60,75 @@ export class URLSearchParams {
return isPresent(mapParam) ? mapParam : [];
}

set(param: string, val: string) {
var mapParam = this.paramsMap.get(param);
var list = isPresent(mapParam) ? mapParam : [];
ListWrapper.clear(list);
list.push(val);
this.paramsMap.set(param, list);
}

// A merge operation
// For each name-values pair in `searchParams`, perform `set(name, values[0])`
//
// E.g: "a=[1,2,3], c=[8]" + "a=[4,5,6], b=[7]" = "a=[4], c=[8], b=[7]"
//
// TODO(@caitp): document this better
setAll(searchParams: URLSearchParams) {
MapWrapper.forEach(searchParams.paramsMap, (value, param) => {
var mapParam = this.paramsMap.get(param);
var list = isPresent(mapParam) ? mapParam : [];
ListWrapper.clear(list);
list.push(value[0]);
this.paramsMap.set(param, list);
});
}

append(param: string, val: string): void {
var mapParam = this.paramsMap.get(param);
var list = isPresent(mapParam) ? mapParam : [];
list.push(val);
this.paramsMap.set(param, list);
}

// A merge operation
// For each name-values pair in `searchParams`, perform `append(name, value)`
// for each value in `values`.
//
// E.g: "a=[1,2], c=[8]" + "a=[3,4], b=[7]" = "a=[1,2,3,4], c=[8], b=[7]"
//
// TODO(@caitp): document this better
appendAll(searchParams: URLSearchParams) {
MapWrapper.forEach(searchParams.paramsMap, (value, param) => {
var mapParam = this.paramsMap.get(param);
var list = isPresent(mapParam) ? mapParam : [];
for (var i = 0; i < value.length; ++i) {
list.push(value[i]);
}
this.paramsMap.set(param, list);
});
}


// A merge operation
// For each name-values pair in `searchParams`, perform `delete(name)`,
// followed by `set(name, values)`
//
// E.g: "a=[1,2,3], c=[8]" + "a=[4,5,6], b=[7]" = "a=[4,5,6], c=[8], b=[7]"
//
// TODO(@caitp): document this better
replaceAll(searchParams: URLSearchParams) {
MapWrapper.forEach(searchParams.paramsMap, (value, param) => {
var mapParam = this.paramsMap.get(param);
var list = isPresent(mapParam) ? mapParam : [];
ListWrapper.clear(list);
for (var i = 0; i < value.length; ++i) {
list.push(value[i]);
}
this.paramsMap.set(param, list);
});
}

toString(): string {
var paramsList = [];
MapWrapper.forEach(this.paramsMap, (values, k) => {
Expand Down
42 changes: 42 additions & 0 deletions modules/angular2/test/http/http_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {ResponseOptions} from 'angular2/src/http/base_response_options';
import {Request} from 'angular2/src/http/static_request';
import {EventEmitter, ObservableWrapper} from 'angular2/src/facade/async';
import {ConnectionBackend} from 'angular2/src/http/interfaces';
import {URLSearchParams} from 'angular2/src/http/url_search_params';

class SpyObserver extends SpyObject {
onNext: Function;
Expand Down Expand Up @@ -202,6 +203,47 @@ export function main() {
ObservableWrapper.subscribe(http.head(url), res => {});
}));
});


describe('searchParams', () => {
it('should append search params to url', inject([AsyncTestCompleter], async => {
var params = new URLSearchParams();
params.append('q', 'puppies');
ObservableWrapper.subscribe<MockConnection>(backend.connections, c => {
expect(c.request.url).toEqual('https://www.google.com?q=puppies');
backend.resolveAllConnections();
async.done();
});
ObservableWrapper.subscribe(
http.get('https://www.google.com', new RequestOptions({search: params})),
res => {});
}));


it('should append string search params to url', inject([AsyncTestCompleter], async => {
ObservableWrapper.subscribe<MockConnection>(backend.connections, c => {
expect(c.request.url).toEqual('https://www.google.com?q=piggies');
backend.resolveAllConnections();
async.done();
});
ObservableWrapper.subscribe(
http.get('https://www.google.com', new RequestOptions({search: 'q=piggies'})),
res => {});
}));


it('should produce valid url when url already contains a query',
inject([AsyncTestCompleter], async => {
ObservableWrapper.subscribe<MockConnection>(backend.connections, c => {
expect(c.request.url).toEqual('https://www.google.com?q=angular&as_eq=1.x');
backend.resolveAllConnections();
async.done();
});
ObservableWrapper.subscribe(http.get('https://www.google.com?q=angular',
new RequestOptions({search: 'as_eq=1.x'})),
res => {});
}));
});
});
});
}
45 changes: 45 additions & 0 deletions modules/angular2/test/http/url_search_params_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,51 @@ export function main() {
expect(searchParams.toString()).toEqual("q=URLUtils.searchParams&topic=api&topic=webdev");
searchParams.delete("topic");
expect(searchParams.toString()).toEqual("q=URLUtils.searchParams");

// Test default constructor
expect(new URLSearchParams().toString()).toBe("");
});


it('should support map-like merging operation via setAll()', () => {
var mapA = new URLSearchParams('a=1&a=2&a=3&c=8');
var mapB = new URLSearchParams('a=4&a=5&a=6&b=7');
mapA.setAll(mapB);
expect(mapA.has('a')).toBe(true);
expect(mapA.has('b')).toBe(true);
expect(mapA.has('c')).toBe(true);
expect(mapA.getAll('a')).toEqual(['4']);
expect(mapA.getAll('b')).toEqual(['7']);
expect(mapA.getAll('c')).toEqual(['8']);
expect(mapA.toString()).toEqual('a=4&c=8&b=7');
});


it('should support multimap-like merging operation via appendAll()', () => {
var mapA = new URLSearchParams('a=1&a=2&a=3&c=8');
var mapB = new URLSearchParams('a=4&a=5&a=6&b=7');
mapA.appendAll(mapB);
expect(mapA.has('a')).toBe(true);
expect(mapA.has('b')).toBe(true);
expect(mapA.has('c')).toBe(true);
expect(mapA.getAll('a')).toEqual(['1', '2', '3', '4', '5', '6']);
expect(mapA.getAll('b')).toEqual(['7']);
expect(mapA.getAll('c')).toEqual(['8']);
expect(mapA.toString()).toEqual('a=1&a=2&a=3&a=4&a=5&a=6&c=8&b=7');
});


it('should support multimap-like merging operation via replaceAll()', () => {
var mapA = new URLSearchParams('a=1&a=2&a=3&c=8');
var mapB = new URLSearchParams('a=4&a=5&a=6&b=7');
mapA.replaceAll(mapB);
expect(mapA.has('a')).toBe(true);
expect(mapA.has('b')).toBe(true);
expect(mapA.has('c')).toBe(true);
expect(mapA.getAll('a')).toEqual(['4', '5', '6']);
expect(mapA.getAll('b')).toEqual(['7']);
expect(mapA.getAll('c')).toEqual(['8']);
expect(mapA.toString()).toEqual('a=4&a=5&a=6&c=8&b=7');
});
});
}