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) => {
Err(Error::new_spanned(
&attr,
format_error_output("invalid attribute format", HELP_ATTR),
))
}
Err(err) => {
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(_) => {
Err(Error::new_spanned(
&input,
format_error_output("item is not a struct", HELP_ITEM),
))
}
Err(_) => unreachable!(), }?;
match &item.fields {
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() {
assert!(parse(
quote!(OrigStruct),
quote!(
struct SubStruct {}
),
)
.is_ok());
}
}