use std::collections::HashMap;
use syn::{parse_quote, Attribute, Fields, Ident, Path, Result, Type, WherePredicate};
use super::{
parse_assign_from_str, parse_assign_inflection, parse_bound, parse_concrete, Attr,
ContainerAttr, Serde, Tagged,
};
use crate::{
attr::{parse_assign_str, EnumAttr, Inflection, VariantAttr},
utils::{parse_attrs, parse_docs},
};
#[derive(Default, Clone)]
pub struct StructAttr {
crate_rename: Option<Path>,
pub type_as: Option<Type>,
pub type_override: Option<String>,
pub rename_all: Option<Inflection>,
pub rename: Option<String>,
pub export_to: Option<String>,
pub export: bool,
pub tag: Option<String>,
pub docs: String,
pub concrete: HashMap<Ident, Type>,
pub bound: Option<Vec<WherePredicate>>,
}
impl StructAttr {
pub fn from_attrs(attrs: &[Attribute]) -> Result<Self> {
let mut result = parse_attrs::<Self>(attrs)?;
if cfg!(feature = "serde-compat") {
let serde_attr = crate::utils::parse_serde_attrs::<StructAttr>(attrs);
result = result.merge(serde_attr.0);
}
let docs = parse_docs(attrs)?;
result.docs = docs;
Ok(result)
}
pub fn from_variant(
enum_attr: &EnumAttr,
variant_attr: &VariantAttr,
variant_fields: &Fields,
) -> Self {
Self {
crate_rename: Some(enum_attr.crate_rename()),
rename: variant_attr.rename.clone(),
rename_all: variant_attr.rename_all.or(match variant_fields {
Fields::Named(_) => enum_attr.rename_all_fields,
Fields::Unnamed(_) | Fields::Unit => None,
}),
tag: match variant_fields {
Fields::Named(_) => match enum_attr
.tagged()
.expect("The variant attribute is known to be valid at this point")
{
Tagged::Internally { tag } => Some(tag.to_owned()),
_ => None,
},
_ => None,
},
..Self::default()
}
}
}
impl Attr for StructAttr {
type Item = Fields;
fn merge(self, other: Self) -> Self {
Self {
crate_rename: self.crate_rename.or(other.crate_rename),
type_as: self.type_as.or(other.type_as),
type_override: self.type_override.or(other.type_override),
rename: self.rename.or(other.rename),
rename_all: self.rename_all.or(other.rename_all),
export_to: self.export_to.or(other.export_to),
export: self.export || other.export,
tag: self.tag.or(other.tag),
docs: other.docs,
concrete: self.concrete.into_iter().chain(other.concrete).collect(),
bound: match (self.bound, other.bound) {
(Some(a), Some(b)) => Some(a.into_iter().chain(b).collect()),
(Some(bound), None) | (None, Some(bound)) => Some(bound),
(None, None) => None,
},
}
}
fn assert_validity(&self, item: &Self::Item) -> Result<()> {
if self.type_override.is_some() {
if self.type_as.is_some() {
syn_err!("`as` is not compatible with `type`");
}
if self.rename_all.is_some() {
syn_err!("`rename_all` is not compatible with `type`");
}
if self.tag.is_some() {
syn_err!("`tag` is not compatible with `type`");
}
}
if self.type_as.is_some() {
if self.tag.is_some() {
syn_err!("`tag` is not compatible with `as`");
}
if self.rename_all.is_some() {
syn_err!("`rename_all` is not compatible with `as`");
}
}
if !matches!(item, Fields::Named(_)) {
if self.tag.is_some() {
syn_err!("`tag` cannot be used with unit or tuple structs");
}
if self.rename_all.is_some() {
syn_err!("`rename_all` cannot be used with unit or tuple structs");
}
}
Ok(())
}
}
impl ContainerAttr for StructAttr {
fn crate_rename(&self) -> Path {
self.crate_rename
.clone()
.unwrap_or_else(|| parse_quote!(::py_rs))
}
}
impl_parse! {
StructAttr(input, out) {
"crate" => out.crate_rename = Some(parse_assign_from_str(input)?),
"as" => out.type_as = Some(parse_assign_from_str(input)?),
"type" => out.type_override = Some(parse_assign_str(input)?),
"rename" => out.rename = Some(parse_assign_str(input)?),
"rename_all" => out.rename_all = Some(parse_assign_inflection(input)?),
"tag" => out.tag = Some(parse_assign_str(input)?),
"export" => out.export = true,
"export_to" => out.export_to = Some(parse_assign_str(input)?),
"concrete" => out.concrete = parse_concrete(input)?,
"bound" => out.bound = Some(parse_bound(input)?),
}
}
impl_parse! {
Serde<StructAttr>(input, out) {
"rename" => out.0.rename = Some(parse_assign_str(input)?),
"rename_all" => out.0.rename_all = Some(parse_assign_inflection(input)?),
"tag" => out.0.tag = Some(parse_assign_str(input)?),
"bound" => out.0.bound = Some(parse_bound(input)?),
"deny_unknown_fields" | "default" => {
use syn::Token;
if input.peek(Token![=]) {
input.parse::<Token![=]>()?;
parse_assign_str(input)?;
}
},
}
}