diff --git a/core/lib/src/fs/named_file.rs b/core/lib/src/fs/named_file.rs index 29093582..8fd165f2 100644 --- a/core/lib/src/fs/named_file.rs +++ b/core/lib/src/fs/named_file.rs @@ -8,7 +8,8 @@ use crate::request::Request; use crate::response::{self, Responder}; use crate::http::ContentType; -/// A [`Responder`] that sends a file with a Content-Type based on its name. +/// A [`Responder`] that sends file data with a Content-Type based on its +/// file extension. /// /// # Example /// diff --git a/core/lib/src/response/content.rs b/core/lib/src/response/content.rs index cb5006ac..68d7a33d 100644 --- a/core/lib/src/response/content.rs +++ b/core/lib/src/response/content.rs @@ -2,15 +2,11 @@ //! //! # Usage //! -//! Each type wraps a given responder. The `Responder` implementation of each -//! type replaces the Content-Type of the wrapped responder and delegates the -//! remainder of the response to the wrapped responder. This allows for setting -//! the Content-Type of a type that doesn't set it itself or for overriding one -//! that does. -//! -//! The [`Custom`] type allows responding with _any_ `Content-Type`. As a -//! convenience, `(ContentType, R)` where `R: Responder` is _also_ a -//! `Responder`, identical to `Custom`. +//! Each type in this module is a `Responder` that wraps an existing +//! `Responder`, overwriting the `Content-Type` of the response but otherwise +//! delegating the response to the wrapped responder. As a convenience, +//! `(ContentType, R)` where `R: Responder` is _also_ a `Responder` that +//! overrides the `Content-Type` to the value in `.0`: //! //! ```rust //! # use rocket::get; @@ -21,55 +17,24 @@ //! (ContentType::HTML, "Is this HTML?

Sure, why not!

") //! } //! ``` - //! //! # Example //! -//! The following snippet creates an `Html` content response for a string. -//! Normally, raw strings set their response Content-Type to `text/plain`. By -//! using the `Html` content response, the Content-Type will be set to -//! `text/html` instead. +//! The following snippet creates a `RawHtml` response from a string. Normally, +//! raw strings set their response Content-Type to `text/plain`. By using the +//! `RawHtml` content response, the Content-Type will be set to `text/html` +//! instead: //! //! ```rust //! use rocket::response::content; //! -//! # #[allow(unused_variables)] -//! let response = content::Html("

Hello, world!

"); +//! let response = content::RawHtml("

Hello, world!

