Skip to content

Commit a251368

Browse files
committed
Auto merge of #2032 - jtgeibel:error-refactor-step3, r=jtgeibel
Add a concrete `util::Error` type (step 3 of refactoring) This PR may be easier to review commit by commit. Overall this series continues the previous refactoring work in #1920 and #1929. In particular, a concrete `util::Error` error type is introduced. The code is updated to follow a style where: * The new concrete type `util::Error` is great for code that is not part of the request/response lifecycle. Binaries have been converted to `fn main() -> Result<(), Error>` where applicable. This avoids pulling in the unnecessary infrastructure to convert errors into a user facing JSON responses (relative to `AppError`). * There are now 13 results across 6 files for `, Error>`. Usage occurs in `src/bin/`, `src/boot/`, and `src/uploaders.rs`. * `diesel::QueryResult` - There is a lot of code that only deals with query errors. If only one type of error is possible in a function, using that specific error is preferable to the more general `util::Error`. This is especially common in model code. * There are now 49 results across 16 files for `QueryResult<`. Most usage is in `src/models/`, with some usage in `src/publish_rate_limit.rs`, `src/tasks/`, and `src/tests/`. * `util::errors::AppResult` - Some failures should be converted into user facing JSON responses. This error type is more dynamic and is box allocated. Low-level errors are typically not converted to user facing errors and most usage is within the models, controllers, and middleware layers (85 of 102 results below). * There are now 102 results across 36 files for `AppResult<`. Most usage is in `src/controllers/` and `src/models/`, with additional usage in `src/middleware/`, `src/{email,github,publish_rate_limit,router,uploaders}.rs`, and `src/{tests,util}/`. Finally, a `middleware::Result` type is introduced for use in middleware and router code. The behavior of `dyn AppError` and `chain_error` is a remaining area for improvement. There may be cases where we want to wrap an `internal` error in a user facing error (like `cargo_err` or `server_err`) and still have the internal error make it up to the logging middleware. However, I don't see an easy way to propagate this up the middleware stack (without encoding it in an internal response header that we later remove, :sweat:). r? @smarnach
2 parents 54b879f + 541f710 commit a251368

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+310
-286
lines changed

src/background_jobs.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
use std::panic::AssertUnwindSafe;
22
use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
3+
4+
use diesel::r2d2::PoolError;
35
use swirl::PerformError;
46

57
use crate::db::{DieselPool, DieselPooledConn};
68
use crate::git::Repository;
79
use crate::uploaders::Uploader;
8-
use crate::util::errors::{AppErrToStdErr, AppResult};
910

1011
impl<'a> swirl::db::BorrowedConnection<'a> for DieselPool {
1112
type Connection = DieselPooledConn<'a>;
1213
}
1314

1415
impl swirl::db::DieselPool for DieselPool {
15-
type Error = AppErrToStdErr;
16+
type Error = PoolError;
1617

1718
fn get(&self) -> Result<swirl::db::DieselPooledConn<'_, Self>, Self::Error> {
18-
self.get().map_err(AppErrToStdErr)
19+
self.get()
1920
}
2021
}
2122

@@ -56,13 +57,11 @@ impl Environment {
5657
}
5758
}
5859

59-
pub fn connection(&self) -> Result<DieselPooledConn<'_>, PerformError> {
60-
self.connection_pool
61-
.get()
62-
.map_err(|e| AppErrToStdErr(e).into())
60+
pub fn connection(&self) -> Result<DieselPooledConn<'_>, PoolError> {
61+
self.connection_pool.get()
6362
}
6463

65-
pub fn lock_index(&self) -> AppResult<MutexGuard<'_, Repository>> {
64+
pub fn lock_index(&self) -> Result<MutexGuard<'_, Repository>, PerformError> {
6665
let repo = self.index.lock().unwrap_or_else(PoisonError::into_inner);
6766
repo.reset_head()?;
6867
Ok(repo)

src/bin/enqueue-job.rs

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,24 @@
1-
use cargo_registry::util::{cargo_err, AppError, AppResult};
2-
use cargo_registry::{db, env, tasks};
3-
use diesel::PgConnection;
1+
#![deny(clippy::all)]
42

