remove common suffix from AppError enum members

This commit is contained in:
Brent Schroeter 2025-04-11 23:06:27 -07:00
parent 1f08b5a590
commit 2c15cdfd11
7 changed files with 31 additions and 36 deletions

View file

@ -8,18 +8,16 @@ use validator::ValidationErrors;
#[derive(Debug)] #[derive(Debug)]
pub enum AppError { pub enum AppError {
InternalServerError(anyhow::Error), InternalServerError(anyhow::Error),
ForbiddenError(String), Forbidden(String),
NotFoundError(String), NotFound(String),
BadRequestError(String), BadRequest(String),
TooManyRequestsError(String), TooManyRequests(String),
} }
impl AppError { impl AppError {
pub fn from_validation_errors(errs: ValidationErrors) -> Self { pub fn from_validation_errors(errs: ValidationErrors) -> Self {
// TODO: customize validation errors formatting // TODO: customize validation errors formatting
Self::BadRequestError( Self::BadRequest(serde_json::to_string(&errs).unwrap_or("validation error".to_string()))
serde_json::to_string(&errs).unwrap_or("validation error".to_string()),
)
} }
} }
@ -30,21 +28,21 @@ impl IntoResponse for AppError {
tracing::error!("Application error: {:?}", err); tracing::error!("Application error: {:?}", err);
(StatusCode::INTERNAL_SERVER_ERROR, "Something went wrong").into_response() (StatusCode::INTERNAL_SERVER_ERROR, "Something went wrong").into_response()
} }
Self::ForbiddenError(client_message) => { Self::Forbidden(client_message) => {
tracing::info!("Forbidden: {}", client_message); tracing::info!("Forbidden: {}", client_message);
(StatusCode::FORBIDDEN, client_message).into_response() (StatusCode::FORBIDDEN, client_message).into_response()
} }
Self::NotFoundError(client_message) => { Self::NotFound(client_message) => {
tracing::info!("Not found: {}", client_message); tracing::info!("Not found: {}", client_message);
(StatusCode::NOT_FOUND, client_message).into_response() (StatusCode::NOT_FOUND, client_message).into_response()
} }
Self::TooManyRequestsError(client_message) => { Self::TooManyRequests(client_message) => {
// Debug level so that if this is from a runaway loop, it won't // Debug level so that if this is from a runaway loop, it won't
// overwhelm server logs // overwhelm server logs
tracing::debug!("Too many requests: {}", client_message); tracing::debug!("Too many requests: {}", client_message);
(StatusCode::TOO_MANY_REQUESTS, client_message).into_response() (StatusCode::TOO_MANY_REQUESTS, client_message).into_response()
} }
Self::BadRequestError(client_message) => { Self::BadRequest(client_message) => {
tracing::info!("Bad user input: {}", client_message); tracing::info!("Bad user input: {}", client_message);
(StatusCode::BAD_REQUEST, client_message).into_response() (StatusCode::BAD_REQUEST, client_message).into_response()
} }
@ -66,16 +64,16 @@ impl Display for AppError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
AppError::InternalServerError(inner) => inner.fmt(f), AppError::InternalServerError(inner) => inner.fmt(f),
AppError::ForbiddenError(client_message) => { AppError::Forbidden(client_message) => {
write!(f, "ForbiddenError: {}", client_message) write!(f, "ForbiddenError: {}", client_message)
} }
AppError::NotFoundError(client_message) => { AppError::NotFound(client_message) => {
write!(f, "NotFoundError: {}", client_message) write!(f, "NotFoundError: {}", client_message)
} }
AppError::BadRequestError(client_message) => { AppError::BadRequest(client_message) => {
write!(f, "BadRequestError: {}", client_message) write!(f, "BadRequestError: {}", client_message)
} }
AppError::TooManyRequestsError(client_message) => { AppError::TooManyRequests(client_message) => {
write!(f, "TooManyRequestsError: {}", client_message) write!(f, "TooManyRequestsError: {}", client_message)
} }
} }

View file

