codama_attributes/codama_directives/
enum_discriminator_directive.rs

1use crate::{
2    utils::{FromMeta, SetOnce},
3    Attribute, CodamaAttribute, CodamaDirective,
4};
5use codama_errors::CodamaError;
6use codama_nodes::{
7    CamelCaseString, DefinedTypeNode, InstructionArgumentNode, NestedTypeNode, Node,
8    NumberFormat::U8, NumberTypeNode, StructFieldTypeNode, TypeNode,
9};
10use codama_syn_helpers::{extensions::*, Meta};
11
12#[derive(Debug, PartialEq, Default, Clone)]
13pub struct EnumDiscriminatorDirective {
14    pub name: Option<CamelCaseString>,
15    pub size: Option<NestedTypeNode<NumberTypeNode>>,
16}
17
18impl EnumDiscriminatorDirective {
19    pub fn parse(meta: &Meta) -> syn::Result<Self> {
20        let pl = meta
21            .assert_directive("enum_discriminator")?
22            .as_path_list()?;
23
24        let mut name = SetOnce::<CamelCaseString>::new("name");
25        let mut size: SetOnce<NestedTypeNode<NumberTypeNode>> =
26            SetOnce::<NestedTypeNode<NumberTypeNode>>::new("size");
27        pl.each(|ref meta| match meta.path_str().as_str() {
28            "name" => name.set(String::from_meta(meta)?.into(), meta),
29            "size" => {
30                let node = TypeNode::from_meta(&meta.as_path_value()?.value)?;
31                match NestedTypeNode::<NumberTypeNode>::try_from(node) {
32                    Ok(node) => size.set(node, meta),
33                    _ => Err(meta.error("size must be a NumberTypeNode")),
34                }
35            }
36            _ => Err(meta.error("unrecognized attribute")),
37        })?;
38
39        let directive = EnumDiscriminatorDirective {
40            name: name.option(),
41            size: size.option(),
42        };
43
44        if directive.name.is_none() && directive.size.is_none() {
45            return Err(meta.error("enum_discriminator must specify at least one of: name, size"));
46        }
47
48        Ok(directive)
49    }
50}
51
52impl<'a> TryFrom<&'a CodamaAttribute<'a>> for &'a EnumDiscriminatorDirective {
53    type Error = CodamaError;
54
55    fn try_from(attribute: &'a CodamaAttribute) -> Result<Self, Self::Error> {
56        match attribute.directive {
57            CodamaDirective::EnumDiscriminator(ref a) => Ok(a),
58            _ => Err(CodamaError::InvalidCodamaDirective {
59                expected: "enum_discriminator".to_string(),
60                actual: attribute.directive.name().to_string(),
61            }),
62        }
63    }
64}
65
66impl<'a> TryFrom<&'a Attribute<'a>> for &'a EnumDiscriminatorDirective {
67    type Error = CodamaError;
68
69    fn try_from(attribute: &'a Attribute) -> Result<Self, Self::Error> {
70        <&CodamaAttribute>::try_from(attribute)?.try_into()
71    }
72}
73
74impl From<&Option<Node>> for EnumDiscriminatorDirective {
75    fn from(node: &Option<Node>) -> Self {
76        EnumDiscriminatorDirective {
77            size: match &node {
78                Some(Node::DefinedType(DefinedTypeNode {
79                    r#type: TypeNode::Enum(data),
80                    ..
81                })) => Some(data.size.clone()),
82                _ => None,
83            },
84            ..EnumDiscriminatorDirective::default()
85        }
86    }
87}
88
89impl From<&EnumDiscriminatorDirective> for StructFieldTypeNode {
90    fn from(directive: &EnumDiscriminatorDirective) -> Self {
91        StructFieldTypeNode::new(
92            directive.name.clone().unwrap_or("discriminator".into()),
93            directive
94                .size
95                .clone()
96                .unwrap_or(NumberTypeNode::le(U8).into()),
97        )
98    }
99}
100
101impl From<&EnumDiscriminatorDirective> for InstructionArgumentNode {
102    fn from(directive: &EnumDiscriminatorDirective) -> Self {
103        StructFieldTypeNode::from(directive).into()
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110    use codama_nodes::NumberFormat::{U16, U32};
111
112    #[test]
113    fn enum_discriminator_with_name() {
114        let meta: Meta = syn::parse_quote! { enum_discriminator(name = "banana") };
115        let directive = EnumDiscriminatorDirective::parse(&meta).unwrap();
116        assert_eq!(
117            directive,
118            EnumDiscriminatorDirective {
119                name: Some("banana".into()),
120                size: None,
121            }
122        );
123    }
124
125    #[test]
126    fn enum_discriminator_with_size() {
127        let meta: Meta = syn::parse_quote! { enum_discriminator(size = number(u32)) };
128        let directive = EnumDiscriminatorDirective::parse(&meta).unwrap();
129        assert_eq!(
130            directive,
131            EnumDiscriminatorDirective {
132                name: None,
133                size: Some(NumberTypeNode::le(U32).into()),
134            }
135        );
136    }
137
138    #[test]
139    fn enum_discriminator_with_name_and_size() {
140        let meta: Meta =
141            syn::parse_quote! { enum_discriminator(name = "banana", size = number(u16)) };
142        let directive = EnumDiscriminatorDirective::parse(&meta).unwrap();
143        assert_eq!(
144            directive,
145            EnumDiscriminatorDirective {
146                name: Some("banana".into()),
147                size: Some(NumberTypeNode::le(U16).into()),
148            }
149        );
150    }
151
152    #[test]
153    fn empty_enum_discriminator() {
154        let meta: Meta = syn::parse_quote! { enum_discriminator() };
155        let error = EnumDiscriminatorDirective::parse(&meta).unwrap_err();
156        assert_eq!(
157            error.to_string(),
158            "enum_discriminator must specify at least one of: name, size"
159        );
160    }
161}