codama_attributes/codama_directives/
error_directive.rs1use 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}