Skip to content

Commit d20160e

Browse files
committed
Auto merge of #1929 - jtgeibel:fix-some-error-responses, r=smarnach
Error response refactoring step 2 This is a continuation of #1920. This series captures several changes: * The controller prelude is split to provide helpers for either cargo or frontend endpoints. Eventually this will be unnecessary, but I see it as a temporary guard rail to encourage the correct usage. * Several user and session frontend endpoints are updated to return a response with the correct HTTP status. Tests were updated as necessary. * A `util::errors::http` module is added for concrete error types associated with HTTP status codes. There are more types to be moved here, but for review purposes that can be done in another PR. * Once `cargo_err` was refactored to use `http::Ok`, all of the description and fallback logic in the `AppError` trait and `ConcreteAppError` type was no longer necessary. Here is what I have in mind for the next steps in this refactor: * Add a `not_found` helper that allows a 404 status with an error message for the client. (TBD if this can be merged with the existing `NotFound` type or if that functionality should remain independent.) * Finish moving types to `http` as applicable. * Review remaing existing usage of helpers within controllers. After that groundwork, my final step is to investigate the ability to sanitize error response for cargo at the router level so that `cargo_err` can be removed. It isn't really practical currently to use the correct helper type from within models, since model functionality can be used from either type of endpoint (or both). The router seems like a strange place for that behavior, but it's where the [error to response conversion](https://github.com/rust-lang/crates.io/blob/4a1542cb17ae50c2bfd0f7e85876d82617e30415/src/router.rs#L139) already happens, and the middleware layer only sees a generic `dyn Error`, not our `AppError`. With a central place to sanitize errors for old cargo versions, we could even enable it only for old (and empty) user agents. r? @smarnach
2 parents 4f2207f + 2f1e697 commit d20160e

18 files changed

+177
-148
lines changed

