introspect_core/enum/
variant.rs

1//! Rust enum variants.
2
3mod builder;
4
5pub use builder::Builder;
6
7/// An error related to a [`Variant`].
8pub enum Error {
9    /// Encountered an unsupported expression for a documentation attribute.
10    UnsupportedExpression(syn::Expr),
11
12    /// Encountered an unsupported expression literal for a documentation attribute.
13    UnsupportedExpressionLiteral(syn::ExprLit),
14}
15
16impl std::fmt::Debug for Error {
17    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18        match self {
19            Self::UnsupportedExpression(_) => f.debug_tuple("UnsupportedExpression").finish(),
20            Self::UnsupportedExpressionLiteral(_) => {
21                f.debug_tuple("UnsupportedExpressionLiteral").finish()
22            }
23        }
24    }
25}
26
27impl std::fmt::Display for Error {
28    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29        match self {
30            Error::UnsupportedExpression(_) => {
31                write!(f, "unsupported doc attribute expression")
32            }
33            Error::UnsupportedExpressionLiteral(_) => {
34                write!(f, "unsupported doc attribute literal")
35            }
36        }
37    }
38}
39
40impl std::error::Error for Error {}
41
42/// A [`Result`](std::result::Result) with an [`Error`].
43pub type Result<T> = std::result::Result<T, Error>;
44
45/// A Rust enum variant.
46#[derive(Debug)]
47pub struct Variant {
48    /// An identifier for the variant.
49    identifier: String,
50
51    /// The documentation for the variant, if it exists.
52    documentation: Option<String>,
53}
54
55impl Variant {
56    /// Creates a new [`Variant`].
57    ///
58    /// # Examples
59    ///
60    /// ```
61    /// use introspect_core as core;
62    ///
63    /// let variant = core::r#enum::Variant::new(
64    ///     String::from("Name"),
65    ///     Some(String::from("Documentation."))
66    /// );
67    /// ```
68    pub fn new(identifier: String, documentation: Option<String>) -> Self {
69        Self {
70            identifier,
71            documentation,
72        }
73    }
74
75    /// Gets the identifier of the [`Variant`] by reference.
76    ///
77    /// # Examples
78    ///
79    /// ```
80    /// use introspect_core as core;
81    ///
82    /// let variant = core::r#enum::variant::Builder::default()
83    ///                 .identifier("Name")
84    ///                 .documentation("Documentation.")
85    ///                 .try_build()?;
86    ///
87    /// assert_eq!(variant.identifier(), "Name");
88    ///
89    /// # Ok::<(), Box<dyn std::error::Error>>(())
90    /// ```
91    pub fn identifier(&self) -> &str {
92        self.identifier.as_str()
93    }
94
95    /// Gets the documentation of the [`Variant`] by reference.
96    ///
97    /// # Examples
98    ///
99    /// ```
100    /// use introspect_core as core;
101    ///
102    /// let variant = core::r#enum::variant::Builder::default()
103    ///                 .identifier("Name")
104    ///                 .documentation("Documentation.")
105    ///                 .try_build()?;
106    ///
107    /// assert_eq!(variant.documentation(), Some("Documentation."));
108    ///
109    /// # Ok::<(), Box<dyn std::error::Error>>(())
110    /// ```
111    pub fn documentation(&self) -> Option<&str> {
112        self.documentation.as_deref()
113    }
114}
115
116impl std::fmt::Display for Variant {
117    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118        write!(f, "::introspect::r#enum::Variant::new(")?;
119        write!(f, "r#\"{}\"#.into(), ", self.identifier)?;
120
121        match self.documentation.as_ref() {
122            Some(documentation) => write!(f, "Some(r#\"{}\"#.into())", documentation)?,
123            None => write!(f, "None")?,
124        };
125
126        write!(f, ")")
127    }
128}
129
130impl TryFrom<&syn::Variant> for Variant {
131    type Error = Error;
132
133    fn try_from(value: &syn::Variant) -> Result<Self> {
134        let documentation = value
135            .attrs
136            .iter()
137            .filter_map(|attr| match attr.meta.require_name_value() {
138                Ok(v) => Some(v),
139                Err(_) => None,
140            })
141            .filter_map(|variant| {
142                variant
143                    .path
144                    .get_ident()
145                    .map(|ident| (ident, variant.value.clone()))
146            })
147            .filter(|(ident, _)| *ident == "doc")
148            .map(|(_, expr)| match expr {
149                syn::Expr::Lit(expr_lit) => match expr_lit.lit {
150                    syn::Lit::Str(lit_str) => Ok(lit_str.value().trim().to_string()),
151                    _ => Err(Error::UnsupportedExpressionLiteral(expr_lit)),
152                },
153                _ => Err(Error::UnsupportedExpression(expr)),
154            })
155            .collect::<Result<Vec<String>>>()?
156            .join("\n");
157
158        Ok(Self {
159            identifier: value.ident.to_string(),
160            documentation: match documentation.is_empty() {
161                true => None,
162                false => Some(documentation),
163            },
164        })
165    }
166}
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171
172    #[test]
173    fn display_identifier_and_documentation() {
174        let variant = Variant::new(String::from("Name"), Some(String::from("Documentation.")));
175
176        assert_eq!(
177            variant.to_string(),
178            "::introspect::r#enum::Variant::new(r#\"Name\"#.into(), Some(r#\"Documentation.\"#.into()))"
179        )
180    }
181
182    #[test]
183    fn display_only_identifier() {
184        let variant = Variant::new(String::from("Name"), None);
185
186        assert_eq!(
187            variant.to_string(),
188            "::introspect::r#enum::Variant::new(r#\"Name\"#.into(), None)"
189        )
190    }
191}