Skip to main content

sofie_core/
error.rs

1use std::borrow::Cow;
2use std::fmt;
3use std::time::Duration;
4
5#[derive(Debug)]
6pub enum Error {
7    /// Also answered when the principal may not know that the record exists.
8    NotFound,
9    /// Asked for by a name it had before, such as a slug: a GET is sent on to `to`.
10    Moved {
11        from: String,
12        to: String,
13    },
14    Conflict,
15    BadRequest(String),
16    Invalid(Vec<FieldError>),
17    Rule(String),
18    TooManyRequests(Duration),
19    Unavailable(String),
20    Internal {
21        context: String,
22        source: Option<Box<dyn std::error::Error + Send + Sync>>,
23    },
24}
25
26#[derive(Clone, Debug, PartialEq, Eq)]
27pub struct FieldError {
28    pub field: String,
29    pub key: Cow<'static, str>,
30    pub arguments: Vec<(Cow<'static, str>, String)>,
31    pub message: String,
32}
33
34impl FieldError {
35    pub fn new(
36        field: impl Into<String>,
37        key: impl Into<Cow<'static, str>>,
38        message: impl Into<String>,
39    ) -> Self {
40        Self {
41            field: field.into(),
42            key: key.into(),
43            arguments: Vec::new(),
44            message: message.into(),
45        }
46    }
47
48    pub fn required(field: impl Into<String>) -> Self {
49        Self::new(field, REQUIRED, "is required")
50    }
51
52    pub fn invalid(field: impl Into<String>, message: impl Into<String>) -> Self {
53        Self::new(field, INVALID, message)
54    }
55
56    #[must_use]
57    pub fn with(mut self, name: impl Into<Cow<'static, str>>, value: impl Into<String>) -> Self {
58        self.arguments.push((name.into(), value.into()));
59        self
60    }
61}
62
63const INVALID: &str = "validation.invalid";
64const REQUIRED: &str = "validation.required";
65
66pub type Result<T, E = Error> = std::result::Result<T, E>;
67
68impl Error {
69    pub fn internal(
70        context: impl Into<String>,
71        source: impl Into<Box<dyn std::error::Error + Send + Sync>>,
72    ) -> Self {
73        Self::Internal {
74            context: context.into(),
75            source: Some(source.into()),
76        }
77    }
78
79    pub fn internal_message(context: impl Into<String>) -> Self {
80        Self::Internal {
81            context: context.into(),
82            source: None,
83        }
84    }
85
86    pub fn invalid(field: impl Into<String>, message: impl Into<String>) -> Self {
87        Self::Invalid(vec![FieldError::invalid(field, message)])
88    }
89
90    /// Alternate form: the field being read already names the value.
91    #[must_use]
92    pub fn into_deserialize_error<E: serde::de::Error>(self) -> E {
93        E::custom(format_args!("{self:#}"))
94    }
95
96    fn fmt_message(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        match self {
98            Self::BadRequest(message) | Self::Rule(message) => f.write_str(message),
99            Self::Invalid(errors) => {
100                let messages: Vec<&str> =
101                    errors.iter().map(|error| error.message.as_str()).collect();
102                f.write_str(&messages.join("; "))
103            }
104            _ => write!(f, "{self}"),
105        }
106    }
107}
108
109/// `{:#}` prints the message alone, as shown under a field.
110impl fmt::Display for Error {
111    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112        if f.alternate() {
113            return self.fmt_message(f);
114        }
115        match self {
116            Self::NotFound => f.write_str("not found"),
117            Self::Moved { from, to } => write!(f, "moved from `{from}` to `{to}`"),
118            Self::Conflict => f.write_str("the record changed since it was loaded"),
119            Self::BadRequest(message) => write!(f, "bad request: {message}"),
120            Self::Invalid(errors) => {
121                f.write_str("invalid input:")?;
122                for error in errors {
123                    write!(f, " {}: {};", error.field, error.message)?;
124                }
125                Ok(())
126            }
127            Self::Rule(message) => write!(f, "refused: {message}"),
128            Self::TooManyRequests(wait) => {
129                write!(f, "too many requests; try again in {wait:?}")
130            }
131            Self::Unavailable(message) => write!(f, "unavailable: {message}"),
132            Self::Internal { context, source } => match source {
133                Some(source) => write!(f, "{context}: {source}"),
134                None => f.write_str(context),
135            },
136        }
137    }
138}
139
140impl std::error::Error for Error {
141    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
142        match self {
143            Self::Internal {
144                source: Some(source),
145                ..
146            } => Some(source.as_ref()),
147            _ => None,
148        }
149    }
150}