codama_attributes/codama_directives/
error_directive.rs

1use crate::{utils::SetOnce, Attribute, CodamaAttribute, CodamaDirective};
2use codama_errors::CodamaError;
3use codama_syn_helpers::{extensions::*, Meta};
4
5#[derive(Debug, PartialEq, Default, Clone)]
6pub struct ErrorDirective {
7    pub code: Option<usize>,
8    pub message: Option<String>,
9}
10
11impl ErrorDirective {
12    pub fn is_empty(&self) -> bool {
13        self.code.is_none() && self.message.is_none()
14    }
15}
16
17impl ErrorDirective {
18    pub fn parse(meta: &Meta) -> syn::Result<Self> {
19        let pl = meta.assert_directive("error")?.as_path_list()?;
20        let mut code = SetOnce::<usize>::new("code");
21        let mut message = SetOnce::<String>::new("message");
22        pl.each(|ref meta| match (meta.path_str().as_str(), meta) {
23            ("code", _) => code.set(
24                meta.as_path_value()?
25                    .value
26                    .as_expr()?
27                    .as_unsigned_integer()?,
28                meta,
29            ),
30            ("message", _) => {
31                message.set(meta.as_path_value()?.value.as_expr()?.as_string()?, meta)
32            }
33            (_, Meta::Expr(expr)) => {
34                if let Ok(value) = expr.as_unsigned_integer() {
35                    code.set(value, meta)
36                } else if let Ok(value) = expr.as_string() {
37                    message.set(value, meta)
38                } else {
39                    Err(expr.error("expected an integer or a string"))
40                }
41            }
42            _ => Err(meta.error("unrecognized attribute")),
43        })?;
44        let directive = Self {
45            code: code.option(),
46            message: message.option(),
47        };
48        if directive.is_empty() {
49            return Err(pl.error("expected at least one `code` or `message` attribute"));
50        }
51        Ok(directive)
52    }
53}
54
55impl<'a> TryFrom<&'a CodamaAttribute<'a>> for &'a ErrorDirective {
56    type Error = CodamaError;
57
58    fn try_from(attribute: &'a CodamaAttribute) -> Result<Self, Self::Error> {
59        match attribute.directive {
60            CodamaDirective::Error(ref a) => Ok(a),
61            _ => Err(CodamaError::InvalidCodamaDirective {
62                expected: "error".to_string(),
63                actual: attribute.directive.name().to_string(),
64            }),
65        }
66    }
67}
68
69impl<'a> TryFrom<&'a Attribute<'a>> for &'a ErrorDirective {
70    type Error = CodamaError;
71
72    fn try_from(attribute: &'a Attribute) -> Result<Self, Self::Error> {
73        <&CodamaAttribute>::try_from(attribute)?.try_into()
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[test]
82    fn ok() {
83        let meta: Meta = syn::parse_quote! { error(42, "my message") };
84        let directive = ErrorDirective::parse(&meta).unwrap();
85        assert_eq!(
86            directive,
87            ErrorDirective {
88                code: Some(42),
89                message: Some("my message".to_string()),
90            }
91        );
92    }
93
94    #[test]
95    fn ok_with_explicit_labels() {
96        let meta: Meta = syn::parse_quote! { error(code = 42, message = "my message") };
97        let directive = ErrorDirective::parse(&meta).unwrap();
98        assert_eq!(
99            directive,
100            ErrorDirective {
101                code: Some(42),
102                message: Some("my message".to_string()),
103            }
104        );
105    }
106
107    #[test]
108    fn fail_if_nothing_is_provided() {
109        let meta: Meta = syn::parse_quote! { error() };
110        let error = ErrorDirective::parse(&meta).unwrap_err();
111        assert_eq!(
112            error.to_string(),
113            "expected at least one `code` or `message` attribute"
114        );
115    }
116}