src/controllers.rs

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
mod cargo_prelude {
2+
pub use super::prelude::*;
3+
pub use crate::util::cargo_err;
4+
}
5+
6+
mod frontend_prelude {
7+
pub use super::prelude::*;
8+
pub use crate::util::errors::{bad_request, server_error};
9+
}
10+
111
mod prelude {
212
pub use super::helpers::ok_true;
313
pub use diesel::prelude::*;
@@ -6,7 +16,7 @@ mod prelude {
616
pub use conduit_router::RequestParams;
717

818
pub use crate::db::RequestTransaction;
9-
pub use crate::util::{cargo_err, AppResult};
19+
pub use crate::util::{cargo_err, AppResult}; // TODO: Remove cargo_err from here
1020

1121
pub use crate::middleware::app::RequestApp;
1222
pub use crate::middleware::current_user::RequestUser;

src/controllers/crate_owner_invitation.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use super::prelude::*;
1+
use super::frontend_prelude::*;
22

33
use crate::models::{CrateOwner, CrateOwnerInvitation, OwnerKind};
44
use crate::schema::{crate_owner_invitations, crate_owners};
@@ -38,7 +38,7 @@ pub fn handle_invite(req: &mut dyn Request) -> AppResult<Response> {
3838
let conn = &*req.db_conn()?;
3939

4040
let crate_invite: OwnerInvitation =
41-
serde_json::from_str(&body).map_err(|_| cargo_err("invalid json request"))?;
41+
serde_json::from_str(&body).map_err(|_| bad_request("invalid json request"))?;
4242

4343
let crate_invite = crate_invite.crate_owner_invite;
4444

src/controllers/krate/downloads.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
66
use std::cmp;
77

8-
use crate::controllers::prelude::*;
8+
use crate::controllers::frontend_prelude::*;
99

1010
use crate::models::{Crate, CrateVersions, Version, VersionDownload};
1111
use crate::schema::version_downloads;

src/controllers/krate/follow.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use diesel::associations::Identifiable;
44

5-
use crate::controllers::prelude::*;
5+
use crate::controllers::frontend_prelude::*;
66
use crate::db::DieselPooledConn;
77
use crate::models::{Crate, Follow};
88
use crate::schema::*;

src/controllers/krate/metadata.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
//! index or cached metadata which was extracted (client side) from the
55
//! `Cargo.toml` file.
66
7-
use crate::controllers::prelude::*;
7+
use crate::controllers::frontend_prelude::*;
88
use crate::models::{
99
Category, Crate, CrateCategory, CrateKeyword, CrateVersions, Keyword, RecentCrateDownloads,
1010
User, Version,

src/controllers/krate/publish.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use hex::ToHex;
44
use std::sync::Arc;
55
use swirl::Job;
66

7-
use crate::controllers::prelude::*;
7+
use crate::controllers::cargo_prelude::*;
88
use crate::git;
99
use crate::models::dependency;
1010
use crate::models::{

src/controllers/krate/search.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
use diesel::dsl::*;
44
use diesel_full_text_search::*;
55

6+
use crate::controllers::cargo_prelude::*;
67
use crate::controllers::helpers::Paginate;
7-
use crate::controllers::prelude::*;
88
use crate::models::{Crate, CrateBadge, CrateOwner, CrateVersions, OwnerKind, Version};
99
use crate::schema::*;
1010
use crate::views::EncodableCrate;

src/controllers/team.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::controllers::prelude::*;
1+
use crate::controllers::frontend_prelude::*;
22

33
use crate::models::Team;
44
use crate::schema::teams;

src/controllers/user/me.rs

+8-9
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
use std::collections::HashMap;
22

3-
use crate::controllers::prelude::*;
3+
use crate::controllers::frontend_prelude::*;
44

55
use crate::controllers::helpers::*;
66
use crate::email;
7-
use crate::util::bad_request;
87
use crate::util::errors::AppError;
98

109
use crate::models::{CrateOwner, Email, Follow, NewEmail, OwnerKind, User, Version};
@@ -117,7 +116,7 @@ pub fn update_user(req: &mut dyn Request) -> AppResult<Response> {
117116

118117
// need to check if current user matches user to be updated
119118
if &user.id.to_string() != name {
120-
return Err(cargo_err("current user does not match requested user"));
119+
return Err(bad_request("current user does not match requested user"));
121120
}
122121

123122
#[derive(Deserialize)]
@@ -131,17 +130,17 @@ pub fn update_user(req: &mut dyn Request) -> AppResult<Response> {
131130
}
132131

133132
let user_update: UserUpdate =
134-
serde_json::from_str(&body).map_err(|_| cargo_err("invalid json request"))?;
133+
serde_json::from_str(&body).map_err(|_| bad_request("invalid json request"))?;
135134

136135
if user_update.user.email.is_none() {
137-
return Err(cargo_err("empty email rejected"));
136+
return Err(bad_request("empty email rejected"));
138137
}
139138

140139
let user_email = user_update.user.email.unwrap();
141140
let user_email = user_email.trim();
142141

143142
if user_email == "" {
144-
return Err(cargo_err("empty email rejected"));
143+
return Err(bad_request("empty email rejected"));
145144
}
146145

147146
conn.transaction::<_, Box<dyn AppError>, _>(|| {
@@ -157,7 +156,7 @@ pub fn update_user(req: &mut dyn Request) -> AppResult<Response> {
157156
.set(&new_email)
158157
.returning(emails::token)
159158
.get_result::<String>(&*conn)
160-
.map_err(|_| cargo_err("Error in creating token"))?;
159+
.map_err(|_| server_error("Error in creating token"))?;
161160

162161
crate::email::send_user_confirm_email(user_email, &user.gh_login, &token);
163162

@@ -196,7 +195,7 @@ pub fn regenerate_token_and_send(req: &mut dyn Request) -> AppResult<Response> {
196195

197196
// need to check if current user matches user to be updated
198197
if &user.id != name {
199-
return Err(cargo_err("current user does not match requested user"));
198+
return Err(bad_request("current user does not match requested user"));
200199
}
201200

202201
conn.transaction(|| {
@@ -206,7 +205,7 @@ pub fn regenerate_token_and_send(req: &mut dyn Request) -> AppResult<Response> {
206205
.map_err(|_| bad_request("Email could not be found"))?;
207206

208207
email::try_send_user_confirm_email(&email.email, &user.gh_login, &email.token)
209-
.map_err(|_| bad_request("Error in sending email"))
208+
.map_err(|_| server_error("Error in sending email"))
210209
})?;
211210

212211
ok_true()

src/controllers/user/other.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::controllers::prelude::*;
1+
use crate::controllers::frontend_prelude::*;
22

33
use crate::models::{CrateOwner, OwnerKind, User};
44
use crate::schema::{crate_owners, crates, users};

src/controllers/user/session.rs

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
use crate::controllers::prelude::*;
1+
use crate::controllers::frontend_prelude::*;
22

33
use crate::github;
44
use conduit_cookie::RequestSession;
5+
use failure::Fail;
56
use oauth2::{prelude::*, AuthorizationCode, TokenResponse};
67

78
use crate::models::{NewUser, User};
89
use crate::schema::users;
9-
use crate::util::errors::{AppError, ReadOnlyMode};
10+
use crate::util::errors::{AppError, ChainError, ReadOnlyMode};
1011

1112
/// Handles the `GET /api/private/session/begin` route.
1213
///
@@ -83,7 +84,7 @@ pub fn authorize(req: &mut dyn Request) -> AppResult<Response> {
8384
let session_state = req.session().remove(&"github_oauth_state".to_string());
8485
let session_state = session_state.as_ref().map(|a| &a[..]);
8586
if Some(&state[..]) != session_state {
86-
return Err(cargo_err("invalid state parameter"));
87+
return Err(bad_request("invalid state parameter"));
8788
}
8889
}
8990

@@ -94,7 +95,8 @@ pub fn authorize(req: &mut dyn Request) -> AppResult<Response> {
9495
.app()
9596
.github
9697
.exchange_code(code)
97-
.map_err(|s| cargo_err(&s))?;
98+
.map_err(|e| e.compat())
99+
.chain_error(|| server_error("Error obtaining token"))?;
98100
let token = token.access_token();
99101
let ghuser = github::github_api::<GithubUser>(req.app(), "/user", token)?;
100102
let user = ghuser.save_to_database(&token.secret(), &*req.db_conn()?)?;

src/controllers/version/deprecated.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
//! period of time to ensure there are no external users of an endpoint before
66
//! it is removed.
77
8-
use crate::controllers::prelude::*;
8+
use crate::controllers::frontend_prelude::*;
99

1010
use crate::models::{Crate, User, Version};
1111
use crate::schema::*;

src/controllers/version/metadata.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
//! index or cached metadata which was extracted (client side) from the
55
//! `Cargo.toml` file.
66
7-
use crate::controllers::prelude::*;
7+
use crate::controllers::frontend_prelude::*;
88

99
use crate::schema::*;
1010
use crate::views::{EncodableDependency, EncodablePublicUser, EncodableVersion};

src/controllers/version/yank.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use swirl::Job;
44

55
use super::version_and_crate;
6-
use crate::controllers::prelude::*;
6+
use crate::controllers::cargo_prelude::*;
77
use crate::git;
88
use crate::models::{insert_version_owner_action, Rights, VersionAction};
99
use crate::util::AppError;

src/middleware/log_request.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ impl Handler for LogRequests {
5353
);
5454

5555
if let Err(ref e) = res {
56-
print!(" error=\"{}\"", e.description());
56+
print!(" error=\"{}\"", e.to_string());
5757
}
5858

5959
if response_time > 1000 {

src/tests/user.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ fn access_token_needs_data() {
115115
let (_, anon) = TestApp::init().empty();
116116
let json = anon
117117
.get::<()>("/api/private/session/authorize")
118-
.bad_with_status(200); // Change endpoint to 400?
118+
.bad_with_status(400);
119119
assert!(json.errors[0].detail.contains("invalid state"));
120120
}
121121

@@ -486,7 +486,7 @@ fn test_empty_email_not_added() {
486486

487487
let json = user
488488
.update_email_more_control(model.id, Some(""))
489-
.bad_with_status(200);
489+
.bad_with_status(400);
490490
assert!(
491491
json.errors[0].detail.contains("empty email rejected"),
492492
"{:?}",
@@ -495,7 +495,7 @@ fn test_empty_email_not_added() {
495495

496496
let json = user
497497
.update_email_more_control(model.id, None)
498-
.bad_with_status(200);
498+
.bad_with_status(400);
499499

500500
assert!(
501501
json.errors[0].detail.contains("empty email rejected"),
@@ -521,7 +521,7 @@ fn test_other_users_cannot_change_my_email() {
521521
another_user_model.id,
522522
523523
)
524-
.bad_with_status(200);
524+
.bad_with_status(400);
525525
assert!(
526526
json.errors[0]
527527
.detail

0 commit comments

Comments
 (0)