Skip to content

Commit 45131f3

Browse files
committed
Sane error handling
1 parent cb7d75f commit 45131f3

File tree

9 files changed

+444
-126
lines changed

9 files changed

+444
-126
lines changed

src/app.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ pub trait RequestApp<'a> {
7373
fn app(self) -> &'a App;
7474
}
7575

76-
impl<'a> RequestApp<'a> for &'a mut Request {
76+
impl<'a> RequestApp<'a> for &'a Request {
7777
fn app(self) -> &'a App {
7878
&**self.extensions().find(&"crates.io.app")
7979
.and_then(|a| a.as_ref::<Arc<App>>())

src/macros.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#![macro_escape]
2+
3+
macro_rules! try( ($expr:expr) => ({
4+
use util::errors::FromError;
5+
match $expr.map_err(FromError::from_error) {
6+
Ok(val) => val, Err(err) => return Err(err)
7+
}
8+
}) )
9+
10+
macro_rules! raw_try( ($expr:expr) => (
11+
match $expr { Ok(val) => val, Err(err) => return Err(err) }
12+
) )
13+
14+
macro_rules! try_option( ($e:expr) => (
15+
match $e { Some(k) => k, None => return None }
16+
) )

src/main.rs

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,9 @@ use conduit_router::RouteBuilder;
2626
use conduit_middleware::MiddlewareBuilder;
2727

2828
use app::App;
29+
use util::C;
2930

30-
macro_rules! try_option( ($e:expr) => (
31-
match $e { Some(k) => k, None => return None }
32-
) )
31+
mod macros;
3332

