use syn::{Attribute, Result, Type};
use super::http_header::parse_http_header;
use crate::ir::{FieldDefinition, TransparentFieldDefinition};
pub(crate) fn parse_field(field: syn::Field) -> Result<FieldDefinition> {
let is_extension = has_attribute(&field.attrs, "extension");
let is_from = has_attribute(&field.attrs, "from");
let is_source = has_attribute(&field.attrs, "source") || is_from;
let http_header = parse_http_header(&field.attrs)?;
let rust_name = field
.ident
.clone()
.ok_or_else(|| syn::Error::new_spanned(&field, "fields must be named"))?;
let output_name = rust_name.to_string();
let is_option = is_option_type(&field.ty);
Ok(FieldDefinition {
rust_name,
output_name,
ty: field.ty,
is_extension,
is_source,
is_from,
is_option,
http_header,
})
}
pub(crate) fn parse_transparent_field(field: syn::Field) -> Result<TransparentFieldDefinition> {
if field.ident.is_some() {
unreachable!("parse_transparent_field() can only be called on unnamed fields");
}
if let Some(attr) = field
.attrs
.iter()
.find(|attr| attr.path().is_ident("source"))
{
return Err(syn::Error::new_spanned(
attr,
"#[source] attribute is not necessary on transparent variants",
));
}
Ok(TransparentFieldDefinition {
ty: field.ty,
is_from: has_attribute(&field.attrs, "from"),
})
}
fn has_attribute(attrs: &[Attribute], name: &str) -> bool {
attrs.iter().any(|attr| attr.path().is_ident(name))
}
fn is_option_type(ty: &Type) -> bool {
if let Type::Path(type_path) = ty {
let path = &type_path.path;
path.segments
.last()
.is_some_and(|seg| seg.ident == "Option")
&& (path.segments.len() == 1
|| (path.segments.len() == 3
&& (path.segments[0].ident == "std" || path.segments[0].ident == "core")
&& path.segments[1].ident == "option"))
} else {
false
}
}