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() {
23            "code" => code.set(meta.as_value()?.as_expr()?.as_unsigned_integer()?, meta),
24            "message" => message.set(meta.as_value()?.as_expr()?.as_string()?, meta),
25            _ => {
26                if let Ok(expr) = meta.as_expr() {
27                    if let Ok(value) = expr.as_unsigned_integer() {
28                        return code.set(value, meta);
29                    } else if let Ok(value) = expr.as_string() {
30                        return message.set(value, meta);
31                    }
32                }
33                Err(meta.error("unrecognized attribute"))
34            }
35        })?;
36        let directive = Self {
37            code: code.option(),
38            message: message.option(),
39        };
40        if directive.is_empty() {
41            return Err(pl.error("expected at least one `code` or `message` attribute"));
42        }
43        Ok(directive)
44    }
45}
46
47impl<'a> TryFrom<&'a CodamaAttribute<'a>> for &'a ErrorDirective {
48    type Error = CodamaError;
49
50    fn try_from(attribute: &'a CodamaAttribute) -> Result<Self, Self::Error> {
51        match attribute.directive {
52            CodamaDirective::Error(ref a) => Ok(a),
53            _ => Err(CodamaError::InvalidCodamaDirective {
54                expected: "error".to_string(),
55                actual: attribute.directive.name().to_string(),
56            }),
57        }
58    }
59}
60
61impl<'a> TryFrom<&'a Attribute<'a>> for &'a ErrorDirective {
62    type Error = CodamaError;
63
64    fn try_from(attribute: &'a Attribute) -> Result<Self, Self::Error> {
65        <&CodamaAttribute>::try_from(attribute)?.try_into()
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    #[test]
74    fn ok() {
75        let meta: Meta = syn::parse_quote! { error(42, "my message") };
76        let directive = ErrorDirective::parse(&meta).unwrap();
77        assert_eq!(
78            directive,
79            ErrorDirective {
80                code: Some(42),
81                message: Some("my message".to_string()),
82            }
83        );
84    }
85
86    #[test]
87    fn ok_with_explicit_labels() {
88        let meta: Meta = syn::parse_quote! { error(code = 42, message = "my message") };
89        let directive = ErrorDirective::parse(&meta).unwrap();
90        assert_eq!(
91            directive,
92            ErrorDirective {
93                code: Some(42),
94                message: Some("my message".to_string()),
95            }
96        );
97    }
98
99    #[test]
100    fn fail_if_nothing_is_provided() {
101        let meta: Meta = syn::parse_quote! { error() };
102        let error = ErrorDirective::parse(&meta).unwrap_err();
103        assert_eq!(
104            error.to_string(),
105            "expected at least one `code` or `message` attribute"
106        );
107    }
108}