Skip to content

Commit dc894b6

Browse files
committed
Auto merge of #2625 - jtgeibel:fix-naming-of-error-type, r=JohnTitor
Cleanup backend json error structs Finish some cleanup work for how JSON error messages are generated. All associated types are moved to `errors::json`. Going forward, there is some direct usage of `Response::builder()` in the middleware. These return text instead of JSON. We should look into this usage to see if some of them should be switched to JSON. For any remaining we could consider a set of `errors::text` helpers. r? @JohnTitor
2 parents 46de794 + 75a3eec commit dc894b6

File tree

6 files changed

+186
-177
lines changed

6 files changed

+186
-177
lines changed

src/controllers/util.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use super::prelude::*;
22

33
use crate::middleware::current_user::TrustedUserId;
44
use crate::models::{ApiToken, User};
5-
use crate::util::errors::{internal, AppError, AppResult, ChainError, Unauthorized};
5+
use crate::util::errors::{forbidden, internal, AppResult, ChainError};
66

77
#[derive(Debug)]
88
pub struct AuthenticatedUser {
@@ -26,7 +26,7 @@ impl AuthenticatedUser {
2626
}
2727

2828
impl<'a> UserAuthenticationExt for dyn RequestExt + 'a {
29-
/// Obtain `AuthenticatedUser` for the request or return an `Unauthorized` error
29+
/// Obtain `AuthenticatedUser` for the request or return an `Forbidden` error
3030
fn authenticate(&self, conn: &PgConnection) -> AppResult<AuthenticatedUser> {
3131
if let Some(id) = self.extensions().find::<TrustedUserId>() {
3232
// A trusted user_id was provided by a signed cookie (or a test `MockCookieUser`)
@@ -43,11 +43,10 @@ impl<'a> UserAuthenticationExt for dyn RequestExt + 'a {
4343
token_id: Some(token.id),
4444
})
4545
.chain_error(|| internal("invalid token"))
46-
.chain_error(|| Box::new(Unauthorized) as Box<dyn AppError>)
46+
.chain_error(forbidden)
4747
} else {
4848
// Unable to authenticate the user
49-
Err(internal("no cookie session or auth header found"))
50-
.chain_error(|| Box::new(Unauthorized) as Box<dyn AppError>)
49+
Err(internal("no cookie session or auth header found")).chain_error(forbidden)
5150
}
5251
}
5352
}

src/github.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use serde::de::DeserializeOwned;
88
use std::str;
99

1010
use crate::app::App;
11-
use crate::util::errors::{cargo_err, internal, AppError, AppResult, NotFound};
11+
use crate::util::errors::{cargo_err, internal, not_found, AppError, AppResult};
1212

