use crate::UserId;
use async_graphql::ErrorExtensions;
use qm_keycloak::KeycloakError;
use sqlx::types::Uuid;
use thiserror::Error;
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum EntityError {
#[error("{0}")]
Lock(#[from] qm_redis::lock::Error),
#[error("{0}")]
Database(#[from] qm_mongodb::error::Error),
#[error("{0}")]
SQLDatabase(#[from] sea_orm::DbErr),
#[error(transparent)]
KeycloakRequest(#[from] reqwest::Error),
#[error(transparent)]
KeycloakError(#[from] KeycloakError),
#[error(transparent)]
DistributedLocksError(#[from] qm_nats::DistributedLocksError),
#[error(transparent)]
LockManagerError(#[from] qm_nats::LockManagerError),
#[error(transparent)]
SequenceManagerError(#[from] qm_nats::SequenceManagerError),
#[error(transparent)]
UnexpectedError(#[from] anyhow::Error),
#[error(transparent)]
SerdeJson(#[from] serde_json::Error),
#[error("the resource {0} with id '{1}' already exists")]
IdConflict(String, String),
#[error("the resource {0} with name '{1}' already exists")]
NameConflict(String, String),
#[error("the resource {0} with name '{1}' has conflicting unique fields")]
FieldsConflict(String, String, async_graphql::Value),
#[error("forbidden")]
Forbidden,
#[error("internal server error")]
Internal,
#[error("not found")]
NotFound,
#[error("Required fields are missing")]
RequiredFields,
#[error("the user with id '{0}' is unauthorized")]
Unauthorized(String),
#[error("the resource {0} with id '{1}' was not found")]
NotFoundById(String, String),
#[error("the resource {0} with {1} '{2}' was not found")]
NotFoundByField(String, String, String),
#[error("the feature '{0}' is not enabled")]
NotAllowed(String),
#[error("{1}")]
BadRequest(String, String),
#[error("No id field in inserted entity")]
NoId,
#[error("Query document cannot be empty")]
NotEmpty,
#[error("List of ids only allowed with same owner")]
NotSameOwner,
#[error("Bson could not be serialized: {0}")]
Bson(String),
}
pub type EntityResult<T> = Result<T, EntityError>;
impl EntityError {
pub fn unauthorized_user(user_id: Option<&Uuid>) -> Self {
if let Some(user_id) = user_id {
EntityError::Unauthorized(user_id.to_string())
} else {
EntityError::Forbidden
}
}
pub fn unauthorized<T>(ctx: &T) -> Self
where
T: UserId,
{
if let Some(user_id) = ctx.user_id() {
EntityError::Unauthorized(user_id.to_string())
} else {
EntityError::Forbidden
}
}
pub fn name_conflict<T>(name: impl Into<String>) -> Self {
Self::NameConflict(tynm::type_name::<T>(), name.into())
}
pub fn fields_conflict<T>(
name: impl Into<String>,
fields: impl Into<async_graphql::Value>,
) -> Self {
Self::FieldsConflict(tynm::type_name::<T>(), name.into(), fields.into())
}
pub fn not_found_by_id<T>(id: impl Into<String>) -> Self {
Self::NotFoundById(tynm::type_name::<T>(), id.into())
}
pub fn not_found_by_field<T>(field: impl Into<String>, value: impl Into<String>) -> Self {
Self::NotFoundByField(tynm::type_name::<T>(), field.into(), value.into())
}
pub fn bad_request(err_type: impl Into<String>, err_msg: impl Into<String>) -> Self {
Self::BadRequest(err_type.into(), err_msg.into())
}
pub fn not_allowed(err_msg: impl Into<String>) -> Self {
Self::NotAllowed(err_msg.into())
}
pub fn internal() -> Self {
Self::Internal
}
}
impl ErrorExtensions for EntityError {
fn extend(&self) -> async_graphql::Error {
async_graphql::Error::new(format!("{}", self)).extend_with(|_err, e| match self {
EntityError::NameConflict(ty, _) => {
e.set("code", 409);
e.set("type", ty);
e.set("field", "name");
}
EntityError::FieldsConflict(ty, _, fields) => {
e.set("code", 409);
e.set("type", ty);
e.set("details", fields.clone());
}
EntityError::Unauthorized(_) => e.set("code", 401),
EntityError::NotAllowed(_) => e.set("code", 405),
EntityError::Forbidden => e.set("code", 403),
EntityError::Internal => e.set("code", 500),
EntityError::BadRequest(ty, _) => {
e.set("code", 400);
e.set("details", ty);
}
_ => {}
})
}
}