mirror of https://github.com/rwf2/Rocket.git
Progress on errors. Started Todo example.
The error function now takes in a "RoutingError" structure. The idea is that the structure includes all of the information necessary for a user to processor the error as they wish. This interface is very incomplete and may change. At a minimum, the error structure should include: 1) The request that failed. 2) Why the request failed. 3) The chain of attempted route matches, if any. 4) Something else?
This commit is contained in:
parent
5870c2fe92
commit
26b7b814f4
|
@ -2,7 +2,7 @@
|
||||||
#![plugin(rocket_macros)]
|
#![plugin(rocket_macros)]
|
||||||
|
|
||||||
extern crate rocket;
|
extern crate rocket;
|
||||||
use rocket::Rocket;
|
use rocket::{Rocket, RoutingError};
|
||||||
|
|
||||||
#[route(GET, path = "/hello/<name>/<age>")]
|
#[route(GET, path = "/hello/<name>/<age>")]
|
||||||
fn hello(name: &str, age: i8) -> String {
|
fn hello(name: &str, age: i8) -> String {
|
||||||
|
@ -10,8 +10,10 @@ fn hello(name: &str, age: i8) -> String {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[error(code = "404")]
|
#[error(code = "404")]
|
||||||
fn not_found() -> &'static str {
|
fn not_found(error: RoutingError) -> String {
|
||||||
"<h1>Sorry pal.</h1><p>Go to '/hello/<name>/<age>' instead.</p>"
|
format!("<p>Sorry, but '{}' is not a valid path!</p>
|
||||||
|
<p>Try visiting /hello/<name>/<age> instead.</p>",
|
||||||
|
error.request.uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
[package]
|
||||||
|
name = "extended_validation"
|
||||||
|
version = "0.0.1"
|
||||||
|
authors = ["Sergio Benitez <sb@sergio.bz>"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rocket = { path = "../../lib" }
|
||||||
|
rocket_macros = { path = "../../macros" }
|
|
@ -0,0 +1,13 @@
|
||||||
|
use rocket;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::Error as IOError;
|
||||||
|
|
||||||
|
#[route(GET, path = "/")]
|
||||||
|
pub fn index() -> File {
|
||||||
|
File::open("static/index.html").unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[route(GET, path = "/<file>")]
|
||||||
|
pub fn files(file: &str) -> Result<File, IOError> {
|
||||||
|
File::open(format!("static/{}", file))
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
#![feature(plugin, custom_derive)]
|
||||||
|
#![plugin(rocket_macros)]
|
||||||
|
|
||||||
|
extern crate rocket;
|
||||||
|
|
||||||
|
mod files;
|
||||||
|
|
||||||
|
use rocket::Rocket;
|
||||||
|
use rocket::response::Redirect;
|
||||||
|
use rocket::form::FromFormValue;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct StrongPassword<'r>(&'r str);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct AdultAge(isize);
|
||||||
|
|
||||||
|
#[derive(FromForm)]
|
||||||
|
struct UserLogin<'r> {
|
||||||
|
username: &'r str,
|
||||||
|
password: Result<StrongPassword<'r>, &'r str>,
|
||||||
|
age: Result<AdultAge, &'r str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'v> FromFormValue<'v> for StrongPassword<'v> {
|
||||||
|
type Error = &'static str;
|
||||||
|
|
||||||
|
fn parse(v: &'v str) -> Result<Self, Self::Error> {
|
||||||
|
if v.len() < 8 {
|
||||||
|
Err("Too short!")
|
||||||
|
} else {
|
||||||
|
Ok(StrongPassword(v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'v> FromFormValue<'v> for AdultAge {
|
||||||
|
type Error = &'static str;
|
||||||
|
|
||||||
|
fn parse(v: &'v str) -> Result<Self, Self::Error> {
|
||||||
|
let age = match isize::parse(v) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => return Err("Age value is not a number.")
|
||||||
|
};
|
||||||
|
|
||||||
|
match age > 20 {
|
||||||
|
true => Ok(AdultAge(age)),
|
||||||
|
false => Err("Must be at least 21.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[route(POST, path = "/login", form = "<user>")]
|
||||||
|
fn login(user: UserLogin) -> Result<Redirect, String> {
|
||||||
|
if user.age.is_err() {
|
||||||
|
return Err(String::from(user.age.unwrap_err()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.password.is_err() {
|
||||||
|
return Err(String::from(user.password.unwrap_err()));
|
||||||
|
}
|
||||||
|
|
||||||
|
match user.username {
|
||||||
|
"Sergio" => match user.password.unwrap().0 {
|
||||||
|
"password" => Ok(Redirect::other("/user/Sergio")),
|
||||||
|
_ => Err("Wrong password!".to_string())
|
||||||
|
},
|
||||||
|
_ => Err(format!("Unrecognized user, '{}'.", user.username))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[route(GET, path = "/user/<username>")]
|
||||||
|
fn user_page(username: &str) -> String {
|
||||||
|
format!("This is {}'s page.", username)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut rocket = Rocket::new("localhost", 8000);
|
||||||
|
rocket.mount("/", routes![files::index, files::files, user_page, login]);
|
||||||
|
rocket.launch();
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
<h1>Login</h1>
|
||||||
|
|
||||||
|
<form action="/login" method="post" accept-charset="utf-8">
|
||||||
|
Username:<input type="text" name="username">
|
||||||
|
Password:<input type="password" name="password">
|
||||||
|
Age:<input type="number" name="age">
|
||||||
|
<input type="submit" value="Login">
|
||||||
|
</form>
|
|
@ -0,0 +1,8 @@
|
||||||
|
[package]
|
||||||
|
name = "todo"
|
||||||
|
version = "0.0.1"
|
||||||
|
authors = ["Sergio Benitez <sb@sergio.bz>"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rocket = { path = "../../lib" }
|
||||||
|
rocket_macros = { path = "../../macros" }
|
|
@ -0,0 +1,39 @@
|
||||||
|
#![feature(plugin, custom_derive)]
|
||||||
|
#![plugin(rocket_macros)]
|
||||||
|
|
||||||
|
extern crate rocket;
|
||||||
|
|
||||||
|
use rocket::Rocket;
|
||||||
|
use rocket::response::Redirect;
|
||||||
|
|
||||||
|
#[derive(FromForm)]
|
||||||
|
struct Todo<'r> {
|
||||||
|
description: &'r str,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[route(POST, path = "/todo", form = "<todo>")]
|
||||||
|
fn new_todo(todo: Todo) -> Result<Redirect, &'static str> {
|
||||||
|
// if todos.add(todo).is_ok() {
|
||||||
|
// Ok(Redirect::to("/"))
|
||||||
|
// } else {
|
||||||
|
// Err("Could not add todo to list.")
|
||||||
|
// }
|
||||||
|
|
||||||
|
Ok(Redirect::to("/"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[route(GET, path = "/todos")]
|
||||||
|
fn list_todos() -> &'static str {
|
||||||
|
"List all of the todos here!"
|
||||||
|
}
|
||||||
|
|
||||||
|
#[route(GET, path = "/")]
|
||||||
|
fn index() -> Redirect {
|
||||||
|
Redirect::to("/todos")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut rocket = Rocket::new("localhost", 8000);
|
||||||
|
rocket.mount("/", routes![index, list_todos, new_todo]);
|
||||||
|
rocket.launch();
|
||||||
|
}
|
|
@ -1,4 +1,8 @@
|
||||||
use handler::Handler;
|
use handler::Handler;
|
||||||
|
use error::Error;
|
||||||
|
use response::Response;
|
||||||
|
use request::Request;
|
||||||
|
use error::RoutingError;
|
||||||
use codegen::StaticCatchInfo;
|
use codegen::StaticCatchInfo;
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
@ -7,15 +11,23 @@ use term_painter::Color::*;
|
||||||
|
|
||||||
pub struct Catcher {
|
pub struct Catcher {
|
||||||
pub code: u16,
|
pub code: u16,
|
||||||
pub handler: Handler,
|
handler: Handler,
|
||||||
is_default: bool
|
is_default: bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Should `Catcher` be an interface? Should there be an `ErrorHandler`
|
||||||
|
// that takes in a `RoutingError` and returns a `Response`? What's the right
|
||||||
|
// interface here?
|
||||||
|
|
||||||
impl Catcher {
|
impl Catcher {
|
||||||
pub fn new(code: u16, handler: Handler) -> Catcher {
|
pub fn new(code: u16, handler: Handler) -> Catcher {
|
||||||
Catcher::new_with_default(code, handler, false)
|
Catcher::new_with_default(code, handler, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn handle<'a>(&'a self, error: RoutingError<'a>) -> Response {
|
||||||
|
(self.handler)(error.request)
|
||||||
|
}
|
||||||
|
|
||||||
fn new_with_default(code: u16, handler: Handler, default: bool) -> Catcher {
|
fn new_with_default(code: u16, handler: Handler, default: bool) -> Catcher {
|
||||||
Catcher {
|
Catcher {
|
||||||
code: code,
|
code: code,
|
||||||
|
@ -47,13 +59,6 @@ pub mod defaults {
|
||||||
use super::Catcher;
|
use super::Catcher;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
pub fn get() -> HashMap<u16, Catcher> {
|
|
||||||
let mut map = HashMap::new();
|
|
||||||
map.insert(404, Catcher::new_with_default(404, not_found, true));
|
|
||||||
map.insert(500, Catcher::new_with_default(500, internal_error, true));
|
|
||||||
map
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn not_found(_request: Request) -> Response {
|
pub fn not_found(_request: Request) -> Response {
|
||||||
Response::with_status(StatusCode::NotFound, "\
|
Response::with_status(StatusCode::NotFound, "\
|
||||||
<head>\
|
<head>\
|
||||||
|
@ -82,5 +87,11 @@ pub mod defaults {
|
||||||
<small>Rocket</small>\
|
<small>Rocket</small>\
|
||||||
")
|
")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
pub fn get() -> HashMap<u16, Catcher> {
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
map.insert(404, Catcher::new_with_default(404, not_found, true));
|
||||||
|
map.insert(500, Catcher::new_with_default(500, internal_error, true));
|
||||||
|
map
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use request::Request;
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
BadMethod,
|
BadMethod,
|
||||||
|
@ -5,3 +7,29 @@ pub enum Error {
|
||||||
NoRoute,
|
NoRoute,
|
||||||
NoKey
|
NoKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct RoutingError<'r> {
|
||||||
|
pub error: Error,
|
||||||
|
pub request: Request<'r>,
|
||||||
|
pub chain: Option<&'r [&'r str]>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> RoutingError<'a> {
|
||||||
|
pub fn unchained(request: Request<'a>)
|
||||||
|
-> RoutingError<'a> {
|
||||||
|
RoutingError {
|
||||||
|
error: Error::NoRoute,
|
||||||
|
request: request,
|
||||||
|
chain: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(error: Error, request: Request<'a>, chain: &'a [&'a str])
|
||||||
|
-> RoutingError<'a> {
|
||||||
|
RoutingError {
|
||||||
|
error: error,
|
||||||
|
request: request,
|
||||||
|
chain: Some(chain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ pub use codegen::{StaticRouteInfo, StaticCatchInfo};
|
||||||
pub use request::Request;
|
pub use request::Request;
|
||||||
pub use method::Method;
|
pub use method::Method;
|
||||||
pub use response::{Response, Responder};
|
pub use response::{Response, Responder};
|
||||||
pub use error::Error;
|
pub use error::{Error, RoutingError};
|
||||||
pub use param::FromParam;
|
pub use param::FromParam;
|
||||||
pub use router::{Router, Route};
|
pub use router::{Router, Route};
|
||||||
pub use catcher::Catcher;
|
pub use catcher::Catcher;
|
||||||
|
|
|
@ -1,18 +1,22 @@
|
||||||
use error::Error;
|
use error::Error;
|
||||||
use param::FromParam;
|
use param::FromParam;
|
||||||
|
use method::Method;
|
||||||
|
|
||||||
pub use hyper::server::Request as HyperRequest;
|
pub use hyper::server::Request as HyperRequest;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Request<'a> {
|
pub struct Request<'a> {
|
||||||
params: Vec<&'a str>,
|
params: Option<Vec<&'a str>>,
|
||||||
|
pub method: Method,
|
||||||
pub uri: &'a str,
|
pub uri: &'a str,
|
||||||
pub data: &'a [u8]
|
pub data: &'a [u8]
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Request<'a> {
|
impl<'a> Request<'a> {
|
||||||
pub fn new(params: Vec<&'a str>, uri: &'a str, data: &'a [u8]) -> Request<'a> {
|
pub fn new(method: Method, uri: &'a str, params: Option<Vec<&'a str>>,
|
||||||
|
data: &'a [u8]) -> Request<'a> {
|
||||||
Request {
|
Request {
|
||||||
|
method: method,
|
||||||
params: params,
|
params: params,
|
||||||
uri: uri,
|
uri: uri,
|
||||||
data: data
|
data: data
|
||||||
|
@ -24,10 +28,10 @@ impl<'a> Request<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_param<T: FromParam<'a>>(&'a self, n: usize) -> Result<T, Error> {
|
pub fn get_param<T: FromParam<'a>>(&'a self, n: usize) -> Result<T, Error> {
|
||||||
if n >= self.params.len() {
|
if self.params.is_none() || n >= self.params.as_ref().unwrap().len() {
|
||||||
Err(Error::NoKey)
|
Err(Error::NoKey)
|
||||||
} else {
|
} else {
|
||||||
T::from_param(self.params[n])
|
T::from_param(self.params.as_ref().unwrap()[n])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,10 +80,11 @@ impl Rocket {
|
||||||
|
|
||||||
// A closure which we call when we know there is no route.
|
// A closure which we call when we know there is no route.
|
||||||
let handle_not_found = |response: FreshHyperResponse| {
|
let handle_not_found = |response: FreshHyperResponse| {
|
||||||
let request = Request::new(vec![], uri, &buf);
|
|
||||||
let handler_404 = self.catchers.get(&404).unwrap().handler;
|
|
||||||
println!("{}", Red.paint("\t<= Dispatch failed. Returning 404."));
|
println!("{}", Red.paint("\t<= Dispatch failed. Returning 404."));
|
||||||
handler_404(request).respond(response);
|
|
||||||
|
let request = Request::new(method, uri, None, &buf);
|
||||||
|
let catcher = self.catchers.get(&404).unwrap();
|
||||||
|
catcher.handle(RoutingError::unchained(request)).respond(response);
|
||||||
};
|
};
|
||||||
|
|
||||||
// No route found. Handle the not_found error and return.
|
// No route found. Handle the not_found error and return.
|
||||||
|
@ -93,17 +94,20 @@ impl Rocket {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Okay, we've got a route. Unwrap it, generate a request, and try to
|
// Okay, we've got a route. Unwrap it, generate a request, and try to
|
||||||
// dispatch. TODO: keep trying lower ranked routes before dispatching a
|
// dispatch.
|
||||||
// not found error.
|
|
||||||
println!("\t=> {}", Magenta.paint("Dispatching request."));
|
println!("\t=> {}", Magenta.paint("Dispatching request."));
|
||||||
let route = route.unwrap();
|
let route = route.unwrap();
|
||||||
let params = route.get_params(uri);
|
let params = route.get_params(uri);
|
||||||
let request = Request::new(params, uri, &buf);
|
let request = Request::new(method, uri, Some(params), &buf);
|
||||||
let outcome = (route.handler)(request).respond(res);
|
let outcome = (route.handler)(request).respond(res);
|
||||||
|
|
||||||
|
// TODO: keep trying lower ranked routes before dispatching a not found
|
||||||
|
// error.
|
||||||
println!("\t=> {} {}", White.paint("Outcome:"), outcome);
|
println!("\t=> {} {}", White.paint("Outcome:"), outcome);
|
||||||
outcome.map_forward(|res| {
|
outcome.map_forward(|res| {
|
||||||
println!("{}", Red.paint("\t=> No further matching routes."));
|
println!("{}", Red.paint("\t=> No further matching routes."));
|
||||||
|
// TODO: Have some way to know why this was failed forward. Use that
|
||||||
|
// instead of always using an unchained error.
|
||||||
handle_not_found(res);
|
handle_not_found(res);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,7 +62,7 @@ pub fn error_decorator(ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem,
|
||||||
fn $catch_fn_name<'rocket>(_req: rocket::Request<'rocket>)
|
fn $catch_fn_name<'rocket>(_req: rocket::Request<'rocket>)
|
||||||
-> rocket::Response<'rocket> {
|
-> rocket::Response<'rocket> {
|
||||||
// TODO: Figure out what type signature of catcher should be.
|
// TODO: Figure out what type signature of catcher should be.
|
||||||
let result = $fn_name();
|
let result = $fn_name(RoutingError::unchained(_req));
|
||||||
rocket::Response::with_raw_status($catch_code, result)
|
rocket::Response::with_raw_status($catch_code, result)
|
||||||
}
|
}
|
||||||
).unwrap();
|
).unwrap();
|
||||||
|
|
Loading…
Reference in New Issue