"); //! ``` use crate::request::Request; use crate::response::{self, Response, Responder}; use crate::http::ContentType; -/// Sets the Content-Type of a `Responder` to a chosen value. -/// -/// Delegates the remainder of the response to the wrapped responder. -/// -/// # Example -/// -/// Set the Content-Type of a string to PDF. -/// -/// ```rust -/// use rocket::response::content::Custom; -/// use rocket::http::ContentType; -/// -/// # #[allow(unused_variables)] -/// let response = Custom(ContentType::PDF, "Hi."); -/// ``` -#[derive(Debug, Clone, PartialEq)] -pub struct Custom(pub ContentType, pub R); - -/// Overrides the Content-Type of the response to the wrapped `ContentType` then -/// delegates the remainder of the response to the wrapped responder. -impl<'r, 'o: 'r, R: Responder<'r, 'o>> Responder<'r, 'o> for Custom { - fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> { - Response::build() - .merge(self.1.respond_to(req)?) - .header(self.0) - .ok() - } -} - macro_rules! ctrs { ($($name:ident: $ct:ident, $name_str:expr, $ct_str:expr),+) => { $( @@ -80,6 +45,13 @@ macro_rules! ctrs { #[doc="."] /// /// Delegates the remainder of the response to the wrapped responder. + /// + /// **Note:** Unlike types like [`Json`](crate::serde::json::Json) + /// and [`MsgPack`](crate::serde::msgpack::MsgPack), this type _does + /// not_ serialize data in any way. You should _always_ use those + /// types to respond with serializable data. Additionally, you + /// should _always_ use [`NamedFile`](crate::fs::NamedFile), which + /// automatically sets a `Content-Type`, to respond with file data. #[derive(Debug, Clone, PartialEq)] pub struct $name(pub R); @@ -87,7 +59,7 @@ macro_rules! ctrs { /// remainder of the response to the wrapped responder. impl<'r, 'o: 'r, R: Responder<'r, 'o>> Responder<'r, 'o> for $name { fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> { - Custom(ContentType::$ct, self.0).respond_to(req) + (ContentType::$ct, self.0).respond_to(req) } } )+ @@ -96,18 +68,20 @@ macro_rules! ctrs { ctrs! { // FIXME: Add a note that this is _not_ `serde::Json`. - Json: JSON, "JSON", "application/json", - Xml: XML, "XML", "text/xml", - MsgPack: MsgPack, "MessagePack", "application/msgpack", - Html: HTML, "HTML", "text/html", - Plain: Plain, "plain text", "text/plain", - Css: CSS, "CSS", "text/css", - JavaScript: JavaScript, "JavaScript", "application/javascript" + RawJson: JSON, "JSON", "application/json", + RawXml: XML, "XML", "text/xml", + RawMsgPack: MsgPack, "MessagePack", "application/msgpack", + RawHtml: HTML, "HTML", "text/html", + RawText: Text, "plain text", "text/plain", + RawCss: CSS, "CSS", "text/css", + RawJavaScript: JavaScript, "JavaScript", "application/javascript" } impl<'r, 'o: 'r, R: Responder<'r, 'o>> Responder<'r, 'o> for (ContentType, R) { - #[inline(always)] - fn respond_to(self, request: &'r Request<'_>) -> response::Result<'o> { - Custom(self.0, self.1).respond_to(request) + fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> { + Response::build() + .merge(self.1.respond_to(req)?) + .header(self.0) + .ok() } } diff --git a/core/lib/src/serde/json.rs b/core/lib/src/serde/json.rs index 4827ea72..70ff50f0 100644 --- a/core/lib/src/serde/json.rs +++ b/core/lib/src/serde/json.rs @@ -223,7 +223,7 @@ impl<'r, T: Serialize> Responder<'r, 'static> for Json { Status::InternalServerError })?; - content::Json(string).respond_to(req) + content::RawJson(string).respond_to(req) } } @@ -299,7 +299,7 @@ impl<'v, T: Deserialize<'v> + Send> form::FromFormField<'v> for Json { /// and a fixed-size body with the serialized value. impl<'r> Responder<'r, 'static> for Value { fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> { - content::Json(self.to_string()).respond_to(req) + content::RawJson(self.to_string()).respond_to(req) } } diff --git a/core/lib/src/serde/msgpack.rs b/core/lib/src/serde/msgpack.rs index 48d727bb..188041b9 100644 --- a/core/lib/src/serde/msgpack.rs +++ b/core/lib/src/serde/msgpack.rs @@ -194,7 +194,7 @@ impl<'r, T: Serialize> Responder<'r, 'static> for MsgPack { Status::InternalServerError })?; - content::MsgPack(buf).respond_to(req) + content::RawMsgPack(buf).respond_to(req) } } diff --git a/core/lib/tests/head_handling.rs b/core/lib/tests/head_handling.rs index 4832d269..367d212a 100644 --- a/core/lib/tests/head_handling.rs +++ b/core/lib/tests/head_handling.rs @@ -1,6 +1,7 @@ #[macro_use] extern crate rocket; -use rocket::{http::Status, response::content}; +use rocket::http::Status; +use rocket::response::content::RawJson; #[get("/empty")] fn empty() -> Status { @@ -13,8 +14,8 @@ fn index() -> &'static str { } #[head("/other")] -fn other() -> content::Json<&'static str> { - content::Json("{ 'hi': 'hello' }") +fn other() -> RawJson<&'static str> { + RawJson("{ 'hi': 'hello' }") } mod head_handling_tests { diff --git a/examples/cookies/src/main.rs b/examples/cookies/src/main.rs index 3a287acb..b22c2b71 100644 --- a/examples/cookies/src/main.rs +++ b/examples/cookies/src/main.rs @@ -5,12 +5,12 @@ mod session; mod message; -use rocket::response::content::Html; +use rocket::response::content::RawHtml; use rocket_dyn_templates::Template; #[get("/")] -fn index() -> Html<&'static str> { - Html(r#"Set a Message or Use Sessions."#) +fn index() -> RawHtml<&'static str> { + RawHtml(r#"Set a Message or Use Sessions."#) } #[launch] diff --git a/examples/error-handling/src/main.rs b/examples/error-handling/src/main.rs index b31cfe38..ffa0a6b1 100644 --- a/examples/error-handling/src/main.rs +++ b/examples/error-handling/src/main.rs @@ -17,16 +17,16 @@ fn forced_error(code: u16) -> Status { } #[catch(404)] -fn general_not_found() -> content::Html<&'static str> { - content::Html(r#" +fn general_not_found() -> content::RawHtml<&'static str> { + content::RawHtml(r#"

