summaryrefslogtreecommitdiff
path: root/js
diff options
context:
space:
mode:
Diffstat (limited to 'js')
-rw-r--r--js/dom_types.ts18
-rw-r--r--js/fetch.ts105
-rw-r--r--js/fetch_test.ts126
3 files changed, 195 insertions, 54 deletions
diff --git a/js/dom_types.ts b/js/dom_types.ts
index e1fa235bb..e3f75e488 100644
--- a/js/dom_types.ts
+++ b/js/dom_types.ts
@@ -13,7 +13,8 @@ See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
*******************************************************************************/
-export type HeadersInit = string[][] | Record<string, string>;
+export type HeadersInit = Headers | string[][] | Record<string, string>;
+export type URLSearchParamsInit = string | string[][] | Record<string, string>;
type BodyInit =
| Blob
| BufferSource
@@ -74,7 +75,7 @@ export interface ProgressEventInit extends EventInit {
total?: number;
}
-interface URLSearchParams {
+export interface URLSearchParams {
/**
* Appends a specified key/value pair as a new search parameter.
*/
@@ -101,7 +102,20 @@ interface URLSearchParams {
* If there were several values, delete the others.
*/
set(name: string, value: string): void;
+ /**
+ * 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.
+ */
sort(): void;
+ /**
+ * Returns a query string suitable for use in a URL.
+ */
+ toString(): string;
+ /**
+ * Iterates over each name-value pair in the query
+ * and invokes the given function.
+ */
forEach(
callbackfn: (value: string, key: string, parent: URLSearchParams) => void,
// tslint:disable-next-line:no-any
diff --git a/js/fetch.ts b/js/fetch.ts
index 48429ca19..9cf117ed8 100644
--- a/js/fetch.ts
+++ b/js/fetch.ts
@@ -22,85 +22,86 @@ import {
import { TextDecoder } from "./text_encoding";
import { DenoBlob } from "./blob";
-interface Header {
- name: string;
- value: string;
-}
-
+// ref: https://fetch.spec.whatwg.org/#dom-headers
export class DenoHeaders implements Headers {
- private readonly headerList: Header[] = [];
+ private headerMap: Map<string, string> = new Map();
constructor(init?: HeadersInit) {
- if (init) {
- this._fill(init);
- }
- }
-
- private _append(header: Header): void {
- // TODO(qti3e) Check header based on the fetch spec.
- this._appendToHeaderList(header);
- }
-
- private _appendToHeaderList(header: Header): void {
- const lowerCaseName = header.name.toLowerCase();
- for (let i = 0; i < this.headerList.length; ++i) {
- if (this.headerList[i].name.toLowerCase() === lowerCaseName) {
- header.name = this.headerList[i].name;
- }
+ if (arguments.length === 0 || init === undefined) {
+ return;
}
- this.headerList.push(header);
- }
- private _fill(init: HeadersInit): void {
- if (Array.isArray(init)) {
- for (let i = 0; i < init.length; ++i) {
- const header = init[i];
- if (header.length !== 2) {
+ if (init instanceof DenoHeaders) {
+ // init is the instance of Header
+ init.forEach((value: string, name: string) => {
+ this.headerMap.set(name, value);
+ });
+ } else if (Array.isArray(init)) {
+ // init is a sequence
+ init.forEach(item => {
+ if (item.length !== 2) {
throw new TypeError("Failed to construct 'Headers': Invalid value");
}
- this._append({
- name: header[0],
- value: header[1]
- });
- }
+ const [name, value] = this.normalizeParams(item[0], item[1]);
+ const v = this.headerMap.get(name);
+ const str = v ? `${v}, ${value}` : value;
+ this.headerMap.set(name, str);
+ });
+ } else if (Object.prototype.toString.call(init) === "[object Object]") {
+ // init is a object
+ const names = Object.keys(init);
+ names.forEach(name => {
+ const value = (init as Record<string, string>)[name];
+ const [newname, newvalue] = this.normalizeParams(name, value);
+ this.headerMap.set(newname, newvalue);
+ });
} else {
- for (const key in init) {
- this._append({
- name: key,
- value: init[key]
- });
- }
+ throw new TypeError("Failed to construct 'Headers': Invalid value");
}
}
+ private normalizeParams(name: string, value?: string): string[] {
+ name = String(name).toLowerCase();
+ value = String(value).trim();
+ return [name, value];
+ }
+
append(name: string, value: string): void {
- this._appendToHeaderList({ name, value });
+ const [newname, newvalue] = this.normalizeParams(name, value);
+ const v = this.headerMap.get(newname);
+ const str = v ? `${v}, ${newvalue}` : newvalue;
+ this.headerMap.set(newname, str);
}
delete(name: string): void {
- assert(false, "Implement me");
+ const [newname] = this.normalizeParams(name);
+ this.headerMap.delete(newname);
}
+
get(name: string): string | null {
- for (const header of this.headerList) {
- if (header.name.toLowerCase() === name.toLowerCase()) {
- return header.value;
- }
- }
- return null;
+ const [newname] = this.normalizeParams(name);
+ const value = this.headerMap.get(newname);
+ return value || null;
}
+
has(name: string): boolean {
- assert(false, "Implement me");
- return false;
+ const [newname] = this.normalizeParams(name);
+ return this.headerMap.has(newname);
}
+
set(name: string, value: string): void {
- assert(false, "Implement me");
+ const [newname, newvalue] = this.normalizeParams(name, value);
+ this.headerMap.set(newname, newvalue);
}
+
forEach(
callbackfn: (value: string, key: string, parent: Headers) => void,
// tslint:disable-next-line:no-any
thisArg?: any
): void {
- assert(false, "Implement me");
+ this.headerMap.forEach((value, name) => {
+ callbackfn(value, name, this);
+ });
}
}
diff --git a/js/fetch_test.ts b/js/fetch_test.ts
index 25d8c2ed0..9d76cb6ba 100644
--- a/js/fetch_test.ts
+++ b/js/fetch_test.ts
@@ -43,3 +43,129 @@ testPerm({ net: true }, async function fetchBlob() {
assertEqual(blob.type, headers.get("Content-Type"));
assertEqual(blob.size, Number(headers.get("Content-Length")));
});
+
+// Logic heavily copied from web-platform-tests, make
+// sure pass mostly header basic test
+/* tslint:disable-next-line:max-line-length */
+// ref: https://github.com/web-platform-tests/wpt/blob/7c50c216081d6ea3c9afe553ee7b64534020a1b2/fetch/api/headers/headers-basic.html
+/* tslint:disable:no-unused-expression */
+test(function newHeaderTest() {
+ new Headers();
+ new Headers(undefined);
+ new Headers({});
+ try {
+ new Headers(null);
+ } catch (e) {
+ assertEqual(e.message, "Failed to construct 'Headers': Invalid value");
+ }
+
+ try {
+ const init = [["a", "b", "c"]];
+ new Headers(init);
+ } catch (e) {
+ assertEqual(e.message, "Failed to construct 'Headers': Invalid value");
+ }
+});
+
+const headerDict = {
+ "name1": "value1",
+ "name2": "value2",
+ "name3": "value3",
+ "name4": undefined,
+ "Content-Type": "value4"
+};
+const headerSeq = [];
+for (const name in headerDict) {
+ headerSeq.push([name, headerDict[name]]);
+}
+
+test(function newHeaderWithSequence() {
+ const headers = new Headers(headerSeq);
+ for (const name in headerDict) {
+ assertEqual(headers.get(name), String(headerDict[name]));
+ }
+ assertEqual(headers.get("length"), null);
+});
+
+test(function newHeaderWithRecord() {
+ const headers = new Headers(headerDict);
+ for (const name in headerDict) {
+ assertEqual(headers.get(name), String(headerDict[name]));
+ }
+});
+
+test(function newHeaderWithHeadersInstance() {
+ const headers = new Headers(headerDict);
+ const headers2 = new Headers(headers);
+ for (const name in headerDict) {
+ assertEqual(headers2.get(name), String(headerDict[name]));
+ }
+});
+
+test(function headerAppendSuccess() {
+ const headers = new Headers();
+ for (const name in headerDict) {
+ headers.append(name, headerDict[name]);
+ assertEqual(headers.get(name), String(headerDict[name]));
+ }
+});
+
+test(function headerSetSuccess() {
+ const headers = new Headers();
+ for (const name in headerDict) {
+ headers.set(name, headerDict[name]);
+ assertEqual(headers.get(name), String(headerDict[name]));
+ }
+});
+
+test(function headerHasSuccess() {
+ const headers = new Headers(headerDict);
+ for (const name in headerDict) {
+ assert(headers.has(name), "headers has name " + name);
+ /* tslint:disable-next-line:max-line-length */
+ assert(!headers.has("nameNotInHeaders"), "headers do not have header: nameNotInHeaders");
+ }
+});
+
+test(function headerDeleteSuccess() {
+ const headers = new Headers(headerDict);
+ for (const name in headerDict) {
+ assert(headers.has(name), "headers have a header: " + name);
+ headers.delete(name);
+ assert(!headers.has(name), "headers do not have anymore a header: " + name);
+ }
+});
+
+test(function headerGetSuccess() {
+ const headers = new Headers(headerDict);
+ for (const name in headerDict) {
+ assertEqual(headers.get(name), String(headerDict[name]));
+ assertEqual(headers.get("nameNotInHeaders"), null);
+ }
+});
+
+const headerEntriesDict = {
+ "name1": "value1",
+ "Name2": "value2",
+ "name": "value3",
+ "content-Type": "value4",
+ "Content-Typ": "value5",
+ "Content-Types": "value6"
+};
+
+test(function headerForEachSuccess() {
+ const headers = new Headers(headerEntriesDict);
+ const keys = Object.keys(headerEntriesDict);
+ keys.forEach(key => {
+ const value = headerEntriesDict[key];
+ const newkey = key.toLowerCase();
+ headerEntriesDict[newkey] = value;
+ });
+ let callNum = 0;
+ headers.forEach((value, key, container) => {
+ assertEqual(headers, container);
+ assertEqual(value, headerEntriesDict[key]);
+ callNum++;
+ });
+ assertEqual(callNum, keys.length);
+});