Use Outcome as the result of all fallible conversions.

This commit is contained in:
Sergio Benitez 2016-10-13 18:39:23 -07:00
parent aa2f2061ac
commit 722f613686
23 changed files with 298 additions and 215 deletions

View File

@ -105,12 +105,11 @@ impl RouteGenerateExt for RouteParams {
Some(quote_stmt!(ecx, Some(quote_stmt!(ecx,
let $name: $ty = let $name: $ty =
match ::rocket::request::FromData::from_data(&_req, _data) { match ::rocket::request::FromData::from_data(&_req, _data) {
::rocket::request::DataOutcome::Success(d) => d, ::rocket::outcome::Outcome::Success(d) => d,
::rocket::request::DataOutcome::Forward(d) => ::rocket::outcome::Outcome::Forward(d) =>
return ::rocket::Response::forward(d), return ::rocket::Response::forward(d),
::rocket::request::DataOutcome::Failure(_) => { ::rocket::outcome::Outcome::Failure((code, _)) => {
let code = ::rocket::http::StatusCode::BadRequest; return ::rocket::Response::failure(code);
return ::rocket::Response::failed(code);
} }
}; };
).expect("data statement")) ).expect("data statement"))
@ -181,9 +180,13 @@ impl RouteGenerateExt for RouteParams {
let ty = strip_ty_lifetimes(arg.ty.clone()); let ty = strip_ty_lifetimes(arg.ty.clone());
fn_param_statements.push(quote_stmt!(ecx, fn_param_statements.push(quote_stmt!(ecx,
let $ident: $ty = match let $ident: $ty = match
<$ty as ::rocket::request::FromRequest>::from_request(&_req) { ::rocket::request::FromRequest::from_request(&_req) {
Ok(v) => v, ::rocket::outcome::Outcome::Success(v) => v,
Err(_e) => return ::rocket::Response::forward(_data) ::rocket::outcome::Outcome::Forward(_) =>
return ::rocket::Response::forward(_data),
::rocket::outcome::Outcome::Failure((code, _)) => {
return ::rocket::Response::failure(code)
},
}; };
).expect("undeclared param parsing statement")); ).expect("undeclared param parsing statement"));
} }
@ -240,7 +243,7 @@ fn generic_route_decorator(known_method: Option<Spanned<Method>>,
$query_statement $query_statement
$data_statement $data_statement
let result = $user_fn_name($fn_arguments); let result = $user_fn_name($fn_arguments);
::rocket::Response::complete(result) ::rocket::Response::success(result)
} }
).unwrap()); ).unwrap());

View File

@ -5,7 +5,7 @@ use std::ops::{Deref, DerefMut};
use std::io::Read; use std::io::Read;
use rocket::request::{Request, Data, FromData, DataOutcome}; use rocket::request::{Request, Data, FromData, DataOutcome};
use rocket::response::{Responder, Outcome, ResponseOutcome, data}; use rocket::response::{Responder, ResponseOutcome, data};
use rocket::http::StatusCode; use rocket::http::StatusCode;
use rocket::http::hyper::FreshHyperResponse; use rocket::http::hyper::FreshHyperResponse;
@ -73,11 +73,11 @@ impl<T: Deserialize> FromData for JSON<T> {
fn from_data(request: &Request, data: Data) -> DataOutcome<Self, SerdeError> { fn from_data(request: &Request, data: Data) -> DataOutcome<Self, SerdeError> {
if !request.content_type().is_json() { if !request.content_type().is_json() {
error_!("Content-Type is not JSON."); error_!("Content-Type is not JSON.");
return DataOutcome::Forward(data); return DataOutcome::forward(data);
} }
let reader = data.open().take(MAX_SIZE); let reader = data.open().take(MAX_SIZE);
DataOutcome::from(serde_json::from_reader(reader).map(|val| JSON(val))) DataOutcome::of(serde_json::from_reader(reader).map(|val| JSON(val)))
} }
} }
@ -87,7 +87,7 @@ impl<T: Serialize> Responder for JSON<T> {
Ok(json_string) => data::JSON(json_string).respond(res), Ok(json_string) => data::JSON(json_string).respond(res),
Err(e) => { Err(e) => {
error_!("JSON failed to serialize: {:?}", e); error_!("JSON failed to serialize: {:?}", e);
Outcome::Forward((StatusCode::BadRequest, res)) ResponseOutcome::forward(StatusCode::BadRequest, res)
} }
} }
} }

View File

@ -17,7 +17,7 @@ use std::path::{Path, PathBuf};
use std::collections::HashMap; use std::collections::HashMap;
use rocket::Rocket; use rocket::Rocket;
use rocket::response::{Content, Outcome, ResponseOutcome, Responder}; use rocket::response::{Content, ResponseOutcome, Responder};
use rocket::http::hyper::FreshHyperResponse; use rocket::http::hyper::FreshHyperResponse;
use rocket::http::{ContentType, StatusCode}; use rocket::http::{ContentType, StatusCode};
@ -155,7 +155,7 @@ impl Responder for Template {
match self.0 { match self.0 {
Some(ref render) => Content(content_type, render.as_str()).respond(res), Some(ref render) => Content(content_type, render.as_str()).respond(res),
None => Outcome::Forward((StatusCode::InternalServerError, res)), None => ResponseOutcome::forward(StatusCode::InternalServerError, res),
} }
} }
} }