Hmm... What are you looking for?

Say hello! "#) } #[catch(404)] -fn hello_not_found(req: &Request<'_>) -> content::Html { - content::Html(format!("\ +fn hello_not_found(req: &Request<'_>) -> content::RawHtml { + content::RawHtml(format!("\

Sorry, but '{}' is not a valid path!

\

Try visiting /hello/<name>/<age> instead.

", req.uri())) diff --git a/examples/pastebin/src/main.rs b/examples/pastebin/src/main.rs index 0b990310..e88e0c07 100644 --- a/examples/pastebin/src/main.rs +++ b/examples/pastebin/src/main.rs @@ -7,7 +7,7 @@ use std::io; use rocket::data::{Data, ToByteUnit}; use rocket::http::uri::Absolute; -use rocket::response::content::Plain; +use rocket::response::content::RawText; use rocket::tokio::fs::{self, File}; use crate::paste_id::PasteId; @@ -24,8 +24,8 @@ async fn upload(paste: Data<'_>) -> io::Result { } #[get("/")] -async fn retrieve(id: PasteId<'_>) -> Option> { - File::open(id.file_path()).await.map(Plain).ok() +async fn retrieve(id: PasteId<'_>) -> Option> { + File::open(id.file_path()).await.map(RawText).ok() } #[delete("/")] diff --git a/examples/responders/src/main.rs b/examples/responders/src/main.rs index ca5328ac..e7ba7cea 100644 --- a/examples/responders/src/main.rs +++ b/examples/responders/src/main.rs @@ -89,26 +89,26 @@ fn maybe_redir(name: &str) -> Result<&'static str, Redirect> { use rocket::Request; use rocket::response::content; -// NOTE: This example explicitly uses the `Json` type from `response::content` -// for demonstration purposes. In a real application, _always_ prefer to use -// `rocket::serde::json::Json` instead! +// NOTE: This example explicitly uses the `RawJson` type from +// `response::content` for demonstration purposes. In a real application, +// _always_ prefer to use `rocket::serde::json::Json` instead! // In a `GET` request and all other non-payload supporting request types, the // preferred media type in the Accept header is matched against the `format` in // the route attribute. Because the client can use non-specific media types like // `*/*` in `Accept`, these first two routes would collide without `rank`. #[get("/content", format = "xml", rank = 1)] -fn xml() -> content::Xml<&'static str> { - content::Xml("I'm here") +fn xml() -> content::RawXml<&'static str> { + content::RawXml("I'm here") } #[get("/content", format = "json", rank = 2)] -fn json() -> content::Json<&'static str> { - content::Json(r#"{ "payload": "I'm here" }"#) +fn json() -> content::RawJson<&'static str> { + content::RawJson(r#"{ "payload": "I'm here" }"#) } #[catch(404)] -fn not_found(request: &Request<'_>) -> content::Html { +fn not_found(request: &Request<'_>) -> content::RawHtml { let html = match request.format() { Some(ref mt) if !(mt.is_xml() || mt.is_html()) => { format!("

'{}' requests are not supported.

", mt) @@ -118,24 +118,24 @@ fn not_found(request: &Request<'_>) -> content::Html { request.uri()) }; - content::Html(html) + content::RawHtml(html) } /******************************* `Either` Responder ***************************/ use rocket::Either; -use rocket::response::content::{Json, MsgPack}; +use rocket::response::content::{RawJson, RawMsgPack}; use rocket::http::uncased::AsUncased; // NOTE: In a real application, we'd use `Json` and `MsgPack` from // `rocket::serde`, which perform automatic serialization of responses and // automatically set the `Content-Type`. #[get("/content/")] -fn json_or_msgpack(kind: &str) -> Either, MsgPack<&'static [u8]>> { +fn json_or_msgpack(kind: &str) -> Either, RawMsgPack<&'static [u8]>> { if kind.as_uncased() == "msgpack" { - Either::Right(MsgPack(&[162, 104, 105])) + Either::Right(RawMsgPack(&[162, 104, 105])) } else { - Either::Left(Json("\"hi\"")) + Either::Left(RawJson("\"hi\"")) } } @@ -143,7 +143,7 @@ fn json_or_msgpack(kind: &str) -> Either, MsgPack<&'static [u use std::borrow::Cow; -use rocket::response::content::Html; +use rocket::response::content::RawHtml; #[derive(Responder)] enum StoredData { @@ -151,7 +151,7 @@ enum StoredData { String(Cow<'static, str>), Bytes(Vec), #[response(status = 401)] - NotAuthorized(Html<&'static str>), + NotAuthorized(RawHtml<&'static str>), } #[derive(FromFormField, UriDisplayQuery)] @@ -170,7 +170,7 @@ async fn custom(kind: Option) -> StoredData { }, Some(Kind::String) => StoredData::String("Hey, I'm some data.".into()), Some(Kind::Bytes) => StoredData::Bytes(vec![72, 105]), - None => StoredData::NotAuthorized(Html("No no no!")) + None => StoredData::NotAuthorized(RawHtml("No no no!")) } } diff --git a/examples/state/src/managed_hit_count.rs b/examples/state/src/managed_hit_count.rs index 6138eead..1b02ce7b 100644 --- a/examples/state/src/managed_hit_count.rs +++ b/examples/state/src/managed_hit_count.rs @@ -1,15 +1,15 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use rocket::State; -use rocket::response::content; +use rocket::response::content::RawHtml; use rocket::fairing::AdHoc; struct HitCount(AtomicUsize); #[get("/")] -fn index(hit_count: &State) -> content::Html { +fn index(hit_count: &State) -> RawHtml { let count = hit_count.0.fetch_add(1, Ordering::Relaxed) + 1; - content::Html(format!("Your visit is recorded!

