Skip to main content

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, DisplaySkip, InstructionArgumentNode, NestedTypeNode, Node, NumberFormat::U8,
8    NumberTypeNode, StructFieldDisplayNode, 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(meta.as_value()?.as_expr()?.as_string()?.into(), meta),
29            "size" => {
30                let node = TypeNode::from_meta(meta.as_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.as_ref() {
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        let size = match node {
77            Some(Node::DefinedType(defined_type)) => match defined_type.r#type.as_ref() {
78                TypeNode::Enum(data) => Some(data.size.clone()),
79                _ => None,
80            },
81            _ => None,
82        };
83        EnumDiscriminatorDirective {
84            size,
85            ..EnumDiscriminatorDirective::default()
86        }
87    }
88}
89
90impl From<&EnumDiscriminatorDirective> for StructFieldTypeNode {
91    fn from(directive: &EnumDiscriminatorDirective) -> Self {
92        StructFieldTypeNode {
93            display: Some(StructFieldDisplayNode::skipped(DisplaySkip::Always)),
94            ..StructFieldTypeNode::new(
95                directive.name.clone().unwrap_or("discriminator".into()),
96                directive
97                    .size
98                    .clone()
99                    .unwrap_or(NumberTypeNode::le(U8).into()),
100            )
101        }
102    }
103}
104
105impl From<&EnumDiscriminatorDirective> for InstructionArgumentNode {
106    fn from(directive: &EnumDiscriminatorDirective) -> Self {
107        StructFieldTypeNode::from(directive).into()
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114    use codama_nodes::NumberFormat::{U16, U32};
115
116    #[test]
117    fn enum_discriminator_with_name() {
118        let meta: Meta = syn::parse_quote! { enum_discriminator(name = "banana") };
119        let directive = EnumDiscriminatorDirective::parse(&meta).unwrap();
120        assert_eq!(
121            directive,
122            EnumDiscriminatorDirective {
123                name: Some("banana".into()),
124                size: None,
125            }
126        );
127    }
128
129    #[test]
130    fn enum_discriminator_with_size() {
131        let meta: Meta = syn::parse_quote! { enum_discriminator(size = number(u32)) };
132        let directive = EnumDiscriminatorDirective::parse(&meta).unwrap();
133        assert_eq!(
134            directive,
135            EnumDiscriminatorDirective {
136                name: None,
137                size: Some(NumberTypeNode::le(U32).into()),
138            }
139        );
140    }
141
142    #[test]
143    fn enum_discriminator_with_name_and_size() {
144        let meta: Meta =
145            syn::parse_quote! { enum_discriminator(name = "banana", size = number(u16)) };
146        let directive = EnumDiscriminatorDirective::parse(&meta).unwrap();
147        assert_eq!(
148            directive,
149            EnumDiscriminatorDirective {
150                name: Some("banana".into()),
151                size: Some(NumberTypeNode::le(U16).into()),
152            }
153        );
154    }
155
156    #[test]
157    fn empty_enum_discriminator() {
158        let meta: Meta = syn::parse_quote! { enum_discriminator() };
159        let error = EnumDiscriminatorDirective::parse(&meta).unwrap_err();
160        assert_eq!(
161            error.to_string(),
162            "enum_discriminator must specify at least one of: name, size"
163        );
164    }
165
166    #[test]
167    fn creates_hidden_struct_fields() {
168        let directive = EnumDiscriminatorDirective::default();
169        let field = StructFieldTypeNode::from(&directive);
170
171        assert_eq!(
172            field.display,
173            Some(StructFieldDisplayNode::skipped(DisplaySkip::Always))
174        );
175    }
176
177    #[test]
178    fn creates_hidden_instruction_arguments() {
179        let directive = EnumDiscriminatorDirective::default();
180        let argument = InstructionArgumentNode::from(&directive);
181
182        assert_eq!(
183            argument.display,
184            Some(StructFieldDisplayNode::skipped(DisplaySkip::Always))
185        );
186    }
187}