1//! Analyze structs
  2use syn::{ItemStruct, Visibility};
  3
  4use crate::data_model::{Field, Struct};
  5
  6use super::{docstring_from_attrs, type_::convert_type};
  7
  8impl Struct {
  9    /// Fully qualified name of the variant
 10    pub fn path_str(&self) -> String {
 11        self.path.join("::")
 12    }
 13    /// Extract the relevant information from the AST
 14    pub fn parse(parent: &[&str], ast: &ItemStruct) -> Self {
 15        let name = ast.ident.to_string();
 16        let path = parent
 17            .iter()
 18            .copied()
 19            .chain(Some(name.as_str()))
 20            .collect::<Vec<&str>>();
 21        let docstring = docstring_from_attrs(&ast.attrs);
 22        let mut struct_ = Self {
 23            path: path.iter().map(|s| s.to_string()).collect(),
 24            docstring,
 25            fields: vec![],
 26        };
 27        for (i, field) in ast.fields.iter().enumerate() {
 28            if let Visibility::Public(_) = field.vis {
 29                struct_.fields.push(Field::parse(&path, i, field));
 30            }
 31        }
 32        struct_
 33    }
 34}
 35
 36impl Field {
 37    /// Extract the relevant information from the AST
 38    pub fn parse(parent: &[&str], position: usize, ast: &syn::Field) -> Self {
 39        let name = ast
 40            .ident
 41            .as_ref()
 42            .map(|name| name.to_string())
 43            .unwrap_or(position.to_string());
 44        let path = parent
 45            .iter()
 46            .copied()
 47            .chain(Some(name.as_str()))
 48            .collect::<Vec<&str>>();
 49        let docstring = docstring_from_attrs(&ast.attrs);
 50        let type_ = convert_type(&ast.ty);
 51        Self {
 52            path: path.iter().map(|s| s.to_string()).collect(),
 53            docstring,
 54            type_,
 55        }
 56    }
 57}
 58
 59#[cfg(test)]
 60mod tests {
 61    use super::*;
 62    use insta::assert_yaml_snapshot;
 63    use syn::parse_quote;
 64
 65    #[test]
 66    fn test_parse_struct_no_fields() {
 67        let ast: ItemStruct = parse_quote! {
 68            /// Multi-line
 69            /// docstring
 70            pub struct MyStruct;
 71        };
 72        let struct_ = Struct::parse(&["crate"], &ast);
 73        assert_yaml_snapshot!(struct_, @r###"
 74        ---
 75        path:
 76          - crate
 77          - MyStruct
 78        docstring: "Multi-line\ndocstring"
 79        fields: []
 80        "###);
 81    }
 82
 83    #[test]
 84    fn test_parse_struct_fields() {
 85        let ast: ItemStruct = parse_quote! {
 86            /// Multi-line
 87            /// docstring
 88            pub struct MyStruct {
 89                /// Docstring
 90                pub my_field: [T; 1],
 91                /// a non-public field
 92                other: String,
 93            }
 94        };
 95        let struct_ = Struct::parse(&["crate"], &ast);
 96        assert_yaml_snapshot!(struct_, @r###"
 97        ---
 98        path:
 99          - crate
100          - MyStruct
101        docstring: "Multi-line\ndocstring"
102        fields:
103          - path:
104              - crate
105              - MyStruct
106              - my_field
107            docstring: Docstring
108            type_:
109              - String: "["
110              - Path: T
111              - String: "; 1]"
112        "###);
113    }
114}