View File

@ -4,7 +4,7 @@
extern crate rocket; extern crate rocket;
use std::fmt; use std::fmt;
use rocket::request::{Request, FromRequest}; use rocket::request::{Request, FromRequest, RequestOutcome};
#[derive(Debug)] #[derive(Debug)]
struct HeaderCount(usize); struct HeaderCount(usize);
@ -17,8 +17,8 @@ impl fmt::Display for HeaderCount {
impl<'r> FromRequest<'r> for HeaderCount { impl<'r> FromRequest<'r> for HeaderCount {
type Error = (); type Error = ();
fn from_request(request: &'r Request) -> Result<Self, Self::Error> { fn from_request(request: &'r Request) -> RequestOutcome<Self, Self::Error> {
Ok(HeaderCount(request.headers().len())) RequestOutcome::success(HeaderCount(request.headers().len()))
} }
} }

View File

@ -13,35 +13,35 @@ fn forward(_req: &Request, data: Data) -> Response<'static> {
} }
fn hi(_req: &Request, _: Data) -> Response<'static> { fn hi(_req: &Request, _: Data) -> Response<'static> {
Response::complete("Hello!") Response::success("Hello!")
} }
fn name<'a>(req: &'a Request, _: Data) -> Response<'a> { fn name<'a>(req: &'a Request, _: Data) -> Response<'a> {
Response::complete(req.get_param(0).unwrap_or("unnamed")) Response::success(req.get_param(0).unwrap_or("unnamed"))
} }
fn echo_url<'a>(req: &'a Request, _: Data) -> Response<'a> { fn echo_url<'a>(req: &'a Request, _: Data) -> Response<'a> {
let param = req.uri().as_str().split_at(6).1; let param = req.uri().as_str().split_at(6).1;
Response::complete(String::from_param(param)) Response::success(String::from_param(param))
} }
fn upload(req: &Request, data: Data) -> Response { fn upload(req: &Request, data: Data) -> Response {
if !req.content_type().is_text() { if !req.content_type().is_text() {
println!(" => Content-Type of upload must be data. Ignoring."); println!(" => Content-Type of upload must be data. Ignoring.");
return Response::failed(StatusCode::BadRequest); return Response::failure(StatusCode::BadRequest);
} }
let file = File::create("/tmp/upload.txt"); let file = File::create("/tmp/upload.txt");
if let Ok(mut file) = file { if let Ok(mut file) = file {
if let Ok(n) = io::copy(&mut data.open(), &mut file) { if let Ok(n) = io::copy(&mut data.open(), &mut file) {
return Response::complete(format!("OK: {} bytes uploaded.", n)); return Response::success(format!("OK: {} bytes uploaded.", n));
} }
println!(" => Failed copying."); println!(" => Failed copying.");
Response::failed(StatusCode::InternalServerError) Response::failure(StatusCode::InternalServerError)
} else { } else {
println!(" => Couldn't open file: {:?}", file.unwrap_err()); println!(" => Couldn't open file: {:?}", file.unwrap_err());
Response::failed(StatusCode::InternalServerError) Response::failure(StatusCode::InternalServerError)
} }
} }

View File

@ -7,7 +7,6 @@ authors = ["Sergio Benitez <sb@sergio.bz>"]
term-painter = "^0.2" term-painter = "^0.2"
log = "^0.3" log = "^0.3"
url = "^1" url = "^1"
# mime = "^0.2"
toml = "^0.2" toml = "^0.2"
[dependencies.hyper] [dependencies.hyper]

View File

@ -1,7 +1,6 @@
#![feature(question_mark)] #![feature(question_mark)]
#![feature(specialization)] #![feature(specialization)]
#![feature(conservative_impl_trait)] #![feature(conservative_impl_trait)]
#![feature(associated_type_defaults)]
//! # Rocket - Core API Documentation //! # Rocket - Core API Documentation
//! //!
@ -31,9 +30,9 @@
//! ## Usage //! ## Usage
//! //!
//! The sanctioned way to use Rocket is via the code generation plugin. This //! The sanctioned way to use Rocket is via the code generation plugin. This
//! makes Rocket easier to use and allows a somewhat stable API as Rust matures. //! makes Rocket easier to use and allows a somewhat stable API as Rocket
//! To use Rocket in your Cargo-based project, add the following to //! matures. To use Rocket with the code generation plugin in your Cargo-based
//! `Cargo.toml`: //! project, add the following to `Cargo.toml`:
//! //!
//! ```rust,ignore //! ```rust,ignore
//! [dependencies] //! [dependencies]
@ -54,7 +53,7 @@
//! ``` //! ```
//! //!
//! See the [guide](https://guide.rocket.rs) for more information on how to //! See the [guide](https://guide.rocket.rs) for more information on how to
//! write Rocket application. //! write Rocket applications.
//! //!
//! ## Configuration //! ## Configuration
//! //!
@ -65,7 +64,6 @@
extern crate term_painter; extern crate term_painter;
extern crate hyper; extern crate hyper;
extern crate url; extern crate url;
// extern crate mime;
extern crate toml; extern crate toml;
#[cfg(test)] #[macro_use] extern crate lazy_static; #[cfg(test)] #[macro_use] extern crate lazy_static;

View File

