headless_lms_authorization/
error.rs1use std::fmt::Display;
6use std::panic::Location;
7
8use backtrace::Backtrace;
9use headless_lms_base::error::backend_error::BackendError;
10use headless_lms_models::ModelError;
11use headless_lms_utils::error::util_error::UtilError;
12use tracing_error::SpanTrace;
13
14pub type AuthorizationResult<T> = Result<T, AuthorizationError>;
18
19#[derive(Debug, PartialEq, Eq)]
21pub enum AuthorizationErrorType {
22 Unauthorized,
24
25 Forbidden,
27
28 InternalServerError,
30
31 Model,
36}
37
38pub struct AuthorizationError {
52 error_type: <AuthorizationError as BackendError>::ErrorType,
53 message: String,
54 source: Option<anyhow::Error>,
56 span_trace: Box<SpanTrace>,
58 backtrace: Box<Backtrace>,
60 location: Option<&'static Location<'static>>,
62}
63
64headless_lms_base::impl_clean_debug!(
66 AuthorizationError,
67 [AuthorizationError, ModelError, UtilError]
68);
69
70impl std::error::Error for AuthorizationError {
71 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
72 self.source
73 .as_deref()
74 .map(|e| e as &(dyn std::error::Error + 'static))
75 }
76
77 fn cause(&self) -> Option<&dyn std::error::Error> {
78 self.source()
79 }
80}
81
82impl Display for AuthorizationError {
83 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84 write!(
85 f,
86 "AuthorizationError {:?} {:?}",
87 self.error_type, self.message
88 )
89 }
90}
91
92impl BackendError for AuthorizationError {
93 type ErrorType = AuthorizationErrorType;
94
95 fn backtrace(&self) -> Option<&Backtrace> {
96 Some(&self.backtrace)
97 }
98
99 fn error_type(&self) -> &Self::ErrorType {
100 &self.error_type
101 }
102
103 fn message(&self) -> &str {
104 &self.message
105 }
106
107 fn span_trace(&self) -> &SpanTrace {
108 &self.span_trace
109 }
110
111 fn location(&self) -> Option<&'static Location<'static>> {
112 self.location
113 }
114
115 fn new_with_traces_and_location<M: Into<String>, S: Into<Option<anyhow::Error>>>(
116 error_type: Self::ErrorType,
117 message: M,
118 source_error: S,
119 backtrace: Backtrace,
120 span_trace: SpanTrace,
121 location: Option<&'static Location<'static>>,
122 ) -> Self {
123 Self {
124 error_type,
125 message: message.into(),
126 source: source_error.into(),
127 span_trace: Box::new(span_trace),
128 backtrace: Box::new(backtrace),
129 location,
130 }
131 }
132}
133
134impl AuthorizationError {
135 pub fn is_denial(&self) -> bool {
139 matches!(
140 self.error_type,
141 AuthorizationErrorType::Unauthorized | AuthorizationErrorType::Forbidden
142 )
143 }
144
145 pub fn into_model_error(mut self) -> Result<ModelError, Self> {
150 if !matches!(self.error_type, AuthorizationErrorType::Model) {
151 return Err(self);
152 }
153 match self.source.take().map(|source| source.downcast()) {
154 Some(Ok(model_error)) => Ok(model_error),
155 Some(Err(source)) => {
156 self.source = Some(source);
157 Err(self)
158 }
159 None => Err(self),
160 }
161 }
162}
163
164impl From<ModelError> for AuthorizationError {
165 fn from(err: ModelError) -> Self {
166 let message = err.message().to_string();
167 Self::new(AuthorizationErrorType::Model, message, Some(err.into()))
168 }
169}
170
171headless_lms_utils::define_err_macro!(
172 authorization_err,
173 AuthorizationError,
174 AuthorizationErrorType,
175 AuthorizationErrorType,
176 "Create an AuthorizationError with less boilerplate."
177);
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182 use headless_lms_models::ModelErrorType;
183
184 #[test]
185 fn into_model_error_recovers_the_wrapped_model_error() {
186 let err = AuthorizationError::from(ModelError::new(
187 ModelErrorType::RecordNotFound,
188 "row missing".to_string(),
189 None,
190 ));
191
192 let model_error = err.into_model_error().expect("model source");
193 assert!(matches!(
194 model_error.error_type(),
195 ModelErrorType::RecordNotFound
196 ));
197 }
198
199 #[test]
200 fn into_model_error_hands_other_error_types_back() {
201 let source = ModelError::new(ModelErrorType::Generic, "boom".to_string(), None);
202 let err = authorization_err!(InternalServerError, "Denied".to_string(), source);
203
204 let err = err.into_model_error().expect_err("not a model error");
205 assert!(std::error::Error::source(&err).is_some());
206 }
207
208 #[test]
209 fn only_refusals_are_denials() {
210 assert!(authorization_err!(Forbidden, "no".to_string()).is_denial());
211 assert!(authorization_err!(Unauthorized, "no".to_string()).is_denial());
212 assert!(!authorization_err!(InternalServerError, "no".to_string()).is_denial());
213 }
214}