1use crate::UserId;
2use async_graphql::ErrorExtensions;
3use qm_keycloak::KeycloakError;
4use sqlx::types::Uuid;
5use thiserror::Error;
6
7#[derive(Debug, Error)]
9#[non_exhaustive]
10pub enum EntityError {
11 #[error("{0}")]
13 Lock(#[from] qm_redis::lock::Error),
14 #[error("{0}")]
16 Database(#[from] qm_mongodb::error::Error),
17 #[error("{0}")]
19 SQLDatabase(#[from] sea_orm::DbErr),
20 #[error(transparent)]
22 KeycloakRequest(#[from] reqwest::Error),
23 #[error(transparent)]
25 KeycloakError(#[from] KeycloakError),
26 #[error(transparent)]
28 DistributedLocksError(#[from] qm_nats::DistributedLocksError),
29 #[error(transparent)]
31 LockManagerError(#[from] qm_nats::LockManagerError),
32 #[error(transparent)]
34 SequenceManagerError(#[from] qm_nats::SequenceManagerError),
35 #[error(transparent)]
37 UnexpectedError(#[from] anyhow::Error),
38 #[error(transparent)]
40 SerdeJson(#[from] serde_json::Error),
41 #[error("the resource {0} with id '{1}' already exists")]
43 IdConflict(String, String),
44 #[error("the resource {0} with name '{1}' already exists")]
46 NameConflict(String, String),
47 #[error("the resource {0} with name '{1}' has conflicting unique fields")]
49 FieldsConflict(String, String, async_graphql::Value),
50 #[error("forbidden")]
52 Forbidden,
53 #[error("internal server error")]
55 Internal,
56 #[error("not found")]
58 NotFound,
59 #[error("Required fields are missing")]
61 RequiredFields,
62 #[error("the user with id '{0}' is unauthorized")]
64 Unauthorized(String),
65 #[error("the resource {0} with id '{1}' was not found")]
67 NotFoundById(String, String),
68 #[error("the resource {0} with {1} '{2}' was not found")]
70 NotFoundByField(String, String, String),
71 #[error("the feature '{0}' is not enabled")]
73 NotAllowed(String),
74 #[error("{1}")]
76 BadRequest(String, String),
77 #[error("No id field in inserted entity")]
79 NoId,
80 #[error("Query document cannot be empty")]
82 NotEmpty,
83 #[error("List of ids only allowed with same owner")]
85 NotSameOwner,
86 #[error("Bson could not be serialized: {0}")]
88 Bson(String),
89}
90
91pub type EntityResult<T> = Result<T, EntityError>;
93
94impl EntityError {
95 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 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 pub fn name_conflict<T>(name: impl Into<String>) -> Self {
118 Self::NameConflict(tynm::type_name::<T>(), name.into())
119 }
120
121 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 pub fn not_found_by_id<T>(id: impl Into<String>) -> Self {
131 Self::NotFoundById(tynm::type_name::<T>(), id.into())
132 }
133
134 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 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 pub fn not_allowed(err_msg: impl Into<String>) -> Self {
146 Self::NotAllowed(err_msg.into())
147 }
148
149 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}