@ -1,71 +1,115 @@
use std::fmt; use std::fmt;
use term_painter::Color::*; use term_painter::Color::*;
use term_painter::Color;
use term_painter::ToStyle; use term_painter::ToStyle;
#[must_use] #[must_use]
pub enum Outcome<T> { pub enum Outcome<S, E, F> {
/// Signifies that all processing completed successfully. /// Contains the success value.
Success, Success(S),
/// Signifies that some processing occurred that ultimately resulted in /// Contains the failure error value.
/// failure. As a result, no further processing can occur. Failure(E),
Failure, /// Contains the value to forward on.
/// Signifies that no processing occured and as such, processing can be Forward(F),
/// forwarded to the next available target.
Forward(T),
} }
impl<T> Outcome<T> { impl<S, E, F> Outcome<S, E, F> {
pub fn of<A, B: fmt::Debug>(result: Result<A, B>) -> Outcome<T> { /// Unwraps the Outcome, yielding the contents of a Success.
if let Err(e) = result { ///
error_!("{:?}", e); /// # Panics
return Outcome::Failure; ///
/// Panics if the value is not Success.
#[inline(always)]
pub fn unwrap(self) -> S {
match self {
Outcome::Success(val) => val,
_ => panic!("Expected a successful outcome!")
} }
Outcome::Success
} }
pub fn as_str(&self) -> &'static str { /// Return true if this `Outcome` is a `Success`.
#[inline(always)]
pub fn is_success(&self) -> bool {
match *self { match *self {
Outcome::Success => "Success", Outcome::Success(_) => true,
Outcome::Failure => "FailStop", _ => false
Outcome::Forward(..) => "Forward",
} }
} }
fn as_int(&self) -> isize { /// Return true if this `Outcome` is a `Failure`.
#[inline(always)]
pub fn is_failure(&self) -> bool {
match *self { match *self {
Outcome::Success => 0, Outcome::Failure(_) => true,
Outcome::Failure => 1, _ => false
Outcome::Forward(..) => 2,
} }
} }
pub fn expect_success(&self) { /// Return true if this `Outcome` is a `Forward`.
if *self != Outcome::Success { #[inline(always)]
panic!("expected a successful outcome"); pub fn is_forward(&self) -> bool {
match *self {
Outcome::Forward(_) => true,
_ => false
}
}
/// Converts from `Outcome<S, E, F>` to `Option<S>`.
///
/// Returns the `Some` of the `Success` if this is a `Success`, otherwise
/// returns `None`. `self` is consumed, and all other values are discarded.
#[inline(always)]
pub fn succeeded(self) -> Option<S> {
match self {
Outcome::Success(val) => Some(val),
_ => None
}
}
/// Converts from `Outcome<S, E, F>` to `Option<E>`.
///
/// Returns the `Some` of the `Failure` if this is a `Failure`, otherwise
/// returns `None`. `self` is consumed, and all other values are discarded.
#[inline(always)]
pub fn failed(self) -> Option<E> {
match self {
Outcome::Failure(val) => Some(val),
_ => None
}
}
/// Converts from `Outcome<S, E, F>` to `Option<F>`.
///
/// Returns the `Some` of the `Forward` if this is a `Forward`, otherwise
/// returns `None`. `self` is consumed, and all other values are discarded.
#[inline(always)]
pub fn forwarded(self) -> Option<F> {
match self {
Outcome::Forward(val) => Some(val),
_ => None
}
}
#[inline(always)]
fn formatting(&self) -> (Color, &'static str) {
match *self {
Outcome::Success(..) => (Green, "Succcess"),
Outcome::Failure(..) => (Red, "Failure"),
Outcome::Forward(..) => (Yellow, "Forward"),
} }
} }
} }
impl<T> PartialEq for Outcome<T> { impl<S, E, F> fmt::Debug for Outcome<S, E, F> {
fn eq(&self, other: &Outcome<T>) -> bool {
self.as_int() == other.as_int()
}
}
impl<T> fmt::Debug for Outcome<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Outcome::{}", self.as_str()) write!(f, "Outcome::{}", self.formatting().1)
} }
} }
impl<T> fmt::Display for Outcome<T> { impl<S, E, F> fmt::Display for Outcome<S, E, F> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self { let (color, string) = self.formatting();
Outcome::Success => write!(f, "{}", Green.paint("Success")), write!(f, "{}", color.paint(string))
Outcome::Failure => write!(f, "{}", Red.paint("Failure")),
Outcome::Forward(..) => write!(f, "{}", Cyan.paint("Forwarding")),
}
} }
} }

View File