1313
/// Does all the nonsense for sending a GET to Github. Doesn't handle parsing
1414
/// because custom error-code handling may be desirable. Use
@@ -46,7 +46,7 @@ fn handle_error_response(app: &App, error: &reqwest::Error) -> Box<dyn AppError>
4646
https://{}/login",
4747
app.config.domain_name,
4848
)),
49-
Some(Status::NOT_FOUND) => Box::new(NotFound),
49+
Some(Status::NOT_FOUND) => not_found(),
5050
_ => internal(&format_args!(
5151
"didn't get a 200 result from github: {}",
5252
error

src/router.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ impl Handler for R404 {
178178
req.mut_extensions().insert(m.params.clone());
179179
m.handler.call(req)
180180
}
181-
Err(..) => Ok(NotFound.response().unwrap()),
181+
Err(..) => Ok(NotFound.into()),
182182
}
183183
}
184184
}
@@ -187,7 +187,7 @@ impl Handler for R404 {
187187
mod tests {
188188
use super::*;
189189
use crate::util::errors::{
190-
bad_request, cargo_err, internal, AppError, ChainError, NotFound, Unauthorized,
190+
bad_request, cargo_err, forbidden, internal, not_found, AppError, ChainError,
191191
};
192192
use crate::util::EndpointResult;
193193

@@ -209,7 +209,7 @@ mod tests {
209209
StatusCode::BAD_REQUEST
210210
);
211211
assert_eq!(
212-
C(|_| err(Unauthorized)).call(&mut req).unwrap().status(),
212+
C(|_| Err(forbidden())).call(&mut req).unwrap().status(),
213213
StatusCode::FORBIDDEN
214214
);
215215
assert_eq!(
@@ -220,7 +220,7 @@ mod tests {
220220
StatusCode::NOT_FOUND
221221
);
222222
assert_eq!(
223-
C(|_| err(NotFound)).call(&mut req).unwrap().status(),
223+
C(|_| Err(not_found())).call(&mut req).unwrap().status(),
224224
StatusCode::NOT_FOUND
225225
);
226226

src/util/errors.rs

Lines changed: 15 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,22 @@ use std::any::{Any, TypeId};
1616
use std::error::Error;
1717
use std::fmt;
1818

19-
use chrono::NaiveDateTime;
20-
use conduit::{header, StatusCode};
2119
use diesel::result::Error as DieselError;
2220

23-
use crate::util::{json_response, AppResponse};
21+
use crate::util::AppResponse;
2422

2523
pub(super) mod concrete;
26-
mod http;
24+
mod json;
25+
26+
pub(crate) use json::{NotFound, ReadOnlyMode, TooManyRequests};
2727

2828
/// Returns an error with status 200 and the provided description as JSON
2929
///
3030
/// This is for backwards compatibility with cargo endpoints. For all other
3131
/// endpoints, use helpers like `bad_request` or `server_error` which set a
3232
/// correct status code.
3333
pub fn cargo_err<S: ToString + ?Sized>(error: &S) -> Box<dyn AppError> {
34-
Box::new(http::Ok(error.to_string()))
34+
Box::new(json::Ok(error.to_string()))
3535
}
3636

3737
// The following are intended to be used for errors being sent back to the Ember
@@ -41,30 +41,20 @@ pub fn cargo_err<S: ToString + ?Sized>(error: &S) -> Box<dyn AppError> {
4141

4242
/// Return an error with status 400 and the provided description as JSON
4343
pub fn bad_request<S: ToString + ?Sized>(error: &S) -> Box<dyn AppError> {
44-
Box::new(http::BadRequest(error.to_string()))
44+
Box::new(json::BadRequest(error.to_string()))
4545
}
4646

47-
/// Returns an error with status 500 and the provided description as JSON
48-
pub fn server_error<S: ToString + ?Sized>(error: &S) -> Box<dyn AppError> {
49-
Box::new(http::ServerError(error.to_string()))
47+
pub fn forbidden() -> Box<dyn AppError> {
48+
Box::new(json::Forbidden)
5049
}
5150

52-
#[derive(Serialize)]
53-
struct StringError<'a> {
54-
detail: &'a str,
55-
}
56-
#[derive(Serialize)]
57-
struct Bad<'a> {
58-
errors: Vec<StringError<'a>>,
51+
pub fn not_found() -> Box<dyn AppError> {
52+
Box::new(json::NotFound)
5953
}
6054

61-
/// Generates a response with the provided status and description as JSON
62-
fn json_error(detail: &str, status: StatusCode) -> AppResponse {
63-
let mut response = json_response(&Bad {
64-
errors: vec![StringError { detail }],
65-
});
66-
*response.status_mut() = status;
67-
response
55+
/// Returns an error with status 500 and the provided description as JSON
56+
pub fn server_error<S: ToString + ?Sized>(error: &S) -> Box<dyn AppError> {
57+
Box::new(json::ServerError(error.to_string()))
6858
}
6959

7060
// =============================================================================
@@ -99,7 +89,7 @@ impl dyn AppError {
9989

10090
fn try_convert(err: &(dyn Error + Send + 'static)) -> Option<Box<Self>> {
10191
match err.downcast_ref() {
102-
Some(DieselError::NotFound) => Some(Box::new(NotFound)),
92+
Some(DieselError::NotFound) => Some(not_found()),
10393
Some(DieselError::DatabaseError(_, info))
10494
if info.message().ends_with("read-only transaction") =>
10595
{
@@ -217,45 +207,6 @@ impl AppError for InternalAppError {
217207
}
218208
}
219209

220-
// TODO: The remaining can probably move under `http`
221-
222-
#[derive(Debug, Clone, Copy)]
223-
pub struct NotFound;
224-
225-
impl From<NotFound> for AppResponse {
226-
fn from(_: NotFound) -> AppResponse {
227-
json_error("Not Found", StatusCode::NOT_FOUND)
228-
}
229-
}
230-
231-
impl AppError for NotFound {
232-
fn response(&self) -> Option<AppResponse> {
233-
Some(NotFound.into())
234-
}
235-
}
236-
237-
impl fmt::Display for NotFound {
238-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
239-
"Not Found".fmt(f)
240-
}
241-
}
242-
243-
#[derive(Debug, Clone, Copy)]
244-
pub struct Unauthorized;
245-
246-
impl AppError for Unauthorized {
247-
fn response(&self) -> Option<AppResponse> {
248-
let detail = "must be logged in to perform that action";
249-
Some(json_error(detail, StatusCode::FORBIDDEN))
250-
}
251-
}
252-
253-
impl fmt::Display for Unauthorized {
254-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
255-
"must be logged in to perform that action".fmt(f)
256-
}
257-
}
258-
259210
pub fn internal<S: ToString + ?Sized>(error: &S) -> Box<dyn AppError> {
260211
Box::new(InternalAppError {
261212
description: error.to_string(),
@@ -277,59 +228,6 @@ pub(crate) fn std_error(e: Box<dyn AppError>) -> Box<dyn Error + Send> {
277228
Box::new(AppErrToStdErr(e))
278229
}
279230

280-
#[derive(Debug, Clone, Copy)]
281-
pub struct ReadOnlyMode;
282-
283-
impl AppError for ReadOnlyMode {
284-
fn response(&self) -> Option<AppResponse> {
285-
let detail = "Crates.io is currently in read-only mode for maintenance. \
286-
Please try again later.";
287-
Some(json_error(detail, StatusCode::SERVICE_UNAVAILABLE))
288-
}
289-
}
290-
291-
impl fmt::Display for ReadOnlyMode {
292-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
293-
"Tried to write in read only mode".fmt(f)
294-
}
295-
}
296-
297-
#[derive(Debug, Clone, Copy)]
298-
pub struct TooManyRequests {
299-
pub retry_after: NaiveDateTime,
300-
}
301-
302-
impl AppError for TooManyRequests {
303-
fn response(&self) -> Option<AppResponse> {
304-
use std::convert::TryInto;
305-
306-
const HTTP_DATE_FORMAT: &str = "%a, %d %b %Y %H:%M:%S GMT";
307-
let retry_after = self.retry_after.format(HTTP_DATE_FORMAT);
308-
309-
let detail = format!(
310-
"You have published too many crates in a \
311-
short period of time. Please try again after {} or email \
312-
[email protected] to have your limit increased.",
313-
retry_after
314-
);
315-
let mut response = json_error(&detail, StatusCode::TOO_MANY_REQUESTS);
316-
response.headers_mut().insert(
317-
header::RETRY_AFTER,
318-
retry_after
319-
.to_string()
320-
.try_into()
321-
.expect("HTTP_DATE_FORMAT contains invalid char"),
322-
);
323-
Some(response)
324-
}
325-
}
326-
327-
impl fmt::Display for TooManyRequests {
328-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
329-
"Too many requests".fmt(f)
330-
}
331-
}
332-
333231
#[test]
334232
fn chain_error_internal() {
335233
assert_eq!(
@@ -358,7 +256,7 @@ fn chain_error_internal() {
358256
"outer caused by inner"
359257
);
360258
assert_eq!(
361-
Err::<(), _>(Unauthorized)
259+
Err::<(), _>(forbidden())
362260
.chain_error(|| internal("outer"))
363261
.unwrap_err()
364262
.to_string(),

src/util/errors/http.rs

Lines changed: 0 additions & 49 deletions
This file was deleted.

0 commit comments

Comments
 (0)