introspect_core/struct/
field.rs1mod builder;
4
5pub use builder::Builder;
6
7pub enum Error {
9 UnsupportedExpression(syn::Expr),
11
12 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
42pub type Result<T> = std::result::Result<T, Error>;
44
45#[derive(Debug)]
47pub struct Field {
48 identifier: Option<String>,
50
51 documentation: Option<String>,
53}
54
55impl Field {
56 pub fn new(identifier: Option<String>, documentation: Option<String>) -> Self {
69 Self {
70 identifier,
71 documentation,
72 }
73 }
74
75 pub fn identifier(&self) -> Option<&str> {
90 self.identifier.as_deref()
91 }
92
93 pub fn documentation(&self) -> Option<&str> {
108 self.documentation.as_deref()
109 }
110}
111
112impl std::fmt::Display for Field {
113 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
114 write!(f, "::introspect::r#struct::Field::new(")?;
115
116 match self.identifier.as_ref() {
117 Some(identifier) => write!(f, "Some(r#\"{}\"#.into())", identifier)?,
118 None => write!(f, "None")?,
119 };
120
121 write!(f, ", ")?;
122
123 match self.documentation.as_ref() {
124 Some(documentation) => write!(f, "Some(r#\"{}\"#.into())", documentation)?,
125 None => write!(f, "None")?,
126 };
127
128 write!(f, ")")
129 }
130}
131
132impl TryFrom<&syn::Field> for Field {
133 type Error = Error;
134
135 fn try_from(value: &syn::Field) -> Result<Self> {
136 let documentation = value
137 .attrs
138 .iter()
139 .filter_map(|attr| match attr.meta.require_name_value() {
140 Ok(v) => Some(v),
141 Err(_) => None,
142 })
143 .filter_map(|field| {
144 field
145 .path
146 .get_ident()
147 .map(|ident| (ident, field.value.clone()))
148 })
149 .filter(|(ident, _)| *ident == "doc")
150 .map(|(_, expr)| match expr {
151 syn::Expr::Lit(expr_lit) => match expr_lit.lit {
152 syn::Lit::Str(lit_str) => Ok(lit_str.value().trim().to_string()),
153 _ => Err(Error::UnsupportedExpressionLiteral(expr_lit)),
154 },
155 _ => Err(Error::UnsupportedExpression(expr)),
156 })
157 .collect::<Result<Vec<String>>>()?
158 .join("\n");
159
160 Ok(Self {
161 identifier: value.ident.as_ref().map(|ident| ident.to_string()),
162 documentation: match documentation.is_empty() {
163 true => None,
164 false => Some(documentation),
165 },
166 })
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173
174 #[test]
175 fn display_identifier_and_documentation() {
176 let field = Field::new(
177 Some(String::from("Name")),
178 Some(String::from("Documentation.")),
179 );
180
181 assert_eq!(
182 field.to_string(),
183 "::introspect::r#struct::Field::new(Some(r#\"Name\"#.into()), Some(r#\"Documentation.\"#.into()))"
184 )
185 }
186
187 #[test]
188 fn display_only_identifier() {
189 let field = Field::new(Some(String::from("Name")), None);
190
191 assert_eq!(
192 field.to_string(),
193 "::introspect::r#struct::Field::new(Some(r#\"Name\"#.into()), None)"
194 )
195 }
196
197 #[test]
198 fn display_only_documentation() {
199 let field = Field::new(None, Some(String::from("Documentation.")));
200
201 assert_eq!(
202 field.to_string(),
203 "::introspect::r#struct::Field::new(None, Some(r#\"Documentation.\"#.into()))"
204 )
205 }
206
207 #[test]
208 fn display_neither() {
209 let field = Field::new(None, None);
210
211 assert_eq!(
212 field.to_string(),
213 "::introspect::r#struct::Field::new(None, None)"
214 )
215 }
216}