Skip to content

Commit

Permalink
Sane error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
alexcrichton committed Jul 16, 2014
1 parent cb7d75f commit 45131f3
Show file tree
Hide file tree
Showing 9 changed files with 444 additions and 126 deletions.
2 changes: 1 addition & 1 deletion src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<Arc<App>>())
Expand Down
16 changes: 16 additions & 0 deletions src/macros.rs
Original file line number Diff line number Diff line change
@@ -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 }
) )
27 changes: 15 additions & 12 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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::<package::UpdateRequest>);
m
});
router.post("/packages/new", {
let mut m = MiddlewareBuilder::new(C(package::new));
m.add(conduit_json_parser::BodyReader::<package::NewRequest>);
m
});

let app = App::new();

Expand All @@ -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};
Expand Down
111 changes: 68 additions & 43 deletions src/package.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -23,16 +22,27 @@ impl Package {
}
}

pub fn find(app: &App, slug: &str) -> Option<Package> {
pub fn find(app: &App, slug: &str) -> CargoResult<Package> {
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()
}
}

Expand All @@ -52,20 +62,19 @@ pub fn setup(conn: &PostgresConnection) {
[&"Test2", &"test2"]).unwrap();
}

pub fn index(req: &mut Request) -> IoResult<Response> {
pub fn index(req: &mut Request) -> CargoResult<Response> {
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)]
Expand All @@ -79,20 +88,12 @@ pub fn index(req: &mut Request) -> IoResult<Response> {
}))
}

pub fn show(req: &mut Request) -> IoResult<Response> {
pub fn show(req: &mut Request) -> CargoResult<Response> {
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 }))
}

Expand All @@ -104,25 +105,49 @@ pub struct UpdatePackage {
name: String,
}

pub fn update(req: &mut Request) -> IoResult<Response> {
if req.user().is_none() {
return Ok(req.unauthorized())
}
pub fn update(req: &mut Request) -> CargoResult<Response> {
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::<UpdateRequest>(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<Response> {
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::<UpdateRequest>(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::<NewRequest>(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 }))
}
16 changes: 10 additions & 6 deletions src/user/middleware.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use conduit_cookie::RequestSession;

use app::RequestApp;
use super::User;
use util::errors::{CargoResult, Unauthorized, CargoError};

pub struct Middleware;

Expand All @@ -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);
Expand All @@ -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::<User>()
})
}) {
Some(user) => Ok(user),
None => Err(Unauthorized.box_error()),
}
}
}
Loading

0 comments on commit 45131f3

Please sign in to comment.