use super::TypeIdent;
use crate::types::format_bounds;
use crate::{casing::Casing, docs::get_doc_lines};
use quote::ToTokens;
use std::convert::TryFrom;
use syn::{
ext::IdentExt, parenthesized, parse::Parse, parse::ParseStream, Attribute, Error, GenericParam,
Ident, ItemStruct, LitStr, Result, Token,
};
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct Struct {
pub ident: TypeIdent,
pub fields: Vec<Field>,
pub doc_lines: Vec<String>,
pub options: StructOptions,
}
pub(crate) fn parse_struct_item(item: ItemStruct) -> Struct {
let ident = TypeIdent {
name: item.ident.to_string(),
generic_args: item
.generics
.params
.iter()
.filter_map(|param| match param {
GenericParam::Type(ty) => {
Some((TypeIdent::from(ty.ident.to_string()), format_bounds(ty)))
}
_ => None,
})
.collect(),
..Default::default()
};
let fields = item
.fields
.iter()
.map(|field| Field {
name: field.ident.as_ref().map(Ident::to_string),
ty: TypeIdent::try_from(&field.ty)
.unwrap_or_else(|_| panic!("Invalid field type in struct {}", ident)),
doc_lines: get_doc_lines(&field.attrs),
attrs: FieldAttrs::from_attrs(&field.attrs),
})
.collect();
Struct {
ident,
fields,
doc_lines: get_doc_lines(&item.attrs),
options: StructOptions::from_attrs(&item.attrs),
}
}
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
pub struct StructOptions {
pub field_casing: Casing,
pub rust_module: Option<String>,
}
impl StructOptions {
pub fn from_attrs(attrs: &[Attribute]) -> Self {
let mut opts = Self::default();
for attr in attrs {
if attr.path.is_ident("fp") || attr.path.is_ident("serde") {
opts.merge_with(
&syn::parse2::<Self>(attr.tokens.clone()).expect("Could not parse attributes"),
);
}
}
opts
}
fn merge_with(&mut self, other: &Self) {
if other.field_casing != Casing::default() {
self.field_casing = other.field_casing;
}
if let Some(other_rust_module) = &other.rust_module {
self.rust_module = Some(other_rust_module.clone());
}
}
pub fn to_serde_attrs(&self) -> Vec<String> {
let mut serde_attrs = vec![];
if let Some(casing) = &self.field_casing.as_maybe_str() {
serde_attrs.push(format!("rename_all = \"{casing}\""));
}
serde_attrs
}
}
impl Parse for StructOptions {
fn parse(input: ParseStream) -> Result<Self> {
let content;
parenthesized!(content in input);
let parse_value = || -> Result<String> {
content.parse::<Token![=]>()?;
Ok(content
.parse::<LitStr>()?
.to_token_stream()
.to_string()
.trim_matches('"')
.to_owned())
};
let mut result = Self::default();
loop {
let key: Ident = content.call(IdentExt::parse_any)?;
match key.to_string().as_ref() {
"rename_all" => {
result.field_casing = Casing::try_from(parse_value()?.as_ref())
.map_err(|err| Error::new(content.span(), err))?
}
"rust_module" => {
result.rust_module = Some(parse_value()?);
}
other => {
return Err(Error::new(
content.span(),
format!("Unexpected attribute: {other}"),
))
}
}
if content.is_empty() {
break;
}
content.parse::<Token![,]>()?;
}
Ok(result)
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct Field {
pub name: Option<String>,
pub ty: TypeIdent,
pub doc_lines: Vec<String>,
pub attrs: FieldAttrs,
}
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
pub struct FieldAttrs {
pub default: Option<String>,
pub deserialize_with: Option<String>,
pub flatten: bool,
pub rename: Option<String>,
pub serialize_with: Option<String>,
pub skip_serializing_if: Option<String>,
}
impl FieldAttrs {
pub fn from_attrs(attrs: &[Attribute]) -> Self {
let mut opts = Self::default();
for attr in attrs {
if attr.path.is_ident("fp") || attr.path.is_ident("serde") {
opts.merge_with(
&syn::parse2::<Self>(attr.tokens.clone())
.expect("Could not parse field attributes"),
);
}
}
opts
}
fn merge_with(&mut self, other: &Self) {
if other.default.is_some() {
self.default = other.default.clone();
}
if other.deserialize_with.is_some() {
self.deserialize_with = other.deserialize_with.clone();
}
if other.flatten {
self.flatten = other.flatten;
}
if other.rename.is_some() {
self.rename = other.rename.clone();
}
if other.serialize_with.is_some() {
self.serialize_with = other.serialize_with.clone();
}
if other.skip_serializing_if.is_some() {
self.skip_serializing_if = other.skip_serializing_if.clone();
}
}
pub fn to_serde_attrs(&self) -> Vec<String> {
let mut serde_attrs = vec![];
if let Some(default) = self.default.as_ref() {
if default.is_empty() {
serde_attrs.push("default".to_owned());
} else {
serde_attrs.push(format!("default = \"{default}\""));
}
}
match (self.deserialize_with.as_ref(), self.serialize_with.as_ref()) {
(Some(deserialize_with), Some(serialize_with))
if deserialize_with == serialize_with =>
{
serde_attrs.push(format!("with = \"{deserialize_with}\""));
}
(Some(deserialize_with), Some(serialize_with)) => {
serde_attrs.push(format!("deserialize_with = \"{deserialize_with}\""));
serde_attrs.push(format!("serialize_with = \"{serialize_with}\""));
}
(Some(deserialize_with), None) => {
serde_attrs.push(format!("deserialize_with = \"{deserialize_with}\""));
}
(None, Some(serialize_with)) => {
serde_attrs.push(format!("serialize_with = \"{serialize_with}\""));
}
(None, None) => {}
}
if self.flatten {
serde_attrs.push("flatten".to_owned());
}
if let Some(rename) = self.rename.as_ref() {
serde_attrs.push(format!("rename = \"{rename}\""));
}
if let Some(skip_serializing_if) = self.skip_serializing_if.as_ref() {
serde_attrs.push(format!("skip_serializing_if = \"{skip_serializing_if}\""));
}
serde_attrs
}
}
impl Parse for FieldAttrs {
fn parse(input: ParseStream) -> Result<Self> {
let content;
parenthesized!(content in input);
let parse_value = || -> Result<String> {
content.parse::<Token![=]>()?;
Ok(content
.parse::<LitStr>()?
.to_token_stream()
.to_string()
.trim_matches('"')
.to_owned())
};
let parse_optional_value = || -> Result<String> {
if content.peek(Token![=]) {
parse_value()
} else {
Ok(String::new())
}
};
let mut result = Self::default();
loop {
let key: Ident = content.call(IdentExt::parse_any)?;
match key.to_string().as_ref() {
"default" => result.default = Some(parse_optional_value()?),
"deserialize_with" => result.deserialize_with = Some(parse_value()?),
"flatten" => result.flatten = true,
"rename" => result.rename = Some(parse_value()?),
"serialize_with" => result.serialize_with = Some(parse_value()?),
"skip_serializing_if" => result.skip_serializing_if = Some(parse_value()?),
"with" => {
let value = parse_value()?;
result.deserialize_with = Some(value.clone());
result.serialize_with = Some(value);
}
other => {
return Err(Error::new(
content.span(),
format!("Unexpected field attribute: {other}"),
))
}
}
if content.is_empty() {
break;
}
content.parse::<Token![,]>()?;
}
Ok(result)
}
}