diff --git a/src/main.rs b/src/main.rs index 8547f6bd6842f970ee4fd7d64d88a219ae29da5c..fdbfed230fe498b01976b5c20a4ee974659d40d6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -74,7 +74,7 @@ type DbPool = r2d2::Pool<ConnectionManager<PgConnection>>; async fn update_database( mut job_result_receiver: broadcast::Receiver<JobResult>, pool: DbPool, -) -> Result<(), PostError> { +) -> Result<(), PageError> { loop { let job_result = job_result_receiver.recv().await.unwrap(); if let JobResult { @@ -285,28 +285,28 @@ async fn main() -> Result<(), Box<dyn Error>> { } #[derive(Error, Debug)] -#[error("unauthorized")] -struct UnauthorizedError {} - -#[derive(Error, Debug)] -enum PostError { +enum PageError { + #[error("Unauthorized")] + Unauthorized(), + #[error("Couldn't render: {0}")] + Render(#[from] handlebars::RenderError), #[error(transparent)] - Unauthorized(#[from] UnauthorizedError), + SessionGet(#[from] actix_session::SessionGetError), #[error("{0}")] Custom(String), #[error("{0}")] Forbidden(String), #[error("{0}")] Validation(String), - #[error("couldn't get connection from pool")] + #[error("Couldn't get connection from pool")] ConnectionPool(#[from] r2d2::Error), - #[error("couldn't hash")] + #[error("Couldn't hash")] UserHashing(#[from] user::UserHashingError), #[error(transparent)] Web(#[from] actix_web::Error), #[error(transparent)] Queue(#[from] async_channel::SendError<Job>), - #[error("couldn't fetch result from database")] + #[error("Couldn't fetch result from database")] Database(#[from] diesel::result::Error), #[error("couldn't insert session")] SessionInsert(#[from] actix_session::SessionInsertError), @@ -316,8 +316,6 @@ enum PostError { Zip(#[from] zip::result::ZipError), } -type PostResult = Result<HttpResponse, PostError>; - fn error_response_and_log(me: &impl actix_web::error::ResponseError) -> HttpResponse { error!("{}", me); HttpResponse::build(me.status_code()) @@ -325,69 +323,32 @@ fn error_response_and_log(me: &impl actix_web::error::ResponseError) -> HttpResp .body(me.to_string()) } -impl actix_web::error::ResponseError for PostError { - fn error_response(&self) -> HttpResponse { - error_response_and_log(self) - } - - fn status_code(&self) -> StatusCode { - match *self { - PostError::Unauthorized(_) => StatusCode::UNAUTHORIZED, - PostError::Validation(_) => StatusCode::BAD_REQUEST, - PostError::Forbidden(_) => StatusCode::FORBIDDEN, - PostError::Custom(_) - | PostError::SessionInsert(_) - | PostError::ConnectionPool(_) - | PostError::Web(_) - | PostError::Queue(_) - | PostError::Database(_) - | PostError::Io(_) - | PostError::UserHashing(_) - | PostError::Zip(_) => StatusCode::INTERNAL_SERVER_ERROR, - } - } -} - -#[derive(Error, Debug)] -enum GetError { - #[error("Unauthorized")] - Unauthorized(#[from] UnauthorizedError), - #[error("{0}")] - Forbidden(String), - #[error("Couldn't render: {0}")] - Render(#[from] handlebars::RenderError), - #[error(transparent)] - Actix(#[from] actix_web::Error), - #[error(transparent)] - SessionGet(#[from] actix_session::SessionGetError), - #[error("Couldn't fetch result from database")] - Diesel(#[from] diesel::result::Error), - #[error("Couldn't get connection from pool")] - R2d2Pool(#[from] r2d2::Error), - #[error("Couldn't find file")] - Io(#[from] std::io::Error), -} - -impl actix_web::error::ResponseError for GetError { +impl actix_web::error::ResponseError for PageError { fn error_response(&self) -> HttpResponse { error_response_and_log(self) } fn status_code(&self) -> StatusCode { match *self { - GetError::Unauthorized(_) => StatusCode::UNAUTHORIZED, - GetError::Forbidden(_) => StatusCode::FORBIDDEN, - GetError::Io(_) => StatusCode::NOT_FOUND, - GetError::Render(_) - | GetError::SessionGet(_) - | GetError::Actix(_) - | GetError::Diesel(_) - | GetError::R2d2Pool(_) => StatusCode::INTERNAL_SERVER_ERROR, + PageError::Unauthorized() => StatusCode::UNAUTHORIZED, + PageError::Validation(_) => StatusCode::BAD_REQUEST, + PageError::Forbidden(_) => StatusCode::FORBIDDEN, + PageError::Custom(_) + | PageError::SessionInsert(_) + | PageError::ConnectionPool(_) + | PageError::Web(_) + | PageError::Render(_) + | PageError::Queue(_) + | PageError::SessionGet(_) + | PageError::Database(_) + | PageError::Io(_) + | PageError::UserHashing(_) + | PageError::Zip(_) => StatusCode::INTERNAL_SERVER_ERROR, } } } -type GetResult = Result<HttpResponse, GetError>; +type PageResult = Result<HttpResponse, PageError>; #[derive(Serialize)] struct BaseContext { @@ -425,7 +386,7 @@ fn render<Err: From<RenderError>, Ctx: serde::Serialize>( } #[get("/login")] -async fn get_login(base: BaseContext, hb: web::Data<Handlebars<'_>>) -> GetResult { +async fn get_login(base: BaseContext, hb: web::Data<Handlebars<'_>>) -> PageResult { #[derive(Serialize)] struct Context { base: BaseContext, @@ -434,7 +395,7 @@ async fn get_login(base: BaseContext, hb: web::Data<Handlebars<'_>>) -> GetResul } #[get("/me")] -async fn get_me(base: BaseContext, identity: Identity, hb: web::Data<Handlebars<'_>>) -> GetResult { +async fn get_me(base: BaseContext, identity: Identity, hb: web::Data<Handlebars<'_>>) -> PageResult { require_identity(&identity)?; #[derive(Serialize)] struct Context { @@ -448,7 +409,7 @@ async fn get_problem_by_id_assets( identity: Identity, pool: web::Data<DbPool>, path: web::Path<(i32, String)>, -) -> Result<NamedFile, GetError> { +) -> Result<NamedFile, PageError> { let logged_user = require_identity(&identity)?; let (problem_id, asset_filename) = path.into_inner(); let mut connection = pool.get()?; @@ -496,8 +457,8 @@ fn get_identity(identity: &Option<&Identity>) -> Option<LoggedUser> { }) } -fn require_identity(identity: &Identity) -> Result<LoggedUser, UnauthorizedError> { - get_identity(&Some(identity)).ok_or(UnauthorizedError {}) +fn require_identity(identity: &Identity) -> Result<LoggedUser, PageError> { + get_identity(&Some(identity)).ok_or(PageError::Unauthorized()) } fn format_duration(duration: chrono::Duration) -> String { @@ -567,7 +528,7 @@ async fn get_contest_by_id( hb: web::Data<Handlebars<'_>>, path: web::Path<(i32,)>, tz: web::Data<Tz>, -) -> GetResult { +) -> PageResult { let logged_user = require_identity(&identity)?; let (contest_id,) = path.into_inner(); @@ -614,7 +575,7 @@ async fn get_contest_scoreboard_by_id( hb: web::Data<Handlebars<'_>>, path: web::Path<(i32,)>, tz: web::Data<Tz>, -) -> GetResult { +) -> PageResult { let logged_user = require_identity(&identity)?; let (contest_id,) = path.into_inner(); @@ -682,14 +643,14 @@ async fn get_contest_scoreboard_by_id( ) } -fn assert_contest_not_started(logged_user: &LoggedUser, contest: &Contest) -> Result<(), GetError> { +fn assert_contest_not_started(logged_user: &LoggedUser, contest: &Contest) -> Result<(), PageError> { if contest .start_instant .map(|s| s > Local::now().naive_utc()) .unwrap_or(false) && !logged_user.is_admin { - return Err(GetError::Forbidden( + return Err(PageError::Forbidden( "Essa competição ainda não começou".into(), )); } @@ -706,7 +667,7 @@ async fn get_contest_problem_by_id_label( session: Session, path: web::Path<(i32, String)>, tz: web::Data<Tz>, -) -> GetResult { +) -> PageResult { let logged_user = require_identity(&identity)?; let (contest_id, problem_label) = path.into_inner(); @@ -781,7 +742,7 @@ struct LoggedUser { } #[post("/logout")] -async fn post_logout(identity: Identity) -> PostResult { +async fn post_logout(identity: Identity) -> PageResult { identity.logout(); Ok(redirect_to_root()) } @@ -791,23 +752,23 @@ async fn post_login( pool: web::Data<DbPool>, form: web::Form<LoginForm>, request: HttpRequest, -) -> PostResult { +) -> PageResult { let mut connection = pool.get()?; match web::block(move || { user::check_matching_password(&mut connection, &form.name, &form.password) }) .await - .map_err(|e| PostError::Web(e.into()))? + .map_err(|e| PageError::Web(e.into()))? .map_err(|e| match e { - UserHashingError::Database(e) => PostError::Database(e), - UserHashingError::Hash(_) => PostError::Validation("Senha inválida".into()), + UserHashingError::Database(e) => PageError::Database(e), + UserHashingError::Hash(_) => PageError::Validation("Senha inválida".into()), })? { PasswordMatched::UserDoesntExist => { - Err(PostError::Validation("Usuário inexistente".into())) + Err(PageError::Validation("Usuário inexistente".into())) } PasswordMatched::PasswordDoesntMatch => { - Err(PostError::Validation("Senha incorreta".into())) + Err(PageError::Validation("Senha incorreta".into())) } PasswordMatched::PasswordMatches(logged_user) => { Identity::login( @@ -817,9 +778,9 @@ async fn post_login( name: (&logged_user.name).into(), is_admin: logged_user.is_admin, }) - .map_err(|_| PostError::Custom("Usuário no banco de dados inconsistente".into()))?, + .map_err(|_| PageError::Custom("Usuário no banco de dados inconsistente".into()))?, ) - .map_err(|_| PostError::Custom("Impossível fazer login".into()))?; + .map_err(|_| PageError::Custom("Impossível fazer login".into()))?; Ok(redirect_to_root()) } } @@ -891,7 +852,7 @@ async fn get_submissions( pool: web::Data<DbPool>, hb: web::Data<Handlebars<'_>>, tz: web::Data<Tz>, -) -> GetResult { +) -> PageResult { let logged_user = require_identity(&identity)?; let mut connection = pool.get()?; @@ -924,7 +885,7 @@ async fn get_submissions_me( pool: web::Data<DbPool>, hb: web::Data<Handlebars<'_>>, tz: web::Data<Tz>, -) -> GetResult { +) -> PageResult { let logged_user = require_identity(&identity)?; let mut connection = pool.get()?; @@ -954,7 +915,7 @@ async fn get_submission( hb: web::Data<Handlebars<'_>>, path: web::Path<(String,)>, tz: web::Data<Tz>, -) -> GetResult { +) -> PageResult { let logged_user = require_identity(&identity)?; let (submission_uuid,) = path.into_inner(); let mut connection = pool.get()?; @@ -984,7 +945,7 @@ async fn get_submission( submission::get_submission_by_uuid(&mut connection, submission_uuid)?; if user.id != logged_user.id && !logged_user.is_admin { - return Err(GetError::Forbidden( + return Err(PageError::Forbidden( "Não é possível acessar uma submissão de outro usuário".into(), )); } @@ -1020,7 +981,7 @@ async fn get_submissions_me_by_contest_id( hb: web::Data<Handlebars<'_>>, path: web::Path<(i32,)>, tz: web::Data<Tz>, -) -> GetResult { +) -> PageResult { let logged_user = require_identity(&identity)?; let (contest_id,) = path.into_inner(); let mut connection = pool.get()?; @@ -1052,7 +1013,7 @@ async fn get_submissions_me_by_contest_id_problem_label( hb: web::Data<Handlebars<'_>>, path: web::Path<(i32, String)>, tz: web::Data<Tz>, -) -> GetResult { +) -> PageResult { let logged_user = require_identity(&identity)?; let mut connection = pool.get()?; @@ -1125,10 +1086,10 @@ async fn change_password( form: web::Form<ChangePasswordForm>, pool: web::Data<DbPool>, request: HttpRequest, -) -> PostResult { +) -> PageResult { let identity = require_identity(&identity)?; if form.new_password != form.new_password_repeat { - return Err(PostError::Validation("Senhas são diferentes".into())); + return Err(PageError::Validation("Senhas são diferentes".into())); } let mut connection = pool.get()?; @@ -1163,10 +1124,10 @@ async fn create_user( pool: web::Data<DbPool>, form: web::Form<CreateUserForm>, request: HttpRequest, -) -> PostResult { +) -> PageResult { let identity = require_identity(&identity)?; if !identity.is_admin { - return Err(PostError::Forbidden( + return Err(PageError::Forbidden( "Apenas administradores podem fazer isso".into(), )); } @@ -1201,10 +1162,10 @@ async fn impersonate_user( pool: web::Data<DbPool>, form: web::Form<ImpersonateUserForm>, request: HttpRequest, -) -> PostResult { +) -> PageResult { let my_identity = require_identity(&identity)?; if !my_identity.is_admin { - return Err(PostError::Forbidden( + return Err(PageError::Forbidden( "Apenas administradores podem fazer isso".into(), )); } @@ -1219,9 +1180,9 @@ async fn impersonate_user( name: (&user.name).into(), is_admin: user.is_admin, }) - .map_err(|_| PostError::Custom("Usuário no banco de dados inconsistente".into()))?, + .map_err(|_| PageError::Custom("Usuário no banco de dados inconsistente".into()))?, ) - .map_err(|_| PostError::Custom("Impossível fazer login".into()))?; + .map_err(|_| PageError::Custom("Impossível fazer login".into()))?; Ok(redirect_to_referer( "Personificado com sucesso".into(), @@ -1238,16 +1199,16 @@ async fn create_submission( languages: web::Data<Arc<DashMap<String, Language>>>, session: Session, request: HttpRequest, -) -> PostResult { +) -> PageResult { let logged_user = require_identity(&identity)?; let mut connection = pool.get()?; languages .get(&form.language) - .ok_or(PostError::Validation("Linguagem inexistente".into()))?; + .ok_or(PageError::Validation("Linguagem inexistente".into()))?; if !logged_user.is_admin && form.language != "cpp.17.g++" { - return Err(PostError::Validation( + return Err(PageError::Validation( "Somente é possível submeter em C++".into(), )); } @@ -1267,10 +1228,7 @@ async fn create_submission( let contest = contest::get_contest_by_contest_problem_id(&mut connection, form.contest_problem_id)?; - assert_contest_not_started(&logged_user, &contest).map_err(|e| match e { - GetError::Forbidden(m) => PostError::Forbidden(m), - _ => PostError::Custom("Erro desconhecido".into()), - })?; + assert_contest_not_started(&logged_user, &contest)?; let metadata = problem::get_problem_by_contest_id_metadata(&mut connection, form.contest_problem_id)?; @@ -1306,12 +1264,12 @@ async fn rejudge_submission( job_sender: web::Data<Sender<Job>>, request: HttpRequest, path: web::Path<(String,)>, -) -> PostResult { +) -> PageResult { let logged_user = require_identity(&identity)?; let mut connection = pool.get()?; if !logged_user.is_admin { - return Err(PostError::Forbidden( + return Err(PageError::Forbidden( "Apenas administradores podem fazer isso".into(), )); } @@ -1401,7 +1359,7 @@ fn get_formatted_contests( connection: &mut PgConnection, user_id: Option<i32>, tz: &Tz, -) -> Result<Vec<FormattedContest>, GetError> { +) -> Result<Vec<FormattedContest>, PageError> { Ok(match user_id { Some(user_id) => contest::get_contests_with_acs(connection, user_id)? .iter() @@ -1421,7 +1379,7 @@ async fn get_main( pool: web::Data<DbPool>, hb: web::Data<Handlebars<'_>>, tz: web::Data<Tz>, -) -> GetResult { +) -> PageResult { let logged_user = get_identity(&identity.as_ref()); #[derive(Serialize)] @@ -1452,7 +1410,7 @@ async fn get_contests( pool: web::Data<DbPool>, hb: web::Data<Handlebars<'_>>, tz: web::Data<Tz>, -) -> GetResult { +) -> PageResult { let logged_user = require_identity(&identity)?; #[derive(Serialize)] @@ -1481,10 +1439,10 @@ async fn create_contest( job_result_sender: web::Data<broadcast::Sender<JobResult>>, tz: web::Data<Tz>, request: HttpRequest, -) -> PostResult { +) -> PageResult { let logged_user = require_identity(&identity)?; if !logged_user.is_admin { - return Err(PostError::Forbidden( + return Err(PageError::Forbidden( "Apenas administradores podem fazer isso".into(), )); } @@ -1514,16 +1472,16 @@ async fn create_contest( let data = chunk.unwrap(); cursor .write(&data) - .map_err(|_| PostError::Validation("Corpo inválido".into()))?; + .map_err(|_| PageError::Validation("Corpo inválido".into()))?; } cursor.set_position(0); - fn parse_field(field: &str, cursor: &mut Cursor<Vec<u8>>) -> Result<String, PostError> { + fn parse_field(field: &str, cursor: &mut Cursor<Vec<u8>>) -> Result<String, PageError> { let mut value = String::new(); cursor .read_to_string(&mut value) - .map_err(|_| PostError::Validation(format!("Campo {} inválido", field)))?; + .map_err(|_| PageError::Validation(format!("Campo {} inválido", field)))?; Ok(value) } @@ -1548,9 +1506,9 @@ async fn create_contest( let polygon_zip = form .polygon_zip - .ok_or(PostError::Validation("Arquivo não informado".into()))?; + .ok_or(PageError::Validation("Arquivo não informado".into()))?; let imported = import_contest::import_file(polygon_zip) - .map_err(|e| PostError::Validation(format!("Não foi possível importar: {}", e)))?; + .map_err(|e| PageError::Validation(format!("Não foi possível importar: {}", e)))?; let mut connection = pool.get()?; let contest = if form.name.as_ref().unwrap() != "" { @@ -1652,10 +1610,10 @@ async fn create_contest( std::io::copy(&mut zip.by_name(&name)?, &mut File::create(data_path)?)?; } - fn map_codeforces_language(input: &String) -> Result<String, PostError> { + fn map_codeforces_language(input: &String) -> Result<String, PageError> { Ok(CODEFORCES_LANGUAGE_TO_JUGHISTO .get(input) - .ok_or_else(|| PostError::Validation(format!("Linguagem {} não suportada", input)))? + .ok_or_else(|| PageError::Validation(format!("Linguagem {} não suportada", input)))? .into()) } @@ -1665,7 +1623,7 @@ async fn create_contest( .solution .iter() .find(|s| s.tag == "main") - .ok_or(PostError::Validation("No main solution".into()))? + .ok_or(PageError::Validation("No main solution".into()))? .source; let problem = problem::upsert_problem( @@ -1740,11 +1698,11 @@ async fn create_contest( ) .await .map_err(|_| { - PostError::Validation("Couldn't use an intermediate program".into()) + PageError::Validation("Couldn't use an intermediate program".into()) })?; if run_stats.result != i32::from(job_result::run_cached::Result::Ok) { - return Err(PostError::Validation( + return Err(PageError::Validation( "Couldn't run an intermediate program".into(), )); } @@ -1762,9 +1720,9 @@ async fn create_contest( problem.time_limit_ms, ) .await - .map_err(|_| PostError::Validation("Couldn't run solution on test".into()))?; + .map_err(|_| PageError::Validation("Couldn't run solution on test".into()))?; if run_stats.exit_code != 0 { - return Err(PostError::Validation( + return Err(PageError::Validation( "Couldn't run solution on test".into(), )); } @@ -1786,7 +1744,7 @@ async fn create_contest( problem.time_limit_ms, ) .await - .map_err(|_| PostError::Validation("Couldn't judge main solution".into()))?; + .map_err(|_| PageError::Validation("Couldn't judge main solution".into()))?; if form.name.as_ref().unwrap() != "" { contest::relate_problem( @@ -1794,7 +1752,7 @@ async fn create_contest( contest::NewContestProblems { label: problem_label .get(&problem_id_without_revision) - .ok_or(PostError::Validation( + .ok_or(PageError::Validation( "Arquivo não contém problemas listados".into(), ))? .to_string()