extruct-macros 0.2.1

Implementation detail of the `extruct` crate. Use https://crates.io/crates/extruct in your code.
Documentation
use proc_macro2::TokenStream;
use syn::{Error, Fields, Item, ItemStruct, Meta, Path};

use crate::utils::format_error_output;

pub(crate) struct ParsedData {
    pub attr: Path,
    pub item: ItemStruct,
}

pub(crate) type Ast = ParsedData;

pub(crate) fn parse(attr: TokenStream, input: TokenStream) -> syn::Result<Ast> {
    const HELP_ATTR: &str = "`extruct_from` expects a single attribute which is a struct name";
    const HELP_ITEM: &str = "`extruct_from` can only be used on structs with named fields";
    let attr = match syn::parse2::<Meta>(attr.clone()) {
        Ok(Meta::Path(attr)) => Ok(attr),
        Ok(attr) => {
            // ../tests/fails/extruct-doesnt-work-if-attr-is-key-value.rs
            // ../tests/fails/extruct-doesnt-work-if-attr-is-list.rs
            Err(Error::new_spanned(
                &attr,
                format_error_output("invalid attribute format", HELP_ATTR),
            ))
        }
        Err(err) => {
            // ../tests/fails/extruct-doesnt-work-if-attr-is-malformed.rs
            Err(Error::new(
                err.span(),
                format_error_output(&err.to_string(), HELP_ATTR),
            ))
        }
    }?;
    let item = match syn::parse2::<Item>(input.clone()) {
        Ok(Item::Struct(item)) => Ok(item),
        Ok(_) => {
            // ../tests/fails/extruct-doesnt-work-for-non-struct-item.rs
            Err(Error::new_spanned(
                &input,
                format_error_output("item is not a struct", HELP_ITEM),
            ))
        }
        Err(_) => unreachable!(), // attribute macros can only be attached to Items
    }?;

    match &item.fields {
        // ../tests/fails/extruct-doesnt-work-for-structs-with-unnamed-fields.rs
        Fields::Unnamed(_) | Fields::Unit => Err(Error::new_spanned(
            &item,
            format_error_output("must be a struct with named fields", HELP_ITEM),
        )),
        _ => Ok(Ast { attr, item }),
    }
}

#[cfg(test)]
mod tests {
    use quote::quote;

    use super::*;

    #[test]
    fn valid_syntax_struct_named_fields() {
        assert!(parse(
            quote!(OrigStruct),
            quote!(
                struct SubStruct {
                    first: u32,
                    second: String,
                }
            ),
        )
        .is_ok());
    }

    #[test]
    fn valid_syntax_empty_struct_named_fields() {
        // Pretty useless but technically legit
        assert!(parse(
            quote!(OrigStruct),
            quote!(
                struct SubStruct {}
            ),
        )
        .is_ok());
    }
}