introspect_core/
struct.rs

1//! Rust structs.
2
3mod builder;
4pub mod field;
5
6pub use builder::Builder;
7pub use field::Field;
8
9/// An error related to a [`Field`].
10pub enum Error {
11    /// Encountered an unsupported expression for a documentation attribute.
12    UnsupportedExpression(syn::Expr),
13
14    /// Encountered an unsupported expression literal for a documentation attribute.
15    UnsupportedExpressionLiteral(syn::ExprLit),
16}
17
18impl std::fmt::Debug for Error {
19    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20        match self {
21            Self::UnsupportedExpression(_) => f.debug_tuple("UnsupportedExpression").finish(),
22            Self::UnsupportedExpressionLiteral(_) => {
23                f.debug_tuple("UnsupportedExpressionLiteral").finish()
24            }
25        }
26    }
27}
28
29impl std::fmt::Display for Error {
30    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31        match self {
32            Error::UnsupportedExpression(_) => {
33                write!(f, "unsupported doc attribute expression")
34            }
35            Error::UnsupportedExpressionLiteral(_) => {
36                write!(f, "unsupported doc attribute literal")
37            }
38        }
39    }
40}
41
42impl std::error::Error for Error {}
43
44/// A [`Result`](std::result::Result) with an [`Error`].
45pub type Result<T> = std::result::Result<T, Error>;
46
47/// A struct.
48#[derive(Debug)]
49pub struct Struct {
50    identifier: String,
51
52    documentation: Option<String>,
53}
54
55impl Struct {
56    /// Creates a new [`Struct`].
57    ///
58    /// # Examples
59    ///
60    /// ```
61    /// use introspect_core as core;
62    ///
63    /// let struct_ = core::Struct::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 [`Struct`] by reference.
76    ///
77    /// # Examples
78    ///
79    /// ```
80    /// use introspect_core as core;
81    ///
82    /// let struct_ = core::r#struct::Builder::default()
83    ///                 .identifier("Name")
84    ///                 .documentation("Documentation.")
85    ///                 .try_build()?;
86    ///
87    /// assert_eq!(struct_.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 [`Struct`] by reference.
96    ///
97    /// # Examples
98    ///
99    /// ```
100    /// use introspect_core as core;
101    ///
102    /// let struct_ = core::r#struct::Builder::default()
103    ///                 .identifier("Name")
104    ///                 .documentation("Documentation.")
105    ///                 .try_build()?;
106    ///
107    /// assert_eq!(struct_.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 Struct {
117    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118        write!(f, "::introspect::Struct::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::ItemStruct> for Struct {
131    type Error = Error;
132
133    fn try_from(value: &syn::ItemStruct) -> 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(|field| {
142                field
143                    .path
144                    .get_ident()
145                    .map(|ident| (ident, field.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}