Skip to main content

qm_entity/
error.rs

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