@ -1,63 +1,61 @@
use std::fmt::Debug; use outcome::Outcome;
use http::StatusCode;
use request::{Request, Data}; use request::{Request, Data};
pub type DataOutcome<S, E> = Outcome<S, (StatusCode, E), Data>;
impl<S, E> DataOutcome<S, E> {
#[inline(always)]
pub fn of(result: Result<S, E>) -> Self {
match result {
Ok(val) => DataOutcome::success(val),
Err(err) => DataOutcome::failure(StatusCode::InternalServerError, err)
}
}
#[inline(always)]
pub fn success(s: S) -> Self {
Outcome::Success(s)
}
#[inline(always)]
pub fn failure(status: StatusCode, e: E) -> Self {
Outcome::Failure((status, e))
}
#[inline(always)]
pub fn forward(data: Data) -> Self {
Outcome::Forward(data)
}
}
/// Trait used to derive an object from incoming request data. /// Trait used to derive an object from incoming request data.
pub trait FromData: Sized { pub trait FromData: Sized {
type Error = (); type Error;
fn from_data(request: &Request, data: Data) -> DataOutcome<Self, Self::Error>; fn from_data(request: &Request, data: Data) -> DataOutcome<Self, Self::Error>;
} }
impl<T: FromData> FromData for Result<T, T::Error> { impl<T: FromData> FromData for Result<T, T::Error> {
type Error = ();
fn from_data(request: &Request, data: Data) -> DataOutcome<Self, Self::Error> { fn from_data(request: &Request, data: Data) -> DataOutcome<Self, Self::Error> {
match T::from_data(request, data) { match T::from_data(request, data) {
DataOutcome::Success(val) => DataOutcome::Success(Ok(val)), Outcome::Success(val) => DataOutcome::success(Ok(val)),
DataOutcome::Failure(val) => DataOutcome::Success(Err(val)), Outcome::Failure((_, val)) => DataOutcome::success(Err(val)),
DataOutcome::Forward(data) => DataOutcome::Forward(data) Outcome::Forward(data) => DataOutcome::forward(data),
} }
} }
} }
impl<T: FromData> FromData for Option<T> { impl<T: FromData> FromData for Option<T> {
type Error = ();
fn from_data(request: &Request, data: Data) -> DataOutcome<Self, Self::Error> { fn from_data(request: &Request, data: Data) -> DataOutcome<Self, Self::Error> {
match T::from_data(request, data) { match T::from_data(request, data) {
DataOutcome::Success(val) => DataOutcome::Success(Some(val)), Outcome::Success(val) => DataOutcome::success(Some(val)),
DataOutcome::Failure(_) => DataOutcome::Success(None), Outcome::Failure(_) => DataOutcome::success(None),
DataOutcome::Forward(data) => DataOutcome::Forward(data) Outcome::Forward(_) => DataOutcome::success(None)
}
}
}
#[must_use]
pub enum DataOutcome<T, E> {
/// Signifies that all processing completed successfully.
Success(T),
/// Signifies that some processing occurred that ultimately resulted in
/// failure. As a result, no further processing can occur.
Failure(E),
/// Signifies that no processing occured and as such, processing can be
/// forwarded to the next available target.
Forward(Data),
}
impl<T, E: Debug> From<Result<T, E>> for DataOutcome<T, E> {
fn from(result: Result<T, E>) -> Self {
match result {
Ok(val) => DataOutcome::Success(val),
Err(e) => {
error_!("{:?}", e);
DataOutcome::Failure(e)
}
}
}
}
impl<T> From<Option<T>> for DataOutcome<T, ()> {
fn from(result: Option<T>) -> Self {
match result {
Some(val) => DataOutcome::Success(val),
None => DataOutcome::Failure(())
} }
} }
} }

View File

@ -155,7 +155,9 @@ impl Drop for Data {
} }
impl FromData for Data { impl FromData for Data {
type Error = ();
fn from_data(_: &Request, data: Data) -> DataOutcome<Self, Self::Error> { fn from_data(_: &Request, data: Data) -> DataOutcome<Self, Self::Error> {
DataOutcome::Success(data) DataOutcome::success(data)
} }
} }

View File

