codama_attributes/
codama_attribute.rs

1use crate::{utils::SetOnce, Attribute, AttributeContext, CodamaDirective};
2use codama_errors::CodamaError;
3use codama_syn_helpers::extensions::*;
4
5#[derive(Debug, PartialEq)]
6pub struct CodamaAttribute<'a> {
7    pub ast: &'a syn::Attribute,
8    pub directive: Box<CodamaDirective>,
9}
10
11impl<'a> CodamaAttribute<'a> {
12    pub fn parse(ast: &'a syn::Attribute, ctx: &AttributeContext) -> syn::Result<Self> {
13        let unfeatured = ast.unfeatured();
14        let effective = unfeatured.as_ref().unwrap_or(ast);
15        Self::parse_from(ast, effective, ctx)
16    }
17
18    /// Parse a codama attribute using the effective attribute for content extraction.
19    /// `ast` is stored as the original attribute reference (for error spans).
20    /// `effective` is used to parse the actual directive content.
21    pub fn parse_from(
22        ast: &'a syn::Attribute,
23        effective: &syn::Attribute,
24        ctx: &AttributeContext,
25    ) -> syn::Result<Self> {
26        let list = effective.meta.require_list()?;
27        if !list.path.is_strict("codama") {
28            return Err(list.path.error("expected #[codama(...)]"));
29        };
30
31        let mut directive = SetOnce::<CodamaDirective>::new("codama");
32        list.each(|ref meta| directive.set(CodamaDirective::parse(meta, ctx)?, meta))?;
33        Ok(Self {
34            ast,
35            directive: Box::new(directive.take(effective)?),
36        })
37    }
38}
39
40impl<'a> TryFrom<&'a Attribute<'a>> for &'a CodamaAttribute<'a> {
41    type Error = CodamaError;
42
43    fn try_from(attribute: &'a Attribute) -> Result<Self, Self::Error> {
44        match attribute {
45            Attribute::Codama(a) => Ok(a),
46            _ => Err(CodamaError::InvalidAttribute {
47                expected: "codama".to_string(),
48                actual: attribute.name(),
49            }),
50        }
51    }
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57    use syn::parse_quote;
58
59    #[test]
60    fn test_codama_attribute() {
61        let ast = parse_quote! { #[codama(type = boolean)] };
62        let file = syn::File::empty();
63        let ctx = AttributeContext::File(&file);
64        let attribute = CodamaAttribute::parse(&ast, &ctx).unwrap();
65
66        assert_eq!(attribute.ast, &ast);
67        assert!(matches!(
68            attribute.directive.as_ref(),
69            CodamaDirective::Type(_)
70        ));
71    }
72
73    #[test]
74    fn test_feature_gated_codama_attribute() {
75        let ast = parse_quote! { #[cfg_attr(feature = "some_feature", codama(type = boolean))] };
76        let file = syn::File::empty();
77        let ctx = AttributeContext::File(&file);
78        let attribute = CodamaAttribute::parse(&ast, &ctx).unwrap();
79
80        assert_eq!(attribute.ast, &ast);
81        assert!(matches!(
82            attribute.directive.as_ref(),
83            CodamaDirective::Type(_)
84        ));
85    }
86}