@ -167,7 +167,7 @@ async fn callback(
})?; })?;
if session_csrf_token != query.state { if session_csrf_token != query.state {
tracing::debug!("oauth csrf tokens did not match"); tracing::debug!("oauth csrf tokens did not match");
return Err(AppError::ForbiddenError( return Err(AppError::Forbidden(
"OAuth CSRF tokens do not match.".to_string(), "OAuth CSRF tokens do not match.".to_string(),
)); ));
} }

View file

@ -40,7 +40,7 @@ fn get_channel_by_params<'a>(
.filter(Channel::with_team(team_id)) .filter(Channel::with_team(team_id))
.first(conn) .first(conn)
{ {
diesel::QueryResult::Err(diesel::result::Error::NotFound) => Err(AppError::NotFoundError( diesel::QueryResult::Err(diesel::result::Error::NotFound) => Err(AppError::NotFound(
"Channel with that team and ID not found.".to_string(), "Channel with that team and ID not found.".to_string(),
)), )),
diesel::QueryResult::Err(err) => Err(err.into()), diesel::QueryResult::Err(err) => Err(err.into()),
@ -153,7 +153,7 @@ async fn post_new_channel(
.await .await
.unwrap()?, .unwrap()?,
_ => { _ => {
return Err(AppError::BadRequestError( return Err(AppError::BadRequest(
"Channel type not recognized.".to_string(), "Channel type not recognized.".to_string(),
)); ));
} }
@ -189,7 +189,7 @@ async fn channel_page(
.unwrap()? .unwrap()?
{ {
None => { None => {
return Err(AppError::NotFoundError( return Err(AppError::NotFound(
"Channel with that team and ID not found".to_string(), "Channel with that team and ID not found".to_string(),
)); ));
} }
@ -272,7 +272,7 @@ async fn update_channel(
.context("Failed to load Channel while updating.")? .context("Failed to load Channel while updating.")?
}; };
if updated_rows != 1 { if updated_rows != 1 {
return Err(AppError::NotFoundError( return Err(AppError::NotFound(
"Channel with that team and ID not found".to_string(), "Channel with that team and ID not found".to_string(),
)); ));
} }
@ -308,7 +308,7 @@ async fn update_channel_email_recipient(
guards::require_team_membership(&current_user, &team_id, &db_conn).await?; guards::require_team_membership(&current_user, &team_id, &db_conn).await?;
if !is_permissible_email(&form_body.recipient) { if !is_permissible_email(&form_body.recipient) {
return Err(AppError::BadRequestError( return Err(AppError::BadRequest(
"Unable to validate email address format.".to_string(), "Unable to validate email address format.".to_string(),
)); ));
} }
@ -400,7 +400,7 @@ async fn verify_email(
guards::require_team_membership(&current_user, &team_id, &db_conn).await?; guards::require_team_membership(&current_user, &team_id, &db_conn).await?;
if form_body.code.len() != VERIFICATION_CODE_LEN { if form_body.code.len() != VERIFICATION_CODE_LEN {
return Err(AppError::BadRequestError(format!( return Err(AppError::BadRequest(format!(
"Verification code must be {} characters long.", "Verification code must be {} characters long.",
VERIFICATION_CODE_LEN VERIFICATION_CODE_LEN
))); )));
@ -414,15 +414,13 @@ async fn verify_email(
let channel = get_channel_by_params(conn, &team_id, &channel_id)?; let channel = get_channel_by_params(conn, &team_id, &channel_id)?;
let config: EmailBackendConfig = channel.backend_config.try_into()?; let config: EmailBackendConfig = channel.backend_config.try_into()?;
if config.verified { if config.verified {
return Err(AppError::BadRequestError( return Err(AppError::BadRequest(
"Channel's email address is already verified.".to_string(), "Channel's email address is already verified.".to_string(),
)); ));
} }
const MAX_VERIFICATION_GUESSES: u32 = 100; const MAX_VERIFICATION_GUESSES: u32 = 100;
if config.verification_code_guesses > MAX_VERIFICATION_GUESSES { if config.verification_code_guesses > MAX_VERIFICATION_GUESSES {
return Err(AppError::BadRequestError( return Err(AppError::BadRequest("Verification expired.".to_string()));
"Verification expired.".to_string(),
));
} }
let new_config = if config.verification_code == verification_code { let new_config = if config.verification_code == verification_code {
EmailBackendConfig { EmailBackendConfig {

View file

@ -34,7 +34,7 @@ pub async fn require_team_membership(
}; };
match maybe_team { match maybe_team {
Some((team, _)) => Ok(team), Some((team, _)) => Ok(team),
None => Err(AppError::ForbiddenError( None => Err(AppError::Forbidden(
"not a member of requested team".to_string(), "not a member of requested team".to_string(),
)), )),
} }
@ -50,6 +50,6 @@ pub async fn require_valid_csrf_token(
if validate_csrf_token(db_conn, csrf_token, Some(current_user.id)).await? { if validate_csrf_token(db_conn, csrf_token, Some(current_user.id)).await? {
Ok(()) Ok(())
} else { } else {
Err(AppError::ForbiddenError("invalid CSRF token".to_string())) Err(AppError::Forbidden("invalid CSRF token".to_string()))
} }
} }

View file

@ -124,7 +124,7 @@ async fn project_page(
.filter(Project::with_team(&team_id)) .filter(Project::with_team(&team_id))
.first(conn) .first(conn)
{ {
diesel::QueryResult::Err(diesel::NotFound) => Err(AppError::NotFoundError( diesel::QueryResult::Err(diesel::NotFound) => Err(AppError::NotFound(
"Project with that team and ID not found.".to_string(), "Project with that team and ID not found.".to_string(),
)), )),
other => other other => other
@ -213,7 +213,7 @@ async fn update_enabled_channels(
.filter(Project::with_team(&team_id)) .filter(Project::with_team(&team_id))
.first(conn) .first(conn)
{ {
diesel::QueryResult::Err(diesel::NotFound) => Err(AppError::NotFoundError( diesel::QueryResult::Err(diesel::NotFound) => Err(AppError::NotFound(
"Project with that team and ID not found.".to_string(), "Project with that team and ID not found.".to_string(),
)), )),
other => other other => other

View file

@ -139,7 +139,7 @@ async fn remove_api_key(
"there should never be more than 1 API key with the same ID" "there should never be more than 1 API key with the same ID"
); );
if n_deleted == 0 { if n_deleted == 0 {
Err(AppError::NotFoundError( Err(AppError::NotFound(
"no API key with that ID and team found".to_owned(), "no API key with that ID and team found".to_owned(),
)) ))
} else { } else {

View file

@ -67,9 +67,8 @@ async fn say_get(
query.validate().map_err(AppError::from_validation_errors)?; query.validate().map_err(AppError::from_validation_errors)?;
let api_key = { let api_key = {
let query_key = try_parse_as_uuid(&query.key).or(Err(AppError::ForbiddenError( let query_key = try_parse_as_uuid(&query.key)
"key not accepted".to_string(), .or(Err(AppError::Forbidden("key not accepted".to_string())))?;
)))?;
db_conn db_conn
.interact::<_, Result<ApiKey, AppError>>(move |conn| { .interact::<_, Result<ApiKey, AppError>>(move |conn| {
update(api_keys::table.filter(ApiKey::with_id(&query_key))) update(api_keys::table.filter(ApiKey::with_id(&query_key)))
@ -78,7 +77,7 @@ async fn say_get(
.get_result(conn) .get_result(conn)
.optional() .optional()
.context("failed to get API key")? .context("failed to get API key")?
.ok_or(AppError::ForbiddenError("key not accepted.".to_string())) .ok_or(AppError::Forbidden("key not accepted.".to_string()))
}) })
.await .await
.unwrap()? .unwrap()?
@ -146,7 +145,7 @@ async fn say_get(
.unwrap()? .unwrap()?
.is_none() .is_none()
{ {
return Err(AppError::TooManyRequestsError( return Err(AppError::TooManyRequests(
"team rate limit exceeded".to_string(), "team rate limit exceeded".to_string(),
)); ));
} }