@ -9,7 +9,7 @@
//! ``` //! ```
//! //!
//! Form parameter types must implement the [FromForm](trait.FromForm.html) //! Form parameter types must implement the [FromForm](trait.FromForm.html)
//! trait, which is automatically derivable. Automatically deriving `FromForm` //! trait, which is automaForwarp derivable. Automatically deriving `FromForm`
//! for a structure requires that all of its fields implement //! for a structure requires that all of its fields implement
//! [FromFormValue](trait.FormFormValue.html). See the //! [FromFormValue](trait.FormFormValue.html). See the
//! [codegen](/rocket_codegen/) documentation or the [forms guide](/guide/forms) //! [codegen](/rocket_codegen/) documentation or the [forms guide](/guide/forms)
@ -27,6 +27,7 @@ use std::marker::PhantomData;
use std::fmt::{self, Debug}; use std::fmt::{self, Debug};
use std::io::Read; use std::io::Read;
use http::StatusCode;
use request::{Request, FromData, Data, DataOutcome}; use request::{Request, FromData, Data, DataOutcome};
// This works, and it's safe, but it sucks to have the lifetime appear twice. // This works, and it's safe, but it sucks to have the lifetime appear twice.
@ -103,20 +104,20 @@ impl<'f, T: FromForm<'f>> FromData for Form<'f, T> where T::Error: Debug {
fn from_data(request: &Request, data: Data) -> DataOutcome<Self, Self::Error> { fn from_data(request: &Request, data: Data) -> DataOutcome<Self, Self::Error> {
if !request.content_type().is_form() { if !request.content_type().is_form() {
warn_!("Form data does not have form content type."); warn_!("Form data does not have form content type.");
return DataOutcome::Forward(data); return DataOutcome::forward(data);
} }
let mut form_string = String::with_capacity(4096); let mut form_string = String::with_capacity(4096);
let mut stream = data.open().take(32768); let mut stream = data.open().take(32768);
if let Err(e) = stream.read_to_string(&mut form_string) { if let Err(e) = stream.read_to_string(&mut form_string) {
error_!("IO Error: {:?}", e); error_!("IO Error: {:?}", e);
DataOutcome::Failure(None) DataOutcome::failure(StatusCode::InternalServerError, None)
} else { } else {
match Form::new(form_string) { match Form::new(form_string) {
Ok(form) => DataOutcome::Success(form), Ok(form) => DataOutcome::success(form),
Err((form_string, e)) => { Err((form_string, e)) => {
error_!("Failed to parse value from form: {:?}", e); error_!("Failed to parse value from form: {:?}", e);
DataOutcome::Failure(Some(form_string)) DataOutcome::failure(StatusCode::BadRequest, Some(form_string))
} }
} }
} }

View File

@ -1,62 +1,95 @@
use std::fmt::Debug; use std::fmt::Debug;
use request::Request; use request::Request;
use http::{ContentType, Method, Cookies}; use outcome::Outcome;
use http::{StatusCode, ContentType, Method, Cookies};
pub type RequestOutcome<T, E> = Outcome<T, (StatusCode, E), ()>;
impl<T, E> RequestOutcome<T, E> {
#[inline(always)]
pub fn of(result: Result<T, E>) -> Self {
match result {
Ok(val) => Outcome::Success(val),
Err(_) => Outcome::Forward(())
}
}
#[inline(always)]
pub fn success(t: T) -> Self {
Outcome::Success(t)
}
#[inline(always)]
pub fn failure(code: StatusCode, error: E) -> Self {
Outcome::Failure((code, error))
}
#[inline(always)]
pub fn forward() -> Self {
Outcome::Forward(())
}
}
pub trait FromRequest<'r>: Sized { pub trait FromRequest<'r>: Sized {
type Error: Debug; type Error: Debug;
fn from_request(request: &'r Request) -> Result<Self, Self::Error>; fn from_request(request: &'r Request) -> RequestOutcome<Self, Self::Error>;
} }
impl<'r> FromRequest<'r> for &'r Request { impl<'r> FromRequest<'r> for &'r Request {
type Error = (); type Error = ();
fn from_request(request: &'r Request) -> Result<Self, Self::Error> { fn from_request(request: &'r Request) -> RequestOutcome<Self, Self::Error> {
Ok(request) RequestOutcome::success(request)
} }
} }
impl<'r> FromRequest<'r> for Method { impl<'r> FromRequest<'r> for Method {
type Error = (); type Error = ();
fn from_request(request: &'r Request) -> Result<Self, Self::Error> { fn from_request(request: &'r Request) -> RequestOutcome<Self, Self::Error> {
Ok(request.method) RequestOutcome::success(request.method)
} }
} }
impl<'r> FromRequest<'r> for &'r Cookies { impl<'r> FromRequest<'r> for &'r Cookies {
type Error = (); type Error = ();
fn from_request(request: &'r Request) -> Result<Self, Self::Error> {
Ok(request.cookies()) fn from_request(request: &'r Request) -> RequestOutcome<Self, Self::Error> {
RequestOutcome::success(request.cookies())
} }
} }
impl<'r> FromRequest<'r> for ContentType { impl<'r> FromRequest<'r> for ContentType {
type Error = (); type Error = ();
fn from_request(request: &'r Request) -> Result<Self, Self::Error> { fn from_request(request: &'r Request) -> RequestOutcome<Self, Self::Error> {
Ok(request.content_type()) RequestOutcome::success(request.content_type())
}
}
impl<'r, T: FromRequest<'r>> FromRequest<'r> for Option<T> {
type Error = ();
fn from_request(request: &'r Request) -> Result<Self, Self::Error> {
let opt = match T::from_request(request) {
Ok(v) => Some(v),
Err(_) => None,
};
Ok(opt)
} }
} }
impl<'r, T: FromRequest<'r>> FromRequest<'r> for Result<T, T::Error> { impl<'r, T: FromRequest<'r>> FromRequest<'r> for Result<T, T::Error> {
type Error = (); type Error = ();
fn from_request(request: &'r Request) -> Result<Self, Self::Error> { fn from_request(request: &'r Request) -> RequestOutcome<Self, Self::Error> {
Ok(T::from_request(request)) match T::from_request(request) {
Outcome::Success(val) => RequestOutcome::success(Ok(val)),
Outcome::Failure((_, e)) => RequestOutcome::success(Err(e)),
Outcome::Forward(_) => RequestOutcome::forward(),
}
} }
} }
impl<'r, T: FromRequest<'r>> FromRequest<'r> for Option<T> {
type Error = ();
fn from_request(request: &'r Request) -> RequestOutcome<Self, Self::Error> {
match T::from_request(request) {
Outcome::Success(val) => RequestOutcome::success(Some(val)),
Outcome::Failure(_) => RequestOutcome::success(None),
Outcome::Forward(_) => RequestOutcome::success(None),
}
}
}

View File

@ -7,7 +7,7 @@ mod data;
mod from_request; mod from_request;
pub use self::request::Request; pub use self::request::Request;
pub use self::from_request::FromRequest; pub use self::from_request::{FromRequest, RequestOutcome};
pub use self::param::{FromParam, FromSegments}; pub use self::param::{FromParam, FromSegments};
pub use self::form::{Form, FromForm, FromFormValue, FormItems}; pub use self::form::{Form, FromForm, FromFormValue, FormItems};
pub use self::data::{Data, FromData, DataOutcome}; pub use self::data::{Data, FromData, DataOutcome};

