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, 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}