lamprey_backend/
error.rs

1use std::num::{ParseFloatError, ParseIntError};
2
3use axum::{extract::ws::Message, http::StatusCode, response::IntoResponse, Json};
4use common::v1::types::error::Error as ApiError;
5use common::v1::types::{MessageEnvelope, MessagePayload};
6use opentelemetry_otlp::ExporterBuildError;
7use serde_json::json;
8use tracing::error;
9
10use crate::types::MessageSync;
11
12#[derive(thiserror::Error, Debug)]
13// TODO: avoid returning actual error messages to prevent leaking stuff
14pub enum Error {
15    #[error("sqlx error: {0}")]
16    Sqlx(sqlx::Error),
17    #[error("blocked by other user")]
18    Blocked,
19    #[error("missing authentication (not provided or invalid/expired session)")]
20    MissingAuth,
21    #[error("bad header")]
22    BadHeader,
23    #[error("session not yet authenticated")]
24    UnauthSession,
25    #[error("not found")]
26    NotFound,
27    #[error("forbidden")]
28    MissingPermissions,
29    #[error("bad request: {0}")]
30    BadStatic(&'static str),
31    #[error("bad request: {0}")]
32    BadRequest(String),
33    #[error("too big :(")]
34    TooBig,
35    #[error("internal error: {0}")]
36    Internal(String),
37    #[error("internal error: {0}")]
38    IoError(#[from] std::io::Error),
39    #[error("can't overwrite already uploaded data!")]
40    CantOverwrite,
41    #[error("internal error: {0}")]
42    Tempfile(#[from] async_tempfile::Error),
43    #[error("serde error: {0}")]
44    Serde(#[from] serde_json::Error),
45    #[error("axum error")]
46    Axum(#[from] axum::Error),
47    #[error("sushi send error: {0}")]
48    SushiSend(#[from] tokio::sync::broadcast::error::SendError<MessageSync>),
49    #[error("parse int error: {0}")]
50    ParseInt(#[from] ParseIntError),
51    #[error("parse float error: {0}")]
52    ParseFloat(#[from] ParseFloatError),
53    #[error("opendal error: {0}")]
54    Opendal(#[from] opendal::Error),
55    #[error("migrate error: {0}")]
56    SqlxMigrate(#[from] sqlx::migrate::MigrateError),
57    #[error("tracing subscriber error: {0}")]
58    TracingSubscriber(#[from] tracing::subscriber::SetGlobalDefaultError),
59    #[error("log format parse error: {0}")]
60    LogFormatParse(#[from] tracing_subscriber::filter::ParseError),
61    #[error("reqwest error: {0}")]
62    Reqwest(#[from] reqwest::Error),
63    #[error("figment error: {0}")]
64    Figment(#[from] figment::Error),
65    #[error("url parse error: {0}")]
66    UrlParseError(#[from] url::ParseError),
67
68    #[error("unmodified")]
69    // HACK: not really an error, but still kind of helpful to have here
70    NotModified,
71
72    #[error("ffmpeg or ffprobe didn't like seem to like that very much")]
73    Ffmpeg,
74
75    #[error("media type error: {0}")]
76    Media(#[from] mediatype::MediaTypeError),
77
78    #[error("not yet implemented...")]
79    Unimplemented,
80
81    #[error("image error: {0}")]
82    ImageError(#[from] image::ImageError),
83
84    #[error("unknown image format")]
85    UnknownImageFormat,
86
87    #[error("url embed io error: {0}")]
88    UrlEmbed(std::io::Error),
89
90    #[error("url embed error: {0}")]
91    UrlEmbedOther(String),
92
93    #[error("validation error: {0}")]
94    Validation(#[from] validator::ValidationErrors),
95
96    #[error("lettre error: {0}")]
97    Lettre(#[from] lettre::error::Error),
98
99    #[error("invalid credentials")]
100    InvalidCredentials,
101
102    #[error("email address already exists for this user")]
103    EmailAlreadyExists,
104
105    #[error("generic error: {0}")]
106    GenericError(String),
107
108    #[error("OtelExporterBuildError: {0}")]
109    OtelExporterBuildError(#[from] ExporterBuildError),
110
111    #[error("{0}")]
112    ApiError(ApiError),
113}
114
115impl From<sqlx::Error> for Error {
116    fn from(value: sqlx::Error) -> Self {
117        match value {
118            sqlx::Error::RowNotFound => Error::NotFound,
119            err => Error::Sqlx(err),
120        }
121    }
122}
123
124impl From<axum::http::header::ToStrError> for Error {
125    fn from(_value: axum::http::header::ToStrError) -> Self {
126        Error::BadHeader
127    }
128}
129
130impl Error {
131    fn get_status(&self) -> StatusCode {
132        match self {
133            Error::Blocked => StatusCode::FORBIDDEN,
134            Error::NotFound => StatusCode::NOT_FOUND,
135            Error::BadHeader => StatusCode::BAD_REQUEST,
136            Error::BadStatic(_) => StatusCode::BAD_REQUEST,
137            Error::BadRequest(_) => StatusCode::BAD_REQUEST,
138            Error::Serde(_) => StatusCode::BAD_REQUEST,
139            Error::MissingAuth => StatusCode::UNAUTHORIZED,
140            Error::UnauthSession => StatusCode::UNAUTHORIZED,
141            Error::TooBig => StatusCode::PAYLOAD_TOO_LARGE,
142            Error::MissingPermissions => StatusCode::FORBIDDEN,
143            Error::CantOverwrite => StatusCode::CONFLICT,
144            Error::ParseInt(_) => StatusCode::BAD_REQUEST,
145            Error::ParseFloat(_) => StatusCode::BAD_REQUEST,
146            Error::Unimplemented => StatusCode::NOT_IMPLEMENTED,
147            Error::NotModified => StatusCode::NOT_MODIFIED,
148            Error::Validation(_) => StatusCode::BAD_REQUEST,
149            Error::ApiError(err) => match err {
150                ApiError::UserSuspended => StatusCode::FORBIDDEN,
151            },
152            _ => StatusCode::INTERNAL_SERVER_ERROR,
153        }
154    }
155
156    pub fn fake_clone(&self) -> Error {
157        match self {
158            Error::Blocked => Error::Blocked,
159            Error::MissingAuth => Error::MissingAuth,
160            Error::BadHeader => Error::BadHeader,
161            Error::UnauthSession => Error::UnauthSession,
162            Error::NotFound => Error::NotFound,
163            Error::MissingPermissions => Error::MissingPermissions,
164            Error::ApiError(err) => Error::ApiError(err.clone()),
165            Error::BadStatic(s) => Error::BadStatic(s),
166            Error::BadRequest(s) => Error::BadRequest(s.clone()),
167            Error::TooBig => Error::TooBig,
168            Error::Internal(s) => Error::Internal(s.clone()),
169            Error::CantOverwrite => Error::CantOverwrite,
170            Error::ParseInt(parse_int_error) => Error::ParseInt(parse_int_error.clone()),
171            Error::ParseFloat(parse_float_error) => Error::ParseFloat(parse_float_error.clone()),
172            Error::Figment(error) => Error::Figment(error.clone()),
173            Error::UrlParseError(parse_error) => Error::UrlParseError(*parse_error),
174            Error::NotModified => Error::NotModified,
175            Error::Ffmpeg => Error::Ffmpeg,
176            Error::Media(media_type_error) => Error::Media(*media_type_error),
177            Error::Unimplemented => Error::Unimplemented,
178            Error::UnknownImageFormat => Error::UnknownImageFormat,
179            Error::UrlEmbedOther(s) => Error::UrlEmbedOther(s.to_string()),
180            Error::Validation(validation_errors) => Error::Validation(validation_errors.clone()),
181            _ => Error::GenericError(self.to_string()),
182        }
183    }
184}
185
186impl IntoResponse for Error {
187    fn into_response(self) -> axum::response::Response {
188        if let Error::NotModified = self {
189            return self.get_status().into_response();
190        };
191        error!(
192            "Response error: status {}, message {:?}",
193            self.get_status(),
194            self
195        );
196        (
197            self.get_status(),
198            Json(json!({ "error": self.to_string() })),
199        )
200            .into_response()
201    }
202}
203
204impl From<Error> for Message {
205    fn from(val: Error) -> Self {
206        Message::text(
207            serde_json::to_string(&MessageEnvelope {
208                payload: MessagePayload::Error {
209                    error: val.to_string(),
210                },
211            })
212            .expect("error should always be able to be serialized"),
213        )
214    }
215}
216
217impl From<ApiError> for Error {
218    fn from(err: ApiError) -> Self {
219        Self::ApiError(err)
220    }
221}
222
223pub type Result<T> = std::result::Result<T, Error>;