View File

@ -1,4 +1,4 @@
use response::{ResponseOutcome, Outcome, Responder}; use response::{ResponseOutcome, Responder};
use http::hyper::{FreshHyperResponse, StatusCode}; use http::hyper::{FreshHyperResponse, StatusCode};
#[derive(Debug)] #[derive(Debug)]
@ -6,6 +6,6 @@ pub struct Failure(pub StatusCode);
impl Responder for Failure { impl Responder for Failure {
fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> ResponseOutcome<'a> { fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> ResponseOutcome<'a> {
Outcome::Forward((self.0, res)) ResponseOutcome::forward(self.0, res)
} }
} }

View File

@ -1,7 +1,7 @@
use std::convert::AsRef; use std::convert::AsRef;
use response::{ResponseOutcome, Responder}; use response::{ResponseOutcome, Responder};
use request::{Request, FromRequest}; use request::{Request, FromRequest, RequestOutcome};
use http::hyper::{HyperSetCookie, HyperCookiePair, FreshHyperResponse}; use http::hyper::{HyperSetCookie, HyperCookiePair, FreshHyperResponse};
const FLASH_COOKIE_NAME: &'static str = "_flash"; const FLASH_COOKIE_NAME: &'static str = "_flash";
@ -70,18 +70,12 @@ impl Flash<()> {
// TODO: Using Flash<()> is ugly. Either create a type FlashMessage = Flash<()> // TODO: Using Flash<()> is ugly. Either create a type FlashMessage = Flash<()>
// or create a Flash under request that does this. // or create a Flash under request that does this.
// TODO: Consider not removing the 'flash' cookie until after this thing is
// dropped. This is because, at the moment, if Flash is including as a
// from_request param, and some other param fails, then the flash message will
// be dropped needlessly. This may or may not be the intended behavior.
// Alternatively, provide a guarantee about the order that from_request params
// will be evaluated and recommend that Flash is last.
impl<'r> FromRequest<'r> for Flash<()> { impl<'r> FromRequest<'r> for Flash<()> {
type Error = (); type Error = ();
fn from_request(request: &'r Request) -> Result<Self, Self::Error> { fn from_request(request: &'r Request) -> RequestOutcome<Self, Self::Error> {
trace_!("Flash: attemping to retrieve message."); trace_!("Flash: attemping to retrieve message.");
request.cookies().find(FLASH_COOKIE_NAME).ok_or(()).and_then(|cookie| { let r = request.cookies().find(FLASH_COOKIE_NAME).ok_or(()).and_then(|cookie| {
// Clear the flash message. // Clear the flash message.
trace_!("Flash: retrieving message: {:?}", cookie); trace_!("Flash: retrieving message: {:?}", cookie);
request.cookies().remove(FLASH_COOKIE_NAME); request.cookies().remove(FLASH_COOKIE_NAME);
@ -96,6 +90,8 @@ impl<'r> FromRequest<'r> for Flash<()> {
let name_len: usize = len_str.parse().map_err(|_| ())?; let name_len: usize = len_str.parse().map_err(|_| ())?;
let (name, msg) = (&rest[..name_len], &rest[name_len..]); let (name, msg) = (&rest[..name_len], &rest[name_len..]);
Ok(Flash::named(name, msg)) Ok(Flash::named(name, msg))
}) });
RequestOutcome::of(r)
} }
} }

View File

@ -18,56 +18,63 @@ pub use self::data::Content;
pub use self::failure::Failure; pub use self::failure::Failure;
pub use outcome::Outcome; pub use outcome::Outcome;
use std::fmt;
use request::Data; use request::Data;
use http::hyper::{StatusCode, FreshHyperResponse}; use http::hyper::{StatusCode, FreshHyperResponse};
use term_painter::Color::*;
use term_painter::ToStyle;
pub type ResponseOutcome<'a> = Outcome<(StatusCode, FreshHyperResponse<'a>)>; pub type ResponseOutcome<'a> = Outcome<(), (), (StatusCode, FreshHyperResponse<'a>)>;
pub enum Response<'a> { impl<'a> ResponseOutcome<'a> {
Forward(Data), #[inline(always)]
Complete(Box<Responder + 'a>) pub fn of<A, B>(result: Result<A, B>) -> Self {
match result {
Ok(_) => Outcome::Success(()),
Err(_) => Outcome::Failure(())
}
}
#[inline(always)]
pub fn success() -> ResponseOutcome<'a> {
Outcome::Success(())
}
#[inline(always)]
pub fn failure() -> ResponseOutcome<'a> {
Outcome::Failure(())
}
#[inline(always)]
pub fn forward(s: StatusCode, r: FreshHyperResponse<'a>) -> ResponseOutcome<'a> {
Outcome::Forward((s, r))
}
} }
pub type Response<'a> = Outcome<Box<Responder + 'a>, StatusCode, Data>;
impl<'a> Response<'a> { impl<'a> Response<'a> {
#[inline(always)] #[inline(always)]
pub fn complete<T: Responder + 'a>(body: T) -> Response<'a> { pub fn success<T: Responder + 'a>(responder: T) -> Response<'a> {
Response::Complete(Box::new(body)) Outcome::Success(Box::new(responder))
}
#[inline(always)]
pub fn failure(code: StatusCode) -> Response<'static> {
Outcome::Failure(code)
} }
#[inline(always)] #[inline(always)]
pub fn forward(data: Data) -> Response<'static> { pub fn forward(data: Data) -> Response<'static> {
Response::Forward(data) Outcome::Forward(data)
}
#[inline(always)]
pub fn failed(code: StatusCode) -> Response<'static> {
Response::complete(Failure(code))
} }
#[inline(always)] #[inline(always)]
pub fn with_raw_status<T: Responder + 'a>(status: u16, body: T) -> Response<'a> { pub fn with_raw_status<T: Responder + 'a>(status: u16, body: T) -> Response<'a> {
let status_code = StatusCode::from_u16(status); let status_code = StatusCode::from_u16(status);
Response::complete(StatusResponse::new(status_code, body)) Response::success(StatusResponse::new(status_code, body))
} }
#[doc(hidden)] #[doc(hidden)]
#[inline(always)] #[inline(always)]
pub fn responder(self) -> Option<Box<Responder + 'a>> { pub fn responder(self) -> Option<Box<Responder + 'a>> {
match self { self.succeeded()
Response::Complete(responder) => Some(responder),
_ => None
}
}
}
impl<'a> fmt::Display for Response<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Response::Complete(..) => write!(f, "{}", Green.paint("Complete")),
Response::Forward(..) => write!(f, "{}", Yellow.paint("Forwarding")),
}
} }
} }

