Skip to content

Commit e055c88

Browse files
committed
Improved error handling.
Based on seanmonstar/warp#458 , an error is not a rejection, but an error in a web server should impl Reply, and then `Result<impl Repy, Error>` should do so too.
1 parent 8d6dbd4 commit e055c88

File tree

8 files changed

+263
-249
lines changed

8 files changed

+263
-249
lines changed

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ slug = "0.1.4"
2626
structopt = { version = "0.3.2", features = ["wrap_help"] }
2727
tokio = { version = "0.2", features = ["macros"] }
2828
tokio-diesel = "0.3.0"
29-
warp = { version = "0.2.0", default-features = false }
29+
#warp = { version = "0.2.0", default-features = false }
30+
warp = { git = "https://github.com/cjbassi/warp", branch="error", default-features = false }
3031
roxmltree = "0.13.0"
3132

3233
[dependencies.diesel]

src/server/covers.rs

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use super::{custom, redirect, PgPool};
1+
use super::{redirect, PgPool, ServerError};
22
use crate::schema::covers::dsl as c;
33
use crate::schema::issues::dsl as i;
44
use crate::templates::statics::xcover_jpg;
@@ -8,36 +8,36 @@ use mime::IMAGE_JPEG;
88
use std::str::FromStr;
99
use tokio_diesel::{AsyncRunQueryDsl, OptionalExtension};
1010
use warp::http::header::{CONTENT_TYPE, EXPIRES};
11-
use warp::http::{Response, StatusCode};
12-
use warp::reject::not_found;
13-
use warp::{Rejection, Reply};
11+
use warp::http::{response::Builder, StatusCode};
12+
use warp::reply::Response;
13+
use warp::Reply;
1414

15-
#[allow(clippy::needless_pass_by_value)]
1615
pub async fn cover_image(
1716
db: PgPool,
1817
issue: CoverRef,
19-
) -> Result<impl Reply, Rejection> {
18+
) -> Result<Response, ServerError> {
2019
let data = i::issues
2120
.inner_join(c::covers)
2221
.select(c::image)
2322
.filter(i::year.eq(issue.year))
2423
.filter(i::number.eq(issue.number))
2524
.first_async::<Vec<u8>>(&db)
2625
.await
27-
.optional()
28-
.map_err(custom)?;
26+
.optional()?;
2927

3028
if let Some(data) = data {
3129
let medium_expires = Utc::now() + Duration::days(90);
32-
Ok(Response::builder()
30+
Ok(Builder::new()
3331
.header(CONTENT_TYPE, IMAGE_JPEG.as_ref())
3432
.header(EXPIRES, medium_expires.to_rfc2822())
35-
.body(data))
33+
.body(data.into())
34+
.unwrap())
3635
} else {
37-
Ok(Response::builder()
36+
Ok(Builder::new()
3837
.status(StatusCode::NOT_FOUND)
3938
.header(CONTENT_TYPE, xcover_jpg.mime.as_ref())
40-
.body(xcover_jpg.content.to_vec()))
39+
.body(xcover_jpg.content.to_vec().into())
40+
.unwrap())
4141
}
4242
}
4343

@@ -66,18 +66,17 @@ pub async fn redirect_cover(
6666
db: PgPool,
6767
year: CYear,
6868
issue: SIssue,
69-
) -> Result<impl Reply, Rejection> {
69+
) -> Result<impl Reply, ServerError> {
7070
let exists = i::issues
7171
.filter(i::year.eq(year.0))
7272
.filter(i::number.eq(issue.0))
7373
.count()
7474
.get_result_async::<i64>(&db)
75-
.await
76-
.map_err(custom)?;
75+
.await?;
7776
if exists > 0 {
78-
redirect(&format!("/c/f{}-{}.jpg", year.0, issue.0))
77+
Ok(redirect(&format!("/c/f{}-{}.jpg", year.0, issue.0)))
7978
} else {
80-
Err(not_found())
79+
Err(ServerError::not_found())
8180
}
8281
}
8382

src/server/creators.rs

