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