codama_attributes/codama_directives/
field_directive.rs1use crate::{
2 codama_directives::type_nodes::StructFieldMetaConsumer,
3 utils::{FromMeta, MetaConsumer},
4 Attribute, CodamaAttribute, CodamaDirective, Resolvable,
5};
6use codama_errors::{CodamaError, CodamaResult};
7use codama_nodes::{
8 CamelCaseString, DefaultValueStrategy, Docs, StructFieldTypeNode, TypeNode, ValueNode,
9};
10use codama_syn_helpers::Meta;
11
12#[derive(Debug, PartialEq)]
13pub struct FieldDirective {
14 pub after: bool,
15 pub name: CamelCaseString,
16 pub r#type: Resolvable<TypeNode>,
17 pub docs: Docs,
18 pub default_value: Option<Resolvable<ValueNode>>,
19 pub default_value_strategy: Option<DefaultValueStrategy>,
20}
21
22impl FieldDirective {
23 pub fn parse(meta: &Meta) -> syn::Result<Self> {
24 meta.assert_directive("field")?;
25 let consumer = StructFieldMetaConsumer::from_meta(meta)?
26 .consume_field()?
27 .consume_default_value()?
28 .consume_after()?
29 .assert_fully_consumed()?;
30
31 let default_value = consumer.default_value_node();
32 let default_value_strategy = consumer.default_value_strategy();
33
34 Ok(FieldDirective {
35 after: consumer.after.option().unwrap_or(false),
36 name: consumer.name.take(meta)?,
37 r#type: consumer.r#type.take(meta)?,
38 docs: consumer.docs.option().unwrap_or_default(),
39 default_value,
40 default_value_strategy,
41 })
42 }
43
44 pub fn to_struct_field_type_node(&self) -> CodamaResult<StructFieldTypeNode> {
47 Ok(StructFieldTypeNode {
48 name: self.name.clone(),
49 r#type: self.r#type.try_resolved()?.clone(),
50 docs: self.docs.clone(),
51 default_value: self
52 .default_value
53 .as_ref()
54 .map(|r| r.try_resolved().cloned())
55 .transpose()?,
56 default_value_strategy: self.default_value_strategy,
57 })
58 }
59}
60
61impl<'a> TryFrom<&'a CodamaAttribute<'a>> for &'a FieldDirective {
62 type Error = CodamaError;
63
64 fn try_from(attribute: &'a CodamaAttribute) -> Result<Self, Self::Error> {
65 match attribute.directive.as_ref() {
66 CodamaDirective::Field(ref a) => Ok(a),
67 _ => Err(CodamaError::InvalidCodamaDirective {
68 expected: "field".to_string(),
69 actual: attribute.directive.name().to_string(),
70 }),
71 }
72 }
73}
74
75impl<'a> TryFrom<&'a Attribute<'a>> for &'a FieldDirective {
76 type Error = CodamaError;
77
78 fn try_from(attribute: &'a Attribute) -> Result<Self, Self::Error> {
79 <&CodamaAttribute>::try_from(attribute)?.try_into()
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86 use codama_nodes::{NumberFormat::U8, NumberTypeNode, NumberValueNode};
87
88 #[test]
89 fn ok() {
90 let meta: Meta = syn::parse_quote! { field("age", number(u8)) };
91 let directive = FieldDirective::parse(&meta).unwrap();
92 assert_eq!(
93 directive,
94 FieldDirective {
95 after: false,
96 name: "age".into(),
97 r#type: Resolvable::Resolved(NumberTypeNode::le(U8).into()),
98 docs: Docs::default(),
99 default_value: None,
100 default_value_strategy: None,
101 }
102 );
103 }
104
105 #[test]
106 fn after() {
107 let meta: Meta = syn::parse_quote! { field(after, "age", number(u8)) };
108 let directive = FieldDirective::parse(&meta).unwrap();
109 assert_eq!(
110 directive,
111 FieldDirective {
112 after: true,
113 name: "age".into(),
114 r#type: Resolvable::Resolved(NumberTypeNode::le(U8).into()),
115 docs: Docs::default(),
116 default_value: None,
117 default_value_strategy: None,
118 }
119 );
120 }
121
122 #[test]
123 fn with_default_value() {
124 let meta: Meta = syn::parse_quote! { field("age", number(u8), default_value = 42) };
125 let directive = FieldDirective::parse(&meta).unwrap();
126 assert_eq!(
127 directive,
128 FieldDirective {
129 after: false,
130 name: "age".into(),
131 r#type: Resolvable::Resolved(NumberTypeNode::le(U8).into()),
132 docs: Docs::default(),
133 default_value: Some(Resolvable::Resolved(NumberValueNode::new(42u8).into())),
134 default_value_strategy: None,
135 }
136 );
137 }
138
139 #[test]
140 fn with_docs_string() {
141 let meta: Meta = syn::parse_quote! { field("splines", number(u8), docs = "Splines") };
142 let directive = FieldDirective::parse(&meta).unwrap();
143 assert_eq!(directive.docs, vec!["Splines".to_string()].into());
144 }
145
146 #[test]
147 fn with_docs_array() {
148 let meta: Meta = syn::parse_quote! { field("age", number(u8), docs = ["Splines", "Must be pre-reticulated"]) };
149 let directive = FieldDirective::parse(&meta).unwrap();
150 assert_eq!(
151 directive.docs,
152 vec!["Splines".to_string(), "Must be pre-reticulated".to_string()].into()
153 );
154 }
155}