Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Web API: URLSearchParams #1049

Merged
merged 10 commits into from Oct 21, 2018
3 changes: 2 additions & 1 deletion BUILD.gn
Expand Up @@ -107,6 +107,7 @@ ts_sources = [
"js/trace.ts",
"js/truncate.ts",
"js/types.ts",
"js/urlsearchparams.ts",
Copy link
Member

Choose a reason for hiding this comment

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

url_search_params.ts

"js/util.ts",
"js/v8_source_maps.ts",
"js/write_file.ts",
Expand Down Expand Up @@ -142,7 +143,7 @@ rust_executable("hyper_hello") {
source_root = "tools/hyper_hello.rs"
extern = [
"$rust_build:hyper",
"$rust_build:ring"
"$rust_build:ring",
]
}

Expand Down
3 changes: 3 additions & 0 deletions js/globals.ts
Expand Up @@ -6,6 +6,7 @@ import { globalEval } from "./global_eval";
import { libdeno } from "./libdeno";
import * as textEncoding from "./text_encoding";
import * as timers from "./timers";
import * as urlsearchparams from "./urlsearchparams";

// During the build process, augmentations to the variable `window` in this
// file are tracked and created as part of default library that is built into
Expand Down Expand Up @@ -33,6 +34,8 @@ window.TextDecoder = textEncoding.TextDecoder;
window.atob = textEncoding.atob;
window.btoa = textEncoding.btoa;

window.URLSearchParams = urlsearchparams.URLSearchParams;

window.fetch = fetch_.fetch;

window.Headers = fetch_.DenoHeaders;
Expand Down
1 change: 1 addition & 0 deletions js/unit_tests.ts
Expand Up @@ -28,4 +28,5 @@ import "./truncate_test.ts";
import "./v8_source_maps_test.ts";
import "../website/app_test.js";
import "./metrics_test.ts";
import "./urlsearchparams_test.ts";
import "./util_test.ts";
207 changes: 207 additions & 0 deletions js/urlsearchparams.ts
@@ -0,0 +1,207 @@
// Copyright 2018 the Deno authors. All rights reserved. MIT license.
export class URLSearchParams {
private params: Array<[string, string]> = [];

public constructor(init: string | string[][] | Record<string, string> = "") {
if (typeof init === "string") {
// Overload: USVString
// If init is a string and starts with U+003F (?),
// remove the first code point from init.
if (init.charCodeAt(0) === 0x003f) {
init = init.slice(1);
}

for (const pair of init.split("&")) {
// Empty params are ignored
if (pair.length === 0) {
continue;
}
const position = pair.indexOf("=");
const name = pair.slice(0, position === -1 ? pair.length : position);
const value = pair.slice(name.length + 1);
this.append(decodeURIComponent(name), decodeURIComponent(value));
}
} else if (Array.isArray(init)) {
// Overload: sequence<sequence<USVString>>
for (const tuple of init) {
this.append(tuple[0], tuple[1]);
}
} else if (Object(init) === init) {
// Overload: record<USVString, USVString>
for (const key of Object.keys(init)) {
this.append(key, init[key]);
}
}
}

/** Appends a specified key/value pair as a new search parameter.
*
* searchParams.append('name', 'first');
* searchParams.append('name', 'second');
*/
public append(name: string, value: string): void {
this.params.push([name, value]);
}

/** Deletes the given search parameter and its associated value,
* from the list of all search parameters.
*
* searchParams.delete('name');
*/
public delete(name: string): void {
let i = 0;
while (i < this.params.length) {
if (this.params[i][0] === name) {
this.params.splice(i, 1);
} else {
i++;
}
}
}

/** Returns all the values associated with a given search parameter
* as an array.
*
* searchParams.getAll('name');
*/
public getAll(name: string): string[] {
const values = [];
for (const entry of this.params) {
if (entry[0] === name) {
values.push(entry[1]);
}
}

return values;
}

/** Returns the first value associated to the given search parameter.
*
* searchParams.get('name');
*/
public get(name: string): string | null {
for (const entry of this.params) {
if (entry[0] === name) {
return entry[1];
}
}

return null;
}

/** Returns a Boolean that indicates whether a parameter with the
* specified name exists.
*
* searchParams.has('name');
*/
public has(name: string): boolean {
return this.params.some(entry => entry[0] === name);
}

/** Sets the value associated with a given search parameter to the
* given value. If there were several matching values, this method
* deletes the others. If the search parameter doesn't exist, this
* method creates it.
*
* searchParams.set('name', 'value');
*/
public set(name: string, value: string): void {
this.delete(name);
this.append(name, value);
}

/** Sort all key/value pairs contained in this object in place and
* return undefined. The sort order is according to Unicode code
* points of the keys.
*
* searchParams.sort();
*/
public sort(): void {
this.params = this.params.sort(
(a, b) => (a[0] === b[0] ? 0 : a[0] > b[0] ? 1 : -1)
);
}

/** Calls a function for each element contained in this object in
* place and return undefined. Optionally accepts an object to use
* as this when executing callback as second argument.
*
* searchParams.forEach((value, key, parent) => {
* console.log(value, key, parent);
* });
*
*/
public forEach(
callbackfn: (value: string, key: string, parent: URLSearchParams) => void,
// tslint:disable-next-line:no-any
thisArg?: any
) {
if (typeof thisArg !== "undefined") {
callbackfn = callbackfn.bind(thisArg);
}
for (const [key, value] of this.entries()) {
callbackfn(value, key, this);
}
}

/** Returns an iterator allowing to go through all keys contained
* in this object.
*
* for (const key of searchParams.keys()) {
* console.log(key);
* }
*/
public *keys(): Iterable<string> {
for (const entry of this.params) {
yield entry[0];
}
}

/** Returns an iterator allowing to go through all values contained
* in this object.
*
* for (const value of searchParams.values()) {
* console.log(value);
* }
*/
public *values(): Iterable<string> {
for (const entry of this.params) {
yield entry[1];
}
}

/** Returns an iterator allowing to go through all key/value
* pairs contained in this object.
*
* for (const [key, value] of searchParams.entries()) {
* console.log(key, value);
* }
*/
public *entries(): Iterable<[string, string]> {
yield* this.params;
}

/** Returns an iterator allowing to go through all key/value
* pairs contained in this object.
*
* for (const [key, value] of searchParams[Symbol.iterator]()) {
* console.log(key, value);
* }
*/
public *[Symbol.iterator](): Iterable<[string, string]> {
yield* this.params;
}

/** Returns a query string suitable for use in a URL.
*
* searchParams.toString();
*/
public toString(): string {
return this.params
.map(
tuple =>
`${encodeURIComponent(tuple[0])}=${encodeURIComponent(tuple[1])}`
)
.join("&");
}
}
114 changes: 114 additions & 0 deletions js/urlsearchparams_test.ts
@@ -0,0 +1,114 @@
// Copyright 2018 the Deno authors. All rights reserved. MIT license.
import { test, assert, assertEqual } from "./test_util.ts";

test(function initString() {
Copy link
Member

Choose a reason for hiding this comment

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

Can you prefix call of the test function names with “url” or “urlSearchParams”
This is so we can easily filter

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, fixed in next commit 👍

const init = "c=4&a=2&b=3&%C3%A1=1";
const searchParams = new URLSearchParams(init);
assert(
init === searchParams.toString(),
"The init query string does not match"
);
});

test(function initIterable() {
const init = [["a", "54"], ["b", "true"]];
const searchParams = new URLSearchParams(init);
assertEqual(searchParams.toString(), "a=54&b=true");
});

test(function initRecord() {
const init = { a: "54", b: "true" };
const searchParams = new URLSearchParams(init);
assertEqual(searchParams.toString(), "a=54&b=true");
});

test(function appendSuccess() {
const searchParams = new URLSearchParams();
searchParams.append("a", "true");
assertEqual(searchParams.toString(), "a=true");
});

test(function deleteSuccess() {
const init = "a=54&b=true";
const searchParams = new URLSearchParams(init);
searchParams.delete("b");
assertEqual(searchParams.toString(), "a=54");
});

test(function getAllSuccess() {
const init = "a=54&b=true&a=true";
const searchParams = new URLSearchParams(init);
assertEqual(searchParams.getAll("a"), ["54", "true"]);
assertEqual(searchParams.getAll("b"), ["true"]);
assertEqual(searchParams.getAll("c"), []);
});

test(function getSuccess() {
const init = "a=54&b=true&a=true";
const searchParams = new URLSearchParams(init);
assertEqual(searchParams.get("a"), "54");
assertEqual(searchParams.get("b"), "true");
assertEqual(searchParams.get("c"), null);
});

test(function hasSuccess() {
const init = "a=54&b=true&a=true";
const searchParams = new URLSearchParams(init);
assert(searchParams.has("a"));
assert(searchParams.has("b"));
assert(!searchParams.has("c"));
});

test(function setSuccess() {
const init = "a=54&b=true&a=true";
const searchParams = new URLSearchParams(init);
searchParams.set("a", "false");
assertEqual(searchParams.toString(), "b=true&a=false");
});

test(function sortSuccess() {
const init = "c=4&a=2&b=3&a=1";
const searchParams = new URLSearchParams(init);
searchParams.sort();
assertEqual(searchParams.toString(), "a=2&a=1&b=3&c=4");
});

test(function forEachSuccess() {
const init = [["a", "54"], ["b", "true"]];
const searchParams = new URLSearchParams(init);
let callNum = 0;
searchParams.forEach((value, key, parent) => {
assertEqual(searchParams, parent);
assertEqual(value, init[callNum][1]);
assertEqual(key, init[callNum][0]);
callNum++;
});
assertEqual(callNum, init.length);
});

test(function missingName() {
const init = "=4";
const searchParams = new URLSearchParams(init);
assertEqual(searchParams.get(""), "4");
assertEqual(searchParams.toString(), "=4");
});

test(function missingValue() {
const init = "4=";
const searchParams = new URLSearchParams(init);
assertEqual(searchParams.get("4"), "");
assertEqual(searchParams.toString(), "4=");
});

test(function missingEqualSign() {
const init = "4";
const searchParams = new URLSearchParams(init);
assertEqual(searchParams.get("4"), "");
assertEqual(searchParams.toString(), "4=");
});

test(function missingPair() {
const init = "c=4&&a=54&";
const searchParams = new URLSearchParams(init);
assertEqual(searchParams.toString(), "c=4&a=54");
});
6 changes: 1 addition & 5 deletions tools/http_benchmark.py
Expand Up @@ -33,11 +33,7 @@ def http_benchmark(deno_exe, hyper_hello_exe):
node_rps = node_http_benchmark()
hyper_http_rps = hyper_http_benchmark(hyper_hello_exe)

return {
"deno": deno_rps,
"node": node_rps,
"hyper": hyper_http_rps
}
return {"deno": deno_rps, "node": node_rps, "hyper": hyper_http_rps}


def run(server_cmd):
Expand Down