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 #[error("{0}")]
12 Lock(#[from] qm_redis::lock::Error),
13 #[error("{0}")]
15 Database(#[from] qm_mongodb::error::Error),
16 #[error("{0}")]
18 SQLDatabase(#[from] sea_orm::DbErr),
19 #[error(transparent)]
21 KeycloakRequest(#[from] reqwest::Error),
22 #[error(transparent)]
24 KeycloakError(#[from] KeycloakError),
25 #[error(transparent)]
27 DistributedLocksError(#[from] qm_nats::DistributedLocksError),
28 #[error(transparent)]
30 LockManagerError(#[from] qm_nats::LockManagerError),
31 #[error(transparent)]
33 SequenceManagerError(#[from] qm_nats::SequenceManagerError),
34 #[error(transparent)]
36 UnexpectedError(#[from] anyhow::Error),
37 #[error(transparent)]
38 SerdeJson(#[from] serde_json::Error),
39 #[error("the resource {0} with id '{1}' already exists")]
41 IdConflict(String, String),
42 #[error("the resource {0} with name '{1}' already exists")]
44 NameConflict(String, String),
45 #[error("the resource {0} with name '{1}' has conflicting unique fields")]
47 FieldsConflict(String, String, async_graphql::Value),
48 #[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 #[error("the user with id '{0}' is unauthorized")]
59 Unauthorized(String),
60 #[error("the resource {0} with id '{1}' was not found")]
62 NotFoundById(String, String),
63 #[error("the resource {0} with {1} '{2}' was not found")]
65 NotFoundByField(String, String, String),
66 #[error("the feature '{0}' is not enabled")]
68 NotAllowed(String),
69 #[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}