diff --git a/responder_test.mbt b/responder_test.mbt
new file mode 100644
index 0000000..3e97096
--- /dev/null
+++ b/responder_test.mbt
@@ -0,0 +1,122 @@
+///|
+async test "direct string responder sets text content type" {
+ let app = new()
+ app.get("/text", _ => "hello")
+ let response = dispatch_http(app, "GET", "/text", {}, b"")
+ inspect(response.status_code.to_int(), content="200")
+ @test.assert_eq(
+ response.headers.get("Content-Type"),
+ Some("text/plain; charset=utf-8"),
+ )
+ let body : String = response.read_body()
+ @test.assert_eq(body, "hello")
+}
+
+///|
+async test "direct json responder sets json content type" {
+ let app = new()
+ app.get("/json", _ => ({ "ok": true } : Json))
+ let response = dispatch_http(app, "GET", "/json", {}, b"")
+ inspect(response.status_code.to_int(), content="200")
+ @test.assert_eq(
+ response.headers.get("Content-Type"),
+ Some("application/json; charset=utf-8"),
+ )
+ let body : Json = response.read_body()
+ json_inspect(body, content={ "ok": true })
+}
+
+///|
+async test "direct html responder sets html content type" {
+ let app = new()
+ app.get("/html", _ => html("
Hello
"))
+ let response = dispatch_http(app, "GET", "/html", {}, b"")
+ inspect(response.status_code.to_int(), content="200")
+ @test.assert_eq(
+ response.headers.get("Content-Type"),
+ Some("text/html; charset=utf-8"),
+ )
+ let body : String = response.read_body()
+ @test.assert_eq(body, "Hello
")
+}
+
+///|
+async test "custom status body infers text content type" {
+ let app = new()
+ app.get("/bad-request", _ => {
+ HttpResponse::new(BadRequest).body("Invalid JSON")
+ })
+ let response = dispatch_http(app, "GET", "/bad-request", {}, b"")
+ inspect(response.status_code.to_int(), content="400")
+ @test.assert_eq(
+ response.headers.get("Content-Type"),
+ Some("text/plain; charset=utf-8"),
+ )
+ let body : String = response.read_body()
+ @test.assert_eq(body, "Invalid JSON")
+}
+
+///|
+async test "custom status html body infers html content type" {
+ let app = new()
+ app.get("/not-found", _ => {
+ HttpResponse::new(NotFound).body(html("404
"))
+ })
+ let response = dispatch_http(app, "GET", "/not-found", {}, b"")
+ inspect(response.status_code.to_int(), content="404")
+ @test.assert_eq(
+ response.headers.get("Content-Type"),
+ Some("text/html; charset=utf-8"),
+ )
+ let body : String = response.read_body()
+ @test.assert_eq(body, "404
")
+}
+
+///|
+async test "json response infers json content type" {
+ let app = new()
+ app.get("/created", _ => {
+ HttpResponse::new(Created).json({ "ok": true })
+ })
+ let response = dispatch_http(app, "GET", "/created", {}, b"")
+ inspect(response.status_code.to_int(), content="201")
+ @test.assert_eq(
+ response.headers.get("Content-Type"),
+ Some("application/json; charset=utf-8"),
+ )
+ let body : Json = response.read_body()
+ json_inspect(body, content={ "ok": true })
+}
+
+///|
+async test "explicit content type wins over inferred body type" {
+ let app = new()
+ app.get("/custom-body", _ => {
+ HttpResponse::new(
+ OK,
+ headers={ "Content-Type": "application/vnd.custom" },
+ ).body("plain text")
+ })
+ app.get("/custom-json", _ => {
+ HttpResponse::new(
+ OK,
+ headers={ "Content-Type": "application/vnd.custom+json" },
+ ).json({ "ok": true })
+ })
+
+ let body_response = dispatch_http(app, "GET", "/custom-body", {}, b"")
+ @test.assert_eq(
+ body_response.headers.get("Content-Type"),
+ Some("application/vnd.custom"),
+ )
+ let body : String = body_response.read_body()
+ @test.assert_eq(body, "plain text")
+
+ let json_response = dispatch_http(app, "GET", "/custom-json", {}, b"")
+ @test.assert_eq(
+ json_response.headers.get("Content-Type"),
+ Some("application/vnd.custom+json"),
+ )
+ let json : Json = json_response.read_body()
+ json_inspect(json, content={ "ok": true })
+}
diff --git a/response.mbt b/response.mbt
index 9584449..42c0412 100644
--- a/response.mbt
+++ b/response.mbt
@@ -32,10 +32,25 @@ pub fn HttpResponse::new(
}
///|
+/// Sets the response body from any `Responder`.
+///
+/// The body responder's default `Content-Type` is carried into this
+/// response only when no explicit `"Content-Type"` header was set on this
+/// response. Explicit response headers always take precedence.
+///
+/// Only the default `Content-Type` is inferred. The responder's status,
+/// other headers, cookies, and any other responder effects are not merged,
+/// so `self.status_code` is preserved.
pub fn HttpResponse::body(
self : HttpResponse,
body : &Responder,
) -> HttpResponse {
+ let probe = HttpResponse::new(OK)
+ body.options(probe)
+ if probe.headers.get("Content-Type") is Some(content_type) &&
+ !self.headers.contains("Content-Type") {
+ self.headers["Content-Type"] = content_type
+ }
let buf = Buffer()
body.output(buf)
self.raw_body = buf.to_bytes()
@@ -43,6 +58,12 @@ pub fn HttpResponse::body(
}
///|
+/// Sets a JSON response body.
+///
+/// Infers `Content-Type: application/json; charset=utf-8` under the same
+/// rule as `HttpResponse::body`: an explicit `"Content-Type"` header already
+/// present on this response takes precedence, and `self.status_code` is
+/// preserved.
pub fn HttpResponse::json(self : HttpResponse, obj : &ToJson) -> HttpResponse {
self.body(obj.to_json())
}