From 45131f36dc371b50559fa2a85df3a63da5d25b74 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 16 Jul 2014 11:47:27 -0700 Subject: [PATCH] Sane error handling --- src/app.rs | 2 +- src/macros.rs | 16 +++ src/main.rs | 27 +++-- src/package.rs | 111 ++++++++++------- src/user/middleware.rs | 16 ++- src/user/mod.rs | 108 +++++++++-------- src/util/errors.rs | 225 +++++++++++++++++++++++++++++++++++ src/{util.rs => util/mod.rs} | 38 +++--- src/util/result.rs | 27 +++++ 9 files changed, 444 insertions(+), 126 deletions(-) create mode 100644 src/macros.rs create mode 100644 src/util/errors.rs rename src/{util.rs => util/mod.rs} (68%) create mode 100644 src/util/result.rs diff --git a/src/app.rs b/src/app.rs index 48b733bdfe..76992f32d0 100644 --- a/src/app.rs +++ b/src/app.rs @@ -73,7 +73,7 @@ pub trait RequestApp<'a> { fn app(self) -> &'a App; } -impl<'a> RequestApp<'a> for &'a mut Request { +impl<'a> RequestApp<'a> for &'a Request { fn app(self) -> &'a App { &**self.extensions().find(&"crates.io.app") .and_then(|a| a.as_ref::>()) diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 0000000000..017beecb60 --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,16 @@ +#![macro_escape] + +macro_rules! try( ($expr:expr) => ({ + use util::errors::FromError; + match $expr.map_err(FromError::from_error) { + Ok(val) => val, Err(err) => return Err(err) + } +}) ) + +macro_rules! raw_try( ($expr:expr) => ( + match $expr { Ok(val) => val, Err(err) => return Err(err) } +) ) + +macro_rules! try_option( ($e:expr) => ( + match $e { Some(k) => k, None => return None } +) ) diff --git a/src/main.rs b/src/main.rs index ea0af5d1b5..cda1dc2000 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,10 +26,9 @@ use conduit_router::RouteBuilder; use conduit_middleware::MiddlewareBuilder; use app::App; +use util::C; -macro_rules! try_option( ($e:expr) => ( - match $e { Some(k) => k, None => return None } -) ) +mod macros; mod app; mod db; @@ -40,18 +39,23 @@ mod util; fn main() { let mut router = RouteBuilder::new(); - router.get("/authorize_url", user::github_authorize); - router.get("/authorize", user::github_access_token); - router.get("/logout", user::logout); - router.get("/me", user::me); - router.put("/me/reset_token", user::reset_token); - router.get("/packages", package::index); - router.get("/packages/:package_id", package::show); + router.get("/authorize_url", C(user::github_authorize)); + router.get("/authorize", C(user::github_access_token)); + router.get("/logout", C(user::logout)); + router.get("/me", C(user::me)); + router.put("/me/reset_token", C(user::reset_token)); + router.get("/packages", C(package::index)); + router.get("/packages/:package_id", C(package::show)); router.put("/packages/:package_id", { - let mut m = MiddlewareBuilder::new(package::update); + let mut m = MiddlewareBuilder::new(C(package::update)); m.add(conduit_json_parser::BodyReader::); m }); + router.post("/packages/new", { + let mut m = MiddlewareBuilder::new(C(package::new)); + m.add(conduit_json_parser::BodyReader::); + m + }); let app = App::new(); @@ -69,7 +73,6 @@ fn main() { wait_for_sigint(); } - // libnative doesn't have signal handling yet fn wait_for_sigint() { use green::{SchedPool, PoolConfig, GreenTaskBuilder}; diff --git a/src/package.rs b/src/package.rs index 9a59c28c3a..cb068f5a3f 100644 --- a/src/package.rs +++ b/src/package.rs @@ -1,13 +1,12 @@ -use std::io::IoResult; - use conduit::{Request, Response}; use conduit_router::RequestParams; use conduit_json_parser; use pg::{PostgresConnection, PostgresRow}; use app::{App, RequestApp}; -use user::RequestUser; -use util::RequestUtils; +use user::{RequestUser, User}; +use util::{RequestUtils, CargoResult, Require, internal}; +use util::errors::{NotFound, CargoError}; #[deriving(Encodable)] pub struct Package { @@ -23,16 +22,27 @@ impl Package { } } - pub fn find(app: &App, slug: &str) -> Option { + pub fn find(app: &App, slug: &str) -> CargoResult { let conn = app.db(); - let stmt = conn.prepare("SELECT * FROM packages WHERE slug = $1 LIMIT 1") - .unwrap(); - stmt.query([&slug]).unwrap().next().map(|row| { - Package { - id: row.get("slug"), - name: row.get("name"), + let stmt = try!(conn.prepare("SELECT * FROM packages \ + WHERE slug = $1 LIMIT 1")); + match try!(stmt.query([&slug])).next() { + Some(row) => Ok(Package::from_row(&row)), + None => Err(NotFound.box_error()), + } + } + + fn name_to_slug(name: &str) -> String { + name.chars().filter_map(|c| { + match c { + 'A' .. 'Z' | + 'a' .. 'z' | + '0' .. '9' | + '-' | '_' => Some(c.to_lowercase()), + _ => None + } - }) + }).collect() } } @@ -52,20 +62,19 @@ pub fn setup(conn: &PostgresConnection) { [&"Test2", &"test2"]).unwrap(); } -pub fn index(req: &mut Request) -> IoResult { +pub fn index(req: &mut Request) -> CargoResult { let limit = 10i64; let offset = 0i64; let conn = req.app().db(); - let stmt = conn.prepare("SELECT * FROM packages LIMIT $1 OFFSET $2") - .unwrap(); + let stmt = try!(conn.prepare("SELECT * FROM packages LIMIT $1 OFFSET $2")); let mut pkgs = Vec::new(); - for row in stmt.query([&limit, &offset]).unwrap() { + for row in try!(stmt.query([&limit, &offset])) { pkgs.push(Package::from_row(&row)); } - let stmt = conn.prepare("SELECT COUNT(*) FROM packages").unwrap(); - let row = stmt.query([]).unwrap().next().unwrap(); + let stmt = try!(conn.prepare("SELECT COUNT(*) FROM packages")); + let row = try!(stmt.query([])).next().unwrap(); let total = row.get(0u); #[deriving(Encodable)] @@ -79,20 +88,12 @@ pub fn index(req: &mut Request) -> IoResult { })) } -pub fn show(req: &mut Request) -> IoResult { +pub fn show(req: &mut Request) -> CargoResult { let slug = req.params()["package_id"]; - let conn = req.app().db(); - let stmt = conn.prepare("SELECT * FROM packages WHERE slug = $1 LIMIT 1") - .unwrap(); - let row = match stmt.query([&slug.as_slice()]).unwrap().next() { - Some(row) => row, - None => return Ok(req.not_found()), - }; + let pkg = try!(Package::find(req.app(), slug.as_slice())); #[deriving(Encodable)] struct R { package: Package } - - let pkg = Package::from_row(&row); Ok(req.json(&R { package: pkg })) } @@ -104,25 +105,49 @@ pub struct UpdatePackage { name: String, } -pub fn update(req: &mut Request) -> IoResult { - if req.user().is_none() { - return Ok(req.unauthorized()) - } +pub fn update(req: &mut Request) -> CargoResult { + try!(req.user()); let slug = req.params()["package_id"]; - let mut pkg = match Package::find(req.app(), slug.as_slice()) { - Some(pkg) => pkg, - None => return Ok(req.not_found()), + let mut pkg = try!(Package::find(req.app(), slug.as_slice())); + + let conn = req.app().db(); + let update = conduit_json_parser::json_params::(req); + pkg.name = update.unwrap().package.name.clone(); + try!(conn.execute("UPDATE packages SET name = $1 WHERE slug = $2", + [&pkg.name.as_slice(), &slug.as_slice()])); + + #[deriving(Encodable)] + struct R { package: Package } + Ok(req.json(&R { package: pkg })) +} + +#[deriving(Decodable)] +pub struct NewRequest { package: NewPackage } + +#[deriving(Decodable)] +pub struct NewPackage { + name: String, +} + +pub fn new(req: &mut Request) -> CargoResult { + let app = req.app(); + let db = app.db(); + let tx = try!(db.transaction()); + let _user = { + let header = try!(req.headers().find("X-Cargo-Auth").require(|| { + internal("missing X-Cargo-Auth header") + })); + try!(User::find_by_api_token(app, header.get(0).as_slice())) }; - { - let conn = req.app().db(); - let update = conduit_json_parser::json_params::(req); - pkg.name = update.unwrap().package.name.clone(); - conn.execute("UPDATE packages SET name = $1 WHERE slug = $2", - [&pkg.name.as_slice(), &slug.as_slice()]) - .unwrap(); - } + + let update = conduit_json_parser::json_params::(req).unwrap(); + let name = update.package.name.as_slice(); + let slug = Package::name_to_slug(name); + try!(tx.execute("INSERT INTO packages (name, slug) VALUES ($1, $2)", + [&name, &slug])); #[deriving(Encodable)] struct R { package: Package } + let pkg = try!(Package::find(app, slug.as_slice())); Ok(req.json(&R { package: pkg })) } diff --git a/src/user/middleware.rs b/src/user/middleware.rs index 6fe7ddec49..f53aa94ec3 100644 --- a/src/user/middleware.rs +++ b/src/user/middleware.rs @@ -7,6 +7,7 @@ use conduit_cookie::RequestSession; use app::RequestApp; use super::User; +use util::errors::{CargoResult, Unauthorized, CargoError}; pub struct Middleware; @@ -18,8 +19,8 @@ impl conduit_middleware::Middleware for Middleware { None => return Ok(()), }; let user = match User::find(req.app(), id) { - Some(user) => user, - None => return Ok(()), + Ok(user) => user, + Err(..) => return Ok(()), }; req.mut_extensions().insert("crates.io.user", box user); @@ -28,13 +29,16 @@ impl conduit_middleware::Middleware for Middleware { } pub trait RequestUser<'a> { - fn user(self) -> Option<&'a User>; + fn user(self) -> CargoResult<&'a User>; } impl<'a> RequestUser<'a> for &'a Request { - fn user(self) -> Option<&'a User> { - self.extensions().find_equiv(&"crates.io.user").and_then(|r| { + fn user(self) -> CargoResult<&'a User> { + match self.extensions().find_equiv(&"crates.io.user").and_then(|r| { r.as_ref::() - }) + }) { + Some(user) => Ok(user), + None => Err(Unauthorized.box_error()), + } } } diff --git a/src/user/mod.rs b/src/user/mod.rs index cb1ee899c5..e5d8204ecb 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -1,5 +1,4 @@ use std::any::AnyRefExt; -use std::io::IoResult; use std::rand::{task_rng, Rng}; use std::str; use serialize::json; @@ -8,11 +7,12 @@ use conduit::{Request, Response}; use conduit_cookie::{RequestSession}; use curl::http; use oauth2::Authorization; -use pg::PostgresConnection; +use pg::{PostgresConnection, PostgresRow}; use pg::error::PgDbError; use app::{App, RequestApp}; -use util::RequestUtils; +use util::{RequestUtils, CargoResult, internal, Require, ChainError}; +use util::errors::NotFound; pub use self::middleware::{Middleware, RequestUser}; @@ -34,20 +34,32 @@ pub struct EncodableUser { } impl User { - pub fn find(app: &App, id: i32) -> Option { + pub fn find(app: &App, id: i32) -> CargoResult { let conn = app.db(); - let stmt = conn.prepare("SELECT * FROM users WHERE id = $1 LIMIT 1") - .unwrap(); - stmt.query([&id]).unwrap().next().map(|row| { - User { - id: row.get("id"), - email: row.get("email"), - gh_access_token: row.get("gh_access_token"), - api_token: row.get("api_token"), - } + let stmt = try!(conn.prepare("SELECT * FROM users WHERE id = $1 LIMIT 1")); + return try!(stmt.query([&id])).next().map(User::from_row).require(|| { + NotFound }) } + pub fn find_by_api_token(app: &App, token: &str) -> CargoResult { + let conn = app.db(); + let stmt = try!(conn.prepare("SELECT * FROM users \ + WHERE api_token = $1 LIMIT 1")); + return try!(stmt.query([&token])).next().map(User::from_row).require(|| { + NotFound + }) + } + + fn from_row(row: PostgresRow) -> User { + User { + id: row.get("id"), + email: row.get("email"), + gh_access_token: row.get("gh_access_token"), + api_token: row.get("api_token"), + } + } + pub fn new_api_token() -> String { task_rng().gen_ascii_chars().take(32).collect() } @@ -70,10 +82,10 @@ pub fn setup(conn: &PostgresConnection) { unique_email UNIQUE (email)", []).unwrap(); conn.execute("INSERT INTO users (email, gh_access_token, api_token) \ VALUES ($1, $2, $3)", - [&"foo@bar.com", &"wut", &User::new_api_token()]).unwrap(); + [&"foo@bar.com", &"wut", &"api-token"]).unwrap(); } -pub fn github_authorize(req: &mut Request) -> IoResult { +pub fn github_authorize(req: &mut Request) -> CargoResult { let state: String = task_rng().gen_ascii_chars().take(16).collect(); req.session().insert("github_oauth_state".to_string(), state.clone()); @@ -81,7 +93,7 @@ pub fn github_authorize(req: &mut Request) -> IoResult { Ok(req.json(&url.to_string())) } -pub fn github_access_token(req: &mut Request) -> IoResult { +pub fn github_access_token(req: &mut Request) -> CargoResult { #[deriving(Encodable)] struct R { ok: bool, error: Option, user: Option } @@ -110,19 +122,24 @@ pub fn github_access_token(req: &mut Request) -> IoResult { Err(s) => return Ok(req.json(&R { ok: false, error: Some(s), user: None })) }; - // TODO: none of this should be fallible - let resp = http::handle().get("https://api.github.com/user") - .header("Accept", "application/vnd.github.v3+json") - .header("User-Agent", "hello!") - .auth_with(&token) - .exec().unwrap(); - assert_eq!(resp.get_code(), 200); + let resp = try!(http::handle().get("https://api.github.com/user") + .header("Accept", "application/vnd.github.v3+json") + .header("User-Agent", "hello!") + .auth_with(&token) + .exec()); + if resp.get_code() != 200 { + return Err(internal(format!("didn't get a 200 result from github: {}", + resp))) + } - // TODO: more fallibility #[deriving(Decodable)] struct GithubUser { email: String } - let json = str::from_utf8(resp.get_body()).expect("non-utf8 body"); - let ghuser: GithubUser = json::decode(json).unwrap(); + let json = try!(str::from_utf8(resp.get_body()).require(||{ + internal("github didn't send a utf8-response") + })); + let ghuser: GithubUser = try!(json::decode(json).chain_error(|| { + internal("github didn't send a valid json response") + })); // Into the database! let conn = req.app().db(); @@ -140,10 +157,11 @@ pub fn github_access_token(req: &mut Request) -> IoResult { } // Who did we just insert? - let stmt = conn.prepare("SELECT * FROM users WHERE email = $1 LIMIT 1") - .unwrap(); - let row = stmt.query([&ghuser.email.as_slice()]).unwrap() - .next().expect("no user with email we just found"); + let stmt = try!(conn.prepare("SELECT * FROM users WHERE email = $1 LIMIT 1")); + let mut rows = try!(stmt.query([&ghuser.email.as_slice()])); + let row = try!(rows.next().require(|| { + internal("no user with email we just found") + })); let user = User { api_token: row.get("api_token"), @@ -156,34 +174,28 @@ pub fn github_access_token(req: &mut Request) -> IoResult { Ok(req.json(&R { ok: true, error: None, user: Some(user.encodable()) })) } -pub fn logout(req: &mut Request) -> IoResult { +pub fn logout(req: &mut Request) -> CargoResult { req.session().remove(&"user_id".to_string()); Ok(req.json(&true)) } -pub fn reset_token(req: &mut Request) -> IoResult { - #[deriving(Encodable)] - struct R { ok: bool, api_token: String } +pub fn reset_token(req: &mut Request) -> CargoResult { + let user = try!(req.user()); - if req.user().is_none() { - return Ok(req.unauthorized()) - } - let user_id = req.user().unwrap().id; let token = User::new_api_token(); let conn = req.app().db(); - conn.execute("UPDATE users SET api_token = $1 WHERE id = $2", - [&token, &user_id]) - .unwrap(); + try!(conn.execute("UPDATE users SET api_token = $1 WHERE id = $2", + [&token, &user.id])); + + #[deriving(Encodable)] + struct R { ok: bool, api_token: String } Ok(req.json(&R { ok: true, api_token: token })) } -pub fn me(req: &mut Request) -> IoResult { +pub fn me(req: &mut Request) -> CargoResult { + let user = try!(req.user()); + #[deriving(Encodable)] struct R { ok: bool, user: EncodableUser } - - if req.user().is_none() { - return Ok(req.unauthorized()) - } - let user = req.user().unwrap().clone().encodable(); - Ok(req.json(&R{ ok: true, user: user })) + Ok(req.json(&R{ ok: true, user: user.clone().encodable() })) } diff --git a/src/util/errors.rs b/src/util/errors.rs new file mode 100644 index 0000000000..00c0150563 --- /dev/null +++ b/src/util/errors.rs @@ -0,0 +1,225 @@ +use std::io::{IoError, MemReader}; +use std::fmt; +use std::fmt::{Show, Formatter, FormatError}; +use std::collections::HashMap; + +use conduit::Response; +use curl::ErrCode; +use pg::error::PostgresError; +use serialize::json; + +pub trait CargoError: Send { + fn description(&self) -> String; + fn detail(&self) -> Option { None } + fn cause<'a>(&'a self) -> Option<&'a CargoError + Send> { None } + + fn to_error>(self) -> E { + FromError::from_error(self) + } + + fn box_error(self) -> Box { + box self as Box + } + + fn concrete(&self) -> ConcreteCargoError { + ConcreteCargoError { + description: self.description(), + detail: self.detail(), + cause: self.cause().map(|c| box c.concrete() as Box), + } + } + + fn with_cause(self, cause: E) -> Box { + let mut concrete = self.concrete(); + concrete.cause = Some(cause.box_error()); + box concrete as Box + } + + fn response(&self) -> Option { None } +} + +pub trait FromError { + fn from_error(error: E) -> Self; +} + +impl FromError for Box { + fn from_error(error: E) -> Box { + error.box_error() + } +} + +macro_rules! from_error ( + ($ty:ty) => { + impl FromError<$ty> for $ty { + fn from_error(error: $ty) -> $ty { + error + } + } + } +) + +impl Show for Box { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}", self.description()) + } +} + +impl CargoError for Box { + fn description(&self) -> String { + (*self).description() + } + + fn detail(&self) -> Option { + (*self).detail() + } + + fn cause<'a>(&'a self) -> Option<&'a CargoError + Send> { + (*self).cause() + } + + fn box_error(self) -> Box { + self + } +} + +pub type CargoResult = Result>; + +pub trait BoxError { + fn box_error(self) -> CargoResult; +} + +pub trait ChainError { + fn chain_error(self, callback: || -> E) -> CargoResult ; +} + +impl<'a, T> ChainError for ||:'a -> CargoResult { + fn chain_error(self, callback: || -> E) -> CargoResult { + self().map_err(|err| callback().with_cause(err)) + } +} + +impl BoxError for Result { + fn box_error(self) -> CargoResult { + self.map_err(|err| err.box_error()) + } +} + +impl ChainError for Result { + fn chain_error(self, callback: || -> E) -> CargoResult { + self.map_err(|err| callback().with_cause(err)) + } +} + +impl CargoError for IoError { + fn description(&self) -> String { self.to_string() } +} + +from_error!(IoError) + +impl CargoError for FormatError { + fn description(&self) -> String { + "formatting failed".to_string() + } +} + +from_error!(FormatError) + +impl CargoError for PostgresError { + fn description(&self) -> String { self.to_string() } +} + +from_error!(PostgresError) + +impl CargoError for ErrCode { + fn description(&self) -> String { self.to_string() } +} + +from_error!(ErrCode) + +impl CargoError for json::DecoderError { + fn description(&self) -> String { self.to_string() } +} + +from_error!(json::DecoderError) + +pub struct ConcreteCargoError { + description: String, + detail: Option, + cause: Option>, +} + +impl Show for ConcreteCargoError { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}", self.description) + } +} + +impl CargoError for ConcreteCargoError { + fn description(&self) -> String { + self.description.clone() + } + + fn detail(&self) -> Option { + self.detail.clone() + } + + fn cause<'a>(&'a self) -> Option<&'a CargoError + Send> { + self.cause.as_ref().map(|c| { let err: &CargoError + Send = *c; err }) + } + + fn with_cause(mut self, + err: E) -> Box { + self.cause = Some(err.box_error()); + box self as Box + } +} + +pub struct NotFound; + +impl CargoError for NotFound { + fn description(&self) -> String { "not found".to_string() } + + fn response(&self) -> Option { + Some(Response { + status: (404, "Not Found"), + headers: HashMap::new(), + body: box MemReader::new(Vec::new()), + }) + } +} + +from_error!(NotFound) + +pub struct Unauthorized; + +impl CargoError for Unauthorized { + fn description(&self) -> String { "unauthorized".to_string() } + + fn response(&self) -> Option { + Some(Response { + status: (403, "Forbidden"), + headers: HashMap::new(), + body: box MemReader::new(Vec::new()), + }) + } +} + +from_error!(Unauthorized) + +#[allow(dead_code)] +pub fn internal_error(error: S1, + detail: S2) -> Box { + box ConcreteCargoError { + description: error.as_slice().to_string(), + detail: Some(detail.as_slice().to_string()), + cause: None, + } as Box +} + +pub fn internal(error: S) -> Box { + box ConcreteCargoError { + description: error.to_string(), + detail: None, + cause: None, + } as Box +} diff --git a/src/util.rs b/src/util/mod.rs similarity index 68% rename from src/util.rs rename to src/util/mod.rs index bd8cc4cd07..06e6abec13 100644 --- a/src/util.rs +++ b/src/util/mod.rs @@ -1,20 +1,27 @@ use std::io::{MemReader, IoError}; use std::collections::HashMap; +use std::fmt::Show; + use serialize::{json, Encodable}; use url; -use conduit::{Request, Response}; +use conduit::{Request, Response, Handler}; + +pub use self::errors::{CargoError, CargoResult, internal, internal_error}; +pub use self::errors::{ChainError, BoxError}; +pub use self::result::{Require, Wrap}; + +pub mod errors; +pub mod result; pub trait RequestUtils { - fn not_found(self) -> Response; - fn unauthorized(self) -> Response; fn redirect(self, url: String) -> Response; fn json<'a, T: Encodable, IoError>>(self, t: &T) -> Response; fn query(self) -> HashMap; } -impl<'a> RequestUtils for &'a mut Request { +impl<'a> RequestUtils for &'a Request { fn json<'a, T: Encodable, IoError>>(self, t: &T) -> Response { let s = json::encode(t); let mut headers = HashMap::new(); @@ -47,20 +54,19 @@ impl<'a> RequestUtils for &'a mut Request { body: box MemReader::new(Vec::new()), } } +} - fn not_found(self) -> Response { - Response { - status: (404, "Not Found"), - headers: HashMap::new(), - body: box MemReader::new(Vec::new()), - } - } +pub struct C(pub fn(&mut Request) -> CargoResult); - fn unauthorized(self) -> Response { - Response { - status: (403, "Forbidden"), - headers: HashMap::new(), - body: box MemReader::new(Vec::new()), +impl Handler for C { + fn call(&self, req: &mut Request) -> Result> { + let C(f) = *self; + match f(req) { + Ok(req) => Ok(req), + Err(e) => match e.response() { + Some(response) => Ok(response), + None => Err(box e as Box), + } } } } diff --git a/src/util/result.rs b/src/util/result.rs new file mode 100644 index 0000000000..108e08488d --- /dev/null +++ b/src/util/result.rs @@ -0,0 +1,27 @@ +use util::errors::{CargoResult, CargoError}; + +pub trait Wrap { + fn wrap(self, error: E) -> Self; +} + +impl Wrap for Result> { + fn wrap(self, error: E) -> CargoResult { + match self { + Ok(x) => Ok(x), + Err(e) => Err(error.with_cause(e)) + } + } +} + +pub trait Require { + fn require(self, err: || -> E) -> CargoResult; +} + +impl Require for Option { + fn require(self, err: || -> E) -> CargoResult { + match self { + Some(x) => Ok(x), + None => Err(box err().concrete() as Box) + } + } +}