5-
fn main() -> AppResult<()> {
3+
use cargo_registry::{db, env, tasks, util::Error};
4+
use swirl::Job;
5+
6+
fn main() -> Result<(), Error> {
67
let conn = db::connect_now()?;
78
let mut args = std::env::args().skip(1);
89

910
let job = args.next().unwrap_or_default();
1011
println!("Enqueueing background job: {}", job);
1112

1213
match &*job {
13-
"update_downloads" => tasks::update_downloads().enqueue(&conn),
14+
"update_downloads" => Ok(tasks::update_downloads().enqueue(&conn)?),
1415
"dump_db" => {
1516
let database_url = args.next().unwrap_or_else(|| env("DATABASE_URL"));
1617
let target_name = args
1718
.next()
1819
.unwrap_or_else(|| String::from("db-dump.tar.gz"));
19-
tasks::dump_db(database_url, target_name).enqueue(&conn)
20+
Ok(tasks::dump_db(database_url, target_name).enqueue(&conn)?)
2021
}
21-
other => Err(cargo_err(&format!("Unrecognized job type `{}`", other))),
22+
other => Err(Error::from(format!("Unrecognized job type `{}`", other))),
2223
}
2324
}
24-
25-
/// Helper to map the `PerformError` returned by `swirl::Job::enqueue()` to a
26-
/// `AppError`. Can be removed once `map_err()` isn't needed any more.
27-
trait Enqueue: swirl::Job {
28-
fn enqueue(self, conn: &PgConnection) -> AppResult<()> {
29-
<Self as swirl::Job>::enqueue(self, conn).map_err(|e| AppError::from_std_error(e))
30-
}
31-
}
32-
33-
impl<J: swirl::Job> Enqueue for J {}

src/bin/monitor.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,18 @@
88

99
mod on_call;
1010

11-
use cargo_registry::{db, schema::*, util::AppResult};
11+
use cargo_registry::{db, schema::*, util::Error};
1212
use diesel::prelude::*;
1313

14-
fn main() -> AppResult<()> {
14+
fn main() -> Result<(), Error> {
1515
let conn = db::connect_now()?;
1616

1717
check_stalled_background_jobs(&conn)?;
1818
check_spam_attack(&conn)?;
1919
Ok(())
2020
}
2121

22-
fn check_stalled_background_jobs(conn: &PgConnection) -> AppResult<()> {
22+
fn check_stalled_background_jobs(conn: &PgConnection) -> Result<(), Error> {
2323
use cargo_registry::schema::background_jobs::dsl::*;
2424
use diesel::dsl::*;
2525

@@ -55,7 +55,7 @@ fn check_stalled_background_jobs(conn: &PgConnection) -> AppResult<()> {
5555
Ok(())
5656
}
5757

58-
fn check_spam_attack(conn: &PgConnection) -> AppResult<()> {
58+
fn check_spam_attack(conn: &PgConnection) -> Result<(), Error> {
5959
use cargo_registry::models::krate::canon_crate_name;
6060
use diesel::dsl::*;
6161
use diesel::sql_types::Bool;
@@ -116,7 +116,7 @@ fn check_spam_attack(conn: &PgConnection) -> AppResult<()> {
116116
Ok(())
117117
}
118118

119-
fn log_and_trigger_event(event: on_call::Event) -> AppResult<()> {
119+
fn log_and_trigger_event(event: on_call::Event) -> Result<(), Error> {
120120
match event {
121121
on_call::Event::Trigger {
122122
ref description, ..

src/bin/on_call/mod.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use cargo_registry::util::{internal, AppResult};
1+
use cargo_registry::util::Error;
22

33
use reqwest::{header, StatusCode as Status};
44

@@ -25,7 +25,7 @@ impl Event {
2525
///
2626
/// If the variant is `Trigger`, this will page whoever is on call
2727
/// (potentially waking them up at 3 AM).
28-
pub fn send(self) -> AppResult<()> {
28+
pub fn send(self) -> Result<(), Error> {
2929
let api_token = dotenv::var("PAGERDUTY_API_TOKEN")?;
3030
let service_key = dotenv::var("PAGERDUTY_INTEGRATION_KEY")?;
3131

@@ -43,14 +43,15 @@ impl Event {
4343
s if s.is_success() => Ok(()),
4444
Status::BAD_REQUEST => {
4545
let error = response.json::<InvalidEvent>()?;
46-
Err(internal(&format_args!("pagerduty error: {:?}", error)))
46+
Err(format!("pagerduty error: {:?}", error))
4747
}
48-
Status::FORBIDDEN => Err(internal("rate limited by pagerduty")),
49-
n => Err(internal(&format_args!(
48+
Status::FORBIDDEN => Err("rate limited by pagerduty".to_string()),
49+
n => Err(format!(
5050
"Got a non 200 response code from pagerduty: {} with {:?}",
5151
n, response
52-
))),
52+
)),
5353
}
54+
.map_err(Into::into)
5455
}
5556
}
5657

src/bin/test-pagerduty.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ mod on_call;
1111

1212
use std::env::args;
1313

14-
fn main() {
14+
use cargo_registry::util::Error;
15+
16+
fn main() -> Result<(), Error> {
1517
let args = args().collect::<Vec<_>>();
1618

1719
let event_type = &*args[1];
@@ -32,5 +34,5 @@ fn main() {
3234
},
3335
_ => panic!("Event type must be trigger, acknowledge, or resolve"),
3436
};
35-
event.send().unwrap()
37+
event.send()
3638
}

src/boot/categories.rs

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
// Sync available crate categories from `src/categories.toml`.
22
// Runs when the server is started.
33

4-
use crate::{
5-
db,
6-
util::errors::{internal, AppResult, ChainError},
7-
};
4+
use crate::{db, util::Error};
85

96
use diesel::prelude::*;
107

@@ -37,9 +34,12 @@ impl Category {
3734
}
3835
}
3936

40-
fn required_string_from_toml<'a>(toml: &'a toml::value::Table, key: &str) -> AppResult<&'a str> {
41-
toml.get(key).and_then(toml::Value::as_str).chain_error(|| {
42-
internal(&format_args!(
37+
fn required_string_from_toml<'a>(
38+
toml: &'a toml::value::Table,
39+
key: &str,
40+
) -> Result<&'a str, Error> {
41+
toml.get(key).and_then(toml::Value::as_str).ok_or_else(|| {
42+
Error::from(format!(
4343
"Expected category TOML attribute '{}' to be a String",
4444
key
4545
))
@@ -53,13 +53,13 @@ fn optional_string_from_toml<'a>(toml: &'a toml::value::Table, key: &str) -> &'a
5353
fn categories_from_toml(
5454
categories: &toml::value::Table,
5555
parent: Option<&Category>,
56-
) -> AppResult<Vec<Category>> {
56+
) -> Result<Vec<Category>, Error> {
5757
let mut result = vec![];
5858

5959
for (slug, details) in categories {
6060
let details = details
6161
.as_table()
62-
.chain_error(|| internal(&format_args!("category {} was not a TOML table", slug)))?;
62+
.ok_or_else(|| Error::from(format!("category {} was not a TOML table", slug)))?;
6363

6464
let category = Category::from_parent(
6565
slug,
@@ -69,12 +69,9 @@ fn categories_from_toml(
6969
);
7070

7171
if let Some(categories) = details.get("categories") {
72-
let categories = categories.as_table().chain_error(|| {
73-
internal(&format_args!(
74-
"child categories of {} were not a table",
75-
slug
76-
))
77-
})?;
72+
let categories = categories
73+
.as_table()
74+
.ok_or_else(|| format!("child categories of {} were not a table", slug))?;
7875

7976
result.extend(categories_from_toml(categories, Some(&category))?);
8077
}
@@ -85,12 +82,12 @@ fn categories_from_toml(
8582
Ok(result)
8683
}
8784

88-
pub fn sync(toml_str: &str) -> AppResult<()> {
85+
pub fn sync(toml_str: &str) -> Result<(), Error> {
8986
let conn = db::connect_now().unwrap();
9087
sync_with_connection(toml_str, &conn)
9188
}
9289

93-
pub fn sync_with_connection(toml_str: &str, conn: &PgConnection) -> AppResult<()> {
90+
pub fn sync_with_connection(toml_str: &str, conn: &PgConnection) -> Result<(), Error> {
9491
use crate::schema::categories::dsl::*;
9592
use diesel::dsl::all;
9693
use diesel::pg::upsert::excluded;

src/controllers.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
mod cargo_prelude {
22
pub use super::prelude::*;
3-
pub use crate::util::cargo_err;
3+
pub use crate::util::errors::cargo_err;
44
}
55

66
mod frontend_prelude {
@@ -16,7 +16,7 @@ mod prelude {
1616
pub use conduit_router::RequestParams;
1717

1818
pub use crate::db::RequestTransaction;
19-
pub use crate::util::{cargo_err, AppResult}; // TODO: Remove cargo_err from here
19+
pub use crate::util::errors::{cargo_err, AppError, AppResult, ChainError}; // TODO: Remove cargo_err from here
2020

2121
pub use crate::middleware::app::RequestApp;
2222
pub use crate::middleware::current_user::RequestUser;

src/controllers/helpers.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::util::{json_response, AppResult};
1+
use crate::util::{errors::AppResult, json_response};
22
use conduit::Response;
33

44
pub(crate) mod pagination;

src/controllers/helpers/pagination.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::models::helpers::with_count::*;
2-
use crate::util::errors::*;
2+
use crate::util::errors::{cargo_err, AppResult};
33
use diesel::pg::Pg;
44
use diesel::prelude::*;
55
use diesel::query_builder::*;

src/controllers/krate/publish.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ use crate::models::{
1313
};
1414

1515
use crate::render;
16-
use crate::util::{read_fill, read_le_u32};
17-
use crate::util::{AppError, ChainError, Maximums};
16+
use crate::util::{read_fill, read_le_u32, Maximums};
1817
use crate::views::{EncodableCrateUpload, GoodCrate, PublishWarnings};
1918

2019
/// Handles the `PUT /crates/new` route.

src/controllers/token.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
use super::prelude::*;
1+
use super::frontend_prelude::*;
22

33
use crate::middleware::current_user::AuthenticationSource;
44
use crate::models::ApiToken;
55
use crate::schema::api_tokens;
6-
use crate::util::{bad_request, read_fill, ChainError};
6+
use crate::util::read_fill;
77
use crate::views::EncodableApiTokenWithToken;
88

99
use serde_json as json;

src/controllers/user/me.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ use crate::controllers::frontend_prelude::*;
44

55
use crate::controllers::helpers::*;
66
use crate::email;
7-
use crate::util::errors::{AppError, ChainError};
87

98
use crate::models::{
109
CrateOwner, Email, Follow, NewEmail, OwnerKind, User, Version, VersionOwnerAction,

src/controllers/user/session.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use oauth2::{prelude::*, AuthorizationCode, TokenResponse};
77

88
use crate::models::{NewUser, User};
99
use crate::schema::users;
10-
use crate::util::errors::{AppError, ChainError, ReadOnlyMode};
10+
use crate::util::errors::ReadOnlyMode;
1111

1212
/// Handles the `GET /api/private/session/begin` route.
1313
///

src/controllers/version/yank.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ use swirl::Job;
55
use super::version_and_crate;
66
use crate::controllers::cargo_prelude::*;
77
use crate::git;
8-
use crate::models::{insert_version_owner_action, Rights, VersionAction};
9-
use crate::util::AppError;
8+
use crate::models::Rights;
9+
use crate::models::{insert_version_owner_action, VersionAction};
1010

1111
/// Handles the `DELETE /crates/:crate_id/:version/yank` route.
1212
/// This does not delete a crate version, it makes the crate

src/db.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ use std::sync::Arc;
77
use url::Url;
88

99
use crate::middleware::app::RequestApp;
10-
use crate::util::AppResult;
1110
use crate::Env;
1211

1312
#[allow(missing_debug_implementations)]
@@ -18,7 +17,7 @@ pub enum DieselPool {
1817
}
1918

2019
impl DieselPool {
21-
pub fn get(&self) -> AppResult<DieselPooledConn<'_>> {
20+
pub fn get(&self) -> Result<DieselPooledConn<'_>, r2d2::PoolError> {
2221
match self {
2322
DieselPool::Pool(pool) => Ok(DieselPooledConn::Pool(pool.get()?)),
2423
DieselPool::Test(conn) => Ok(DieselPooledConn::Test(conn.lock())),
@@ -89,12 +88,12 @@ pub trait RequestTransaction {
8988
///
9089
/// The connection will live for the lifetime of the request.
9190
// FIXME: This description does not match the implementation below.
92-
fn db_conn(&self) -> AppResult<DieselPooledConn<'_>>;
91+
fn db_conn(&self) -> Result<DieselPooledConn<'_>, r2d2::PoolError>;
9392
}
9493

9594
impl<T: Request + ?Sized> RequestTransaction for T {
96-
fn db_conn(&self) -> AppResult<DieselPooledConn<'_>> {
97-
self.app().diesel_database.get().map_err(Into::into)
95+
fn db_conn(&self) -> Result<DieselPooledConn<'_>, r2d2::PoolError> {
96+
self.app().diesel_database.get()
9897
}
9998
}
10099

src/email.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::path::Path;
22

3-
use crate::util::{bad_request, AppResult};
3+
use crate::util::errors::{server_error, AppResult};
44

55
use failure::Fail;
66
use lettre::file::FileTransport;
@@ -118,12 +118,12 @@ fn send_email(recipient: &str, subject: &str, body: &str) -> AppResult<()> {
118118
.transport();
119119

120120
let result = transport.send(email);
121-
result.map_err(|_| bad_request("Error in sending email"))?;
121+
result.map_err(|_| server_error("Error in sending email"))?;
122122
}
123123
None => {
124124
let mut sender = FileTransport::new(Path::new("/tmp"));
125125
let result = sender.send(email);
126-
result.map_err(|_| bad_request("Email file could not be generated"))?;
126+
result.map_err(|_| server_error("Email file could not be generated"))?;
127127
}
128128
}
129129

0 commit comments

Comments
 (0)