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

Change DenoHeaders to be more idiomatic TypeScript #1062

Merged
merged 2 commits into from
Oct 23, 2018
Merged
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
26 changes: 23 additions & 3 deletions js/dom_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
*******************************************************************************/

export type HeadersInit = Headers | string[][] | Record<string, string>;
export type HeadersInit =
| Headers
| Array<[string, string]>
| Record<string, string>;
export type URLSearchParamsInit = string | string[][] | Record<string, string>;
type BodyInit =
| Blob
Expand All @@ -36,6 +39,18 @@ export type EventListenerOrEventListenerObject =
| EventListener
| EventListenerObject;

export interface DomIterable<K, V> {
keys(): IterableIterator<K>;
values(): IterableIterator<V>;
entries(): IterableIterator<[K, V]>;
[Symbol.iterator](): IterableIterator<[K, V]>;
forEach(
callback: (value: V, key: K, parent: this) => void,
// tslint:disable-next-line:no-any
thisArg?: any
): void;
}

interface Element {
// TODO
}
Expand Down Expand Up @@ -289,7 +304,7 @@ interface Body {
text(): Promise<string>;
}

export interface Headers {
export interface Headers extends DomIterable<string, string> {
/** Appends a new value onto an existing header inside a `Headers` object, or
* adds the header if it does not already exist.
*/
Expand Down Expand Up @@ -322,7 +337,7 @@ export interface Headers {
*/
values(): IterableIterator<string>;
forEach(
callbackfn: (value: string, key: string, parent: Headers) => void,
callbackfn: (value: string, key: string, parent: this) => void,
// tslint:disable-next-line:no-any
thisArg?: any
): void;
Expand All @@ -332,6 +347,11 @@ export interface Headers {
[Symbol.iterator](): IterableIterator<[string, string]>;
}

export interface HeadersConstructor {
new (init?: HeadersInit): Headers;
prototype: Headers;
}

type RequestCache =
| "default"
| "no-store"
Expand Down
140 changes: 60 additions & 80 deletions js/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,126 +5,106 @@ import {
createResolvable,
Resolvable,
typedArrayToArrayBuffer,
notImplemented,
CreateIterableIterator
notImplemented
} from "./util";
import * as flatbuffers from "./flatbuffers";
import { sendAsync } from "./dispatch";
import * as msg from "gen/msg_generated";
import * as domTypes from "./dom_types";
import { TextDecoder } from "./text_encoding";
import { DenoBlob } from "./blob";
import { DomIterableMixin } from "./mixins/dom_iterable";

// ref: https://fetch.spec.whatwg.org/#dom-headers
export class DenoHeaders implements domTypes.Headers {
private headerMap: Map<string, string> = new Map();
// tslint:disable-next-line:no-any
function isHeaders(value: any): value is domTypes.Headers {
return value instanceof Headers;
}

constructor(init?: domTypes.HeadersInit) {
if (arguments.length === 0 || init === undefined) {
return;
}
const headerMap = Symbol("header map");

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");
}
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 {
throw new TypeError("Failed to construct 'Headers': Invalid value");
}
}
// ref: https://fetch.spec.whatwg.org/#dom-headers
class HeadersBase {
private [headerMap]: Map<string, string>;

private normalizeParams(name: string, value?: string): string[] {
private _normalizeParams(name: string, value?: string): string[] {
name = String(name).toLowerCase();
value = String(value).trim();
return [name, value];
}

constructor(init?: domTypes.HeadersInit) {
if (init === null) {
throw new TypeError(
"Failed to construct 'Headers'; The provided value was not valid"
);
} else if (isHeaders(init)) {
this[headerMap] = new Map(init);
} else {
this[headerMap] = new Map();
if (Array.isArray(init)) {
for (const [rawName, rawValue] of init) {
const [name, value] = this._normalizeParams(rawName, rawValue);
const existingValue = this[headerMap].get(name);
this[headerMap].set(
name,
existingValue ? `${existingValue}, ${value}` : value
);
}
} else if (init) {
const names = Object.keys(init);
for (const rawName of names) {
const rawValue = init[rawName];
const [name, value] = this._normalizeParams(rawName, rawValue);
this[headerMap].set(name, value);
}
}
}
}

append(name: string, value: string): void {
const [newname, newvalue] = this.normalizeParams(name, value);
const v = this.headerMap.get(newname);
const [newname, newvalue] = this._normalizeParams(name, value);
const v = this[headerMap].get(newname);
const str = v ? `${v}, ${newvalue}` : newvalue;
this.headerMap.set(newname, str);
this[headerMap].set(newname, str);
}

delete(name: string): void {
const [newname] = this.normalizeParams(name);
this.headerMap.delete(newname);
}

entries(): IterableIterator<[string, string]> {
const iterators = this.headerMap.entries();
return new CreateIterableIterator(iterators);
const [newname] = this._normalizeParams(name);
this[headerMap].delete(newname);
}

get(name: string): string | null {
const [newname] = this.normalizeParams(name);
const value = this.headerMap.get(newname);
const [newname] = this._normalizeParams(name);
const value = this[headerMap].get(newname);
return value || null;
}

has(name: string): boolean {
const [newname] = this.normalizeParams(name);
return this.headerMap.has(newname);
}

keys(): IterableIterator<string> {
const iterators = this.headerMap.keys();
return new CreateIterableIterator(iterators);
const [newname] = this._normalizeParams(name);
return this[headerMap].has(newname);
}

set(name: string, value: string): void {
const [newname, newvalue] = this.normalizeParams(name, value);
this.headerMap.set(newname, newvalue);
}

values(): IterableIterator<string> {
const iterators = this.headerMap.values();
return new CreateIterableIterator(iterators);
}

forEach(
callbackfn: (value: string, key: string, parent: domTypes.Headers) => void,
// tslint:disable-next-line:no-any
thisArg?: any
): void {
this.headerMap.forEach((value, name) => {
callbackfn(value, name, this);
});
}

[Symbol.iterator](): IterableIterator<[string, string]> {
return this.entries();
const [newname, newvalue] = this._normalizeParams(name, value);
this[headerMap].set(newname, newvalue);
}
}

// @internal
// tslint:disable-next-line:variable-name
export const Headers = DomIterableMixin<string, string, typeof HeadersBase>(
HeadersBase,
headerMap
);

class FetchResponse implements domTypes.Response {
readonly url: string = "";
body: null;
bodyUsed = false; // TODO
statusText = "FIXME"; // TODO
readonly type = "basic"; // TODO
redirected = false; // TODO
headers: DenoHeaders;
headers: domTypes.Headers;
readonly trailer: Promise<domTypes.Headers>;
//private bodyChunks: Uint8Array[] = [];
private first = true;
Expand All @@ -138,7 +118,7 @@ class FetchResponse implements domTypes.Response {
) {
this.bodyWaiter = createResolvable();
this.trailer = createResolvable();
this.headers = new DenoHeaders(headersList);
this.headers = new Headers(headersList);
this.bodyData = body_;
setTimeout(() => {
this.bodyWaiter.resolve(body_);
Expand Down
25 changes: 4 additions & 21 deletions js/fetch_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,6 @@ testPerm({ net: true }, async function fetchHeaders() {
assert(headers.get("Server").startsWith("SimpleHTTP"));
});

test(async function headersAppend() {
let err;
try {
const headers = new Headers([["foo", "bar", "baz"]]);
} catch (e) {
err = e;
}
assert(err instanceof TypeError);
});

testPerm({ net: true }, async function fetchBlob() {
const response = await fetch("http://localhost:4545/package.json");
const headers = response.headers;
Expand Down Expand Up @@ -69,14 +59,10 @@ test(function newHeaderTest() {
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");
assertEqual(
e.message,
"Failed to construct 'Headers'; The provided value was not valid"
);
}
});

Expand Down Expand Up @@ -163,7 +149,6 @@ test(function headerGetSuccess() {
test(function headerEntriesSuccess() {
const headers = new Headers(headerDict);
const iterators = headers.entries();
assertEqual(Object.prototype.toString.call(iterators), "[object Iterator]");
for (const it of iterators) {
const key = it[0];
const value = it[1];
Expand All @@ -175,7 +160,6 @@ test(function headerEntriesSuccess() {
test(function headerKeysSuccess() {
const headers = new Headers(headerDict);
const iterators = headers.keys();
assertEqual(Object.prototype.toString.call(iterators), "[object Iterator]");
for (const it of iterators) {
assert(headers.has(it));
}
Expand All @@ -189,7 +173,6 @@ test(function headerValuesSuccess() {
for (const pair of entries) {
values.push(pair[1]);
}
assertEqual(Object.prototype.toString.call(iterators), "[object Iterator]");
for (const it of iterators) {
assert(values.includes(it));
}
Expand Down
5 changes: 4 additions & 1 deletion js/globals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { libdeno } from "./libdeno";
import * as textEncoding from "./text_encoding";
import * as timers from "./timers";
import * as urlSearchParams from "./url_search_params";
import * as domTypes from "./dom_types";

// 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 @@ -38,5 +39,7 @@ window.URLSearchParams = urlSearchParams.URLSearchParams;

window.fetch = fetch_.fetch;

window.Headers = fetch_.DenoHeaders;
// using the `as` keyword to mask the internal types when generating the
// runtime library
window.Headers = fetch_.Headers as domTypes.HeadersConstructor;
window.Blob = blob.DenoBlob;
Loading