shared/
error.rs

1//! Home of the error types.
2
3mod account;
4mod billing;
5mod config;
6mod service;
7
8use std::error::Error;
9use std::fmt::{self, Debug, Display, Formatter};
10
11#[cfg(feature = "backend")]
12use actix_web::{body::BoxBody, HttpResponse, ResponseError};
13
14use serde::{Deserialize, Serialize};
15use thiserror::Error;
16
17use crate::domain::meta::MetaKind;
18use crate::media::MediaGroupKind;
19
20pub use account::AccountError;
21pub use billing::BillingError;
22pub use config::ConfigError;
23pub use service::{ServiceError, ServiceKindError};
24
25/// An `extra` error type that represents "no extension"
26#[derive(Serialize, Deserialize, Debug, Default, thiserror::Error)]
27#[error("EmptyError")]
28pub struct EmptyError {}
29
30/// Metadata associated with this operation could not be found.
31#[derive(Serialize, Deserialize, Debug)]
32pub struct MetadataNotFound {
33    /// The (Optional) id of the item.
34    pub id: Option<uuid::Uuid>,
35    /// The (Optional) index of the item.
36    pub index: Option<i16>,
37    /// The item's kind.
38    pub kind: MetaKind,
39    /// The (Optional) media group of the item where the error originated, for metadata types that
40    /// are split per media group kind.
41    pub media_group_kind: Option<MediaGroupKind>,
42}
43impl Display for MetadataNotFound {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        write!(f, "Metadata not found")
46    }
47}
48impl Error for MetadataNotFound {}
49
50/// Helper trait
51pub trait IntoAnyhow<T> {
52    /// Convert `self` into a result with an anyhow error
53    ///
54    /// # Errors
55    ///
56    /// Maps the error in a Result into an [`anyhow::Error`].
57    fn into_anyhow(self) -> anyhow::Result<T>;
58}
59
60// Blanket impl
61impl<T, E> IntoAnyhow<T> for Result<T, E>
62where
63    E: Error + Send + Sync + 'static + Into<anyhow::Error>,
64{
65    fn into_anyhow(self) -> anyhow::Result<T> {
66        self.map_err(Into::into)
67    }
68}
69
70/// Useful for serializing errors that don't implement Serialize, or errors where we don't want the
71/// error details to be transported to the client.
72#[derive(Debug, Error)]
73pub enum TransientError<T: Debug + Display> {
74    /// The actual error
75    #[error("API error {0}")]
76    Error(T),
77    /// An error placeholder
78    #[error("")]
79    Missing,
80}
81
82impl<T: Debug + Display> Default for TransientError<T> {
83    fn default() -> Self {
84        Self::Missing
85    }
86}
87
88impl<T: Debug + Display> From<T> for TransientError<T> {
89    fn from(value: T) -> Self {
90        Self::Error(value)
91    }
92}
93
94#[cfg(feature = "backend")]
95impl<T> TransientError<T>
96where
97    T: ResponseError + Debug + Display,
98{
99    #[allow(missing_docs)]
100    fn status_code(&self) -> http::StatusCode {
101        match self {
102            Self::Error(inner) => inner.status_code(),
103            Self::Missing => http::StatusCode::INTERNAL_SERVER_ERROR,
104        }
105    }
106}
107
108#[allow(missing_docs)]
109#[derive(Debug, Serialize, Deserialize)]
110#[serde(untagged)]
111pub enum ApiError<T> {
112    ApiError(T),
113    ConfigError(ConfigError),
114}
115
116#[cfg(feature = "backend")]
117impl<T> ResponseError for ApiError<T>
118where
119    T: ResponseError + Serialize,
120{
121    fn status_code(&self) -> http::StatusCode {
122        match self {
123            Self::ConfigError(error) => error.status_code(),
124            Self::ApiError(error) => error.status_code(),
125        }
126    }
127
128    fn error_response(&self) -> HttpResponse<BoxBody> {
129        HttpResponse::build(self.status_code()).json(self)
130    }
131}
132
133impl<T: Display> Display for ApiError<T> {
134    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
135        match self {
136            Self::ConfigError(error) => write!(f, "{error}"),
137            Self::ApiError(error) => write!(f, "{error}"),
138        }
139    }
140}