features, cleanup and bug fixes #26

Merged
ben merged 30 commits from ben/Jobboerse:main into main 2022-06-09 17:36:55 +02:00
10 changed files with 225 additions and 214 deletions
Showing only changes of commit d98df7839d - Show all commits

reactor Error types

better separate response error types used for route handlers and other errors
- with an exception of PresentationError
Bennet Bleßmann 2022-05-30 21:10:05 +02:00 committed by Bennet Bleßmann
Signed by: ben
GPG key ID: 3BE1A1A3CBC3CF99

View file

@ -42,11 +42,6 @@ impl Display for LoginRequired {
} }
} }
impl Error for LoginRequired {} impl Error for LoginRequired {}
impl ResponseError for LoginRequired {
fn status_code(&self) -> StatusCode {
StatusCode::UNAUTHORIZED
}
}
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum MultipartFieldError { pub enum MultipartFieldError {
@ -83,39 +78,18 @@ pub enum MultipartFieldError {
Runtime(#[from] tokio::task::JoinError), Runtime(#[from] tokio::task::JoinError),
} }
impl ResponseError for MultipartFieldError {
fn status_code(&self) -> StatusCode {
match self {
MultipartFieldError::IOError(_) | MultipartFieldError::Runtime(_) => {
StatusCode::INTERNAL_SERVER_ERROR
}
MultipartFieldError::ContentTooLarge { .. }
| MultipartFieldError::Date { .. }
| MultipartFieldError::MultipartError(_)
| MultipartFieldError::NotAFile { field: _ }
| MultipartFieldError::TooManyOccurrences { .. }
| MultipartFieldError::UTF8Error(_)
| MultipartFieldError::MissingField { field: _ }
| MultipartFieldError::InvalidAddress(_) => StatusCode::BAD_REQUEST,
}
}
}
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub(crate) enum PresentationError { pub(crate) enum PresentationError {
#[error("Failed to render page template: {0}")] #[error("Failed to render page template: {0}")]
Render(#[from] RenderError), Render(#[from] RenderError),
#[error("Failed to generate URL for route: {0}")] #[error("Failed to generate URL for route: {0}")]
Url(#[from] UrlGenerationError), Url(#[from] UrlGenerationError),
#[error("Couldn't generate response as login is required")]
LoginRequired(#[from] LoginRequired),
} }
impl ResponseError for PresentationError { impl ResponseError for PresentationError {
fn status_code(&self) -> StatusCode { fn status_code(&self) -> StatusCode {
match self { match self {
Self::Render(_) | Self::Url(_) => StatusCode::INTERNAL_SERVER_ERROR, Self::Render(_) | Self::Url(_) => StatusCode::INTERNAL_SERVER_ERROR,
Self::LoginRequired(_) => StatusCode::UNAUTHORIZED,
} }
} }
fn error_response(&self) -> HttpResponse<BoxBody> { fn error_response(&self) -> HttpResponse<BoxBody> {
@ -185,8 +159,8 @@ mod ldap_response_code {
pub const UNAVAILABLE: u32 = 52; pub const UNAVAILABLE: u32 = 52;
} }
impl ResponseError for AuthenticationError { impl AuthenticationError {
fn status_code(&self) -> StatusCode { pub(crate) fn as_status_code(&self) -> StatusCode {
match self { match self {
AuthenticationError::Ldap(ldap_error) => match ldap_error { AuthenticationError::Ldap(ldap_error) => match ldap_error {
LdapError::LdapResult { result } => match result.rc { LdapError::LdapResult { result } => match result.rc {
@ -203,3 +177,16 @@ impl ResponseError for AuthenticationError {
} }
} }
} }
#[derive(Debug, Error)]
#[error("Authentication failed: {0}")]
pub(crate) struct LoginResponseError(#[from] AuthenticationError);
impl ResponseError for LoginResponseError {
fn status_code(&self) -> StatusCode {
self.0.as_status_code()
}
fn error_response(&self) -> HttpResponse<BoxBody> {
default_error_response(self, self.status_code())
}
}

View file

@ -11,7 +11,7 @@ use std::time::Duration;
use actix_web::error::UrlGenerationError; use actix_web::error::UrlGenerationError;
use actix_web::{HttpRequest, Result}; use actix_web::{HttpRequest, Result};
use chrono::{FixedOffset, TimeZone, Timelike}; use chrono::{FixedOffset, TimeZone, Timelike};
use error::{DeleteError, SaveError}; use error::DeleteError;
use lettre::Address; use lettre::Address;
use log::{debug, error, info, warn}; use log::{debug, error, info, warn};
use rand::distributions::DistString; use rand::distributions::DistString;
@ -22,6 +22,7 @@ use toml::value::Datetime;
use crate::auth::User; use crate::auth::User;
use crate::error::PresentationError; use crate::error::PresentationError;
use crate::job_offers::error::SaveError;
use crate::route::{JOBOFFER_ATTACHMENT_ROUTE, PREVIEW_ATTACHMENT_ROUTE}; use crate::route::{JOBOFFER_ATTACHMENT_ROUTE, PREVIEW_ATTACHMENT_ROUTE};
use crate::route::{JOBOFFER_DELETION_ROUTE, JOBOFFER_PUBLISH_ROUTE, JOBOFFER_UNPUBLISH_ROUTE}; use crate::route::{JOBOFFER_DELETION_ROUTE, JOBOFFER_PUBLISH_ROUTE, JOBOFFER_UNPUBLISH_ROUTE};
use crate::util::{toml_date_to_chrono_date, toml_datetime_to_chrono_datetime}; use crate::util::{toml_date_to_chrono_date, toml_datetime_to_chrono_datetime};

View file

@ -6,26 +6,20 @@ use http::StatusCode;
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub(crate) enum SaveError { pub(crate) enum SaveError {
#[error("Creating a new Job Offer failed as the generated ID is already taken")]
AlreadyExists,
#[error("{0}")] #[error("{0}")]
IO(#[from] std::io::Error), IO(#[from] std::io::Error),
#[error("{0}")] #[error("{0}")]
Persist(#[from] tempfile::PersistError), Persist(#[from] tempfile::PersistError),
#[error("Could not serialize Job Offer: {0}")] #[error("Could not serialize Job Offer: {0}")]
Serialize(#[from] toml::ser::Error), Serialize(#[from] toml::ser::Error),
#[error("Could not generate url for confirmation link: {0}")] #[error("The Runtime encountered an error!: {0}")]
Url(#[from] UrlGenerationError),
#[error("Could not send Confirmation E-Mail: {0}")]
Email(#[from] EmailError),
#[error("The Runtime encountered an error!")]
Runtime(#[from] tokio::task::JoinError), Runtime(#[from] tokio::task::JoinError),
#[error("A reviewer-only setting was specified, but no valid session was found.")] #[error("Creating a new Job Offer failed as the generated ID is already taken")]
LoginRequired(#[from] LoginRequired), AlreadyExists,
} }
impl ResponseError for SaveError { impl SaveError {
fn status_code(&self) -> StatusCode { pub(crate) fn as_status_code(&self) -> StatusCode {
match self { match self {
// add special handling of ErrorKind::StorageFull | FilesystemQuotaExceeded once those error kinds are stabilized // add special handling of ErrorKind::StorageFull | FilesystemQuotaExceeded once those error kinds are stabilized
/* /*
@ -42,14 +36,35 @@ impl ResponseError for SaveError {
{ {
StatusCode::INSUFFICIENT_STORAGE StatusCode::INSUFFICIENT_STORAGE
}*/ }*/
SaveError::Persist(_) SaveError::IO(_)
| SaveError::IO(_) | SaveError::Persist(_)
| SaveError::Serialize(_) | SaveError::Serialize(_)
| SaveError::Url(_)
| SaveError::Runtime(_) => StatusCode::INTERNAL_SERVER_ERROR, | SaveError::Runtime(_) => StatusCode::INTERNAL_SERVER_ERROR,
SaveError::AlreadyExists => StatusCode::CONFLICT, SaveError::AlreadyExists => StatusCode::CONFLICT,
SaveError::Email(_) => StatusCode::INTERNAL_SERVER_ERROR, }
SaveError::LoginRequired(_) => StatusCode::UNAUTHORIZED, }
}
#[derive(thiserror::Error, Debug)]
pub(crate) enum SaveResponseError {
#[error("Could not generate url for confirmation link: {0}")]
Url(#[from] UrlGenerationError),
#[error("Could not save job offer: {0}")]
Save(#[from] SaveError),
#[error("Could not send Confirmation E-Mail: {0}")]
Email(#[from] EmailError),
#[error("A reviewer-only setting was specified, but no valid session was found.")]
Login(#[from] LoginRequired),
}
impl ResponseError for SaveResponseError {
fn status_code(&self) -> StatusCode {
match self {
SaveResponseError::Save(inner) => inner.as_status_code(),
SaveResponseError::Url(_) | SaveResponseError::Email(_) => {
StatusCode::INTERNAL_SERVER_ERROR
}
SaveResponseError::Login(_) => StatusCode::UNAUTHORIZED,
} }
} }
fn error_response(&self) -> HttpResponse<BoxBody> { fn error_response(&self) -> HttpResponse<BoxBody> {
@ -72,17 +87,3 @@ pub(crate) enum DeleteError {
#[error("{0}")] #[error("{0}")]
IO(#[from] std::io::Error), IO(#[from] std::io::Error),
} }
impl ResponseError for DeleteError {
fn status_code(&self) -> StatusCode {
match self {
DeleteError::IO(_) => StatusCode::INTERNAL_SERVER_ERROR,
}
}
fn error_response(&self) -> HttpResponse<BoxBody> {
let status_code = self.status_code();
match self {
DeleteError::IO(inner) => default_error_response(inner, status_code),
}
}
}

View file

@ -6,7 +6,7 @@ use log::{error, trace};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::json; use serde_json::json;
use crate::error::{AuthenticationError, PresentationError}; use crate::error::{LoginResponseError, PresentationError};
use crate::route::HTML_CONTENT; use crate::route::HTML_CONTENT;
use crate::{template, ServerConfig}; use crate::{template, ServerConfig};
@ -73,7 +73,7 @@ pub(crate) async fn login_post(
query: web::Query<LoginQuery>, query: web::Query<LoginQuery>,
config: web::Data<ServerConfig>, config: web::Data<ServerConfig>,
session: Session, session: Session,
) -> Result<HttpResponse, AuthenticationError> { ) -> Result<HttpResponse, LoginResponseError> {
let result = crate::auth::User::login(&form.username, &form.password, &session, &config).await; let result = crate::auth::User::login(&form.username, &form.password, &session, &config).await;
match result { match result {
@ -99,7 +99,7 @@ pub(crate) async fn login_post(
} }
Err(err) => { Err(err) => {
error!("failed to perform authentication for login: {}", err); error!("failed to perform authentication for login: {}", err);
Err(err) Err(err.into())
} }
} }
} }

View file

@ -1,10 +1,12 @@
use crate::auth::User; use crate::auth::User;
use crate::error::{AuthenticationError, LoginRequired, MultipartFieldError, PresentationError}; use crate::error::{MultipartFieldError, PresentationError};
use crate::job_offers::error::{DeleteError, SaveError}; use crate::job_offers::error::SaveResponseError;
use crate::route::LOGIN_ROUTE; use crate::route::LOGIN_ROUTE;
use crate::{route, ServerConfig}; use crate::{route, ServerConfig};
use crate::route::job_offer::error::{ConfirmationError, DeletionError, SubmissionError}; use crate::route::job_offer::error::{
ConfirmationResponseError, DeletionResponseError, SubmissionResponseError, SyncResponseError,
};
use actix_session::SessionExt; use actix_session::SessionExt;
use actix_web::dev::ServiceResponse; use actix_web::dev::ServiceResponse;
use actix_web::error::UrlGenerationError; use actix_web::error::UrlGenerationError;
@ -22,9 +24,9 @@ use url::Url;
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
#[error("Some error occurred while attempting to display an error page")] #[error("Some error occurred while attempting to display an error page")]
pub struct ErrorHandlerError; pub struct ErrorHandlerResponseError;
impl ResponseError for ErrorHandlerError {} impl ResponseError for ErrorHandlerResponseError {}
pub(crate) fn generic_server_error_handler<B>( pub(crate) fn generic_server_error_handler<B>(
res: ServiceResponse, res: ServiceResponse,
@ -32,8 +34,8 @@ pub(crate) fn generic_server_error_handler<B>(
title: &str, title: &str,
msg: Option<&str>, msg: Option<&str>,
) -> Result<ErrorHandlerResponse<B>, actix_web::Error> { ) -> Result<ErrorHandlerResponse<B>, actix_web::Error> {
let hb: &Data<Handlebars> = res.request().app_data().ok_or(ErrorHandlerError)?; let hb: &Data<Handlebars> = res.request().app_data().ok_or(ErrorHandlerResponseError)?;
let config: &Data<ServerConfig> = res.request().app_data().ok_or(ErrorHandlerError)?; let config: &Data<ServerConfig> = res.request().app_data().ok_or(ErrorHandlerResponseError)?;
let base = route::base(res.request(), config, title)?; let base = route::base(res.request(), config, title)?;
let session = res.get_session(); let session = res.get_session();
@ -61,19 +63,15 @@ pub(crate) fn internal_server_error_handler<B>(
res: ServiceResponse, res: ServiceResponse,
) -> Result<ErrorHandlerResponse<B>, actix_web::Error> { ) -> Result<ErrorHandlerResponse<B>, actix_web::Error> {
if let Some(err) = res.response().error() { if let Some(err) = res.response().error() {
if let Some(err) = err.as_error::<SaveError>() { if let Some(err) = err.as_error::<SaveResponseError>() {
error!("Internal Server Error due to SaveError: {}", err) error!("Internal Server Error due to SaveError: {}", err)
} else if let Some(err) = err.as_error::<DeleteError>() { } else if let Some(err) = err.as_error::<DeletionResponseError>() {
error!("Internal Server Error due to DeleteError: {}", err) error!("Internal Server Error due to DeleteError: {}", err)
} else if let Some(err) = err.as_error::<ErrorHandlerError>() { } else if let Some(err) = err.as_error::<ErrorHandlerResponseError>() {
error!("Internal Server Error due to ErrorHandlerError: {}", err) error!("Internal Server Error due to ErrorHandlerError: {}", err)
} else if let Some(err) = err.as_error::<MultipartFieldError>() {
error!("Internal Server Error due to MultipartFieldError: {}", err)
} else if let Some(err) = err.as_error::<PresentationError>() { } else if let Some(err) = err.as_error::<PresentationError>() {
error!("Internal Server Error due to PresentationError: {}", err) error!("Internal Server Error due to PresentationError: {}", err)
} else if let Some(err) = err.as_error::<AuthenticationError>() { } else if let Some(err) = err.as_error::<SubmissionResponseError>() {
error!("Internal Server Error due to AuthenticationError: {}", err)
} else if let Some(err) = err.as_error::<SubmissionError>() {
error!("Internal Server Error due to SubmissionError: {}", err) error!("Internal Server Error due to SubmissionError: {}", err)
} else { } else {
error!("Unknown Error Type for Internal Server Error") error!("Unknown Error Type for Internal Server Error")
@ -100,15 +98,15 @@ pub(crate) fn bad_request<B>(
) -> Result<ErrorHandlerResponse<B>, actix_web::Error> { ) -> Result<ErrorHandlerResponse<B>, actix_web::Error> {
let msg; let msg;
let msg = if let Some(err) = res.response().error() { let msg = if let Some(err) = res.response().error() {
if let Some(err) = err.as_error::<SubmissionError>() { if let Some(err) = err.as_error::<SubmissionResponseError>() {
match err { match err {
SubmissionError::MissingLinkOrAttachment => { SubmissionResponseError::MissingLinkOrAttachment => {
Some("Es muss mindestens ein Link oder ein Anhang angegeben werden.") Some("Es muss mindestens ein Link oder ein Anhang angegeben werden.")
} }
SubmissionError::Form(MultipartFieldError::Date { err:_ }) => { SubmissionResponseError::Form(MultipartFieldError::Date { err:_ }) => {
Some("Eine Datumsangabe entsprach nicht dem erwarteten Format.") Some("Eine Datumsangabe entsprach nicht dem erwarteten Format.")
} }
SubmissionError::Form(MultipartFieldError::ContentTooLarge { SubmissionResponseError::Form(MultipartFieldError::ContentTooLarge {
field, field,
max_byte_size, max_byte_size,
}) => { }) => {
@ -117,32 +115,32 @@ pub(crate) fn bad_request<B>(
); );
Some(msg.as_str()) Some(msg.as_str())
} }
SubmissionError::Form(MultipartFieldError::TooManyOccurrences { field, limit }) => { SubmissionResponseError::Form(MultipartFieldError::TooManyOccurrences { field, limit }) => {
msg = format!( msg = format!(
"Das Feld mit der ID {field} wurde zu oft vorhanden, erlaubt sind {limit} vorkommen." "Das Feld mit der ID {field} wurde zu oft vorhanden, erlaubt sind {limit} vorkommen."
); );
Some(msg.as_str()) Some(msg.as_str())
} }
SubmissionError::Form(MultipartFieldError::MultipartError(mpe)) => { SubmissionResponseError::Form(MultipartFieldError::MultipartError(mpe)) => {
warn!("{}", mpe); warn!("{}", mpe);
msg = format!("{}", mpe); msg = format!("{}", mpe);
Some(msg.as_str()) Some(msg.as_str())
} }
SubmissionError::Form(MultipartFieldError::NotAFile { field }) => { SubmissionResponseError::Form(MultipartFieldError::NotAFile { field }) => {
msg = format!( msg = format!(
"Das Feld mit der ID {field} erwartet eine Datei, aber der zugehörige ContentDisposition-Header enthielt keinen Dateinamen." "Das Feld mit der ID {field} erwartet eine Datei, aber der zugehörige ContentDisposition-Header enthielt keinen Dateinamen."
); );
Some(msg.as_str()) Some(msg.as_str())
} }
SubmissionError::Form(MultipartFieldError::MissingField { field }) => { SubmissionResponseError::Form(MultipartFieldError::MissingField { field }) => {
msg = msg =
format!("Das Feld mit der ID {field} fehlt obwohl es nicht optional ist."); format!("Das Feld mit der ID {field} fehlt obwohl es nicht optional ist.");
Some(msg.as_str()) Some(msg.as_str())
} }
SubmissionError::Form(MultipartFieldError::UTF8Error(_err)) => { SubmissionResponseError::Form(MultipartFieldError::UTF8Error(_err)) => {
Some("Ein Feld das im UTF-8 format erwartet wurde konnte nicht als UTF-8 geparst werden.") Some("Ein Feld das im UTF-8 format erwartet wurde konnte nicht als UTF-8 geparst werden.")
} }
SubmissionError::Form(MultipartFieldError::InvalidAddress(reason)) => { SubmissionResponseError::Form(MultipartFieldError::InvalidAddress(reason)) => {
Some(match reason { Some(match reason {
AddressError::MissingParts => { "Unvollständige E-Mail Address" } AddressError::MissingParts => { "Unvollständige E-Mail Address" }
AddressError::Unbalanced => { "Unausgeglichene Klammern '<' & '>' in E-Mail Address. " } AddressError::Unbalanced => { "Unausgeglichene Klammern '<' & '>' in E-Mail Address. " }
@ -150,11 +148,11 @@ pub(crate) fn bad_request<B>(
AddressError::InvalidDomain => { "Der Host/Domain Teil (nach dem @) der E-Mail Address ist ungültig." } AddressError::InvalidDomain => { "Der Host/Domain Teil (nach dem @) der E-Mail Address ist ungültig." }
}) })
} }
SubmissionError::Save(_) SubmissionResponseError::Save(_)
| SubmissionError::TooManyRequests | SubmissionResponseError::TooManyRequests
| SubmissionError::Render(_) | SubmissionResponseError::Render(_)
| SubmissionError::Form(MultipartFieldError::IOError(_)) | SubmissionResponseError::Form(MultipartFieldError::IOError(_))
| SubmissionError::Form(MultipartFieldError::Runtime(_)) => { | SubmissionResponseError::Form(MultipartFieldError::Runtime(_)) => {
error!( error!(
"Response Status Code (Bad Request) and Error appear to disagree : {}", "Response Status Code (Bad Request) and Error appear to disagree : {}",
err err
@ -162,17 +160,17 @@ pub(crate) fn bad_request<B>(
None None
} }
} }
} else if let Some(err) = err.as_error::<ConfirmationError>() { } else if let Some(err) = err.as_error::<ConfirmationResponseError>() {
match err { match err {
ConfirmationError::InvalidRequest => { ConfirmationResponseError::InvalidRequest => {
Some("Die Stellenanzeige erwartet keine Bestätigung, die Stellenanzeigen ID ist ungültig oder der Bestätigungstoken is ungültig.") Some("Die Stellenanzeige erwartet keine Bestätigung, die Stellenanzeigen ID ist ungültig oder der Bestätigungstoken is ungültig.")
} }
ConfirmationError::Save(_) ConfirmationResponseError::Save(_)
| ConfirmationError::Delete(_) | ConfirmationResponseError::Delete(_)
| ConfirmationError::SuccessRenderError(_) | ConfirmationResponseError::SuccessRenderError(_)
| ConfirmationError::RenderError(_) | ConfirmationResponseError::RenderError(_)
| ConfirmationError::Url(_) | ConfirmationResponseError::Url(_)
| ConfirmationError::Presentation(_) => { | ConfirmationResponseError::Presentation(_) => {
error!( error!(
"Response Status Code (Bad Request) and Error appear to disagree : {}", "Response Status Code (Bad Request) and Error appear to disagree : {}",
err err
@ -180,17 +178,17 @@ pub(crate) fn bad_request<B>(
None None
} }
} }
} else if let Some(err) = err.as_error::<MultipartFieldError>() { } else if let Some(err) = err.as_error::<DeletionResponseError>() {
warn!("Unexpected MultipartFieldError as top level error!");
msg = err.to_string();
Some(msg.as_str())
} else if let Some(err) = err.as_error::<DeletionError>() {
match err { match err {
DeletionError::Delete(err) => { DeletionResponseError::Presentation(err) => {
warn!("Failed to present deletion response: {}", err);
Some("Failed to generate response.")
}
DeletionResponseError::Delete(err) => {
warn!("Couldn't delete Job Offer: {}", err); warn!("Couldn't delete Job Offer: {}", err);
Some("Could not delete a Job Offer") Some("Could not delete a Job Offer")
} }
DeletionError::Login(_) => { DeletionResponseError::Login(_) => {
error!( error!(
"Response Status Code (Bad Request) and Error appear to disagree : {}", "Response Status Code (Bad Request) and Error appear to disagree : {}",
err err
@ -226,23 +224,22 @@ pub(crate) fn unauthorized_error_handler<B>(
.response() .response()
.error() .error()
.and_then(|err| { .and_then(|err| {
err.as_error::<LoginRequired>() err.as_error::<SaveResponseError>()
.or_else(|| { .and_then(|elem| match elem {
err.as_error::<SaveError>().and_then(|elem| match elem { SaveResponseError::Login(login) => Some(login),
SaveError::LoginRequired(login) => Some(login), _ => None,
_ => None,
})
}) })
.or_else(|| { .or_else(|| {
err.as_error::<DeletionError>().and_then(|elem| match elem { err.as_error::<DeletionResponseError>()
DeletionError::Login(login) => Some(login),
_ => None,
})
})
.or_else(|| {
err.as_error::<PresentationError>()
.and_then(|elem| match elem { .and_then(|elem| match elem {
PresentationError::LoginRequired(login) => Some(login), DeletionResponseError::Login(login) => Some(login),
_ => None,
})
})
.or_else(|| {
err.as_error::<SyncResponseError>()
.and_then(|elem| match elem {
SyncResponseError::Login(login) => Some(login),
_ => None, _ => None,
}) })
}) })

View file

@ -3,7 +3,7 @@ use actix_session::Session;
use actix_web::{ use actix_web::{
get, http::header, post, web, web::ServiceConfig, HttpRequest, HttpResponse, Responder, Result, get, http::header, post, web, web::ServiceConfig, HttpRequest, HttpResponse, Responder, Result,
}; };
use error::AttachmentError; use error::AttachmentResponseError;
use handlebars::Handlebars; use handlebars::Handlebars;
use http::header::CONTENT_TYPE; use http::header::CONTENT_TYPE;
use serde::Deserialize; use serde::Deserialize;
@ -17,7 +17,7 @@ pub(crate) mod error;
use crate::auth::User; use crate::auth::User;
use crate::error::PresentationError; use crate::error::PresentationError;
use crate::job_offers::JobOffers; use crate::job_offers::JobOffers;
use crate::route::job_offer::error::SyncError; use crate::route::job_offer::error::SyncResponseError;
use crate::route::{HTML_CONTENT, JSON_CONTENT}; use crate::route::{HTML_CONTENT, JSON_CONTENT};
use crate::server_config::ServerConfig; use crate::server_config::ServerConfig;
use crate::template; use crate::template;
@ -93,14 +93,14 @@ pub(crate) async fn job_offer_attachment(
config: web::Data<ServerConfig>, config: web::Data<ServerConfig>,
session: Session, session: Session,
offers: web::Data<JobOffers>, offers: web::Data<JobOffers>,
) -> Result<impl Responder, AttachmentError> { ) -> Result<impl Responder, AttachmentResponseError> {
let id = &path.id; let id = &path.id;
let attachment_name = path.attachment.as_str(); let attachment_name = path.attachment.as_str();
let offer = offers let offer = offers
.get_offer(id) .get_offer(id)
.await .await
.ok_or(AttachmentError::OfferNotFound)?; .ok_or(AttachmentResponseError::OfferNotFound)?;
if !offer.is_published() if !offer.is_published()
&& !query && !query
@ -174,7 +174,7 @@ pub(crate) async fn sync(
session: Session, session: Session,
config: web::Data<ServerConfig>, config: web::Data<ServerConfig>,
offers: web::Data<JobOffers>, offers: web::Data<JobOffers>,
) -> Result<HttpResponse, SyncError> { ) -> Result<HttpResponse, SyncResponseError> {
// TODO return the user to a page where they are asked to confirm syncing // TODO return the user to a page where they are asked to confirm syncing
// aka. the get variant of this route // aka. the get variant of this route
User::current(&session)?; User::current(&session)?;

View file

@ -7,7 +7,7 @@ use serde_json::json;
use crate::auth::User; use crate::auth::User;
use crate::error::PresentationError; use crate::error::PresentationError;
use crate::route::job_offer::error::{DeletionError, StateChangeError}; use crate::route::job_offer::error::{DeletionResponseError, StateChangeResponseError};
use crate::route::{HTML_CONTENT, JOBOFFER_OVERVIEW_ROUTE}; use crate::route::{HTML_CONTENT, JOBOFFER_OVERVIEW_ROUTE};
use crate::{auth, template, JobOffers, ServerConfig}; use crate::{auth, template, JobOffers, ServerConfig};
@ -20,7 +20,7 @@ pub(crate) async fn delete_joboffer(
offers: web::Data<JobOffers>, offers: web::Data<JobOffers>,
config: web::Data<ServerConfig>, config: web::Data<ServerConfig>,
session: Session, session: Session,
) -> actix_web::Result<HttpResponse, DeletionError> { ) -> actix_web::Result<HttpResponse, DeletionResponseError> {
// TODO return the user to a page where they are asked to confirm deletion // TODO return the user to a page where they are asked to confirm deletion
// aka. the get variant of this route // aka. the get variant of this route
let _user = auth::User::current(&session)?; let _user = auth::User::current(&session)?;
@ -47,7 +47,7 @@ pub(crate) async fn delete_expired_joboffers(
offers: web::Data<JobOffers>, offers: web::Data<JobOffers>,
config: web::Data<ServerConfig>, config: web::Data<ServerConfig>,
session: Session, session: Session,
) -> actix_web::Result<HttpResponse, PresentationError> { ) -> actix_web::Result<HttpResponse, DeletionResponseError> {
let user = User::current(&session)?; let user = User::current(&session)?;
let offers_guard = offers.get_offers().await; let offers_guard = offers.get_offers().await;
@ -57,14 +57,17 @@ pub(crate) async fn delete_expired_joboffers(
Some(&user), Some(&user),
); );
let base = crate::route::base(&req, &config, "Delete Expired")?; let base =
crate::route::base(&req, &config, "Delete Expired").map_err(PresentationError::Url)?;
let data = json! {{ let data = json! {{
"base": base, "base": base,
"expired_job_offers": job_offers, "expired_job_offers": job_offers,
}}; }};
let rendered = hb.render(template::JOBOFFER_DELETE_EXPIRED, &data)?; let rendered = hb
.render(template::JOBOFFER_DELETE_EXPIRED, &data)
.map_err(PresentationError::Render)?;
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
.insert_header((http::header::CONTENT_TYPE, HTML_CONTENT.clone())) .insert_header((http::header::CONTENT_TYPE, HTML_CONTENT.clone()))
@ -127,7 +130,7 @@ pub(crate) async fn bulk_delete(
config: web::Data<ServerConfig>, config: web::Data<ServerConfig>,
form: web::Form<BulkDeleteData>, form: web::Form<BulkDeleteData>,
session: Session, session: Session,
) -> actix_web::Result<HttpResponse, DeletionError> { ) -> actix_web::Result<HttpResponse, DeletionResponseError> {
debug!("Received bulk deletion request!"); debug!("Received bulk deletion request!");
let _user = User::current(&session)?; let _user = User::current(&session)?;
@ -154,7 +157,7 @@ pub(crate) async fn review_joboffer(
session: Session, session: Session,
config: web::Data<ServerConfig>, config: web::Data<ServerConfig>,
offers: web::Data<JobOffers>, offers: web::Data<JobOffers>,
) -> actix_web::Result<HttpResponse, StateChangeError> { ) -> actix_web::Result<HttpResponse, StateChangeResponseError> {
// TODO return the user to a page where they are asked to confirm publishing // TODO return the user to a page where they are asked to confirm publishing
// aka. the get variant of this route // aka. the get variant of this route
let _user = auth::User::current(&session)?; let _user = auth::User::current(&session)?;
@ -182,7 +185,7 @@ pub(crate) async fn unpublish_joboffer(
session: Session, session: Session,
config: web::Data<ServerConfig>, config: web::Data<ServerConfig>,
offers: web::Data<JobOffers>, offers: web::Data<JobOffers>,
) -> actix_web::Result<HttpResponse, StateChangeError> { ) -> actix_web::Result<HttpResponse, StateChangeResponseError> {
// TODO return the user to a page where they are asked to confirm un-publishing // TODO return the user to a page where they are asked to confirm un-publishing
// aka. the get variant of this route // aka. the get variant of this route
let _user = auth::User::current(&session)?; let _user = auth::User::current(&session)?;

View file

@ -6,8 +6,8 @@ use serde_json::json;
use crate::auth::User; use crate::auth::User;
use crate::job_offers::JobOfferData; use crate::job_offers::JobOfferData;
use crate::route::job_offer::error::ConfirmationError; use crate::route::job_offer::error::ConfirmationResponseError;
use crate::route::job_offer::error::ConfirmationError::SuccessRenderError; use crate::route::job_offer::error::ConfirmationResponseError::SuccessRenderError;
use crate::route::HTML_CONTENT; use crate::route::HTML_CONTENT;
use crate::{get, template, JobOffers, ServerConfig}; use crate::{get, template, JobOffers, ServerConfig};
@ -35,13 +35,13 @@ pub(crate) async fn confirm_joboffer_get(
config: web::Data<ServerConfig>, config: web::Data<ServerConfig>,
offers: web::Data<JobOffers>, offers: web::Data<JobOffers>,
path: web::Path<(String, String)>, path: web::Path<(String, String)>,
) -> actix_web::Result<HttpResponse, ConfirmationError> { ) -> actix_web::Result<HttpResponse, ConfirmationResponseError> {
let id: &String = &path.0; let id: &String = &path.0;
let req_token = &path.1; let req_token = &path.1;
if let Some(job_offer) = offers.get_offer(id).await { if let Some(job_offer) = offers.get_offer(id).await {
if !job_offer.check_confirmation_token(req_token) { if !job_offer.check_confirmation_token(req_token) {
Err(ConfirmationError::InvalidRequest) Err(ConfirmationResponseError::InvalidRequest)
} else { } else {
let user = User::current(&session).ok(); let user = User::current(&session).ok();
@ -66,14 +66,14 @@ pub(crate) async fn confirm_joboffer_get(
let body = hb let body = hb
.render(template::JOBOFFER_CONFIRM_SUBMISSION, &data) .render(template::JOBOFFER_CONFIRM_SUBMISSION, &data)
.map_err(ConfirmationError::RenderError)?; .map_err(ConfirmationResponseError::RenderError)?;
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
.insert_header((http::header::CONTENT_TYPE, HTML_CONTENT.clone())) .insert_header((http::header::CONTENT_TYPE, HTML_CONTENT.clone()))
.body(body)) .body(body))
} }
} else { } else {
Err(ConfirmationError::InvalidRequest) Err(ConfirmationResponseError::InvalidRequest)
} }
} }
@ -88,7 +88,7 @@ pub(crate) async fn confirm_joboffer_post(
path: web::Path<(String, String)>, path: web::Path<(String, String)>,
offers: web::Data<JobOffers>, offers: web::Data<JobOffers>,
config: web::Data<ServerConfig>, config: web::Data<ServerConfig>,
) -> actix_web::Result<HttpResponse, ConfirmationError> { ) -> actix_web::Result<HttpResponse, ConfirmationResponseError> {
let id = &path.0; let id = &path.0;
let req_token = &path.1; let req_token = &path.1;
@ -113,10 +113,10 @@ pub(crate) async fn confirm_joboffer_post(
.insert_header((http::header::CONTENT_TYPE, HTML_CONTENT.clone())) .insert_header((http::header::CONTENT_TYPE, HTML_CONTENT.clone()))
.body(body)) .body(body))
} }
Err(()) => Err(ConfirmationError::InvalidRequest), Err(()) => Err(ConfirmationResponseError::InvalidRequest),
} }
} else { } else {
Err(ConfirmationError::InvalidRequest) Err(ConfirmationResponseError::InvalidRequest)
} }
} }
@ -130,7 +130,7 @@ pub(crate) async fn reject_joboffer_post(
hb: web::Data<Handlebars<'_>>, hb: web::Data<Handlebars<'_>>,
offers: web::Data<JobOffers>, offers: web::Data<JobOffers>,
config: web::Data<ServerConfig>, config: web::Data<ServerConfig>,
) -> actix_web::Result<HttpResponse, ConfirmationError> { ) -> actix_web::Result<HttpResponse, ConfirmationResponseError> {
let id = &path.0; let id = &path.0;
let req_token = &path.1; let req_token = &path.1;
@ -154,9 +154,9 @@ pub(crate) async fn reject_joboffer_post(
.insert_header((http::header::CONTENT_TYPE, HTML_CONTENT.clone())) .insert_header((http::header::CONTENT_TYPE, HTML_CONTENT.clone()))
.body(body)) .body(body))
} else { } else {
Err(ConfirmationError::InvalidRequest) Err(ConfirmationResponseError::InvalidRequest)
} }
} else { } else {
Err(ConfirmationError::InvalidRequest) Err(ConfirmationResponseError::InvalidRequest)
} }
} }

View file

@ -17,14 +17,14 @@ use url::Url;
use crate::auth::User; use crate::auth::User;
use crate::error::{LoginRequired, MultipartFieldError, PresentationError}; use crate::error::{LoginRequired, MultipartFieldError, PresentationError};
use crate::job_offers::error::{EmailError, SaveError}; use crate::job_offers::error::{EmailError, SaveResponseError};
use crate::job_offers::{ use crate::job_offers::{
Attachment, ConfirmationStatus, JobOffer, JobOfferStatus, JobOffers, Link, MutBorrowedJobOffer, Attachment, ConfirmationStatus, JobOffer, JobOfferStatus, JobOffers, Link, MutBorrowedJobOffer,
ReviewStatus, ReviewStatus,
}; };
use crate::route::job_offer::confirmation::JOBOFFER_CONFIRM_ROUTE; use crate::route::job_offer::confirmation::JOBOFFER_CONFIRM_ROUTE;
use crate::route::job_offer::create::multipart_form::{multi_field, multi_file, once_field}; use crate::route::job_offer::create::multipart_form::{multi_field, multi_file, once_field};
use crate::route::job_offer::error::SubmissionError; use crate::route::job_offer::error::SubmissionResponseError;
use crate::route::HTML_CONTENT; use crate::route::HTML_CONTENT;
use crate::server_config::{EmailConfig, ServerConfig}; use crate::server_config::{EmailConfig, ServerConfig};
use crate::{template, SubmissionLimiter}; use crate::{template, SubmissionLimiter};
@ -62,7 +62,7 @@ pub(crate) async fn create_joboffer_post(
limiter: web::Data<SubmissionLimiter>, limiter: web::Data<SubmissionLimiter>,
hb: web::Data<Handlebars<'_>>, hb: web::Data<Handlebars<'_>>,
multipart: Multipart, multipart: Multipart,
) -> Result<HttpResponse, SubmissionError> { ) -> Result<HttpResponse, SubmissionResponseError> {
let user = User::current(&session).ok(); let user = User::current(&session).ok();
debug!("getting lease for new submission"); debug!("getting lease for new submission");
@ -86,7 +86,7 @@ pub(crate) async fn create_joboffer_post(
if submission_lease.is_none() && user.is_none() { if submission_lease.is_none() && user.is_none() {
debug!("failed to get a lease too many requests"); debug!("failed to get a lease too many requests");
// the user is not authenticated and has reached the quota for un-authenticated users // the user is not authenticated and has reached the quota for un-authenticated users
return Err(SubmissionError::TooManyRequests); return Err(SubmissionResponseError::TooManyRequests);
} }
debug!("got lease, starting to process submission"); debug!("got lease, starting to process submission");
@ -112,7 +112,7 @@ pub(crate) async fn create_joboffer_post(
// submission failed end the lease immediately // submission failed end the lease immediately
lease.end().await lease.end().await
} }
return Err(SubmissionError::MissingLinkOrAttachment); return Err(SubmissionResponseError::MissingLinkOrAttachment);
} }
debug!("validation successful, saving submitted job offer"); debug!("validation successful, saving submitted job offer");
@ -179,7 +179,7 @@ pub(crate) async fn create_job_offer<'data, 'config>(
offers: &'data JobOffers, offers: &'data JobOffers,
config: &'config ServerConfig, config: &'config ServerConfig,
user: Option<&User>, user: Option<&User>,
) -> Result<MutBorrowedJobOffer<'data, 'static, 'config>, SaveError> { ) -> Result<MutBorrowedJobOffer<'data, 'static, 'config>, SaveResponseError> {
let now_date = crate::util::now(); let now_date = crate::util::now();
let token: String = let token: String =
@ -193,7 +193,7 @@ pub(crate) async fn create_job_offer<'data, 'config>(
{ {
// a reviewer-only option is set, but user is not logged-in as a reviewer // a reviewer-only option is set, but user is not logged-in as a reviewer
// maybe a session just expired or some one is messing with the form // maybe a session just expired or some one is messing with the form
return Err(SaveError::LoginRequired(LoginRequired::new())); return Err(SaveResponseError::Login(LoginRequired::new()));
} }
let submission_datetime = user let submission_datetime = user

View file

@ -9,11 +9,11 @@ use log::warn;
use thiserror::Error; use thiserror::Error;
use crate::error::{default_error_response, LoginRequired, MultipartFieldError, PresentationError}; use crate::error::{default_error_response, LoginRequired, MultipartFieldError, PresentationError};
use crate::job_offers::error::{DeleteError, SaveError}; use crate::job_offers::error::{DeleteError, SaveError, SaveResponseError};
use crate::job_offers::JobofferLoadError; use crate::job_offers::JobofferLoadError;
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub(crate) enum ConfirmationError { pub(crate) enum ConfirmationResponseError {
/// The referenced job offer does not exists, /// The referenced job offer does not exists,
/// or does not await confirmation, /// or does not await confirmation,
/// or the token was invalid /// or the token was invalid
@ -33,33 +33,37 @@ pub(crate) enum ConfirmationError {
Presentation(#[from] PresentationError), Presentation(#[from] PresentationError),
} }
impl ResponseError for ConfirmationError { impl ResponseError for ConfirmationResponseError {
fn status_code(&self) -> StatusCode { fn status_code(&self) -> StatusCode {
match self { match self {
ConfirmationError::RenderError(_) => StatusCode::INTERNAL_SERVER_ERROR, ConfirmationResponseError::RenderError(_) => StatusCode::INTERNAL_SERVER_ERROR,
ConfirmationError::SuccessRenderError(_) => StatusCode::CREATED, ConfirmationResponseError::SuccessRenderError(_) => StatusCode::CREATED,
ConfirmationError::InvalidRequest => StatusCode::BAD_REQUEST, ConfirmationResponseError::InvalidRequest => StatusCode::BAD_REQUEST,
ConfirmationError::Save(inner) => inner.status_code(), ConfirmationResponseError::Save(inner) => inner.as_status_code(),
ConfirmationError::Delete(inner) => inner.status_code(), ConfirmationResponseError::Delete(DeleteError::IO(_)) => {
ConfirmationError::Url(inner) => inner.status_code(), StatusCode::INTERNAL_SERVER_ERROR
ConfirmationError::Presentation(inner) => inner.status_code(), }
ConfirmationResponseError::Url(inner) => inner.status_code(),
ConfirmationResponseError::Presentation(inner) => inner.status_code(),
} }
} }
fn error_response(&self) -> HttpResponse<BoxBody> { fn error_response(&self) -> HttpResponse<BoxBody> {
let status_code = self.status_code(); let status_code = self.status_code();
match self { match self {
ConfirmationError::SuccessRenderError(inner) => { ConfirmationResponseError::SuccessRenderError(inner) => {
warn!("Failed to render successful submission response {}", inner); warn!("Failed to render successful submission response {}", inner);
HttpResponse::build(status_code).body("The submission was successful, but an error occurred while generating this response.") HttpResponse::build(status_code).body("The submission was successful, but an error occurred while generating this response.")
} }
ConfirmationError::InvalidRequest => { ConfirmationResponseError::InvalidRequest => {
HttpResponse::build(status_code).body("Invalid Request") HttpResponse::build(status_code).body("Invalid Request")
} // TODO more detail } // TODO more detail
ConfirmationError::Save(inner) => inner.error_response(), ConfirmationResponseError::Save(_) | ConfirmationResponseError::Delete(_) => {
ConfirmationError::Delete(inner) => inner.error_response(), default_error_response(self, status_code)
ConfirmationError::Url(inner) => inner.error_response(), }
error @ (ConfirmationError::RenderError(_) | ConfirmationError::Presentation(_)) => { ConfirmationResponseError::Url(inner) => inner.error_response(),
error @ (ConfirmationResponseError::RenderError(_)
| ConfirmationResponseError::Presentation(_)) => {
default_error_response(error, status_code) default_error_response(error, status_code)
} }
} }
@ -67,30 +71,30 @@ impl ResponseError for ConfirmationError {
} }
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub(crate) enum DeletionError { pub(crate) enum DeletionResponseError {
#[error("Could not delete Job Offer: {0}")] #[error("Could not delete Job Offer: {0}")]
Delete(#[from] DeleteError), Delete(#[from] DeleteError),
#[error("Login required to perform job Offer deletion: {0}")] #[error("Login required to perform job Offer deletion: {0}")]
Login(#[from] LoginRequired), Login(#[from] LoginRequired),
#[error("{0}")]
Presentation(#[from] PresentationError),
} }
impl ResponseError for DeletionError { impl ResponseError for DeletionResponseError {
fn status_code(&self) -> StatusCode { fn status_code(&self) -> StatusCode {
match self { match self {
DeletionError::Delete(inner) => inner.status_code(), DeletionResponseError::Delete(DeleteError::IO(_))
DeletionError::Login(inner) => inner.status_code(), | DeletionResponseError::Presentation(_) => StatusCode::INTERNAL_SERVER_ERROR,
DeletionResponseError::Login(_) => StatusCode::UNAUTHORIZED,
} }
} }
fn error_response(&self) -> HttpResponse<BoxBody> { fn error_response(&self) -> HttpResponse<BoxBody> {
match self { default_error_response(self, self.status_code())
DeletionError::Delete(inner) => inner.error_response(),
DeletionError::Login(inner) => inner.error_response(),
}
} }
} }
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub(crate) enum StateChangeError { pub(crate) enum StateChangeResponseError {
#[error("Could not save changes to Job Offer: {0}")] #[error("Could not save changes to Job Offer: {0}")]
Save(#[from] SaveError), Save(#[from] SaveError),
#[error("Login required to perform job Offer deletion: {0}")] #[error("Login required to perform job Offer deletion: {0}")]
@ -99,104 +103,120 @@ pub(crate) enum StateChangeError {
Presentation(#[from] PresentationError), Presentation(#[from] PresentationError),
} }
impl From<RenderError> for StateChangeError { impl From<RenderError> for StateChangeResponseError {
fn from(render: RenderError) -> Self { fn from(render: RenderError) -> Self {
Self::Presentation(PresentationError::Render(render)) Self::Presentation(PresentationError::Render(render))
} }
} }
impl From<UrlGenerationError> for StateChangeError { impl From<UrlGenerationError> for StateChangeResponseError {
fn from(url_gen: UrlGenerationError) -> Self { fn from(url_gen: UrlGenerationError) -> Self {
Self::Presentation(PresentationError::Url(url_gen)) Self::Presentation(PresentationError::Url(url_gen))
} }
} }
impl ResponseError for StateChangeError { impl ResponseError for StateChangeResponseError {
fn status_code(&self) -> StatusCode { fn status_code(&self) -> StatusCode {
match self { match self {
StateChangeError::Save(inner) => inner.status_code(), StateChangeResponseError::Save(inner) => inner.as_status_code(),
StateChangeError::Login(inner) => inner.status_code(), StateChangeResponseError::Login(_inner) => StatusCode::UNAUTHORIZED,
StateChangeError::Presentation(inner) => inner.status_code(), StateChangeResponseError::Presentation(inner) => inner.status_code(),
} }
} }
fn error_response(&self) -> HttpResponse<BoxBody> { fn error_response(&self) -> HttpResponse<BoxBody> {
match self { match self {
StateChangeError::Save(inner) => inner.error_response(), StateChangeResponseError::Login(_) | StateChangeResponseError::Save(_) => {
StateChangeError::Login(inner) => inner.error_response(), default_error_response(self, self.status_code())
StateChangeError::Presentation(inner) => inner.error_response(), }
StateChangeResponseError::Presentation(inner) => inner.error_response(),
} }
} }
} }
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub(crate) enum SubmissionError { pub(crate) enum SubmissionResponseError {
#[error("At least one link or attachment is required!")] #[error("At least one link or attachment is required!")]
MissingLinkOrAttachment, MissingLinkOrAttachment,
#[error("An error occurred while processing the form submission: {0}")] #[error("An error occurred while processing the form submission: {0}")]
Form(#[from] MultipartFieldError), Form(#[from] MultipartFieldError),
#[error("Failed to save the submission: {0}")] #[error("Failed to save the submission: {0}")]
Save(#[from] SaveError), Save(#[from] SaveResponseError),
#[error("{0}")] #[error("{0}")]
Render(#[from] PresentationError), Render(#[from] PresentationError),
#[error("Too many requests!")] #[error("Too many requests!")]
TooManyRequests, TooManyRequests,
} }
impl From<UrlGenerationError> for SubmissionError { impl From<UrlGenerationError> for SubmissionResponseError {
fn from(url: UrlGenerationError) -> Self { fn from(url: UrlGenerationError) -> Self {
Self::Render(PresentationError::Url(url)) Self::Render(PresentationError::Url(url))
} }
} }
impl From<RenderError> for SubmissionError { impl From<RenderError> for SubmissionResponseError {
fn from(render: RenderError) -> Self { fn from(render: RenderError) -> Self {
Self::Render(PresentationError::Render(render)) Self::Render(PresentationError::Render(render))
} }
} }
impl ResponseError for SubmissionError { impl ResponseError for SubmissionResponseError {
fn status_code(&self) -> StatusCode { fn status_code(&self) -> StatusCode {
match self { match self {
SubmissionError::MissingLinkOrAttachment => StatusCode::BAD_REQUEST, SubmissionResponseError::MissingLinkOrAttachment => StatusCode::BAD_REQUEST,
SubmissionError::Form(inner) => inner.status_code(), SubmissionResponseError::Form(MultipartFieldError::Date { .. })
SubmissionError::Save(inner) => inner.status_code(), | SubmissionResponseError::Form(MultipartFieldError::ContentTooLarge { .. })
SubmissionError::Render(inner) => inner.status_code(), | SubmissionResponseError::Form(MultipartFieldError::MultipartError(_))
SubmissionError::TooManyRequests => StatusCode::TOO_MANY_REQUESTS, | SubmissionResponseError::Form(MultipartFieldError::NotAFile { .. })
| SubmissionResponseError::Form(MultipartFieldError::TooManyOccurrences { .. })
| SubmissionResponseError::Form(MultipartFieldError::MissingField { .. })
| SubmissionResponseError::Form(MultipartFieldError::UTF8Error(_))
| SubmissionResponseError::Form(MultipartFieldError::InvalidAddress(_)) => {
StatusCode::BAD_REQUEST
}
SubmissionResponseError::Form(MultipartFieldError::IOError(_))
| SubmissionResponseError::Form(MultipartFieldError::Runtime(_)) => {
StatusCode::INTERNAL_SERVER_ERROR
}
SubmissionResponseError::Save(inner) => inner.status_code(),
SubmissionResponseError::Render(inner) => inner.status_code(),
SubmissionResponseError::TooManyRequests => StatusCode::TOO_MANY_REQUESTS,
} }
} }
fn error_response(&self) -> HttpResponse<BoxBody> { fn error_response(&self) -> HttpResponse<BoxBody> {
let status_code = self.status_code(); let status_code = self.status_code();
match self { match self {
SubmissionError::MissingLinkOrAttachment => default_error_response(self, status_code), SubmissionResponseError::Save(inner) => inner.error_response(),
SubmissionError::Form(inner) => inner.error_response(), SubmissionResponseError::Render(inner) => inner.error_response(),
SubmissionError::Save(inner) => inner.error_response(), SubmissionResponseError::MissingLinkOrAttachment
SubmissionError::Render(inner) => inner.error_response(), | SubmissionResponseError::Form(_)
SubmissionError::TooManyRequests => default_error_response(self, status_code), | SubmissionResponseError::TooManyRequests => default_error_response(self, status_code),
} }
} }
} }
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub(crate) enum SyncError { pub(crate) enum SyncResponseError {
#[error("{0}")] #[error("{0}")]
LoginRequired(#[from] LoginRequired), Login(#[from] LoginRequired),
#[error("{0}")] #[error("{0}")]
Load(#[from] JobofferLoadError), Load(#[from] JobofferLoadError),
#[error("{0}")] #[error("{0}")]
Presentation(#[from] PresentationError), Presentation(#[from] PresentationError),
} }
impl ResponseError for SyncError { impl ResponseError for SyncResponseError {
fn status_code(&self) -> StatusCode { fn status_code(&self) -> StatusCode {
match self { match self {
SyncError::LoginRequired(_) => StatusCode::FORBIDDEN, SyncResponseError::Login(_) => StatusCode::UNAUTHORIZED,
SyncError::Load(_) | SyncError::Presentation(_) => StatusCode::INTERNAL_SERVER_ERROR, SyncResponseError::Load(_) | SyncResponseError::Presentation(_) => {
StatusCode::INTERNAL_SERVER_ERROR
}
} }
} }
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub(crate) enum AttachmentError { pub(crate) enum AttachmentResponseError {
#[error("Job Offer not found!")] #[error("Job Offer not found!")]
OfferNotFound, OfferNotFound,
#[error("Viewing this Job Offers Attachments requires Authentication, as the offer is not yet published: {0}")] #[error("Viewing this Job Offers Attachments requires Authentication, as the offer is not yet published: {0}")]
@ -205,24 +225,26 @@ pub(crate) enum AttachmentError {
IO(#[from] std::io::Error), IO(#[from] std::io::Error),
} }
impl ResponseError for AttachmentError { impl ResponseError for AttachmentResponseError {
fn status_code(&self) -> StatusCode { fn status_code(&self) -> StatusCode {
match self { match self {
AttachmentError::OfferNotFound => StatusCode::NOT_FOUND, AttachmentResponseError::OfferNotFound => StatusCode::NOT_FOUND,
AttachmentError::IO(error) if error.kind() == ErrorKind::NotFound => { AttachmentResponseError::IO(error) if error.kind() == ErrorKind::NotFound => {
StatusCode::NOT_FOUND StatusCode::NOT_FOUND
} }
AttachmentError::LoginRequired(inner) => inner.status_code(), AttachmentResponseError::LoginRequired(_) => StatusCode::UNAUTHORIZED,
AttachmentError::IO(_) => StatusCode::INTERNAL_SERVER_ERROR, AttachmentResponseError::IO(_) => StatusCode::INTERNAL_SERVER_ERROR,
} }
} }
fn error_response(&self) -> HttpResponse<BoxBody> { fn error_response(&self) -> HttpResponse<BoxBody> {
let status_code = self.status_code(); let status_code = self.status_code();
match self { match self {
AttachmentError::OfferNotFound => default_error_response(self, status_code), AttachmentResponseError::OfferNotFound => default_error_response(self, status_code),
AttachmentError::LoginRequired(inner) => inner.error_response(), AttachmentResponseError::LoginRequired(_inner) => {
AttachmentError::IO(inner) => default_error_response(inner, status_code), default_error_response(self, status_code)
}
AttachmentResponseError::IO(inner) => default_error_response(inner, status_code),
} }
} }
} }