Visits: {}", count)) + RawHtml(format!("Your visit is recorded!

Visits: {}", count)) } pub fn stage() -> AdHoc { diff --git a/examples/templating/src/main.rs b/examples/templating/src/main.rs index 72dff9ac..e849f427 100644 --- a/examples/templating/src/main.rs +++ b/examples/templating/src/main.rs @@ -5,12 +5,12 @@ mod tera; #[cfg(test)] mod tests; -use rocket::response::content::Html; +use rocket::response::content::RawHtml; use rocket_dyn_templates::Template; #[get("/")] -fn index() -> Html<&'static str> { - Html(r#"See Tera or Handlebars."#) +fn index() -> RawHtml<&'static str> { + RawHtml(r#"See Tera or Handlebars."#) } #[launch] diff --git a/site/guide/5-responses.md b/site/guide/5-responses.md index 2ee90249..c1a744c4 100644 --- a/site/guide/5-responses.md +++ b/site/guide/5-responses.md @@ -50,7 +50,7 @@ fn new(id: usize) -> status::Accepted { Similarly, the types in the [`content` module](@api/rocket/response/content/) can be used to override the Content-Type of a response. For instance, to set the Content-Type of `&'static str` to JSON, as well as setting the status code to an -arbitrary one like `418 I'm a teapot`, combine [`content::Json`] with +arbitrary one like `418 I'm a teapot`, combine [`content::RawJson`] with [`status::Custom`]: ```rust @@ -59,16 +59,16 @@ use rocket::http::Status; use rocket::response::{content, status}; #[get("/")] -fn json() -> status::Custom> { - status::Custom(Status::ImATeapot, content::Json("{ \"hi\": \"world\" }")) +fn json() -> status::Custom> { + status::Custom(Status::ImATeapot, content::RawJson("{ \"hi\": \"world\" }")) } ``` ! warning: This is _not_ the same as [`serde::json::Json`]! The built-in `(Status, R)` and `(ContentType, R)` responders, where `R: -Responder`, are short-hands for the `status::Custom` and `content::Custom` -responders: +Responder`, also override the `Status` and `Content-Type` of responses, +respectively: ```rust # #[macro_use] extern crate rocket;