3433
mod app;
3534
mod db;
@@ -40,18 +39,23 @@ mod util;
4039
fn main() {
4140
let mut router = RouteBuilder::new();
4241

43-
router.get("/authorize_url", user::github_authorize);
44-
router.get("/authorize", user::github_access_token);
45-
router.get("/logout", user::logout);
46-
router.get("/me", user::me);
47-
router.put("/me/reset_token", user::reset_token);
48-
router.get("/packages", package::index);
49-
router.get("/packages/:package_id", package::show);
42+
router.get("/authorize_url", C(user::github_authorize));
43+
router.get("/authorize", C(user::github_access_token));
44+
router.get("/logout", C(user::logout));
45+
router.get("/me", C(user::me));
46+
router.put("/me/reset_token", C(user::reset_token));
47+
router.get("/packages", C(package::index));
48+
router.get("/packages/:package_id", C(package::show));
5049
router.put("/packages/:package_id", {
51-
let mut m = MiddlewareBuilder::new(package::update);
50+
let mut m = MiddlewareBuilder::new(C(package::update));
5251
m.add(conduit_json_parser::BodyReader::<package::UpdateRequest>);
5352
m
5453
});
54+
router.post("/packages/new", {
55+
let mut m = MiddlewareBuilder::new(C(package::new));
56+
m.add(conduit_json_parser::BodyReader::<package::NewRequest>);
57+
m
58+
});
5559

5660
let app = App::new();
5761

@@ -69,7 +73,6 @@ fn main() {
6973
wait_for_sigint();
7074
}
7175

72-
7376
// libnative doesn't have signal handling yet
7477
fn wait_for_sigint() {
7578
use green::{SchedPool, PoolConfig, GreenTaskBuilder};

src/package.rs

Lines changed: 68 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
use std::io::IoResult;
2-
31
use conduit::{Request, Response};
42
use conduit_router::RequestParams;
53
use conduit_json_parser;
64
use pg::{PostgresConnection, PostgresRow};
75

86
use app::{App, RequestApp};
9-
use user::RequestUser;
10-
use util::RequestUtils;
7+
use user::{RequestUser, User};
8+
use util::{RequestUtils, CargoResult, Require, internal};
9+
use util::errors::{NotFound, CargoError};
1110

1211
#[deriving(Encodable)]
1312
pub struct Package {
@@ -23,16 +22,27 @@ impl Package {
2322
}
2423
}
2524

26-
pub fn find(app: &App, slug: &str) -> Option<Package> {
25+
pub fn find(app: &App, slug: &str) -> CargoResult<Package> {
2726
let conn = app.db();
28-
let stmt = conn.prepare("SELECT * FROM packages WHERE slug = $1 LIMIT 1")
29-
.unwrap();
30-
stmt.query([&slug]).unwrap().next().map(|row| {
31-
Package {
32-
id: row.get("slug"),
33-
name: row.get("name"),
27+
let stmt = try!(conn.prepare("SELECT * FROM packages \
28+
WHERE slug = $1 LIMIT 1"));
29+
match try!(stmt.query([&slug])).next() {
30+
Some(row) => Ok(Package::from_row(&row)),
31+
None => Err(NotFound.box_error()),
32+
}
33+
}
34+
35+
fn name_to_slug(name: &str) -> String {
36+
name.chars().filter_map(|c| {
37+
match c {
38+
'A' .. 'Z' |
39+
'a' .. 'z' |
40+
'0' .. '9' |
41+
'-' | '_' => Some(c.to_lowercase()),
42+
_ => None
43+
3444
}
35-
})
45+
}).collect()
3646
}
3747
}
3848

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

55-
pub fn index(req: &mut Request) -> IoResult<Response> {
65+
pub fn index(req: &mut Request) -> CargoResult<Response> {
5666
let limit = 10i64;
5767
let offset = 0i64;
5868
let conn = req.app().db();
59-
let stmt = conn.prepare("SELECT * FROM packages LIMIT $1 OFFSET $2")
60-
.unwrap();
69+
let stmt = try!(conn.prepare("SELECT * FROM packages LIMIT $1 OFFSET $2"));
6170

6271
let mut pkgs = Vec::new();
63-
for row in stmt.query([&limit, &offset]).unwrap() {
72+
for row in try!(stmt.query([&limit, &offset])) {
6473
pkgs.push(Package::from_row(&row));
6574
}
6675

67-
let stmt = conn.prepare("SELECT COUNT(*) FROM packages").unwrap();
68-
let row = stmt.query([]).unwrap().next().unwrap();
76+
let stmt = try!(conn.prepare("SELECT COUNT(*) FROM packages"));
77+
let row = try!(stmt.query([])).next().unwrap();
6978
let total = row.get(0u);
7079

7180
#[deriving(Encodable)]
@@ -79,20 +88,12 @@ pub fn index(req: &mut Request) -> IoResult<Response> {
7988
}))
8089
}
8190

82-
pub fn show(req: &mut Request) -> IoResult<Response> {
91+
pub fn show(req: &mut Request) -> CargoResult<Response> {
8392
let slug = req.params()["package_id"];
84-
let conn = req.app().db();
85-
let stmt = conn.prepare("SELECT * FROM packages WHERE slug = $1 LIMIT 1")
86-
.unwrap();
87-
let row = match stmt.query([&slug.as_slice()]).unwrap().next() {
88-
Some(row) => row,
89-
None => return Ok(req.not_found()),
90-
};
93+
let pkg = try!(Package::find(req.app(), slug.as_slice()));
9194

9295
#[deriving(Encodable)]
9396
struct R { package: Package }
94-
95-
let pkg = Package::from_row(&row);
9697
Ok(req.json(&R { package: pkg }))
9798
}
9899

@@ -104,25 +105,49 @@ pub struct UpdatePackage {
104105
name: String,
105106
}
106107

107-
pub fn update(req: &mut Request) -> IoResult<Response> {
108-
if req.user().is_none() {
109-
return Ok(req.unauthorized())
110-
}
108+
pub fn update(req: &mut Request) -> CargoResult<Response> {
109+
try!(req.user());
111110
let slug = req.params()["package_id"];
112-
let mut pkg = match Package::find(req.app(), slug.as_slice()) {
113-
Some(pkg) => pkg,
114-
None => return Ok(req.not_found()),
111+
let mut pkg = try!(Package::find(req.app(), slug.as_slice()));
112+
113+
let conn = req.app().db();
114+
let update = conduit_json_parser::json_params::<UpdateRequest>(req);
115+
pkg.name = update.unwrap().package.name.clone();
116+
try!(conn.execute("UPDATE packages SET name = $1 WHERE slug = $2",
117+
[&pkg.name.as_slice(), &slug.as_slice()]));
118+
119+
#[deriving(Encodable)]
120+
struct R { package: Package }
121+
Ok(req.json(&R { package: pkg }))
122+
}
123+
124+
#[deriving(Decodable)]
125+
pub struct NewRequest { package: NewPackage }
126+
127+
#[deriving(Decodable)]
128+
pub struct NewPackage {
129+
name: String,
130+
}
131+
132+
pub fn new(req: &mut Request) -> CargoResult<Response> {
133+
let app = req.app();
134+
let db = app.db();
135+
let tx = try!(db.transaction());
136+
let _user = {
137+
let header = try!(req.headers().find("X-Cargo-Auth").require(|| {
138+
internal("missing X-Cargo-Auth header")
139+
}));
140+
try!(User::find_by_api_token(app, header.get(0).as_slice()))
115141
};
116-
{
117-
let conn = req.app().db();
118-
let update = conduit_json_parser::json_params::<UpdateRequest>(req);
119-
pkg.name = update.unwrap().package.name.clone();
120-
conn.execute("UPDATE packages SET name = $1 WHERE slug = $2",
121-
[&pkg.name.as_slice(), &slug.as_slice()])
122-
.unwrap();
123-
}
142+
143+
let update = conduit_json_parser::json_params::<NewRequest>(req).unwrap();
144+
let name = update.package.name.as_slice();
145+
let slug = Package::name_to_slug(name);
146+
try!(tx.execute("INSERT INTO packages (name, slug) VALUES ($1, $2)",
147+
[&name, &slug]));
124148

125149
#[deriving(Encodable)]
126150
struct R { package: Package }
151+
let pkg = try!(Package::find(app, slug.as_slice()));
127152
Ok(req.json(&R { package: pkg }))
128153
}

src/user/middleware.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use conduit_cookie::RequestSession;
77

88
use app::RequestApp;
99
use super::User;
10+
use util::errors::{CargoResult, Unauthorized, CargoError};
1011

1112
pub struct Middleware;
1213

@@ -18,8 +19,8 @@ impl conduit_middleware::Middleware for Middleware {
1819
None => return Ok(()),
1920
};
2021
let user = match User::find(req.app(), id) {
21-
Some(user) => user,
22-
None => return Ok(()),
22+
Ok(user) => user,
23+
Err(..) => return Ok(()),
2324
};
2425

2526
req.mut_extensions().insert("crates.io.user", box user);
@@ -28,13 +29,16 @@ impl conduit_middleware::Middleware for Middleware {
2829
}
2930

3031
pub trait RequestUser<'a> {
31-
fn user(self) -> Option<&'a User>;
32+
fn user(self) -> CargoResult<&'a User>;
3233
}
3334

3435
impl<'a> RequestUser<'a> for &'a Request {
35-
fn user(self) -> Option<&'a User> {
36-
self.extensions().find_equiv(&"crates.io.user").and_then(|r| {
36+
fn user(self) -> CargoResult<&'a User> {
37+
match self.extensions().find_equiv(&"crates.io.user").and_then(|r| {
3738
r.as_ref::<User>()
38-
})
39+
}) {
40+
Some(user) => Ok(user),
41+
None => Err(Unauthorized.box_error()),
42+
}
3943
}
4044
}

0 commit comments

Comments
 (0)