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

feat(ext/web): implement static Response.json #14566

Merged
merged 2 commits into from
May 13, 2022
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
119 changes: 87 additions & 32 deletions ext/fetch/23_response.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
const { isProxy } = Deno.core;
const webidl = window.__bootstrap.webidl;
const consoleInternal = window.__bootstrap.console;
const { HTTP_TAB_OR_SPACE, regexMatcher } = window.__bootstrap.infra;
const { HTTP_TAB_OR_SPACE, regexMatcher, serializeJSValueToJSONString } =
window.__bootstrap.infra;
const { extractBody, mixinBody } = window.__bootstrap.fetchBody;
const { getLocationHref } = window.__bootstrap.location;
const { extractMimeType } = window.__bootstrap.mimesniff;
Expand Down Expand Up @@ -157,6 +158,56 @@
return resp;
}

/**
* https://fetch.spec.whatwg.org#initialize-a-response
* @param {Response} response
* @param {ResponseInit} init
* @param {{ body: __bootstrap.fetchBody.InnerBody, contentType: string | null } | null} bodyWithType
lucacasonato marked this conversation as resolved.
Show resolved Hide resolved
*/
function initializeAResponse(response, init, bodyWithType) {
// 1.
if ((init.status < 200 || init.status > 599) && init.status != 101) {
throw new RangeError(
`The status provided (${init.status}) is not equal to 101 and outside the range [200, 599].`,
);
}

// 2.
if (
init.statusText &&
!RegExpPrototypeTest(REASON_PHRASE_RE, init.statusText)
) {
throw new TypeError("Status text is not valid.");
}

// 3.
response[_response].status = init.status;

// 4.
response[_response].statusMessage = init.statusText;

// 5.
/** @type {__bootstrap.headers.Headers} */
const headers = response[_headers];
if (init.headers) {
fillHeaders(headers, init.headers);
}

// 6.
if (bodyWithType !== null) {
if (nullBodyStatus(response[_response].status)) {
throw new TypeError(
"Response with null body status cannot have body",
);
}
const { body, contentType } = bodyWithType;
response[_response].body = body;
if (contentType !== null && !headers.has("content-type")) {
headers.append("Content-Type", contentType);
}
}
}

class Response {
get [_mimeType]() {
const values = getDecodeSplitHeader(
Expand Down Expand Up @@ -217,6 +268,32 @@
return response;
}

/**
* @param {any} data
* @param {ResponseInit} init
* @returns {Response}
*/
static json(data, init = {}) {
const prefix = "Failed to call 'Response.json'";
data = webidl.converters.any(data);
init = webidl.converters["ResponseInit_fast"](init, {
prefix,
context: "Argument 2",
});

const str = serializeJSValueToJSONString(data);
const res = extractBody(str);
res.contentType = "application/json";
const response = webidl.createBranded(Response);
response[_response] = newInnerResponse();
response[_headers] = headersFromHeaderList(
response[_response].headerList,
"response",
);
initializeAResponse(response, init, res);
return response;
}

/**
* @param {BodyInit | null} body
* @param {ResponseInit} init
Expand All @@ -232,40 +309,18 @@
context: "Argument 2",
});

if ((init.status < 200 || init.status > 599) && init.status != 101) {
throw new RangeError(
`The status provided (${init.status}) is not equal to 101 and outside the range [200, 599].`,
);
}

if (
init.statusText &&
!RegExpPrototypeTest(REASON_PHRASE_RE, init.statusText)
) {
throw new TypeError("Status text is not valid.");
}
this[_response] = newInnerResponse();
this[_headers] = headersFromHeaderList(
this[_response].headerList,
"response",
);

this[webidl.brand] = webidl.brand;
const response = newInnerResponse(init.status, init.statusText);
/** @type {InnerResponse} */
this[_response] = response;
/** @type {Headers} */
this[_headers] = headersFromHeaderList(response.headerList, "response");
if (init.headers) {
fillHeaders(this[_headers], init.headers);
}
let bodyWithType = null;
if (body !== null) {
if (nullBodyStatus(response.status)) {
throw new TypeError(
"Response with null body status cannot have body",
);
}
const res = extractBody(body);
response.body = res.body;
if (res.contentType !== null && !this[_headers].has("content-type")) {
this[_headers].append("Content-Type", res.contentType);
}
bodyWithType = extractBody(body);
}
initializeAResponse(this, init, bodyWithType);
this[webidl.brand] = webidl.brand;
}

/**
Expand Down
34 changes: 24 additions & 10 deletions ext/web/00_infra.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,24 @@
((window) => {
const core = Deno.core;
const {
Error,
RegExp,
ArrayPrototypeJoin,
ArrayPrototypeMap,
StringPrototypeCharCodeAt,
Error,
JSONStringify,
NumberPrototypeToString,
StringPrototypePadStart,
TypeError,
ArrayPrototypeJoin,
RegExp,
SafeArrayIterator,
String,
StringPrototypeCharAt,
StringPrototypeCharCodeAt,
StringPrototypeMatch,
StringPrototypeSlice,
String,
StringPrototypePadStart,
StringPrototypeReplace,
StringPrototypeToUpperCase,
StringPrototypeToLowerCase,
StringPrototypeSlice,
StringPrototypeSubstring,
StringPrototypeToLowerCase,
StringPrototypeToUpperCase,
TypeError,
} = window.__bootstrap.primordials;

const ASCII_DIGIT = ["\u0030-\u0039"];
Expand Down Expand Up @@ -294,6 +295,18 @@
}
}

/**
* @param {unknown} value
* @returns {string}
*/
function serializeJSValueToJSONString(value) {
const result = JSONStringify(value);
if (result === undefined) {
throw new TypeError("Value is not JSON serializable.");
}
return result;
}

window.__bootstrap.infra = {
collectSequenceOfCodepoints,
ASCII_DIGIT,
Expand All @@ -320,5 +333,6 @@
forgivingBase64Decode,
AssertionError,
assert,
serializeJSValueToJSONString,
};
})(globalThis);
1 change: 1 addition & 0 deletions ext/web/internal.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ declare namespace globalThis {
};
forgivingBase64Encode(data: Uint8Array): string;
forgivingBase64Decode(data: string): Uint8Array;
serializeJSValueToJSONString(value: unknown): string;
};

declare var domException: {
Expand Down
2 changes: 1 addition & 1 deletion test_util/wpt
4 changes: 3 additions & 1 deletion tools/wpt/expectation.json
Original file line number Diff line number Diff line change
Expand Up @@ -3215,7 +3215,9 @@
"response-consume-stream.any.html": true,
"response-consume-stream.any.worker.html": true,
"response-init-contenttype.any.html": true,
"response-init-contenttype.any.worker.html": true
"response-init-contenttype.any.worker.html": true,
"response-static-json.any.html": true,
"response-static-json.any.worker.html": true
},
"body": {
"formdata.any.html": [
Expand Down