View File

@ -1,4 +1,4 @@
use response::{ResponseOutcome, Outcome, Responder}; use response::{ResponseOutcome, Responder};
use http::hyper::{header, FreshHyperResponse, StatusCode}; use http::hyper::{header, FreshHyperResponse, StatusCode};
#[derive(Debug)] #[derive(Debug)]
@ -31,6 +31,6 @@ impl<'a> Responder for Redirect {
res.headers_mut().set(header::ContentLength(0)); res.headers_mut().set(header::ContentLength(0));
res.headers_mut().set(header::Location(self.1.clone())); res.headers_mut().set(header::Location(self.1.clone()));
*(res.status_mut()) = self.0; *(res.status_mut()) = self.0;
Outcome::of(res.send(b"")) ResponseOutcome::of(res.send(b""))
} }
} }

View File

@ -2,7 +2,7 @@ use std::io::{Read, Write};
use std::fs::File; use std::fs::File;
use std::fmt; use std::fmt;
use response::{Outcome, ResponseOutcome}; use response::ResponseOutcome;
use http::mime::{Mime, TopLevel, SubLevel}; use http::mime::{Mime, TopLevel, SubLevel};
use http::hyper::{header, FreshHyperResponse, StatusCode}; use http::hyper::{header, FreshHyperResponse, StatusCode};
@ -21,7 +21,7 @@ impl<'a> Responder for &'a str {
res.headers_mut().set(header::ContentType(mime)); res.headers_mut().set(header::ContentType(mime));
} }
Outcome::of(res.send(self.as_bytes())) ResponseOutcome::of(res.send(self.as_bytes()))
} }
} }
@ -32,7 +32,7 @@ impl Responder for String {
res.headers_mut().set(header::ContentType(mime)); res.headers_mut().set(header::ContentType(mime));
} }
Outcome::of(res.send(self.as_bytes())) ResponseOutcome::of(res.send(self.as_bytes()))
} }
} }
@ -42,18 +42,18 @@ impl Responder for File {
Ok(md) => md.len(), Ok(md) => md.len(),
Err(e) => { Err(e) => {
error_!("Failed to read file metadata: {:?}", e); error_!("Failed to read file metadata: {:?}", e);
return Outcome::Forward((StatusCode::InternalServerError, res)); return ResponseOutcome::forward(StatusCode::InternalServerError, res);
} }
}; };
let mut v = Vec::new(); let mut v = Vec::new();
if let Err(e) = self.read_to_end(&mut v) { if let Err(e) = self.read_to_end(&mut v) {
error_!("Failed to read file: {:?}", e); error_!("Failed to read file: {:?}", e);
return Outcome::Forward((StatusCode::InternalServerError, res)); return ResponseOutcome::forward(StatusCode::InternalServerError, res);
} }
res.headers_mut().set(header::ContentLength(size)); res.headers_mut().set(header::ContentLength(size));
Outcome::of(res.start().and_then(|mut stream| stream.write_all(&v))) ResponseOutcome::of(res.start().and_then(|mut stream| stream.write_all(&v)))
} }
} }
@ -63,7 +63,7 @@ impl<T: Responder> Responder for Option<T> {
val.respond(res) val.respond(res)
} else { } else {
warn_!("Response was `None`."); warn_!("Response was `None`.");
Outcome::Forward((StatusCode::NotFound, res)) ResponseOutcome::forward(StatusCode::NotFound, res)
} }
} }
} }
@ -75,7 +75,7 @@ impl<T: Responder, E: fmt::Debug> Responder for Result<T, E> {
Ok(ref mut val) => val.respond(res), Ok(ref mut val) => val.respond(res),
Err(ref e) => { Err(ref e) => {
error_!("{:?}", e); error_!("{:?}", e);
Outcome::Forward((StatusCode::InternalServerError, res)) ResponseOutcome::forward(StatusCode::InternalServerError, res)
} }
} }
} }

View File

