Deprecate 'Result' specialization. Add 'Debug' responder.

This commit is contained in:
Sergio Benitez 2020-02-29 17:03:51 -08:00
parent 436a5aad57
commit 90eaad852c
10 changed files with 111 additions and 31 deletions

View File

@ -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<T, E>` where `E` implements `Debug` but not `Responder`.
///
/// # Example
///
/// Because of the generic `From<E>` implementation for `Debug<E>`, conversions
/// from `Result<T, E>` to `Result<T, Debug<E>>` through `?` occur
/// automatically:
///
/// ```rust
/// # #![feature(proc_macro_hygiene, decl_macro)]
/// use std::io::{self, Read};
///
/// # use rocket::post;
/// use rocket::Data;
/// use rocket::response::Debug;
///
/// #[post("/", format = "plain", data = "<data>")]
/// fn post(data: Data) -> Result<String, Debug<io::Error>> {
/// 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, decl_macro)]
/// use std::string::FromUtf8Error;
///
/// # use rocket::get;
/// use rocket::response::Debug;
///
/// #[get("/")]
/// fn rand_str() -> Result<String, Debug<FromUtf8Error>> {
/// # /*
/// let bytes: Vec<u8> = random_bytes();
/// # */
/// # let bytes: Vec<u8> = vec![];
/// String::from_utf8(bytes).map_err(Debug)
/// }
/// ```
#[derive(Debug)]
pub struct Debug<E>(pub E);
impl<E> From<E> for Debug<E> {
#[inline(always)]
fn from(e: E) -> Self {
Debug(e)
}
}
impl<'r, E: std::fmt::Debug> Responder<'r> for Debug<E> {
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()
}
}

View File

@ -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.

View File

@ -8,7 +8,12 @@ extern crate serde_json;
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 {
@ -21,10 +26,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("/<name>/<age>", format = "json")]
fn get_hello(name: String, age: u8) -> content::Json<String> {
// In a real application, we'd use the JSON contrib type.
fn get_hello(name: String, age: u8) -> Json<String> {
// 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
@ -34,15 +39,16 @@ fn get_hello(name: String, age: u8) -> content::Json<String> {
// 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("/<age>", format = "plain", data = "<name_data>")]
fn post_hello(age: u8, name_data: Data) -> io::Result<content::Json<String>> {
fn post_hello(age: u8, name_data: Data) -> Result<Json<String>, Debug<io::Error>> {
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<String> {
fn not_found(request: &Request) -> Html<String> {
let html = match request.format() {
Some(ref mt) if !mt.is_json() && !mt.is_plain() => {
format!("<p>'{}' requests are not supported.</p>", mt)
@ -52,7 +58,7 @@ fn not_found(request: &Request) -> content::Html<String> {
request.uri())
};
content::Html(html)
Html(html)
}
fn main() {

View File

@ -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<Form<FormInput>, FormError>) -> String {
}
#[get("/")]
fn index() -> io::Result<NamedFile> {
NamedFile::open("static/index.html")
fn index() -> Option<NamedFile> {
NamedFile::open("static/index.html").ok()
}
fn rocket() -> rocket::Rocket {

View File

@ -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> {
NamedFile::open("static/index.html")
pub fn index() -> Option<NamedFile> {
NamedFile::open("static/index.html").ok()
}
#[get("/<file..>", rank = 2)]
pub fn files(file: PathBuf) -> io::Result<NamedFile> {
NamedFile::open(Path::new("static/").join(file))
pub fn files(file: PathBuf) -> Option<NamedFile> {
NamedFile::open(Path::new("static/").join(file)).ok()
}

View File

@ -8,7 +8,7 @@ use std::fs::File;
use rocket::{Request, Handler, Route, Data, Catcher};
use rocket::http::{Status, RawStr};
use rocket::response::{self, Responder, status::Custom};
use rocket::response::{self, Responder, status::Custom, Debug};
use rocket::handler::Outcome;
use rocket::outcome::IntoOutcome;
use rocket::http::Method::*;
@ -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::from(req, RawStr::from_str(param).url_decode().map_err(Debug))
}
fn upload<'r>(req: &'r Request, data: Data) -> Outcome<'r> {

View File

@ -11,7 +11,7 @@ use std::fs::File;
use std::path::Path;
use rocket::Data;
use rocket::response::content;
use rocket::response::{content, Debug};
use paste_id::PasteID;
@ -19,7 +19,7 @@ const HOST: &str = "http://localhost:8000";
const ID_LENGTH: usize = 3;
#[post("/", data = "<paste>")]
fn upload(paste: Data) -> io::Result<String> {
fn upload(paste: Data) -> Result<String, Debug<io::Error>> {
let id = PasteID::new(ID_LENGTH);
let filename = format!("upload/{id}", id = id);
let url = format!("{host}/{id}\n", host = HOST, id = id);

View File

@ -6,7 +6,9 @@ extern crate rusqlite;
#[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<Connection>;
@ -24,11 +26,12 @@ fn init_database(conn: &Connection) {
}
#[get("/")]
fn hello(db_conn: State<DbConn>) -> Result<String, Error> {
fn hello(db_conn: State<DbConn>) -> Result<String, Debug<Error>> {
db_conn.lock()
.expect("db connection lock")
.query_row("SELECT name FROM entries WHERE id = 0",
&[], |row| { row.get(0) })
.map_err(Debug)
}
fn rocket() -> Rocket {

View File

@ -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 = "<data>")]
fn upload(data: Data) -> io::Result<String> {
data.stream_to_file(env::temp_dir().join("upload.txt")).map(|n| n.to_string())
fn upload(data: Data) -> Result<String, Debug<io::Error>> {
data.stream_to_file(env::temp_dir().join("upload.txt"))
.map(|n| n.to_string())
.map_err(Debug)
}
#[get("/")]

View File

@ -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<Repeat>;
@ -20,8 +20,8 @@ fn root() -> content::Plain<Stream<LimitedRepeat>> {
}
#[get("/big_file")]
fn file() -> io::Result<Stream<File>> {
File::open(FILENAME).map(|file| Stream::from(file))
fn file() -> Option<Stream<File>> {
File::open(FILENAME).map(|file| Stream::from(file)).ok()
}
fn rocket() -> rocket::Rocket {