aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Chris Toshok <toshok@gmail.com> 2023-10-16 15:57:16 -0700
committerGravatar GitHub <noreply@github.com> 2023-10-16 15:57:16 -0700
commitc5354951bacb9efee26683d1be83177443e0d784 (patch)
treea2fcb4ca40ac2e3db359295b867d5da2175c6be4
parentf1658e2e58acb1fcdb3181e8238c50b961b822f6 (diff)
downloadbun-c5354951bacb9efee26683d1be83177443e0d784.tar.gz
bun-c5354951bacb9efee26683d1be83177443e0d784.tar.zst
bun-c5354951bacb9efee26683d1be83177443e0d784.zip
Fix `Response.statusText` (#6151)
-rw-r--r--src/bun.js/api/html_rewriter.zig28
-rw-r--r--src/bun.js/api/server.zig6
-rw-r--r--src/bun.js/bindings/bindings.cpp4
-rw-r--r--src/bun.js/bindings/bindings.zig3
-rw-r--r--src/bun.js/webcore/body.zig156
-rw-r--r--src/bun.js/webcore/request.zig7
-rw-r--r--src/bun.js/webcore/response.zig262
-rw-r--r--src/js/builtins/BunBuiltinNames.h1
-rw-r--r--test/js/bun/util/inspect.test.js8
-rw-r--r--test/js/web/fetch/response.test.ts32
10 files changed, 269 insertions, 238 deletions
diff --git a/src/bun.js/api/html_rewriter.zig b/src/bun.js/api/html_rewriter.zig
index 1bda47512..1f2366ad9 100644
--- a/src/bun.js/api/html_rewriter.zig
+++ b/src/bun.js/api/html_rewriter.zig
@@ -384,10 +384,10 @@ pub const HTMLRewriter = struct {
result.* = Response{
.allocator = bun.default_allocator,
+ .init = .{
+ .status_code = 200,
+ },
.body = .{
- .init = .{
- .status_code = 200,
- },
.value = .{
.Locked = .{
.global = global,
@@ -397,16 +397,16 @@ pub const HTMLRewriter = struct {
},
};
- result.body.init.method = original.body.init.method;
- result.body.init.status_code = original.body.init.status_code;
+ result.init.method = original.init.method;
+ result.init.status_code = original.init.status_code;
+ result.init.status_text = original.init.status_text.clone();
// https://github.com/oven-sh/bun/issues/3334
- if (original.body.init.headers) |headers| {
- result.body.init.headers = headers.cloneThis(global);
+ if (original.init.headers) |headers| {
+ result.init.headers = headers.cloneThis(global);
}
result.url = original.url.clone();
- result.status_text = original.status_text.clone();
var value = original.getBodyValue();
sink.bodyValueBufferer = JSC.WebCore.BodyValueBufferer.init(sink, onFinishedBuffering, sink.global, bun.default_allocator);
sink.bodyValueBufferer.?.run(value) catch |buffering_error| {
@@ -606,10 +606,10 @@ pub const HTMLRewriter = struct {
// result.* = Response{
// .allocator = bun.default_allocator,
+ // .init = .{
+ // .status_code = 200,
+ // },
// .body = .{
- // .init = .{
- // .status_code = 200,
- // },
// .value = .{
// .Locked = .{
// .global = global,
@@ -619,9 +619,9 @@ pub const HTMLRewriter = struct {
// },
// };
- // result.body.init.headers = original.body.init.headers;
- // result.body.init.method = original.body.init.method;
- // result.body.init.status_code = original.body.init.status_code;
+ // result.init.headers = original.init.headers;
+ // result.init.method = original.init.method;
+ // result.init.status_code = original.init.status_code;
// result.url = bun.default_allocator.dupe(u8, original.url) catch unreachable;
// result.status_text = bun.default_allocator.dupe(u8, original.status_text) catch unreachable;
diff --git a/src/bun.js/api/server.zig b/src/bun.js/api/server.zig
index d90c22577..1df908a0b 100644
--- a/src/bun.js/api/server.zig
+++ b/src/bun.js/api/server.zig
@@ -2988,7 +2988,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
var needs_content_type = true;
const content_type: MimeType = brk: {
- if (response.body.init.headers) |headers_| {
+ if (response.init.headers) |headers_| {
if (headers_.fastGet(.ContentType)) |content| {
needs_content_type = false;
break :brk MimeType.byName(content.slice());
@@ -3008,7 +3008,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
};
var has_content_disposition = false;
- if (response.body.init.headers) |headers_| {
+ if (response.init.headers) |headers_| {
has_content_disposition = headers_.fastHas(.ContentDisposition);
needs_content_range = needs_content_range and headers_.fastHas(.ContentRange);
if (needs_content_range) {
@@ -3018,7 +3018,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
this.writeStatus(status);
this.writeHeaders(headers_);
- response.body.init.headers = null;
+ response.init.headers = null;
headers_.deref();
} else if (needs_content_range) {
status = 206;
diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp
index 778ce4bce..49300198e 100644
--- a/src/bun.js/bindings/bindings.cpp
+++ b/src/bun.js/bindings/bindings.cpp
@@ -4100,6 +4100,7 @@ enum class BuiltinNamesMap : uint8_t {
method,
headers,
status,
+ statusText,
url,
body,
data,
@@ -4119,6 +4120,9 @@ static JSC::Identifier builtinNameMap(JSC::JSGlobalObject* globalObject, unsigne
case BuiltinNamesMap::headers: {
return clientData->builtinNames().headersPublicName();
}
+ case BuiltinNamesMap::statusText: {
+ return clientData->builtinNames().statusTextPublicName();
+ }
case BuiltinNamesMap::status: {
return clientData->builtinNames().statusPublicName();
}
diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig
index d11017438..1ab1ca603 100644
--- a/src/bun.js/bindings/bindings.zig
+++ b/src/bun.js/bindings/bindings.zig
@@ -4265,6 +4265,7 @@ pub const JSValue = enum(JSValueReprInt) {
method,
headers,
status,
+ statusText,
url,
body,
data,
@@ -5833,7 +5834,7 @@ pub fn initialize() void {
\\
\\ https://github.com/oven-sh/webkit/blob/main/Source/JavaScriptCore/runtime/OptionsList.h
\\
- \\Environment variables must be prefixed with "BUN_JSC_". This code runs before .env files are loaded, so those won't work here.
+ \\Environment variables must be prefixed with "BUN_JSC_". This code runs before .env files are loaded, so those won't work here.
\\
\\Warning: options change between releases of Bun and WebKit without notice. This is not a stable API, you should not rely on it beyond debugging something, and it may be removed entirely in a future version of Bun.
,
diff --git a/src/bun.js/webcore/body.zig b/src/bun.js/webcore/body.zig
index 76cda1bad..e38f03c08 100644
--- a/src/bun.js/webcore/body.zig
+++ b/src/bun.js/webcore/body.zig
@@ -51,7 +51,6 @@ const Request = JSC.WebCore.Request;
// https://developer.mozilla.org/en-US/docs/Web/API/Body
pub const Body = struct {
- init: Init = Init{ .headers = null, .status_code = 200 },
value: Value, // = Value.empty,
pub inline fn len(this: *const Body) Blob.SizeType {
@@ -68,7 +67,6 @@ pub const Body = struct {
pub fn clone(this: *Body, globalThis: *JSGlobalObject) Body {
return Body{
- .init = this.init.clone(globalThis),
.value = this.value.clone(globalThis),
};
}
@@ -79,19 +77,7 @@ pub const Body = struct {
try formatter.writeIndent(Writer, writer);
try writer.writeAll(comptime Output.prettyFmt("<r>bodyUsed<d>:<r> ", enable_ansi_colors));
formatter.printAs(.Boolean, Writer, writer, JSC.JSValue.jsBoolean(this.value == .Used), .BooleanObject, enable_ansi_colors);
- formatter.printComma(Writer, writer, enable_ansi_colors) catch unreachable;
- try writer.writeAll("\n");
- // if (this.init.headers) |headers| {
- // try formatter.writeIndent(Writer, writer);
- // try writer.writeAll("headers: ");
- // try headers.leak().writeFormat(formatter, writer, comptime enable_ansi_colors);
- // try writer.writeAll("\n");
- // }
-
- try formatter.writeIndent(Writer, writer);
- try writer.writeAll(comptime Output.prettyFmt("<r>status<d>:<r> ", enable_ansi_colors));
- formatter.printAs(.Double, Writer, writer, JSC.JSValue.jsNumber(this.init.status_code), .NumberObject, enable_ansi_colors);
if (this.value == .Blob) {
try formatter.printComma(Writer, writer, enable_ansi_colors);
try writer.writeAll("\n");
@@ -113,87 +99,9 @@ pub const Body = struct {
}
pub fn deinit(this: *Body, _: std.mem.Allocator) void {
- if (this.init.headers) |headers| {
- this.init.headers = null;
-
- headers.deref();
- }
this.value.deinit();
}
- pub const Init = struct {
- headers: ?*FetchHeaders = null,
- status_code: u16,
- method: Method = Method.GET,
-
- pub fn clone(this: Init, ctx: *JSGlobalObject) Init {
- var that = this;
- var headers = this.headers;
- if (headers) |head| {
- that.headers = head.cloneThis(ctx);
- }
-
- return that;
- }
-
- pub fn init(allocator: std.mem.Allocator, ctx: *JSGlobalObject, response_init: JSC.JSValue) !?Init {
- var result = Init{ .status_code = 200 };
-
- if (!response_init.isCell())
- return null;
-
- if (response_init.jsType() == .DOMWrapper) {
- // fast path: it's a Request object or a Response object
- // we can skip calling JS getters
- if (response_init.as(Request)) |req| {
- if (req.headers) |headers| {
- if (!headers.isEmpty()) {
- result.headers = headers.cloneThis(ctx);
- }
- }
-
- result.method = req.method;
- return result;
- }
-
- if (response_init.as(Response)) |req| {
- return req.body.init.clone(ctx);
- }
- }
-
- if (response_init.fastGet(ctx, .headers)) |headers| {
- if (headers.as(FetchHeaders)) |orig| {
- if (!orig.isEmpty()) {
- result.headers = orig.cloneThis(ctx);
- }
- } else {
- result.headers = FetchHeaders.createFromJS(ctx.ptr(), headers);
- }
- }
-
- if (response_init.fastGet(ctx, .status)) |status_value| {
- const number = status_value.coerceToInt64(ctx);
- if ((200 <= number and number < 600) or number == 101) {
- result.status_code = @as(u16, @truncate(@as(u32, @intCast(number))));
- } else {
- const err = ctx.createRangeErrorInstance("The status provided ({d}) must be 101 or in the range of [200, 599]", .{number});
- ctx.throwValue(err);
- return null;
- }
- }
-
- if (response_init.fastGet(ctx, .method)) |method_value| {
- var method_str = method_value.toSlice(ctx, allocator);
- defer method_str.deinit();
- if (method_str.len > 0) {
- result.method = Method.which(method_str.slice()) orelse .GET;
- }
- }
-
- return result;
- }
- };
-
pub const PendingValue = struct {
promise: ?JSValue = null,
readable: ?JSC.WebCore.ReadableStream = null,
@@ -1004,72 +912,12 @@ pub const Body = struct {
}
};
- pub fn @"404"(_: js.JSContextRef) Body {
- return Body{
- .init = Init{
- .headers = null,
- .status_code = 404,
- },
- .value = Value{ .Null = {} },
- };
- }
-
- pub fn @"200"(_: js.JSContextRef) Body {
- return Body{
- .init = Init{
- .status_code = 200,
- },
- .value = Value{ .Null = {} },
- };
- }
-
- pub fn extract(
- globalThis: *JSGlobalObject,
- value: JSValue,
- ) ?Body {
- return extractBody(
- globalThis,
- value,
- false,
- JSValue.zero,
- );
- }
-
- pub fn extractWithInit(
- globalThis: *JSGlobalObject,
- value: JSValue,
- init: JSValue,
- ) ?Body {
- return extractBody(
- globalThis,
- value,
- true,
- init,
- );
- }
-
// https://github.com/WebKit/webkit/blob/main/Source/WebCore/Modules/fetch/FetchBody.cpp#L45
- inline fn extractBody(
+ pub fn extract(
globalThis: *JSGlobalObject,
value: JSValue,
- comptime has_init: bool,
- init: JSValue,
) ?Body {
- var body = Body{
- .value = Value{ .Null = {} },
- .init = Init{ .headers = null, .status_code = 200 },
- };
- var allocator = getAllocator(globalThis);
-
- if (comptime has_init) {
- if (Init.init(allocator, globalThis, init)) |maybeInit| {
- if (maybeInit) |init_| {
- body.init = init_;
- }
- } else |_| {
- return null;
- }
- }
+ var body = Body{ .value = Value{ .Null = {} } };
body.value = Value.fromJS(globalThis, value) orelse return null;
if (body.value == .Blob)
diff --git a/src/bun.js/webcore/request.zig b/src/bun.js/webcore/request.zig
index 7f37bea46..95f92145b 100644
--- a/src/bun.js/webcore/request.zig
+++ b/src/bun.js/webcore/request.zig
@@ -50,6 +50,7 @@ const InternalBlob = JSC.WebCore.InternalBlob;
const BodyMixin = JSC.WebCore.BodyMixin;
const Body = JSC.WebCore.Body;
const Blob = JSC.WebCore.Blob;
+const Response = JSC.WebCore.Response;
const body_value_pool_size: u16 = 256;
pub const BodyValueRef = bun.HiveRef(Body.Value, body_value_pool_size);
@@ -542,12 +543,12 @@ pub const Request = struct {
if (value.as(JSC.WebCore.Response)) |response| {
if (!fields.contains(.method)) {
- req.method = response.body.init.method;
+ req.method = response.init.method;
fields.insert(.method);
}
if (!fields.contains(.headers)) {
- if (response.body.init.headers) |headers| {
+ if (response.init.headers) |headers| {
req.headers = headers.cloneThis(globalThis);
fields.insert(.headers);
}
@@ -623,7 +624,7 @@ pub const Request = struct {
}
if (!fields.contains(.method) or !fields.contains(.headers)) {
- if (Body.Init.init(globalThis.allocator(), globalThis, value) catch null) |init| {
+ if (Response.Init.init(globalThis.allocator(), globalThis, value) catch null) |init| {
if (!explicit_check or (explicit_check and value.fastGet(globalThis, .method) != null)) {
if (!fields.contains(.method)) {
req.method = init.method;
diff --git a/src/bun.js/webcore/response.zig b/src/bun.js/webcore/response.zig
index 9a2ec48ba..f7ada2862 100644
--- a/src/bun.js/webcore/response.zig
+++ b/src/bun.js/webcore/response.zig
@@ -63,8 +63,8 @@ pub const Response = struct {
allocator: std.mem.Allocator,
body: Body,
+ init: Init,
url: bun.String = bun.String.empty,
- status_text: bun.String = bun.String.empty,
redirected: bool = false,
// We must report a consistent value for this
@@ -89,7 +89,7 @@ pub const Response = struct {
return this.reported_estimated_size orelse brk: {
this.reported_estimated_size = @as(
u63,
- @intCast(this.body.value.estimatedSize() + this.url.byteSlice().len + this.status_text.byteSlice().len + @sizeOf(Response)),
+ @intCast(this.body.value.estimatedSize() + this.url.byteSlice().len + this.init.status_text.byteSlice().len + @sizeOf(Response)),
);
break :brk this.reported_estimated_size.?;
};
@@ -104,11 +104,11 @@ pub const Response = struct {
pub fn getFetchHeaders(
this: *Response,
) ?*FetchHeaders {
- return this.body.init.headers;
+ return this.init.headers;
}
pub inline fn statusCode(this: *const Response) u16 {
- return this.body.init.status_code;
+ return this.init.status_code;
}
pub fn redirectLocation(this: *const Response) ?[]const u8 {
@@ -116,7 +116,7 @@ pub const Response = struct {
}
pub fn header(this: *const Response, name: JSC.FetchHeaders.HTTPHeaderName) ?[]const u8 {
- return if ((this.body.init.headers orelse return null).fastGet(name)) |str|
+ return if ((this.init.headers orelse return null).fastGet(name)) |str|
str.slice()
else
null;
@@ -146,14 +146,20 @@ pub const Response = struct {
try writer.writeAll("\n");
try formatter.writeIndent(Writer, writer);
- try writer.writeAll(comptime Output.prettyFmt("<r>headers<d>:<r> ", enable_ansi_colors));
- formatter.printAs(.Private, Writer, writer, this.getHeaders(formatter.globalThis), .DOMWrapper, enable_ansi_colors);
+ try writer.writeAll(comptime Output.prettyFmt("<r>status<d>:<r> ", enable_ansi_colors));
+ formatter.printAs(.Double, Writer, writer, JSC.JSValue.jsNumber(this.init.status_code), .NumberObject, enable_ansi_colors);
formatter.printComma(Writer, writer, enable_ansi_colors) catch unreachable;
try writer.writeAll("\n");
try formatter.writeIndent(Writer, writer);
try writer.writeAll(comptime Output.prettyFmt("<r>statusText<d>:<r> ", enable_ansi_colors));
- try writer.print(comptime Output.prettyFmt("<r>\"<b>{}<r>\"", enable_ansi_colors), .{this.status_text});
+ try writer.print(comptime Output.prettyFmt("<r>\"<b>{}<r>\"", enable_ansi_colors), .{this.init.status_text});
+ formatter.printComma(Writer, writer, enable_ansi_colors) catch unreachable;
+ try writer.writeAll("\n");
+
+ try formatter.writeIndent(Writer, writer);
+ try writer.writeAll(comptime Output.prettyFmt("<r>headers<d>:<r> ", enable_ansi_colors));
+ formatter.printAs(.Private, Writer, writer, this.getHeaders(formatter.globalThis), .DOMWrapper, enable_ansi_colors);
formatter.printComma(Writer, writer, enable_ansi_colors) catch unreachable;
try writer.writeAll("\n");
@@ -162,6 +168,7 @@ pub const Response = struct {
formatter.printAs(.Boolean, Writer, writer, JSC.JSValue.jsBoolean(this.redirected), .BooleanObject, enable_ansi_colors);
formatter.printComma(Writer, writer, enable_ansi_colors) catch unreachable;
try writer.writeAll("\n");
+
formatter.resetLine();
try this.body.writeFormat(Formatter, formatter, writer, enable_ansi_colors);
}
@@ -172,7 +179,7 @@ pub const Response = struct {
}
pub fn isOK(this: *const Response) bool {
- return this.body.init.status_code == 304 or (this.body.init.status_code >= 200 and this.body.init.status_code <= 299);
+ return this.init.status_code >= 200 and this.init.status_code <= 299;
}
pub fn getURL(
@@ -187,7 +194,7 @@ pub const Response = struct {
this: *Response,
globalThis: *JSC.JSGlobalObject,
) callconv(.C) JSC.JSValue {
- if (this.body.init.status_code < 200) {
+ if (this.init.status_code < 200) {
return ZigString.init("error").toValue(globalThis);
}
@@ -199,7 +206,7 @@ pub const Response = struct {
globalThis: *JSC.JSGlobalObject,
) callconv(.C) JSC.JSValue {
// https://developer.mozilla.org/en-US/docs/Web/API/Response/statusText
- return this.status_text.toJS(globalThis);
+ return this.init.status_text.toJS(globalThis);
}
pub fn getRedirected(
@@ -219,18 +226,18 @@ pub const Response = struct {
}
fn getOrCreateHeaders(this: *Response, globalThis: *JSC.JSGlobalObject) *FetchHeaders {
- if (this.body.init.headers == null) {
- this.body.init.headers = FetchHeaders.createEmpty();
+ if (this.init.headers == null) {
+ this.init.headers = FetchHeaders.createEmpty();
if (this.body.value == .Blob) {
const content_type = this.body.value.Blob.content_type;
if (content_type.len > 0) {
- this.body.init.headers.?.put("content-type", content_type, globalThis);
+ this.init.headers.?.put("content-type", content_type, globalThis);
}
}
}
- return this.body.init.headers.?;
+ return this.init.headers.?;
}
pub fn getHeaders(
@@ -262,8 +269,8 @@ pub const Response = struct {
new_response.* = Response{
.allocator = allocator,
.body = this.body.clone(globalThis),
+ .init = this.init.clone(globalThis),
.url = this.url.clone(),
- .status_text = this.status_text.clone(),
.redirected = this.redirected,
};
}
@@ -279,17 +286,16 @@ pub const Response = struct {
_: *JSC.JSGlobalObject,
) callconv(.C) JSC.JSValue {
// https://developer.mozilla.org/en-US/docs/Web/API/Response/status
- return JSValue.jsNumber(this.body.init.status_code);
+ return JSValue.jsNumber(this.init.status_code);
}
pub fn finalize(
this: *Response,
) callconv(.C) void {
- this.body.deinit(this.allocator);
-
var allocator = this.allocator;
- this.status_text.deref();
+ this.init.deinit(allocator);
+ this.body.deinit(allocator);
this.url.deref();
allocator.destroy(this);
@@ -348,7 +354,7 @@ pub const Response = struct {
pub fn getContentType(
this: *Response,
) ?ZigString.Slice {
- if (this.body.init.headers) |headers| {
+ if (this.init.headers) |headers| {
if (headers.fastGet(.ContentType)) |value| {
return value.toSlice(bun.default_allocator);
}
@@ -373,11 +379,11 @@ pub const Response = struct {
var response = Response{
.body = Body{
- .init = Body.Init{
- .status_code = 200,
- },
.value = .{ .Empty = {} },
},
+ .init = Response.Init{
+ .status_code = 200,
+ },
.allocator = getAllocator(globalThis),
.url = bun.String.empty,
};
@@ -409,10 +415,10 @@ pub const Response = struct {
if (args.nextEat()) |init| {
if (init.isUndefinedOrNull()) {} else if (init.isNumber()) {
- response.body.init.status_code = @as(u16, @intCast(@min(@max(0, init.toInt32()), std.math.maxInt(u16))));
+ response.init.status_code = @as(u16, @intCast(@min(@max(0, init.toInt32()), std.math.maxInt(u16))));
} else {
- if (Body.Init.init(getAllocator(globalThis), globalThis, init) catch null) |_init| {
- response.body.init = _init;
+ if (Response.Init.init(getAllocator(globalThis), globalThis, init) catch null) |_init| {
+ response.init = _init;
}
}
}
@@ -434,10 +440,10 @@ pub const Response = struct {
// var response = getAllocator(globalThis).create(Response) catch unreachable;
var response = Response{
+ .init = Response.Init{
+ .status_code = 302,
+ },
.body = Body{
- .init = Body.Init{
- .status_code = 302,
- },
.value = .{ .Empty = {} },
},
.allocator = getAllocator(globalThis),
@@ -455,17 +461,17 @@ pub const Response = struct {
if (args.nextEat()) |init| {
if (init.isUndefinedOrNull()) {} else if (init.isNumber()) {
- response.body.init.status_code = @as(u16, @intCast(@min(@max(0, init.toInt32()), std.math.maxInt(u16))));
+ response.init.status_code = @as(u16, @intCast(@min(@max(0, init.toInt32()), std.math.maxInt(u16))));
} else {
- if (Body.Init.init(getAllocator(globalThis), globalThis, init) catch null) |_init| {
- response.body.init = _init;
- response.body.init.status_code = 302;
+ if (Response.Init.init(getAllocator(globalThis), globalThis, init) catch null) |_init| {
+ response.init = _init;
+ response.init.status_code = 302;
}
}
}
- response.body.init.headers = response.getOrCreateHeaders(globalThis);
- var headers_ref = response.body.init.headers.?;
+ response.init.headers = response.getOrCreateHeaders(globalThis);
+ var headers_ref = response.init.headers.?;
headers_ref.put("location", url_string_slice.slice(), globalThis);
var ptr = response.allocator.create(Response) catch unreachable;
ptr.* = response;
@@ -478,10 +484,10 @@ pub const Response = struct {
) callconv(.C) JSValue {
var response = getAllocator(globalThis).create(Response) catch unreachable;
response.* = Response{
+ .init = Response.Init{
+ .status_code = 0,
+ },
.body = Body{
- .init = Body.Init{
- .status_code = 0,
- },
.value = .{ .Empty = {} },
},
.allocator = getAllocator(globalThis),
@@ -494,6 +500,8 @@ pub const Response = struct {
globalThis: *JSC.JSGlobalObject,
callframe: *JSC.CallFrame,
) callconv(.C) ?*Response {
+ var allocator = getAllocator(globalThis);
+
const args_list = brk: {
var args = callframe.arguments(2);
if (args.len > 1 and args.ptr[1].isEmptyOrUndefinedOrNull()) {
@@ -503,17 +511,24 @@ pub const Response = struct {
};
const arguments = args_list.ptr[0..args_list.len];
- const body: Body = @as(?Body, brk: {
+
+ const init: Init = @as(?Init, brk: {
switch (arguments.len) {
0 => {
- break :brk Body.@"200"(globalThis);
+ break :brk Init{
+ .status_code = 200,
+ .headers = null,
+ };
},
1 => {
- break :brk Body.extract(globalThis, arguments[0]);
+ break :brk Init{
+ .status_code = 200,
+ .headers = null,
+ };
},
else => {
if (arguments[1].isObject()) {
- break :brk Body.extractWithInit(globalThis, arguments[0], arguments[1]);
+ break :brk Init.init(allocator, globalThis, arguments[1]) catch null;
}
std.debug.assert(!arguments[1].isEmptyOrUndefinedOrNull());
@@ -526,23 +541,152 @@ pub const Response = struct {
unreachable;
}) orelse return null;
- var response = getAllocator(globalThis).create(Response) catch unreachable;
+ const body: Body = brk: {
+ switch (arguments.len) {
+ 0 => {
+ break :brk Body{
+ .value = Body.Value{ .Null = {} },
+ };
+ },
+ else => {
+ break :brk Body.extract(globalThis, arguments[0]);
+ },
+ }
+ unreachable;
+ } orelse return null;
+
+ var response = allocator.create(Response) catch unreachable;
response.* = Response{
.body = body,
+ .init = init,
.allocator = getAllocator(globalThis),
};
if (response.body.value == .Blob and
- response.body.init.headers != null and
+ response.init.headers != null and
response.body.value.Blob.content_type.len > 0 and
- !response.body.init.headers.?.fastHas(.ContentType))
+ !response.init.headers.?.fastHas(.ContentType))
{
- response.body.init.headers.?.put("content-type", response.body.value.Blob.content_type, globalThis);
+ response.init.headers.?.put("content-type", response.body.value.Blob.content_type, globalThis);
}
return response;
}
+
+ pub const Init = struct {
+ headers: ?*FetchHeaders = null,
+ status_code: u16,
+ status_text: bun.String = bun.String.empty,
+ method: Method = Method.GET,
+
+ pub fn clone(this: Init, ctx: *JSGlobalObject) Init {
+ var that = this;
+ var headers = this.headers;
+ if (headers) |head| {
+ that.headers = head.cloneThis(ctx);
+ }
+ that.status_text = this.status_text.clone();
+
+ return that;
+ }
+
+ pub fn init(allocator: std.mem.Allocator, ctx: *JSGlobalObject, response_init: JSC.JSValue) !?Init {
+ var result = Init{ .status_code = 200 };
+
+ if (!response_init.isCell())
+ return null;
+
+ if (response_init.jsType() == .DOMWrapper) {
+ // fast path: it's a Request object or a Response object
+ // we can skip calling JS getters
+ if (response_init.as(Request)) |req| {
+ if (req.headers) |headers| {
+ if (!headers.isEmpty()) {
+ result.headers = headers.cloneThis(ctx);
+ }
+ }
+
+ result.method = req.method;
+ return result;
+ }
+
+ if (response_init.as(Response)) |resp| {
+ return resp.init.clone(ctx);
+ }
+ }
+
+ if (response_init.fastGet(ctx, .headers)) |headers| {
+ if (headers.as(FetchHeaders)) |orig| {
+ if (!orig.isEmpty()) {
+ result.headers = orig.cloneThis(ctx);
+ }
+ } else {
+ result.headers = FetchHeaders.createFromJS(ctx.ptr(), headers);
+ }
+ }
+
+ if (response_init.fastGet(ctx, .status)) |status_value| {
+ const number = status_value.coerceToInt64(ctx);
+ if ((200 <= number and number < 600) or number == 101) {
+ result.status_code = @as(u16, @truncate(@as(u32, @intCast(number))));
+ } else {
+ const err = ctx.createRangeErrorInstance("The status provided ({d}) must be 101 or in the range of [200, 599]", .{number});
+ ctx.throwValue(err);
+ return null;
+ }
+ }
+
+ if (response_init.fastGet(ctx, .statusText)) |status_text| {
+ result.status_text = bun.String.fromJS(status_text, ctx).dupeRef();
+ }
+
+ if (response_init.fastGet(ctx, .method)) |method_value| {
+ var method_str = method_value.toSlice(ctx, allocator);
+ defer method_str.deinit();
+ if (method_str.len > 0) {
+ result.method = Method.which(method_str.slice()) orelse .GET;
+ }
+ }
+
+ return result;
+ }
+
+ pub fn deinit(this: *Init, _: std.mem.Allocator) void {
+ if (this.headers) |headers| {
+ this.headers = null;
+
+ headers.deref();
+ }
+
+ this.status_text.deref();
+ }
+ };
+
+ pub fn @"404"(globalThis: *JSC.JSGlobalObject) Response {
+ return emptyWithStatus(globalThis, 404);
+ }
+
+ pub fn @"200"(globalThis: *JSC.JSGlobalObject) Response {
+ return emptyWithStatus(globalThis, 200);
+ }
+
+ inline fn emptyWithStatus(globalThis: *JSC.JSGlobalObject, status: u16) Response {
+ var allocator = getAllocator(globalThis);
+ var response = allocator.create(Response) catch unreachable;
+
+ response.* = Response{
+ .body = Body{
+ .value = Body.Value{ .Null = {} },
+ },
+ .init = Init{
+ .status_code = status,
+ },
+ .allocator = getAllocator(globalThis),
+ };
+
+ return response;
+ }
};
const null_fd = bun.invalid_fd;
@@ -1238,13 +1382,13 @@ pub const Fetch = struct {
return Response{
.allocator = allocator,
.url = bun.String.createAtomIfPossible(metadata.url),
- .status_text = bun.String.createAtomIfPossible(http_response.status),
.redirected = this.result.redirected,
+ .init = .{
+ .headers = FetchHeaders.createFromPicoHeaders(http_response.headers),
+ .status_code = @as(u16, @truncate(http_response.status_code)),
+ .status_text = bun.String.createAtomIfPossible(http_response.status),
+ },
.body = .{
- .init = .{
- .headers = FetchHeaders.createFromPicoHeaders(http_response.headers),
- .status_code = @as(u16, @truncate(http_response.status_code)),
- },
.value = this.toBodyValue(),
},
};
@@ -1488,15 +1632,15 @@ pub const Fetch = struct {
response.* = Response{
.body = Body{
- .init = Body.Init{
- .status_code = 200,
- },
.value = .{
.Blob = blob,
},
},
+ .init = Response.Init{
+ .status_code = 200,
+ .status_text = bun.String.createAtom("OK"),
+ },
.allocator = allocator,
- .status_text = bun.String.createAtom("OK"),
.url = data_url.url.dupeRef(),
};
@@ -2009,11 +2153,11 @@ pub const Fetch = struct {
response.* = Response{
.body = Body{
- .init = Body.Init{
- .status_code = 200,
- },
.value = .{ .Blob = bun_file },
},
+ .init = Response.Init{
+ .status_code = 200,
+ },
.allocator = bun.default_allocator,
.url = file_url_string.clone(),
};
diff --git a/src/js/builtins/BunBuiltinNames.h b/src/js/builtins/BunBuiltinNames.h
index 3b29b75b2..651cac59b 100644
--- a/src/js/builtins/BunBuiltinNames.h
+++ b/src/js/builtins/BunBuiltinNames.h
@@ -210,6 +210,7 @@ using namespace JSC;
macro(startedPromise) \
macro(state) \
macro(status) \
+ macro(statusText) \
macro(storedError) \
macro(strategy) \
macro(strategyHWM) \
diff --git a/test/js/bun/util/inspect.test.js b/test/js/bun/util/inspect.test.js
index 32b3f2bfa..c2cfe11bf 100644
--- a/test/js/bun/util/inspect.test.js
+++ b/test/js/bun/util/inspect.test.js
@@ -55,21 +55,21 @@ it("Blob inspect", () => {
expect(Bun.inspect(new Response(new Blob()))).toBe(`Response (0 KB) {
ok: true,
url: "",
- headers: Headers {},
+ status: 200,
statusText: "",
+ headers: Headers {},
redirected: false,
bodyUsed: false,
- status: 200,
[Blob detached]
}`);
expect(Bun.inspect(new Response("Hello"))).toBe(`Response (5 bytes) {
ok: true,
url: "",
- headers: Headers {},
+ status: 200,
statusText: "",
+ headers: Headers {},
redirected: false,
bodyUsed: false,
- status: 200,
Blob (5 bytes)
}`);
});
diff --git a/test/js/web/fetch/response.test.ts b/test/js/web/fetch/response.test.ts
new file mode 100644
index 000000000..ddfcd70f7
--- /dev/null
+++ b/test/js/web/fetch/response.test.ts
@@ -0,0 +1,32 @@
+import { describe, test, expect } from "bun:test";
+
+test("zero args returns an otherwise empty 200 response", () => {
+ const response = new Response();
+ expect(response.status).toBe(200);
+ expect(response.statusText).toBe("");
+});
+
+test("1-arg form returns a 200 response", () => {
+ const response = new Response("body text");
+
+ expect(response.status).toBe(200);
+ expect(response.statusText).toBe("");
+});
+
+describe("2-arg form", () => {
+ test("can fill in status/statusText, and it works", () => {
+ const response = new Response("body text", {
+ status: 202,
+ statusText: "Accepted.",
+ });
+
+ expect(response.status).toBe(202);
+ expect(response.statusText).toBe("Accepted.");
+ });
+ test('empty object continues to return 200/""', () => {
+ const response = new Response("body text", {});
+
+ expect(response.status).toBe(200);
+ expect(response.statusText).toBe("");
+ });
+});