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    KeyedRule(Box<KeyedRule>),
19    TooManyRequests(Duration),
20    Unavailable(String),
21    Internal {
22        context: String,
23        source: Option<Box<dyn std::error::Error + Send + Sync>>,
24    },
25}
26
27#[derive(Clone, Debug, PartialEq, Eq)]
28pub struct FieldError {
29    pub field: String,
30    pub key: Cow<'static, str>,
31    pub arguments: Vec<(Cow<'static, str>, String)>,
32    pub message: String,
33}
34
35impl FieldError {
36    pub fn new(
37        field: impl Into<String>,
38        key: impl Into<Cow<'static, str>>,
39        message: impl Into<String>,
40    ) -> Self {
41        Self {
42            field: field.into(),
43            key: key.into(),
44            arguments: Vec::new(),
45            message: message.into(),
46        }
47    }
48
49    pub fn required(field: impl Into<String>) -> Self {
50        Self::new(field, REQUIRED, "is required")
51    }
52
53    pub fn invalid(field: impl Into<String>, message: impl Into<String>) -> Self {
54        Self::new(field, INVALID, message)
55    }
56
57    #[must_use]
58    pub fn with(mut self, name: impl Into<Cow<'static, str>>, value: impl Into<String>) -> Self {
59        self.arguments.push((name.into(), value.into()));
60        self
61    }
62}
63
64#[derive(Clone, Debug, PartialEq, Eq)]
65pub struct KeyedRule {
66    pub key: Cow<'static, str>,
67    pub arguments: Vec<(Cow<'static, str>, String)>,
68    pub message: String,
69}
70
71#[derive(Clone, Copy, Debug)]
72pub struct RuleText {
73    key: &'static str,
74    message: &'static str,
75}
76
77impl RuleText {
78    #[must_use]
79    pub const fn new(key: &'static str, message: &'static str) -> Self {
80        Self { key, message }
81    }
82}
83
84impl From<RuleText> for Error {
85    fn from(rule: RuleText) -> Self {
86        Self::rule_keyed(rule.key, [], rule.message)
87    }
88}
89
90const INVALID: &str = "validation.invalid";
91const REQUIRED: &str = "validation.required";
92
93pub type Result<T, E = Error> = std::result::Result<T, E>;
94
95impl Error {
96    pub fn internal(
97        context: impl Into<String>,
98        source: impl Into<Box<dyn std::error::Error + Send + Sync>>,
99    ) -> Self {
100        Self::Internal {
101            context: context.into(),
102            source: Some(source.into()),
103        }
104    }
105
106    pub fn internal_message(context: impl Into<String>) -> Self {
107        Self::Internal {
108            context: context.into(),
109            source: None,
110        }
111    }
112
113    pub fn invalid(field: impl Into<String>, message: impl Into<String>) -> Self {
114        Self::Invalid(vec![FieldError::invalid(field, message)])
115    }
116
117    pub fn rule_keyed(
118        key: &'static str,
119        arguments: impl IntoIterator<Item = (&'static str, String)>,
120        message: impl Into<String>,
121    ) -> Self {
122        Self::KeyedRule(Box::new(KeyedRule {
123            key: Cow::Borrowed(key),
124            arguments: arguments
125                .into_iter()
126                .map(|(name, value)| (Cow::Borrowed(name), value))
127                .collect(),
128            message: message.into(),
129        }))
130    }
131
132    /// Alternate form: the field being read already names the value.
133    #[must_use]
134    pub fn into_deserialize_error<E: serde::de::Error>(self) -> E {
135        E::custom(format_args!("{self:#}"))
136    }
137
138    fn fmt_message(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139        match self {
140            Self::BadRequest(message) | Self::Rule(message) => f.write_str(message),
141            Self::KeyedRule(rule) => f.write_str(&rule.message),
142            Self::Invalid(errors) => {
143                let messages: Vec<&str> =
144                    errors.iter().map(|error| error.message.as_str()).collect();
145                f.write_str(&messages.join("; "))
146            }
147            _ => write!(f, "{self}"),
148        }
149    }
150}
151
152/// `{:#}` prints the message alone, as shown under a field.
153impl fmt::Display for Error {
154    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155        if f.alternate() {
156            return self.fmt_message(f);
157        }
158        match self {
159            Self::NotFound => f.write_str("not found"),
160            Self::Moved { from, to } => write!(f, "moved from `{from}` to `{to}`"),
161            Self::Conflict => f.write_str("the record changed since it was loaded"),
162            Self::BadRequest(message) => write!(f, "bad request: {message}"),
163            Self::Invalid(errors) => {
164                f.write_str("invalid input:")?;
165                for error in errors {
166                    write!(f, " {}: {};", error.field, error.message)?;
167                }
168                Ok(())
169            }
170            Self::Rule(message) => write!(f, "refused: {message}"),
171            Self::KeyedRule(rule) => write!(f, "refused: {}", rule.message),
172            Self::TooManyRequests(wait) => {
173                write!(f, "too many requests; try again in {wait:?}")
174            }
175            Self::Unavailable(message) => write!(f, "unavailable: {message}"),
176            Self::Internal { context, source } => match source {
177                Some(source) => write!(f, "{context}: {source}"),
178                None => f.write_str(context),
179            },
180        }
181    }
182}
183
184impl std::error::Error for Error {
185    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
186        match self {
187            Self::Internal {
188                source: Some(source),
189                ..
190            } => Some(source.as_ref()),
191            _ => None,
192        }
193    }
194}