Skip to content

Commit 8207e7e

Browse files
authored
Re-instate Response.json() static method (#263)
1 parent c413fa0 commit 8207e7e

File tree

2 files changed

+140
-167
lines changed

2 files changed

+140
-167
lines changed

builtins/web/fetch/request-response.cpp

Lines changed: 128 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -2380,164 +2380,137 @@ bool Response::redirect(JSContext *cx, unsigned argc, Value *vp) {
23802380
return true;
23812381
}
23822382

2383-
// namespace {
2384-
// bool callbackCalled;
2385-
// bool write_json_to_buf(const char16_t *str, uint32_t strlen, void *out) {
2386-
// callbackCalled = true;
2387-
// auto outstr = static_cast<std::u16string *>(out);
2388-
// outstr->append(str, strlen);
2389-
2390-
// return true;
2391-
// }
2392-
// } // namespace
2393-
2394-
// bool Response::json(JSContext *cx, unsigned argc, JS::Value *vp) {
2395-
// JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
2396-
// if (!args.requireAtLeast(cx, "json", 1)) {
2397-
// return false;
2398-
// }
2399-
// JS::RootedValue data(cx, args.get(0));
2400-
// JS::RootedValue init_val(cx, args.get(1));
2401-
// JS::RootedObject replacer(cx);
2402-
// JS::RootedValue space(cx);
2403-
2404-
// std::u16string out;
2405-
// // 1. Let bytes the result of running serialize a JavaScript value to JSON bytes on data.
2406-
// callbackCalled = false;
2407-
// if (!JS::ToJSON(cx, data, replacer, space, &write_json_to_buf, &out)) {
2408-
// return false;
2409-
// }
2410-
// if (!callbackCalled) {
2411-
// return api::throw_error(cx, api::Errors::WrongType, "Response.json", "data", "be a valid JSON
2412-
// value");
2413-
// }
2414-
// // 2. Let body be the result of extracting bytes.
2415-
2416-
// // 3. Let responseObject be the result of creating a Response object, given a new response,
2417-
// // "response", and this’s relevant Realm.
2418-
// JS::RootedValue status_val(cx);
2419-
// uint16_t status = 200;
2420-
2421-
// JS::RootedValue statusText_val(cx);
2422-
// JS::RootedString statusText(cx, JS_GetEmptyString(cx));
2423-
// JS::RootedValue headers_val(cx);
2424-
2425-
// if (init_val.isObject()) {
2426-
// JS::RootedObject init(cx, init_val.toObjectOrNull());
2427-
// if (!JS_GetProperty(cx, init, "status", &status_val) ||
2428-
// !JS_GetProperty(cx, init, "statusText", &statusText_val) ||
2429-
// !JS_GetProperty(cx, init, "headers", &headers_val)) {
2430-
// return false;
2431-
// }
2432-
2433-
// if (!status_val.isUndefined() && !JS::ToUint16(cx, status_val, &status)) {
2434-
// return false;
2435-
// }
2436-
2437-
// if (status == 204 || status == 205 || status == 304) {
2438-
// auto status_str = std::to_string(status);
2439-
// return api::throw_error(cx, FetchErrors::NonBodyResponseWithBody, "Response.json",
2440-
// status_str.c_str());
2441-
// }
2442-
2443-
// if (!statusText_val.isUndefined() && !(statusText = JS::ToString(cx, statusText_val))) {
2444-
// return false;
2445-
// }
2446-
2447-
// } else if (!init_val.isNullOrUndefined()) {
2448-
// return api::throw_error(cx, FetchErrors::InvalidInitArg, "Response.json");
2449-
// }
2450-
2451-
// auto response_handle_res = host_api::HttpResp::make();
2452-
// if (auto *err = response_handle_res.to_err()) {
2453-
// HANDLE_ERROR(cx, *err);
2454-
// return false;
2455-
// }
2456-
2457-
// auto response_handle = response_handle_res.unwrap();
2458-
// if (!response_handle.is_valid()) {
2459-
// return false;
2460-
// }
2461-
2462-
// auto make_res = host_api::HttpBody::make(response_handle);
2463-
// if (auto *err = make_res.to_err()) {
2464-
// HANDLE_ERROR(cx, *err);
2465-
// return false;
2466-
// }
2467-
2468-
// auto body = make_res.unwrap();
2469-
// JS::RootedString string(cx, JS_NewUCStringCopyN(cx, out.c_str(), out.length()));
2470-
// auto stringChars = core::encode(cx, string);
2471-
2472-
// auto write_res =
2473-
// body.write_all(reinterpret_cast<uint8_t *>(stringChars.begin()), stringChars.len);
2474-
// if (auto *err = write_res.to_err()) {
2475-
// HANDLE_ERROR(cx, *err);
2476-
// return false;
2477-
// }
2478-
// JS::RootedObject response_instance(cx, JS_NewObjectWithGivenProto(cx, &Response::class_,
2479-
// Response::proto_obj));
2480-
// if (!response_instance) {
2481-
// return false;
2482-
// }
2483-
// JS::RootedObject response(cx, create(cx, response_instance, response_handle,
2484-
// body, false));
2485-
// if (!response) {
2486-
// return false;
2487-
// }
2488-
2489-
// // Set `this`’s `response`’s `status` to `init`["status"].
2490-
// auto set_res = response_handle.set_status(status);
2491-
// if (auto *err = set_res.to_err()) {
2492-
// HANDLE_ERROR(cx, *err);
2493-
// return false;
2494-
// }
2495-
// // To ensure that we really have the same status value as the host,
2496-
// // we always read it back here.
2497-
// auto get_res = response_handle.get_status();
2498-
// if (auto *err = get_res.to_err()) {
2499-
// HANDLE_ERROR(cx, *err);
2500-
// return false;
2501-
// }
2502-
// status = get_res.unwrap();
2503-
2504-
// JS::SetReservedSlot(response, static_cast<uint32_t>(Slots::Status), JS::Int32Value(status));
2505-
2506-
// // Set `this`’s `response`’s `status message` to `init`["statusText"].
2507-
// JS::SetReservedSlot(response, static_cast<uint32_t>(Slots::StatusMessage),
2508-
// JS::StringValue(statusText));
2509-
2510-
// // If `init`["headers"] `exists`, then `fill` `this`’s `headers` with
2511-
// // `init`["headers"].
2512-
// JS::RootedObject headers(cx);
2513-
// JS::RootedObject headersInstance(
2514-
// cx, JS_NewObjectWithGivenProto(cx, &Headers::class_, Headers::proto_obj));
2515-
// if (!headersInstance)
2516-
// return false;
2517-
2518-
// headers = Headers::create(cx, headersInstance, Headers::Mode::ProxyToResponse,
2519-
// response, headers_val);
2520-
// if (!headers) {
2521-
// return false;
2522-
// }
2523-
// // 4. Perform initialize a response given responseObject, init, and (body, "application/json").
2524-
// if (!Headers::maybe_add(cx, headers, "content-type", "application/json")) {
2525-
// return false;
2526-
// }
2527-
// JS::SetReservedSlot(response, static_cast<uint32_t>(Slots::Headers),
2528-
// JS::ObjectValue(*headers)); JS::SetReservedSlot(response,
2529-
// static_cast<uint32_t>(Slots::Redirected), JS::FalseValue()); JS::SetReservedSlot(response,
2530-
// static_cast<uint32_t>(Slots::HasBody), JS::TrueValue()); RequestOrResponse::set_url(response,
2531-
// JS_GetEmptyStringValue(cx));
2532-
2533-
// // 5. Return responseObject.
2534-
// args.rval().setObjectOrNull(response);
2535-
// return true;
2536-
// }
2383+
struct JsonCallback {
2384+
std::u16string output;
2385+
bool called = false;
2386+
2387+
static bool write_to_buffer(const char16_t *str, uint32_t strlen, void *data) {
2388+
auto *callback = static_cast<JsonCallback*>(data);
2389+
callback->called = true;
2390+
callback->output.append(str, strlen);
2391+
return true;
2392+
}
2393+
};
2394+
2395+
2396+
bool Response::json(JSContext *cx, unsigned argc, JS::Value *vp) {
2397+
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
2398+
if (!args.requireAtLeast(cx, "json", 1)) {
2399+
return false;
2400+
}
2401+
JS::RootedValue data(cx, args.get(0));
2402+
JS::RootedValue init_val(cx, args.get(1));
2403+
JS::RootedObject replacer(cx);
2404+
JS::RootedValue space(cx);
2405+
2406+
// 1. Serialize the data to JSON
2407+
JsonCallback json_callback;
2408+
if (!JS::ToJSON(cx, data, replacer, space, &JsonCallback::write_to_buffer, &json_callback)) {
2409+
return false;
2410+
}
2411+
if (!json_callback.called) {
2412+
// undefined never invokes the callback, so we throw an error
2413+
return api::throw_error(cx, api::Errors::TypeError, "Response.json", "data", "be a valid JSON value");
2414+
}
2415+
// 2. Parse init object to get status, statusText, and headers
2416+
JS::RootedValue status_val(cx);
2417+
uint16_t status = 200;
2418+
2419+
JS::RootedValue statusText_val(cx);
2420+
JS::RootedString statusText(cx, JS_GetEmptyString(cx));
2421+
JS::RootedValue headers_val(cx);
2422+
2423+
if (init_val.isObject()) {
2424+
JS::RootedObject init(cx, init_val.toObjectOrNull());
2425+
if (!JS_GetProperty(cx, init, "status", &status_val) ||
2426+
!JS_GetProperty(cx, init, "statusText", &statusText_val) ||
2427+
!JS_GetProperty(cx, init, "headers", &headers_val)) {
2428+
return false;
2429+
}
2430+
2431+
if (!status_val.isUndefined() && !JS::ToUint16(cx, status_val, &status)) {
2432+
return false;
2433+
}
2434+
2435+
if (status == 204 || status == 205 || status == 304) {
2436+
auto status_str = std::to_string(status);
2437+
return api::throw_error(cx, FetchErrors::NonBodyResponseWithBody, status_str.c_str());
2438+
}
2439+
2440+
if (!statusText_val.isUndefined() && !(statusText = JS::ToString(cx, statusText_val))) {
2441+
return false;
2442+
}
2443+
2444+
} else if (!init_val.isNullOrUndefined()) {
2445+
return api::throw_error(cx, FetchErrors::InvalidInitArg, "Response.json");
2446+
}
2447+
2448+
2449+
// 3. Create the Response JS object first
2450+
JS::RootedObject response_obj(cx, Response::create(cx));
2451+
if (!response_obj) {
2452+
return false;
2453+
}
2454+
2455+
// Convert JSON string to a proper body value
2456+
JS::RootedString json_string(cx, JS_NewUCStringCopyN(cx, json_callback.output.c_str(), json_callback.output.length()));
2457+
if (!json_string) {
2458+
return false;
2459+
}
2460+
JS::RootedValue body_val(cx, JS::StringValue(json_string));
2461+
2462+
// Create init object with headers that include content-type
2463+
JS::RootedObject init_obj(cx, JS_NewPlainObject(cx));
2464+
if (!init_obj) {
2465+
return false;
2466+
}
2467+
2468+
// Set status
2469+
if (!JS_DefineProperty(cx, init_obj, "status", status_val, JSPROP_ENUMERATE)) {
2470+
return false;
2471+
}
2472+
2473+
// Set statusText
2474+
if (!JS_DefineProperty(cx, init_obj, "statusText", statusText_val, JSPROP_ENUMERATE)) {
2475+
return false;
2476+
}
2477+
2478+
// Create headers object and ensure content-type is set
2479+
JS::RootedObject headers_obj(cx);
2480+
if (!headers_val.isUndefined()) {
2481+
headers_obj = Headers::create(cx, headers_val, Headers::HeadersGuard::Response);
2482+
} else {
2483+
headers_obj = Headers::create(cx, Headers::HeadersGuard::Response);
2484+
}
2485+
if (!headers_obj) {
2486+
return false;
2487+
}
2488+
2489+
// Set content-type header if not already present
2490+
if (!Headers::set_valid_if_undefined(cx, headers_obj, "Content-Type", "application/json")) {
2491+
return false;
2492+
}
2493+
2494+
JS::RootedValue headers_obj_val(cx, JS::ObjectValue(*headers_obj));
2495+
if (!JS_DefineProperty(cx, init_obj, "headers", headers_obj_val, JSPROP_ENUMERATE)) {
2496+
return false;
2497+
}
2498+
2499+
JS::RootedValue init_obj_val(cx, JS::ObjectValue(*init_obj));
2500+
2501+
// 5. Use the existing initialize method
2502+
if (!Response::initialize(cx, response_obj, body_val, init_obj_val)) {
2503+
return false;
2504+
}
2505+
2506+
args.rval().setObject(*response_obj);
2507+
return true;
2508+
}
2509+
25372510

25382511
const JSFunctionSpec Response::static_methods[] = {
25392512
JS_FN("redirect", redirect, 1, JSPROP_ENUMERATE),
2540-
// JS_FN("json", json, 1, JSPROP_ENUMERATE),
2513+
JS_FN("json", json, 1, JSPROP_ENUMERATE),
25412514
JS_FS_END,
25422515
};
25432516

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
{
22
"Check response returned by static json() with init undefined": {
3-
"status": "FAIL"
3+
"status": "PASS"
44
},
55
"Check response returned by static json() with init {\"status\":400}": {
6-
"status": "FAIL"
6+
"status": "PASS"
77
},
88
"Check response returned by static json() with init {\"statusText\":\"foo\"}": {
9-
"status": "FAIL"
9+
"status": "PASS"
1010
},
1111
"Check response returned by static json() with init {\"headers\":{}}": {
12-
"status": "FAIL"
12+
"status": "PASS"
1313
},
1414
"Check response returned by static json() with init {\"headers\":{\"content-type\":\"foo/bar\"}}": {
15-
"status": "FAIL"
15+
"status": "PASS"
1616
},
1717
"Check response returned by static json() with init {\"headers\":{\"x-foo\":\"bar\"}}": {
18-
"status": "FAIL"
18+
"status": "PASS"
1919
},
2020
"Throws TypeError when calling static json() with a status of 204": {
2121
"status": "PASS"
@@ -27,7 +27,7 @@
2727
"status": "PASS"
2828
},
2929
"Check static json() encodes JSON objects correctly": {
30-
"status": "FAIL"
30+
"status": "PASS"
3131
},
3232
"Check static json() throws when data is not encodable": {
3333
"status": "PASS"
@@ -36,15 +36,15 @@
3636
"status": "PASS"
3737
},
3838
"Check static json() propagates JSON serializer errors": {
39-
"status": "FAIL"
39+
"status": "PASS"
4040
},
4141
"Check response returned by static json() with input 𝌆": {
42-
"status": "FAIL"
42+
"status": "PASS"
4343
},
4444
"Check response returned by static json() with input U+df06U+d834": {
45-
"status": "FAIL"
45+
"status": "PASS"
4646
},
4747
"Check response returned by static json() with input U+dead": {
48-
"status": "FAIL"
48+
"status": "PASS"
4949
}
50-
}
50+
}

0 commit comments

Comments
 (0)