qm_entity/
error.rs

1use crate::UserId;
2use async_graphql::ErrorExtensions;
3use qm_keycloak::KeycloakError;
4use sqlx::types::Uuid;
5use thiserror::Error;
6
7#[derive(Debug, Error)]
8#[non_exhaustive]
9pub enum EntityError {
10    /// A unhandled Database error occurred.
11    #[error("{0}")]
12    Lock(#[from] qm_redis::lock::Error),
13    /// A unhandled Database error occurred.
14    #[error("{0}")]
15    Database(#[from] qm_mongodb::error::Error),
16    /// A unhandled Database error occurred.
17    #[error("{0}")]
18    SQLDatabase(#[from] sea_orm::DbErr),
19    /// Keycloak request failure.
20    #[error(transparent)]
21    KeycloakRequest(#[from] reqwest::Error),
22    /// Keycloak error
23    #[error(transparent)]
24    KeycloakError(#[from] KeycloakError),
25    /// distributed locks error
26    #[error(transparent)]
27    DistributedLocksError(#[from] qm_nats::DistributedLocksError),
28    /// lock manager error
29    #[error(transparent)]
30    LockManagerError(#[from] qm_nats::LockManagerError),
31    /// sequence manager error
32    #[error(transparent)]
33    SequenceManagerError(#[from] qm_nats::SequenceManagerError),
34    /// A unexpected error occured.
35    #[error(transparent)]
36    UnexpectedError(#[from] anyhow::Error),
37    #[error(transparent)]
38    SerdeJson(#[from] serde_json::Error),
39    /// Conflicting error, because resource already exists.
40    #[error("the resource {0} with id '{1}' already exists")]
41    IdConflict(String, String),
42    /// Conflicting error, because resource already exists.
43    #[error("the resource {0} with name '{1}' already exists")]
44    NameConflict(String, String),
45    /// Conflicting error, because resource already exists.
46    #[error("the resource {0} with name '{1}' has conflicting unique fields")]
47    FieldsConflict(String, String, async_graphql::Value),
48    /// Forbidden because of missing session.
49    #[error("forbidden")]
50    Forbidden,
51    #[error("internal server error")]
52    Internal,
53    #[error("not found")]
54    NotFound,
55    #[error("Required fields are missing")]
56    RequiredFields,
57    /// Unauthorized user.
58    #[error("the user with id '{0}' is unauthorized")]
59    Unauthorized(String),
60    /// not found by id.
61    #[error("the resource {0} with id '{1}' was not found")]
62    NotFoundById(String, String),
63    /// not found by field.
64    #[error("the resource {0} with {1} '{2}' was not found")]
65    NotFoundByField(String, String, String),
66    /// not allowed
67    #[error("the feature '{0}' is not enabled")]
68    NotAllowed(String),
69    /// bad request.
70    #[error("{1}")]
71    BadRequest(String, String),
72    #[error("No id field in inserted entity")]
73    NoId,
74    #[error("Query document cannot be empty")]
75    NotEmpty,
76    #[error("List of ids only allowed with same owner")]
77    NotSameOwner,
78    #[error("Bson could not be serialized: {0}")]
79    Bson(String),
80}
81
82pub type EntityResult<T> = Result<T, EntityError>;
83
84impl EntityError {
85    pub fn unauthorized_user(user_id: Option<&Uuid>) -> Self {
86        if let Some(user_id) = user_id {
87            EntityError::Unauthorized(user_id.to_string())
88        } else {
89            EntityError::Forbidden
90        }
91    }
92
93    pub fn unauthorized<T>(ctx: &T) -> Self
94    where
95        T: UserId,
96    {
97        if let Some(user_id) = ctx.user_id() {
98            EntityError::Unauthorized(user_id.to_string())
99        } else {
100            EntityError::Forbidden
101        }
102    }
103
104    pub fn name_conflict<T>(name: impl Into<String>) -> Self {
105        Self::NameConflict(tynm::type_name::<T>(), name.into())
106    }
107
108    pub fn fields_conflict<T>(
109        name: impl Into<String>,
110        fields: impl Into<async_graphql::Value>,
111    ) -> Self {
112        Self::FieldsConflict(tynm::type_name::<T>(), name.into(), fields.into())
113    }
114
115    pub fn not_found_by_id<T>(id: impl Into<String>) -> Self {
116        Self::NotFoundById(tynm::type_name::<T>(), id.into())
117    }
118
119    pub fn not_found_by_field<T>(field: impl Into<String>, value: impl Into<String>) -> Self {
120        Self::NotFoundByField(tynm::type_name::<T>(), field.into(), value.into())
121    }
122
123    pub fn bad_request(err_type: impl Into<String>, err_msg: impl Into<String>) -> Self {
124        Self::BadRequest(err_type.into(), err_msg.into())
125    }
126
127    pub fn not_allowed(err_msg: impl Into<String>) -> Self {
128        Self::NotAllowed(err_msg.into())
129    }
130
131    pub fn internal() -> Self {
132        Self::Internal
133    }
134}
135
136impl ErrorExtensions for EntityError {
137    fn extend(&self) -> async_graphql::Error {
138        async_graphql::Error::new(format!("{}", self)).extend_with(|_err, e| match self {
139            EntityError::NameConflict(ty, _) => {
140                e.set("code", 409);
141                e.set("type", ty);
142                e.set("field", "name");
143            }
144            EntityError::FieldsConflict(ty, _, fields) => {
145                e.set("code", 409);
146                e.set("type", ty);
147                e.set("details", fields.clone());
148            }
149            EntityError::Unauthorized(_) => e.set("code", 401),
150            EntityError::NotAllowed(_) => e.set("code", 405),
151            EntityError::Forbidden => e.set("code", 403),
152            EntityError::Internal => e.set("code", 500),
153            EntityError::BadRequest(ty, _) => {
154                e.set("code", 400);
155                e.set("details", ty);
156            }
157            _ => {}
158        })
159    }
160}