Lines changed: 27 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use super::{custom, custom_or_404, goh, redirect};
1+
use super::{goh, redirect, OptionalExtension, ServerError};
22
use super::{FullArticle, FullEpisode, OtherContribs, PgFilter, PgPool};
33
use crate::models::creator_contributions::CreatorContributions;
44
use crate::models::{
@@ -20,20 +20,20 @@ use crate::schema::titles::dsl as t;
2020
use crate::templates::{self, RenderRucte};
2121
use diesel::dsl::{any, min};
2222
use diesel::prelude::*;
23-
use tokio_diesel::{AsyncError, AsyncRunQueryDsl, OptionalExtension};
23+
use tokio_diesel::{AsyncError, AsyncRunQueryDsl};
2424
use warp::filters::BoxedFilter;
2525
use warp::http::response::Builder;
2626
use warp::reply::Response;
27-
use warp::{self, Filter, Rejection, Reply};
27+
use warp::{self, Filter, Reply};
2828

2929
pub fn routes(s: PgFilter) -> BoxedFilter<(impl Reply,)> {
3030
use warp::path::{end, param};
31-
let list = goh().and(end()).and(s.clone()).and_then(list_creators);
32-
let one = goh().and(s).and(param()).and(end()).and_then(one_creator);
31+
let list = goh().and(end()).and(s.clone()).map_async(list_creators);
32+
let one = goh().and(s).and(param()).and(end()).map_async(one_creator);
3333
list.or(one).unify().boxed()
3434
}
3535

36-
async fn list_creators(db: PgPool) -> Result<Response, Rejection> {
36+
async fn list_creators(db: PgPool) -> Result<Response, ServerError> {
3737
use crate::models::creator_contributions::creator_contributions::dsl as cc;
3838
let all = cc::creator_contributions
3939
.select((
@@ -45,21 +45,20 @@ async fn list_creators(db: PgPool) -> Result<Response, Rejection> {
4545
cc::latest_issue,
4646
))
4747
.load_async::<CreatorContributions>(&db)
48-
.await
49-
.map_err(custom)?;
50-
Builder::new().html(|o| templates::creators(o, &all))
48+
.await?;
49+
Ok(Builder::new().html(|o| templates::creators(o, &all))?)
5150
}
5251

5352
async fn one_creator(
5453
db: PgPool,
5554
slug: String,
56-
) -> Result<Response, Rejection> {
55+
) -> Result<Response, ServerError> {
5756
let creator = c::creators
5857
.filter(c::slug.eq(slug.clone()))
5958
.first_async::<Creator>(&db)
6059
.await
61-
.optional()
62-
.map_err(custom)?;
60+
.optional()?;
61+
6362
let creator = if let Some(creator) = creator {
6463
creator
6564
} else {
@@ -69,7 +68,7 @@ async fn one_creator(
6968
.replace(".html", "");
7069
log::info!("Looking for creator fallback {:?} -> {:?}", slug, target);
7170
if target == "anderas%eriksson" || target == "andreas%erikssson" {
72-
return redirect("/who/andreas-eriksson");
71+
return Ok(redirect("/who/andreas-eriksson"));
7372
}
7473
let found = ca::creator_aliases
7574
.inner_join(c::creators)
@@ -78,9 +77,9 @@ async fn one_creator(
7877
.select(c::slug)
7978
.first_async::<String>(&db)
8079
.await
81-
.map_err(custom_or_404)?;
80+
.or_404()?;
8281
log::debug!("Found replacement: {:?}", found);
83-
return redirect(&format!("/who/{}", found));
82+
return Ok(redirect(&format!("/who/{}", found)));
8483
};
8584

8685
let about_raw = a::articles
@@ -92,23 +91,16 @@ async fn one_creator(
9291
.order(min(i::magic))
9392
.group_by(a::articles::all_columns())
9493
.load_async::<Article>(&db)
95-
.await
96-
.map_err(custom)?;
94+
.await?;
9795
let mut about = Vec::with_capacity(about_raw.len());
9896
for article in about_raw.into_iter() {
9997
let published = i::issues
10098
.inner_join(p::publications)
10199
.select((i::year, (i::number, i::number_str)))
102100
.filter(p::article_id.eq(article.id))
103101
.load_async::<IssueRef>(&db)
104-
.await
105-
.map_err(custom)?;
106-
about.push((
107-
FullArticle::load_async(article, &db)
108-
.await
109-
.map_err(custom)?,
110-
published,
111-
));
102+
.await?;
103+
about.push((FullArticle::load_async(article, &db).await?, published));
112104
}
113105

114106
let e_t_columns = (t::titles::all_columns(), e::episodes::all_columns());
@@ -125,13 +117,10 @@ async fn one_creator(
125117
.order(min(i::magic))
126118
.group_by(e_t_columns)
127119
.load_async::<(Title, Episode)>(&db)
128-
.await
129-
.map_err(custom)?;
120+
.await?;
130121
let mut main_episodes = Vec::with_capacity(main_episodes_raw.len());
131122
for (t, ep) in main_episodes_raw.into_iter() {
132-
let e = FullEpisode::load_details_async(ep, &db)
133-
.await
134-
.map_err(custom)?;
123+
let e = FullEpisode::load_details_async(ep, &db).await?;
135124
main_episodes.push((t, e));
136125
}
137126

@@ -143,31 +132,23 @@ async fn one_creator(
143132
.order(min(i::magic))
144133
.group_by(a::articles::all_columns())
145134
.load_async::<Article>(&db)
146-
.await
147-
.map_err(custom)?;
135+
.await?;
148136
let mut articles_by = Vec::with_capacity(articles_by_raw.len());
149137
for article in articles_by_raw.into_iter() {
150138
let published = i::issues
151139
.inner_join(p::publications)
152140
.select((i::year, (i::number, i::number_str)))
153141
.filter(p::article_id.eq(article.id))
154142
.load_async::<IssueRef>(&db)
155-
.await
156-
.map_err(custom)?;
157-
articles_by.push((
158-
FullArticle::load_async(article, &db)
159-
.await
160-
.map_err(custom)?,
161-
published,
162-
))
143+
.await?;
144+
articles_by
145+
.push((FullArticle::load_async(article, &db).await?, published))
163146
}
164147

165-
let covers = CoverSet::by(&creator, &db).await.map_err(custom)?;
166-
let others = OtherContribs::for_creator(&creator, &db)
167-
.await
168-
.map_err(custom)?;
148+
let covers = CoverSet::by(&creator, &db).await?;
149+
let others = OtherContribs::for_creator(&creator, &db).await?;
169150

170-
Builder::new().html(|o| {
151+
Ok(Builder::new().html(|o| {
171152
templates::creator(
172153
o,
173154
&creator,
@@ -177,7 +158,7 @@ async fn one_creator(
177158
&articles_by,
178159
&others,
179160
)
180-
})
161+
})?)
181162
}
182163

183164
pub struct CoverSet {

src/server/error.rs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
use std::fmt::{self, Display};
2+
use tokio_diesel::AsyncError;
3+
use warp::http::StatusCode;
4+
use warp::reply::Response;
5+
use warp::{Rejection, Reply};
6+
7+
#[derive(Debug, Clone)]
8+
pub struct ServerError {
9+
code: StatusCode,
10+
}
11+
12+
impl ServerError {
13+
pub fn not_found() -> Self {
14+
ServerError {
15+
code: StatusCode::NOT_FOUND,
16+
}
17+
}
18+
}
19+
20+
impl Reply for ServerError {
21+
fn into_response(self) -> Response {
22+
use super::templates::{error, notfound, RenderRucte};
23+
use warp::http::response::Builder;
24+
log::error!("{}", self);
25+
let res = Builder::new().status(self.code);
26+
if self.code == StatusCode::NOT_FOUND {
27+
res.html(|o| notfound(o, StatusCode::NOT_FOUND)).unwrap()
28+
} else {
29+
res.html(|o| error(o, self.code)).unwrap()
30+
}
31+
}
32+
}
33+
impl std::error::Error for ServerError {}
34+
35+
impl Display for ServerError {
36+
fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
37+
write!(out, "Error: {}", self.code)
38+
}
39+
}
40+
41+
impl From<Rejection> for ServerError {
42+
fn from(err: Rejection) -> ServerError {
43+
log::error!("Reject {:?}", err);
44+
let code = if err.is_not_found() {
45+
StatusCode::NOT_FOUND
46+
} else if let Some(_) = err.find::<warp::reject::MethodNotAllowed>() {
47+
StatusCode::METHOD_NOT_ALLOWED
48+
} else {
49+
StatusCode::INTERNAL_SERVER_ERROR
50+
};
51+
ServerError { code }
52+
}
53+
}
54+
55+
impl From<AsyncError> for ServerError {
56+
fn from(err: AsyncError) -> ServerError {
57+
let code = match err {
58+
AsyncError::Checkout(e) => {
59+
log::error!("Pool error: {}", e);
60+
StatusCode::SERVICE_UNAVAILABLE
61+
}
62+
AsyncError::Error(e) => {
63+
log::error!("Error: {}", e);
64+
StatusCode::INTERNAL_SERVER_ERROR
65+
}
66+
};
67+
ServerError { code }
68+
}
69+
}
70+
/*impl From<warp::http::Error> for ServerError {
71+
fn from(err: warp::http::Error) -> ServerError {
72+
ServerError {
73+
code: err.into(),
74+
}
75+
}
76+
}*/
77+
78+
pub trait OptionalExtension<T>: tokio_diesel::OptionalExtension<T> {
79+
fn or_404(self) -> Result<T, ServerError>
80+
where
81+
Self: Sized,
82+
{
83+
match tokio_diesel::OptionalExtension::optional(self)? {
84+
Some(t) => Ok(t),
85+
None => Err(ServerError::not_found()),
86+
}
87+
}
88+
fn optional(self) -> Result<Option<T>, ServerError>
89+
where
90+
Self: Sized,
91+
{
92+
Ok(tokio_diesel::OptionalExtension::optional(self)?)
93+
}
94+
}
95+
96+
impl<T> OptionalExtension<T> for Result<T, AsyncError> {}

0 commit comments

Comments
 (0)