From 2537a1164d196be72fb8d0915a1972160ea1a72c Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Mon, 9 Sep 2019 21:54:41 -0700 Subject: [PATCH] Remove 'Result' specialization. Add 'Debug' responder. This removes all uses of specialization in Rocket. --- core/lib/src/handler.rs | 27 ++++++++++ core/lib/src/lib.rs | 1 - core/lib/src/response/debug.rs | 70 ++++++++++++++++++++++++++ core/lib/src/response/mod.rs | 2 + core/lib/src/response/responder.rs | 22 +------- examples/content_types/src/main.rs | 22 +++++--- examples/form_kitchen_sink/src/main.rs | 6 +-- examples/form_validation/src/files.rs | 13 +++-- examples/manual_routes/src/main.rs | 2 +- examples/pastebin/src/main.rs | 4 +- examples/raw_sqlite/src/main.rs | 7 ++- examples/raw_upload/src/main.rs | 8 +-- examples/stream/src/main.rs | 6 +-- 13 files changed, 138 insertions(+), 52 deletions(-) create mode 100644 core/lib/src/response/debug.rs diff --git a/core/lib/src/handler.rs b/core/lib/src/handler.rs index dcd71a80..1689c788 100644 --- a/core/lib/src/handler.rs +++ b/core/lib/src/handler.rs @@ -206,6 +206,33 @@ impl<'r> Outcome<'r> { } } + /// Return the `Outcome` of response to `req` from `responder`. + /// + /// If the responder returns `Ok`, an outcome of `Success` is + /// returned with the response. If the responder returns `Err`, an + /// outcome of `Failure` is returned with the status code. + /// + /// # Example + /// + /// ```rust + /// use rocket::{Request, Data}; + /// use rocket::handler::Outcome; + /// + /// fn str_responder(req: &Request, _: Data) -> Outcome<'static> { + /// Outcome::from(req, "Hello, world!") + /// } + /// ``` + #[inline] + pub fn try_from(req: &Request<'_>, result: Result) -> Outcome<'r> + where T: Responder<'r>, E: std::fmt::Debug + { + let responder = result.map_err(crate::response::Debug); + match responder.respond_to(req) { + Ok(response) => outcome::Outcome::Success(response), + Err(status) => outcome::Outcome::Failure(status) + } + } + /// Return the `Outcome` of response to `req` from `responder`. /// /// If the responder returns `Ok`, an outcome of `Success` is diff --git a/core/lib/src/lib.rs b/core/lib/src/lib.rs index a96221bc..9701aa47 100644 --- a/core/lib/src/lib.rs +++ b/core/lib/src/lib.rs @@ -1,4 +1,3 @@ -#![feature(specialization)] #![feature(try_trait)] #![feature(proc_macro_hygiene)] #![feature(crate_visibility_modifier)] diff --git a/core/lib/src/response/debug.rs b/core/lib/src/response/debug.rs new file mode 100644 index 00000000..e12c5296 --- /dev/null +++ b/core/lib/src/response/debug.rs @@ -0,0 +1,70 @@ +use crate::request::Request; +use crate::response::{self, Response, Responder}; +use crate::http::Status; + +use yansi::Paint; + +/// Debug prints the internal value before responding with a 500 error. +/// +/// This value exists primarily to allow handler return types that would not +/// otherwise implement [`Responder`]. It is typically used in conjunction with +/// `Result` where `E` implements `Debug` but not `Responder`. +/// +/// # Example +/// +/// Because of the generic `From` implementation for `Debug`, conversions +/// from `Result` to `Result>` through `?` occur +/// automatically: +/// +/// ```rust +/// # #![feature(proc_macro_hygiene)] +/// use std::io::{self, Read}; +/// +/// # use rocket::post; +/// use rocket::Data; +/// use rocket::response::Debug; +/// +/// #[post("/", format = "plain", data = "")] +/// fn post(data: Data) -> Result> { +/// let mut name = String::with_capacity(32); +/// data.open().take(32).read_to_string(&mut name)?; +/// Ok(name) +/// } +/// ``` +/// +/// It is also possible to map the error directly to `Debug` via +/// [`Result::map_err()`]: +/// +/// ```rust +/// # #![feature(proc_macro_hygiene)] +/// use std::string::FromUtf8Error; +/// +/// # use rocket::get; +/// use rocket::response::Debug; +/// +/// #[get("/")] +/// fn rand_str() -> Result> { +/// # /* +/// let bytes: Vec = random_bytes(); +/// # */ +/// # let bytes: Vec = vec![]; +/// String::from_utf8(bytes).map_err(Debug) +/// } +/// ``` +#[derive(Debug)] +pub struct Debug(pub E); + +impl From for Debug { + #[inline(always)] + fn from(e: E) -> Self { + Debug(e) + } +} + +impl<'r, E: std::fmt::Debug> Responder<'r> for Debug { + fn respond_to(self, _: &Request<'_>) -> response::Result<'r> { + warn_!("Debug: {:?}", Paint::default(self.0)); + warn_!("Debug always responds with {}.", Status::InternalServerError); + Response::build().status(Status::InternalServerError).ok() + } +} diff --git a/core/lib/src/response/mod.rs b/core/lib/src/response/mod.rs index d1837108..c51c0a39 100644 --- a/core/lib/src/response/mod.rs +++ b/core/lib/src/response/mod.rs @@ -25,6 +25,7 @@ mod redirect; mod named_file; mod stream; mod response; +mod debug; crate mod flash; @@ -39,6 +40,7 @@ pub use self::redirect::Redirect; pub use self::flash::Flash; pub use self::named_file::NamedFile; pub use self::stream::Stream; +pub use self::debug::Debug; #[doc(inline)] pub use self::content::Content; /// Type alias for the `Result` of a `Responder::respond` call. diff --git a/core/lib/src/response/responder.rs b/core/lib/src/response/responder.rs index d04d5049..703cb046 100644 --- a/core/lib/src/response/responder.rs +++ b/core/lib/src/response/responder.rs @@ -1,6 +1,5 @@ use std::fs::File; use std::io::{Cursor, BufReader}; -use std::fmt; use crate::http::{Status, ContentType, StatusClass}; use crate::response::{self, Response, Body}; @@ -268,28 +267,9 @@ impl<'r, R: Responder<'r>> Responder<'r> for Option { } } -/// If `self` is `Ok`, responds with the wrapped `Responder`. Otherwise prints -/// an error message with the `Err` value returns an `Err` of -/// `Status::InternalServerError`. -#[deprecated(since = "0.4.3")] -impl<'r, R: Responder<'r>, E: fmt::Debug> Responder<'r> for Result { - default fn respond_to(self, req: &Request<'_>) -> response::Result<'r> { - self.map(|r| r.respond_to(req)).unwrap_or_else(|e| { - error_!("Response was a non-`Responder` `Err`: {:?}.", e); - warn_!("This `Responder` implementation has been deprecated."); - warn_!( - "In Rocket v0.5, `Result` implements `Responder` only if \ - `E` implements `Responder`. For the previous behavior, use \ - `Result>` where `Debug` is `rocket::response::Debug`." - ); - Err(Status::InternalServerError) - }) - } -} - /// Responds with the wrapped `Responder` in `self`, whether it is `Ok` or /// `Err`. -impl<'r, R: Responder<'r>, E: Responder<'r> + fmt::Debug> Responder<'r> for Result { +impl<'r, R: Responder<'r>, E: Responder<'r>> Responder<'r> for Result { fn respond_to(self, req: &Request<'_>) -> response::Result<'r> { match self { Ok(responder) => responder.respond_to(req), diff --git a/examples/content_types/src/main.rs b/examples/content_types/src/main.rs index ac1d379e..83b9eda7 100644 --- a/examples/content_types/src/main.rs +++ b/examples/content_types/src/main.rs @@ -7,7 +7,12 @@ use std::io::{self, Read}; -use rocket::{Request, response::content, data::Data}; +use rocket::{Request, data::Data}; +use rocket::response::{Debug, content::{Json, Html}}; + +// NOTE: This example explicitly uses the `Json` type from `response::content` +// for demonstration purposes. In a real application, _always_ prefer to use +// `rocket_contrib::json::Json` instead! #[derive(Debug, Serialize, Deserialize)] struct Person { @@ -20,10 +25,10 @@ struct Person { // the route attribute. Note: if this was a real application, we'd use // `rocket_contrib`'s built-in JSON support and return a `JsonValue` instead. #[get("//", format = "json")] -fn get_hello(name: String, age: u8) -> content::Json { - // In a real application, we'd use the JSON contrib type. +fn get_hello(name: String, age: u8) -> Json { + // NOTE: In a real application, we'd use `rocket_contrib::json::Json`. let person = Person { name: name, age: age, }; - content::Json(serde_json::to_string(&person).unwrap()) + Json(serde_json::to_string(&person).unwrap()) } // In a `POST` request and all other payload supporting request types, the @@ -33,15 +38,16 @@ fn get_hello(name: String, age: u8) -> content::Json { // In a real application, we wouldn't use `serde_json` directly; instead, we'd // use `contrib::Json` to automatically serialize a type into JSON. #[post("/", format = "plain", data = "")] -fn post_hello(age: u8, name_data: Data) -> io::Result> { +fn post_hello(age: u8, name_data: Data) -> Result, Debug> { let mut name = String::with_capacity(32); name_data.open().take(32).read_to_string(&mut name)?; let person = Person { name: name, age: age, }; - Ok(content::Json(serde_json::to_string(&person).unwrap())) + // NOTE: In a real application, we'd use `rocket_contrib::json::Json`. + Ok(Json(serde_json::to_string(&person).expect("valid JSON"))) } #[catch(404)] -fn not_found(request: &Request<'_>) -> content::Html { +fn not_found(request: &Request<'_>) -> Html { let html = match request.format() { Some(ref mt) if !mt.is_json() && !mt.is_plain() => { format!("

