Compare commits

..

No commits in common. "eba30d1e9d07670f522ce2d825e93a418e437b54" and "8a4e2dc46750ff180a8cbea9243da5390b3901cc" have entirely different histories.

21 changed files with 118 additions and 228 deletions

View File

@ -16,7 +16,7 @@ use rand::{rngs::OsRng, RngCore};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use subtle::ConstantTimeEq; use subtle::ConstantTimeEq;
use crate::{db, Pool}; use crate::{db, errors::handle_error, Pool};
pub const HASH_LENGTH: usize = 64; pub const HASH_LENGTH: usize = 64;
pub const SALT_LENGTH: usize = 64; pub const SALT_LENGTH: usize = 64;
@ -186,7 +186,7 @@ where
Ok(true) => Ok(claims), Ok(true) => Ok(claims),
Ok(false) => Err(Error::WrongCredentials), Ok(false) => Err(Error::WrongCredentials),
Err(err) => { Err(err) => {
tracing::error!(%err); handle_error(err);
Err(Error::Validation) Err(Error::Validation)
} }
} }

View File

@ -1,4 +1,4 @@
use std::{borrow::Cow, collections::HashMap}; use std::collections::HashMap;
use crate::prelude::*; use crate::prelude::*;
@ -32,79 +32,46 @@ impl From<Option<PermissionRaw>> for PermissionType {
} }
} }
impl From<PermissionType> for PermissionRaw {
fn from(value: PermissionType) -> Self {
match value {
PermissionType::Manage => Self::Manage,
PermissionType::Write => Self::Write,
PermissionType::Read => Self::Read,
PermissionType::NoPermission => unreachable!(),
}
}
}
impl PermissionType { impl PermissionType {
pub fn can_read(self) -> bool { pub fn can_read(self) -> bool {
self >= PermissionType::Read self >= PermissionType::Read
} }
fn can_read_guard(self) -> GeneralResult<()> { pub fn can_read_guard(self) -> Result<(), StatusCode> {
if !self.can_read() { if !self.can_read() {
return Err(GeneralError::message( return Err(StatusCode::NOT_FOUND);
StatusCode::NOT_FOUND,
"Item not found",
));
} }
Ok(()) Ok(())
} }
fn can_write_guard(self) -> GeneralResult<()> { pub fn can_write_guard(self) -> Result<(), StatusCode> {
self.can_read_guard()?; self.can_read_guard()?;
if self < PermissionType::Write { if self < PermissionType::Write {
return Err(GeneralError::message( return Err(StatusCode::FORBIDDEN);
StatusCode::FORBIDDEN,
"Cannot write to the folder",
));
} }
Ok(()) Ok(())
} }
fn can_manage_guard(self) -> GeneralResult<()> { pub fn can_manage_guard(self) -> Result<(), StatusCode> {
self.can_read_guard()?; self.can_read_guard()?;
if self < PermissionType::Manage { if self < PermissionType::Manage {
return Err(GeneralError::message( return Err(StatusCode::FORBIDDEN);
StatusCode::FORBIDDEN,
"Cannot manage the folder",
));
} }
Ok(()) Ok(())
} }
} }
pub trait PermissionExt {
fn can_read_guard(self) -> GeneralResult<()>;
fn can_write_guard(self) -> GeneralResult<()>;
fn can_manage_guard(self) -> GeneralResult<()>;
}
fn permissions_error(error: sqlx::Error) -> GeneralError {
GeneralError {
status_code: StatusCode::INTERNAL_SERVER_ERROR,
message: Cow::Borrowed("Error getting permissions"),
error: Some(error.into()),
}
}
fn apply_guard(
result: sqlx::Result<PermissionType>,
func: impl FnOnce(PermissionType) -> GeneralResult<()>,
) -> GeneralResult<()> {
result.map_err(permissions_error).and_then(func)
}
impl PermissionExt for sqlx::Result<PermissionType> {
fn can_read_guard(self) -> GeneralResult<()> {
apply_guard(self, PermissionType::can_read_guard)
}
fn can_write_guard(self) -> GeneralResult<()> {
apply_guard(self, PermissionType::can_write_guard)
}
fn can_manage_guard(self) -> GeneralResult<()> {
apply_guard(self, PermissionType::can_manage_guard)
}
}
pub async fn insert( pub async fn insert(
user_id: i32, user_id: i32,
folder_id: Uuid, folder_id: Uuid,

View File

@ -1,4 +1,4 @@
use crate::prelude::*; pub use crate::prelude::*;
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
pub struct Params { pub struct Params {
@ -9,26 +9,24 @@ pub async fn delete(
Query(params): Query<Params>, Query(params): Query<Params>,
State(state): State<AppState>, State(state): State<AppState>,
claims: Claims, claims: Claims,
) -> GeneralResult<StatusCode> { ) -> Result<StatusCode, StatusCode> {
db::file::get_permissions(params.file_id, claims.user_id, &state.pool) db::file::get_permissions(params.file_id, claims.user_id, &state.pool)
.await .await
.handle_internal()?
.can_write_guard()?; .can_write_guard()?;
let deleted = db::file::delete(params.file_id, &state.pool) let deleted = db::file::delete(params.file_id, &state.pool)
.await .await
.handle_internal("Error deleting the file")?; .handle_internal()?;
if !deleted { if !deleted {
return Err(GeneralError::message( return Err(StatusCode::NOT_FOUND); // Will not happen most of the time due to can write guard
StatusCode::NOT_FOUND,
"Item not found",
)); // Will not happen most of the time due to can write guard
} }
state state
.storage .storage
.delete(params.file_id) .delete(params.file_id)
.await .await
.handle_internal("Error deleting the file")?; .handle_internal()?;
Ok(StatusCode::NO_CONTENT) Ok(StatusCode::NO_CONTENT)
} }

View File

@ -12,15 +12,16 @@ pub async fn download(
Query(params): Query<Params>, Query(params): Query<Params>,
State(state): State<AppState>, State(state): State<AppState>,
claims: Claims, claims: Claims,
) -> GeneralResult<impl IntoResponse> { ) -> Result<impl IntoResponse, StatusCode> {
db::file::get_permissions(params.file_id, claims.user_id, &state.pool) db::file::get_permissions(params.file_id, claims.user_id, &state.pool)
.await .await
.handle_internal()?
.can_read_guard()?; .can_read_guard()?;
let mut name = db::file::get_name(params.file_id, &state.pool) let mut name = db::file::get_name(params.file_id, &state.pool)
.await .await
.handle_internal("Error getting file info")? .handle_internal()?
.ok_or_else(GeneralError::item_not_found)?; .ok_or(StatusCode::NOT_FOUND)?;
name = name name = name
.chars() .chars()
.fold(String::with_capacity(name.len()), |mut result, char| { .fold(String::with_capacity(name.len()), |mut result, char| {
@ -31,11 +32,7 @@ pub async fn download(
result result
}); });
let file = state let file = state.storage.read(params.file_id).await.handle_internal()?;
.storage
.read(params.file_id)
.await
.handle_internal("Error reading the file")?;
let body = Body::from_stream(ReaderStream::new(file)); let body = Body::from_stream(ReaderStream::new(file));
let disposition = format!("attachment; filename=\"{name}\""); let disposition = format!("attachment; filename=\"{name}\"");
let headers = [(header::CONTENT_DISPOSITION, disposition)]; let headers = [(header::CONTENT_DISPOSITION, disposition)];

View File

@ -12,9 +12,10 @@ pub async fn modify(
State(state): State<AppState>, State(state): State<AppState>,
claims: Claims, claims: Claims,
mut multipart: Multipart, mut multipart: Multipart,
) -> GeneralResult<StatusCode> { ) -> Result<StatusCode, StatusCode> {
db::file::get_permissions(params.file_id, claims.user_id, &state.pool) db::file::get_permissions(params.file_id, claims.user_id, &state.pool)
.await .await
.handle_internal()?
.can_write_guard()?; .can_write_guard()?;
// Very weird work around to get the first file in multipart // Very weird work around to get the first file in multipart
@ -22,12 +23,7 @@ pub async fn modify(
match multipart.next_field().await { match multipart.next_field().await {
Ok(Some(field)) if field.file_name().is_some() => break field, Ok(Some(field)) if field.file_name().is_some() => break field,
Ok(Some(_)) => continue, Ok(Some(_)) => continue,
_ => { _ => return Err(StatusCode::BAD_REQUEST),
return Err(GeneralError::message(
StatusCode::BAD_REQUEST,
"No file in the multipart",
))
}
} }
}; };
@ -35,22 +31,19 @@ pub async fn modify(
.storage .storage
.write(params.file_id) .write(params.file_id)
.await .await
.handle_internal("Error writing to the file")? .handle_internal()?
.ok_or_else(GeneralError::item_not_found)?; .ok_or(StatusCode::NOT_FOUND)?;
let (hash, size) = crate::FileStorage::write_to_file(&mut file, &mut field) let (hash, size) = crate::FileStorage::write_to_file(&mut file, &mut field)
.await .await
.map_err(|err| { .map_err(|err| {
tracing::warn!(%err); tracing::warn!(%err);
GeneralError::message( StatusCode::INTERNAL_SERVER_ERROR
StatusCode::INTERNAL_SERVER_ERROR,
"Error writing to the file",
)
})?; })?;
db::file::update(params.file_id, size, hash, &state.pool) db::file::update(params.file_id, size, hash, &state.pool)
.await .await
.handle_internal("Error updating the file")?; .handle_internal()?;
Ok(StatusCode::NO_CONTENT) Ok(StatusCode::NO_CONTENT)
} }

View File

@ -36,15 +36,16 @@ pub async fn upload(
State(state): State<AppState>, State(state): State<AppState>,
claims: Claims, claims: Claims,
mut multi: Multipart, mut multi: Multipart,
) -> GeneralResult<Json<HashMap<String, Uuid>>> { ) -> Result<Json<HashMap<String, Uuid>>, StatusCode> {
db::folder::get_permissions(params.parent_folder, claims.user_id, &state.pool) db::folder::get_permissions(params.parent_folder, claims.user_id, &state.pool)
.await .await
.handle_internal()?
.can_write_guard()?; .can_write_guard()?;
let existing_names: HashSet<String> = db::folder::get_names(params.parent_folder, &state.pool) let existing_names: HashSet<String> = db::folder::get_names(params.parent_folder, &state.pool)
.try_collect() .try_collect()
.await .await
.handle_internal("Error getting existing names")?; .handle_internal()?;
let mut result = HashMap::new(); let mut result = HashMap::new();
while let Ok(Some(mut field)) = multi.next_field().await { while let Ok(Some(mut field)) = multi.next_field().await {

View File

@ -10,23 +10,22 @@ pub async fn create(
State(pool): State<Pool>, State(pool): State<Pool>,
claims: Claims, claims: Claims,
Json(params): Json<Params>, Json(params): Json<Params>,
) -> GeneralResult<Json<Uuid>> { ) -> Result<Json<Uuid>, StatusCode> {
db::folder::get_permissions(params.parent_folder_id, claims.user_id, &pool) db::folder::get_permissions(params.parent_folder_id, claims.user_id, &pool)
.await .await
.handle_internal()?
.can_write_guard()?; .can_write_guard()?;
let exists = db::folder::name_exists(params.parent_folder_id, &params.folder_name, &pool) let exists = db::folder::name_exists(params.parent_folder_id, &params.folder_name, &pool)
.await .await
.handle_internal("Error getting existing names")?; .handle_internal()?;
if exists { if exists {
return Err(GeneralError::message( return Err(StatusCode::CONFLICT);
StatusCode::CONFLICT,
"Name already taken",
));
} }
db::folder::insert(params.parent_folder_id, &params.folder_name, &pool) let id = db::folder::insert(params.parent_folder_id, &params.folder_name, &pool)
.await .await
.handle_internal("Error creating the folder") .handle_internal()?;
.map(Json)
Ok(Json(id))
} }

View File

@ -9,19 +9,17 @@ pub async fn delete(
State(state): State<AppState>, State(state): State<AppState>,
claims: Claims, claims: Claims,
Json(params): Json<Params>, Json(params): Json<Params>,
) -> GeneralResult<()> { ) -> Result<(), StatusCode> {
let root = db::folder::get_root(claims.user_id, &state.pool) let root = db::folder::get_root(claims.user_id, &state.pool)
.await .await
.handle_internal("Error getting the root folder")?; .handle_internal()?;
if params.folder_id == root { if params.folder_id == root {
return Err(GeneralError::message( return Err(StatusCode::BAD_REQUEST);
StatusCode::BAD_REQUEST,
"Cannot delete the root folder",
));
} }
db::folder::get_permissions(params.folder_id, claims.user_id, &state.pool) db::folder::get_permissions(params.folder_id, claims.user_id, &state.pool)
.await .await
.handle_internal()?
.can_write_guard()?; .can_write_guard()?;
let storage = &state.storage; let storage = &state.storage;
@ -31,5 +29,5 @@ pub async fn delete(
Ok(()) Ok(())
}) })
.await .await
.handle_internal("Error deleting the fodler") .handle_internal()
} }

View File

@ -25,16 +25,16 @@ pub async fn structure(
Query(params): Query<Params>, Query(params): Query<Params>,
State(pool): State<Pool>, State(pool): State<Pool>,
claims: Claims, claims: Claims,
) -> GeneralResult<Json<FolderStructure>> { ) -> Result<Json<FolderStructure>, StatusCode> {
let folder_id = db::folder::process_id(params.folder_id, claims.user_id, &pool) let folder_id = db::folder::process_id(params.folder_id, claims.user_id, &pool)
.await .await
.handle_internal("Error processing id")? .handle_internal()?
.ok_or_else(GeneralError::item_not_found)?; .ok_or(StatusCode::NOT_FOUND)?;
let folder = db::folder::get_by_id(folder_id, &pool) let folder = db::folder::get_by_id(folder_id, &pool)
.await .await
.handle_internal("Error getting folder info")? .handle_internal()?
.ok_or_else(GeneralError::item_not_found)?; .ok_or(StatusCode::NOT_FOUND)?;
let mut response: FolderStructure = folder.into(); let mut response: FolderStructure = folder.into();
let mut stack = vec![&mut response]; let mut stack = vec![&mut response];
@ -45,7 +45,7 @@ pub async fn structure(
.map_ok(Into::into) .map_ok(Into::into)
.try_collect() .try_collect()
) )
.handle_internal("Error getting folder contents")?; .handle_internal()?;
folder.folders = folders; folder.folders = folders;
folder.files = files; folder.files = files;
stack.extend(folder.folders.iter_mut()); stack.extend(folder.folders.iter_mut());

View File

@ -18,17 +18,17 @@ pub async fn list(
Query(params): Query<Params>, Query(params): Query<Params>,
State(pool): State<Pool>, State(pool): State<Pool>,
claims: Claims, claims: Claims,
) -> GeneralResult<Json<Response>> { ) -> Result<Json<Response>, StatusCode> {
let folder_id = db::folder::process_id(params.folder_id, claims.user_id, &pool) let folder_id = db::folder::process_id(params.folder_id, claims.user_id, &pool)
.await .await
.handle_internal("Error processing id")? .handle_internal()?
.handle(StatusCode::NOT_FOUND, "Item not found")?; .ok_or(StatusCode::NOT_FOUND)?;
let (files, folders) = try_join!( let (files, folders) = try_join!(
db::file::get_files(folder_id, &pool).try_collect(), db::file::get_files(folder_id, &pool).try_collect(),
db::folder::get_folders(folder_id, claims.user_id, &pool).try_collect() db::folder::get_folders(folder_id, claims.user_id, &pool).try_collect()
) )
.handle_internal("Error getting folder contents")?; .handle_internal()?;
Ok(Json(Response { Ok(Json(Response {
folder_id, folder_id,

View File

@ -10,16 +10,17 @@ pub async fn delete(
State(pool): State<Pool>, State(pool): State<Pool>,
claims: Claims, claims: Claims,
Json(params): Json<Params>, Json(params): Json<Params>,
) -> GeneralResult<StatusCode> { ) -> Result<StatusCode, StatusCode> {
if params.user_id != claims.user_id { if params.user_id != claims.user_id {
db::folder::get_permissions(params.folder_id, claims.user_id, &pool) db::folder::get_permissions(params.folder_id, claims.user_id, &pool)
.await .await
.handle_internal()?
.can_manage_guard()?; .can_manage_guard()?;
} }
db::permissions::delete_for_folder(params.folder_id, params.user_id, &pool) db::permissions::delete_for_folder(params.folder_id, params.user_id, &pool)
.await .await
.handle_internal("Error deleting the permissions")?; .handle_internal()?;
Ok(StatusCode::NO_CONTENT) Ok(StatusCode::NO_CONTENT)
} }

View File

@ -13,13 +13,14 @@ pub async fn get(
State(pool): State<Pool>, State(pool): State<Pool>,
Query(params): Query<Params>, Query(params): Query<Params>,
claims: Claims, claims: Claims,
) -> GeneralResult<Json<HashMap<String, PermissionRaw>>> { ) -> Result<Json<HashMap<String, PermissionRaw>>, StatusCode> {
db::folder::get_permissions(params.folder_id, claims.user_id, &pool) db::folder::get_permissions(params.folder_id, claims.user_id, &pool)
.await .await
.handle_internal()?
.can_manage_guard()?; .can_manage_guard()?;
let permissions = db::permissions::get_all_for_folder(params.folder_id, &pool) let permissions = db::permissions::get_all_for_folder(params.folder_id, &pool)
.await .await
.handle_internal("Error getting permissions")?; .handle_internal()?;
Ok(Json(permissions)) Ok(Json(permissions))
} }

View File

@ -3,9 +3,9 @@ use crate::prelude::*;
pub async fn get_top_level( pub async fn get_top_level(
State(pool): State<Pool>, State(pool): State<Pool>,
claims: Claims, claims: Claims,
) -> GeneralResult<Json<Vec<Uuid>>> { ) -> Result<Json<Vec<Uuid>>, StatusCode> {
let folders = db::permissions::get_top_level_permitted_folders(claims.user_id, &pool) let folders = db::permissions::get_top_level_permitted_folders(claims.user_id, &pool)
.await .await
.handle_internal("Error reading from the database")?; .handle_internal()?;
Ok(Json(folders)) Ok(Json(folders))
} }

View File

@ -1,4 +1,6 @@
use crate::{db::permissions::PermissionRaw, prelude::*}; use db::permissions::PermissionRaw;
use crate::prelude::*;
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
pub struct Params { pub struct Params {
@ -11,30 +13,25 @@ pub async fn set(
claims: Claims, claims: Claims,
State(pool): State<Pool>, State(pool): State<Pool>,
Json(params): Json<Params>, Json(params): Json<Params>,
) -> GeneralResult<StatusCode> { ) -> Result<StatusCode, StatusCode> {
let root = db::folder::get_root(claims.user_id, &pool) let root = db::folder::get_root(claims.user_id, &pool)
.await .await
.handle_internal("Error getting the root folder")?; .handle_internal()?;
if params.folder_id == root { if params.folder_id == root {
return Err(GeneralError::message( return Err(StatusCode::BAD_REQUEST);
StatusCode::BAD_REQUEST,
"Cannot delete the root folder",
));
} }
db::folder::get_permissions(params.folder_id, claims.user_id, &pool) db::folder::get_permissions(params.folder_id, claims.user_id, &pool)
.await .await
.handle_internal()?
.can_manage_guard()?; .can_manage_guard()?;
let folder_info = db::folder::get_by_id(params.folder_id, &pool) let folder_info = db::folder::get_by_id(params.folder_id, &pool)
.await .await
.handle_internal("Error getting folder info")? .handle_internal()?
.ok_or_else(GeneralError::item_not_found)?; .ok_or(StatusCode::NOT_FOUND)?;
if folder_info.owner_id == params.user_id { if folder_info.owner_id == params.user_id {
return Err(GeneralError::message( return Err(StatusCode::BAD_REQUEST);
StatusCode::BAD_REQUEST,
"Cannot set permissions of the folder owner",
));
} }
db::permissions::insert( db::permissions::insert(
@ -44,7 +41,7 @@ pub async fn set(
&pool, &pool,
) )
.await .await
.handle_internal("Error writing to the database")?; .handle_internal()?;
Ok(StatusCode::NO_CONTENT) Ok(StatusCode::NO_CONTENT)
} }

View File

@ -3,12 +3,12 @@ use crate::prelude::*;
pub async fn delete( pub async fn delete(
State(AppState { pool, ref storage }): State<AppState>, State(AppState { pool, ref storage }): State<AppState>,
claims: Claims, claims: Claims,
) -> GeneralResult<()> { ) -> Result<(), StatusCode> {
db::users::delete_user(claims.user_id, &pool) db::users::delete_user(claims.user_id, &pool)
.try_for_each_concurrent(5, |file_id| async move { .try_for_each_concurrent(5, |file_id| async move {
let _ = storage.delete(file_id).await; let _ = storage.delete(file_id).await;
Ok(()) Ok(())
}) })
.await .await
.handle_internal("Error deleting the user") .handle_internal()
} }

View File

@ -5,13 +5,13 @@ pub struct Params {
user_id: i32, user_id: i32,
} }
type Response = GeneralResult<Json<db::users::UserInfo>>; type Response = Result<Json<db::users::UserInfo>, StatusCode>;
pub async fn get(State(pool): State<Pool>, Query(params): Query<Params>) -> Response { pub async fn get(State(pool): State<Pool>, Query(params): Query<Params>) -> Response {
let info = db::users::get(params.user_id, &pool) let info = db::users::get(params.user_id, &pool)
.await .await
.handle_internal("Error getting the user")? .handle_internal()?
.handle(StatusCode::NOT_FOUND, "User not found")?; .ok_or(StatusCode::NOT_FOUND)?;
Ok(Json(info)) Ok(Json(info))
} }

View File

@ -14,10 +14,13 @@ pub async fn put(
State(pool): State<Pool>, State(pool): State<Pool>,
claims: Claims, claims: Claims,
Json(params): Json<Params>, Json(params): Json<Params>,
) -> GeneralResult<Json<db::users::UserInfo>> { ) -> Result<Json<db::users::UserInfo>, (StatusCode, String)> {
params.validate().map_err(GeneralError::validation)?; params
.validate()
.map_err(|err| (StatusCode::BAD_REQUEST, err.to_string()))?;
db::users::update(claims.user_id, &params.username, &params.email, &pool) db::users::update(claims.user_id, &params.username, &params.email, &pool)
.await .await
.handle_internal("Error updating the user") .handle_internal()
.map_err(|status| (status, String::new()))
.map(Json) .map(Json)
} }

View File

@ -48,22 +48,22 @@ fn validate_password(password: &str) -> Result<(), ValidationError> {
pub async fn register( pub async fn register(
State(pool): State<Pool>, State(pool): State<Pool>,
Form(params): Form<Params>, Form(params): Form<Params>,
) -> Result<Json<Token>, Either<GeneralError, Error>> { ) -> Result<Json<Token>, Either<(StatusCode, String), Error>> {
params params
.validate() .validate()
.map_err(GeneralError::validation) .map_err(|err| Either::E1((StatusCode::BAD_REQUEST, err.to_string())))?;
.map_err(Either::E1)?;
let password = HashedBytes::hash_bytes(params.password.as_bytes()).as_bytes(); let password = HashedBytes::hash_bytes(params.password.as_bytes()).as_bytes();
let id = db::users::create_user(&params.username, &params.email, &password, &pool) let Some(id) = db::users::create_user(&params.username, &params.email, &password, &pool)
.await .await
.handle_internal("Error creating the user") .handle_internal()
.map_err(Either::E1)? .map_err(|status| Either::E1((status, String::new())))?
.handle( else {
return Err(Either::E1((
StatusCode::BAD_REQUEST, StatusCode::BAD_REQUEST,
"The username or the email are taken", "Either the user name or the email are taken".to_owned(),
) )));
.map_err(Either::E1)?; };
let token = Claims::new(id).encode().map_err(Either::E2)?; let token = Claims::new(id).encode().map_err(Either::E2)?;
Ok(Json(token)) Ok(Json(token))

View File

@ -8,12 +8,12 @@ pub struct Params {
pub async fn search( pub async fn search(
State(pool): State<Pool>, State(pool): State<Pool>,
Query(params): Query<Params>, Query(params): Query<Params>,
) -> GeneralResult<Json<Vec<db::users::UserSearch>>> { ) -> sqlx::Result<Json<Vec<db::users::UserSearch>>, StatusCode> {
db::users::search_for_user(&params.search_string, &pool) db::users::search_for_user(&params.search_string, &pool)
.take(20) .take(20)
.try_filter(|user| future::ready(user.similarity > 0.1)) .try_filter(|user| future::ready(user.similarity > 0.1))
.try_collect() .try_collect()
.await .await
.handle_internal("Error getting users from the database") .handle_internal()
.map(Json) .map(Json)
} }

View File

@ -1,88 +1,28 @@
use std::{borrow::Cow, convert::Infallible}; use axum::http::StatusCode;
use axum::{http::StatusCode, response::IntoResponse};
type BoxError = Box<dyn std::error::Error>; type BoxError = Box<dyn std::error::Error>;
pub struct GeneralError { pub fn handle_error(error: impl Into<BoxError>) {
pub status_code: StatusCode, let error: BoxError = error.into();
pub message: Cow<'static, str>, tracing::error!(error);
pub error: Option<BoxError>,
}
pub type GeneralResult<T> = Result<T, GeneralError>;
impl GeneralError {
pub fn message(status_code: StatusCode, message: impl Into<Cow<'static, str>>) -> Self {
Self {
status_code,
message: message.into(),
error: None,
}
}
#[allow(clippy::needless_pass_by_value)]
pub fn validation(error: validator::ValidationErrors) -> Self {
Self::message(StatusCode::BAD_REQUEST, error.to_string())
}
pub const fn item_not_found() -> Self {
GeneralError {
status_code: StatusCode::NOT_FOUND,
message: Cow::Borrowed("Item not found"),
error: None,
}
}
}
impl IntoResponse for GeneralError {
fn into_response(self) -> axum::response::Response {
if let Some(err) = self.error {
tracing::error!(err, message = %self.message, status_code = ?self.status_code);
}
(self.status_code, self.message).into_response()
}
} }
pub trait ErrorHandlingExt<T, E> pub trait ErrorHandlingExt<T, E>
where where
Self: Sized, Self: Sized,
{ {
fn handle( fn handle(self, code: StatusCode) -> Result<T, StatusCode>;
self,
status_code: StatusCode,
message: impl Into<Cow<'static, str>>,
) -> GeneralResult<T>;
fn handle_internal(self, message: impl Into<Cow<'static, str>>) -> GeneralResult<T> { fn handle_internal(self) -> Result<T, StatusCode> {
self.handle(StatusCode::INTERNAL_SERVER_ERROR, message) self.handle(StatusCode::INTERNAL_SERVER_ERROR)
} }
} }
impl<T, E: Into<BoxError>> ErrorHandlingExt<T, E> for Result<T, E> { impl<T, E: Into<BoxError>> ErrorHandlingExt<T, E> for Result<T, E> {
fn handle( fn handle(self, code: StatusCode) -> Result<T, StatusCode> {
self, self.map_err(|err| {
status_code: StatusCode, handle_error(err);
message: impl Into<Cow<'static, str>>, code
) -> GeneralResult<T> {
self.map_err(|err| GeneralError {
status_code,
message: message.into(),
error: Some(err.into()),
})
}
}
impl<T> ErrorHandlingExt<T, Infallible> for Option<T> {
fn handle(
self,
status_code: StatusCode,
message: impl Into<Cow<'static, str>>,
) -> GeneralResult<T> {
self.ok_or_else(|| GeneralError {
status_code,
message: message.into(),
error: None,
}) })
} }
} }

View File

@ -1,9 +1,4 @@
pub(crate) use crate::{ pub(crate) use crate::{auth::Claims, db, errors::ErrorHandlingExt as _, AppState, Pool};
auth::Claims,
db::{self, permissions::PermissionExt as _},
errors::{ErrorHandlingExt as _, GeneralError, GeneralResult},
AppState, Pool,
};
pub use axum::{ pub use axum::{
extract::{Json, Query, State}, extract::{Json, Query, State},
http::StatusCode, http::StatusCode,