@ -1,6 +1,6 @@
use std::io::{Read, Write, ErrorKind}; use std::io::{Read, Write, ErrorKind};
use response::{Responder, Outcome, ResponseOutcome}; use response::{Responder, ResponseOutcome};
use http::hyper::FreshHyperResponse; use http::hyper::FreshHyperResponse;
// TODO: Support custom chunk sizes. // TODO: Support custom chunk sizes.
@ -31,7 +31,7 @@ impl<T: Read> Responder for Stream<T> {
Ok(s) => s, Ok(s) => s,
Err(e) => { Err(e) => {
error_!("Failed opening response stream: {:?}", e); error_!("Failed opening response stream: {:?}", e);
return Outcome::Failure; return ResponseOutcome::failure();
} }
}; };
@ -46,22 +46,22 @@ impl<T: Read> Responder for Stream<T> {
Err(ref e) if e.kind() == ErrorKind::Interrupted => continue, Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
Err(ref e) => { Err(ref e) => {
error_!("Error streaming response: {:?}", e); error_!("Error streaming response: {:?}", e);
return Outcome::Failure; return ResponseOutcome::failure();
} }
} }
} }
if let Err(e) = stream.write_all(&buffer[..read]) { if let Err(e) = stream.write_all(&buffer[..read]) {
error_!("Stream write_all() failed: {:?}", e); error_!("Stream write_all() failed: {:?}", e);
return Outcome::Failure; return ResponseOutcome::failure();
} }
} }
if let Err(e) = stream.end() { if let Err(e) = stream.end() {
error_!("Stream end() failed: {:?}", e); error_!("Stream end() failed: {:?}", e);
return Outcome::Failure; return ResponseOutcome::failure();
} }
Outcome::Success ResponseOutcome::success()
} }
} }

View File

@ -9,7 +9,6 @@ use term_painter::ToStyle;
use config; use config;
use logger; use logger;
use request::{Request, Data, FormItems}; use request::{Request, Data, FormItems};
use response::Response;
use router::{Router, Route}; use router::{Router, Route};
use catcher::{self, Catcher}; use catcher::{self, Catcher};
use outcome::Outcome; use outcome::Outcome;
@ -86,8 +85,11 @@ impl Rocket {
// to be forwarded. If it does, continue the loop to try again. // to be forwarded. If it does, continue the loop to try again.
info_!("{} {}", White.paint("Response:"), response); info_!("{} {}", White.paint("Response:"), response);
let mut responder = match response { let mut responder = match response {
Response::Complete(responder) => responder, Outcome::Success(responder) => responder,
Response::Forward(unused_data) => { Outcome::Failure(status_code) => {
return self.handle_error(status_code, &request, res);
}
Outcome::Forward(unused_data) => {
data = unused_data; data = unused_data;
continue; continue;
} }
@ -104,9 +106,9 @@ impl Rocket {
info_!("{} {}", White.paint("Outcome:"), outcome); info_!("{} {}", White.paint("Outcome:"), outcome);
// Check if the responder wants to forward to a catcher. // Check if the responder wants to forward to a catcher.
match outcome { match outcome.forwarded() {
Outcome::Forward((c, r)) => return self.handle_error(c, &request, r), Some((c, r)) => return self.handle_error(c, &request, r),
Outcome::Success | Outcome::Failure => return, None => return
}; };
} }
@ -149,7 +151,7 @@ impl Rocket {
}); });
if let Some(mut responder) = catcher.handle(Error::NoRoute, req).responder() { if let Some(mut responder) = catcher.handle(Error::NoRoute, req).responder() {
if responder.respond(response) != Outcome::Success { if !responder.respond(response).is_success() {
error_!("Catcher outcome was unsuccessul; aborting response."); error_!("Catcher outcome was unsuccessul; aborting response.");
} else { } else {
info_!("Responded with catcher."); info_!("Responded with catcher.");
@ -160,7 +162,7 @@ impl Rocket {
let catcher = self.default_catchers.get(&code.to_u16()) let catcher = self.default_catchers.get(&code.to_u16())
.unwrap_or(self.default_catchers.get(&500).expect("500 default")); .unwrap_or(self.default_catchers.get(&500).expect("500 default"));
let responder = catcher.handle(Error::Internal, req).responder(); let responder = catcher.handle(Error::Internal, req).responder();
responder.unwrap().respond(response).expect_success() responder.unwrap().respond(response).unwrap()
} }
} }

View File

@ -60,7 +60,7 @@ mod tests {
type SimpleRoute = (Method, &'static str); type SimpleRoute = (Method, &'static str);
fn dummy_handler(_req: &Request, _: Data) -> Response<'static> { fn dummy_handler(_req: &Request, _: Data) -> Response<'static> {
Response::complete("hi") Response::success("hi")
} }
fn m_collide(a: SimpleRoute, b: SimpleRoute) -> bool { fn m_collide(a: SimpleRoute, b: SimpleRoute) -> bool {

View File

@ -75,7 +75,7 @@ mod test {
use {Response, Request, Data}; use {Response, Request, Data};
fn dummy_handler(_req: &Request, _: Data) -> Response<'static> { fn dummy_handler(_req: &Request, _: Data) -> Response<'static> {
Response::complete("hi") Response::success("hi")
} }
fn router_with_routes(routes: &[&'static str]) -> Router { fn router_with_routes(routes: &[&'static str]) -> Router {