'{}' requests are not supported.

", mt) @@ -51,7 +57,7 @@ fn not_found(request: &Request<'_>) -> content::Html { request.uri()) }; - content::Html(html) + Html(html) } fn main() { diff --git a/examples/form_kitchen_sink/src/main.rs b/examples/form_kitchen_sink/src/main.rs index 9b841c30..af494eaf 100644 --- a/examples/form_kitchen_sink/src/main.rs +++ b/examples/form_kitchen_sink/src/main.rs @@ -2,8 +2,6 @@ #[macro_use] extern crate rocket; -use std::io; - use rocket::request::{Form, FormError, FormDataError}; use rocket::response::NamedFile; use rocket::http::RawStr; @@ -39,8 +37,8 @@ fn sink(sink: Result>, FormError<'_>>) -> String { } #[get("/")] -fn index() -> io::Result { - NamedFile::open("static/index.html") +fn index() -> Option { + NamedFile::open("static/index.html").ok() } fn rocket() -> rocket::Rocket { diff --git a/examples/form_validation/src/files.rs b/examples/form_validation/src/files.rs index 343a31f5..60e0545e 100644 --- a/examples/form_validation/src/files.rs +++ b/examples/form_validation/src/files.rs @@ -1,14 +1,13 @@ -use rocket::response::NamedFile; - -use std::io; use std::path::{Path, PathBuf}; +use rocket::response::NamedFile; + #[get("/")] -pub fn index() -> io::Result { - NamedFile::open("static/index.html") +pub fn index() -> Option { + NamedFile::open("static/index.html").ok() } #[get("/", rank = 2)] -pub fn files(file: PathBuf) -> io::Result { - NamedFile::open(Path::new("static/").join(file)) +pub fn files(file: PathBuf) -> Option { + NamedFile::open(Path::new("static/").join(file)).ok() } diff --git a/examples/manual_routes/src/main.rs b/examples/manual_routes/src/main.rs index 9f059d1a..18fc8746 100644 --- a/examples/manual_routes/src/main.rs +++ b/examples/manual_routes/src/main.rs @@ -34,7 +34,7 @@ fn echo_url<'r>(req: &'r Request, _: Data) -> Outcome<'r> { .and_then(|res| res.ok()) .into_outcome(Status::BadRequest)?; - Outcome::from(req, RawStr::from_str(param).url_decode()) + Outcome::try_from(req, RawStr::from_str(param).url_decode()) } fn upload<'r>(req: &'r Request, data: Data) -> Outcome<'r> { diff --git a/examples/pastebin/src/main.rs b/examples/pastebin/src/main.rs index bc0d2319..c561c000 100644 --- a/examples/pastebin/src/main.rs +++ b/examples/pastebin/src/main.rs @@ -10,7 +10,7 @@ use std::fs::File; use std::path::Path; use rocket::Data; -use rocket::response::content; +use rocket::response::{content, Debug}; use crate::paste_id::PasteID; @@ -18,7 +18,7 @@ const HOST: &str = "http://localhost:8000"; const ID_LENGTH: usize = 3; #[post("/", data = "")] -fn upload(paste: Data) -> io::Result { +fn upload(paste: Data) -> Result> { let id = PasteID::new(ID_LENGTH); let filename = format!("upload/{id}", id = id); let url = format!("{host}/{id}\n", host = HOST, id = id); diff --git a/examples/raw_sqlite/src/main.rs b/examples/raw_sqlite/src/main.rs index 37e582cc..4daad628 100644 --- a/examples/raw_sqlite/src/main.rs +++ b/examples/raw_sqlite/src/main.rs @@ -7,7 +7,9 @@ use rusqlite::types::ToSql; #[cfg(test)] mod tests; use std::sync::Mutex; -use rocket::{Rocket, State}; + +use rocket::{Rocket, State, response::Debug}; + use rusqlite::{Connection, Error}; type DbConn = Mutex; @@ -25,11 +27,12 @@ fn init_database(conn: &Connection) { } #[get("/")] -fn hello(db_conn: State<'_, DbConn>) -> Result { +fn hello(db_conn: State<'_, DbConn>) -> Result> { db_conn.lock() .expect("db connection lock") .query_row("SELECT name FROM entries WHERE id = 0", &[] as &[&dyn ToSql], |row| { row.get(0) }) + .map_err(Debug) } fn rocket() -> Rocket { diff --git a/examples/raw_upload/src/main.rs b/examples/raw_upload/src/main.rs index 15bc96d7..9ea67c59 100644 --- a/examples/raw_upload/src/main.rs +++ b/examples/raw_upload/src/main.rs @@ -5,11 +5,13 @@ #[cfg(test)] mod tests; use std::{io, env}; -use rocket::Data; +use rocket::{Data, response::Debug}; #[post("/upload", format = "plain", data = "")] -fn upload(data: Data) -> io::Result { - data.stream_to_file(env::temp_dir().join("upload.txt")).map(|n| n.to_string()) +fn upload(data: Data) -> Result> { + data.stream_to_file(env::temp_dir().join("upload.txt")) + .map(|n| n.to_string()) + .map_err(Debug) } #[get("/")] diff --git a/examples/stream/src/main.rs b/examples/stream/src/main.rs index d7a1b886..20499be4 100644 --- a/examples/stream/src/main.rs +++ b/examples/stream/src/main.rs @@ -6,7 +6,7 @@ use rocket::response::{content, Stream}; -use std::io::{self, repeat, Repeat, Read, Take}; +use std::io::{repeat, Repeat, Read, Take}; use std::fs::File; type LimitedRepeat = Take; @@ -20,8 +20,8 @@ fn root() -> content::Plain> { } #[get("/big_file")] -fn file() -> io::Result> { - File::open(FILENAME).map(|file| Stream::from(file)) +fn file() -> Option> { + File::open(FILENAME).map(|file| Stream::from(file)).ok() } fn rocket() -> rocket::Rocket {