use crate::{
attrs::{parse_doc_attr, ContainerAttributes, FieldAttributes},
util::ToLitStr
};
use proc_macro2::{Ident, Span};
use serde_derive_internals::attr::RenameRule;
use syn::{
punctuated::Punctuated, spanned::Spanned as _, AngleBracketedGenericArguments, DataEnum, DataStruct, DataUnion, Fields,
FieldsNamed, GenericArgument, LitStr, PathArguments, Type, TypePath
};
use syn_path::path;
pub(super) enum TypeOrInline {
Type(Box<Type>),
Inline(ParseData)
}
pub(super) struct ParseDataField {
pub(super) name: LitStr,
pub(super) doc: Vec<String>,
pub(super) ty: TypeOrInline,
pub(super) flatten: bool
}
#[allow(dead_code)]
pub(super) struct ParseData {
pub(super) name: Option<LitStr>,
pub(super) doc: Vec<String>,
pub(super) ty: ParseDataType
}
pub(super) enum ParseDataType {
Struct {
fields: Vec<ParseDataField>,
deny_unknown_fields: bool
},
Enum {
variants: Vec<LitStr>
},
Alternatives {
alts: Vec<TypeOrInline>
},
Unit
}
fn parse_named_fields(named_fields: &FieldsNamed, rename_all: Option<&LitStr>) -> syn::Result<Vec<ParseDataField>> {
let mut fields: Vec<ParseDataField> = Vec::new();
for f in &named_fields.named {
let mut attrs = FieldAttributes::default();
for attr in &f.attrs {
if attr.path.is_ident("serde") {
attrs.parse_from(attr, false)?;
}
}
for attr in &f.attrs {
if attr.path.is_ident("openapi") {
attrs.parse_from(attr, true)?;
}
}
if attrs.skip_serializing && attrs.skip_deserializing {
continue;
}
let mut doc = Vec::new();
for attr in &f.attrs {
if attr.path.is_ident("doc") {
if let Some(lit) = parse_doc_attr(attr)? {
doc.push(lit.value());
}
}
}
let ident = f
.ident
.as_ref()
.ok_or_else(|| syn::Error::new(f.span(), "#[derive(OpenapiType)] does not support fields without an ident"))?;
let mut name = ident.to_lit_str();
if let Some(rename) = attrs.rename {
name = rename;
} else if let Some(rename_all) = rename_all {
let rule = RenameRule::from_str(&rename_all.value())
.map_err(|_| syn::Error::new(rename_all.span(), "Unknown rename_all rule"))?;
let rename = rule.apply_to_field(&name.value());
name = LitStr::new(&rename, name.span());
}
let mut ty = f.ty.to_owned();
if attrs.nullable {
let mut args = Punctuated::new();
args.push(GenericArgument::Type(ty));
let mut path = path!(::core::option::Option);
let last = path.segments.last_mut().unwrap();
last.arguments = PathArguments::AngleBracketed(AngleBracketedGenericArguments {
colon2_token: None,
lt_token: Default::default(),
args,
gt_token: Default::default()
});
ty = Type::Path(TypePath { qself: None, path })
}
fields.push(ParseDataField {
name,
doc,
ty: TypeOrInline::Type(Box::new(ty)),
flatten: attrs.flatten
});
}
Ok(fields)
}
pub(super) fn parse_struct(ident: &Ident, strukt: &DataStruct, attrs: &ContainerAttributes) -> syn::Result<ParseData> {
let name = attrs.rename.clone().unwrap_or_else(|| ident.to_lit_str());
match &strukt.fields {
Fields::Named(named_fields) => {
let fields = parse_named_fields(named_fields, attrs.rename_all.as_ref())?;
Ok(ParseData {
name: Some(name),
doc: attrs.doc.clone(),
ty: ParseDataType::Struct {
fields,
deny_unknown_fields: attrs.deny_unknown_fields
}
})
},
Fields::Unnamed(unnamed_fields) => Err(syn::Error::new(
unnamed_fields.span(),
"#[derive(OpenapiType)] does not support tuple structs"
)),
Fields::Unit => Ok(ParseData {
name: Some(name),
doc: attrs.doc.clone(),
ty: ParseDataType::Unit
})
}
}
pub(super) fn parse_enum(ident: &Ident, inum: &DataEnum, attrs: &ContainerAttributes) -> syn::Result<ParseData> {
let mut strings: Vec<LitStr> = Vec::new();
let mut types: Vec<(LitStr, TypeOrInline)> = Vec::new();
for v in &inum.variants {
let name = v.ident.to_lit_str();
match &v.fields {
Fields::Named(named_fields) => {
let fields = parse_named_fields(named_fields, attrs.rename_all.as_ref())?;
let struct_name = format!("{ident}::{}", name.value());
types.push((
name,
TypeOrInline::Inline(ParseData {
name: Some(struct_name.to_lit_str()),
doc: Vec::new(),
ty: ParseDataType::Struct {
fields,
deny_unknown_fields: attrs.deny_unknown_fields
}
})
));
},
Fields::Unnamed(unnamed_fields) if unnamed_fields.unnamed.len() == 1 => {
let ty = unnamed_fields.unnamed.first().unwrap().ty.clone();
types.push((name, TypeOrInline::Type(Box::new(ty))));
},
Fields::Unnamed(unnamed_fields) => {
return Err(syn::Error::new(
unnamed_fields.span(),
"#[derive(OpenapiType)] does not support tuple variants"
))
},
Fields::Unit => strings.push(name)
}
}
let data_strings = if strings.is_empty() {
None
} else {
Some(match (&attrs.tag, &attrs.content, attrs.untagged) {
(None, None, false) => ParseDataType::Enum { variants: strings },
(Some(tag), _, false) => ParseDataType::Struct {
fields: vec![ParseDataField {
name: tag.clone(),
doc: Vec::new(),
ty: TypeOrInline::Inline(ParseData {
name: None,
doc: Vec::new(),
ty: ParseDataType::Enum { variants: strings }
}),
flatten: false
}],
deny_unknown_fields: true
},
(None, None, true) => ParseDataType::Unit,
_ => return Err(syn::Error::new(Span::call_site(), "Unknown enum representation"))
})
};
let data_types = if types.is_empty() {
None
} else {
Some(ParseData {
name: Some(ident.to_lit_str()),
doc: attrs.doc.clone(),
ty: ParseDataType::Alternatives {
alts: types
.into_iter()
.map(|(name, mut data)| {
Ok(match (&attrs.tag, &attrs.content, attrs.untagged) {
(None, None, false) => {
let struct_name = format!("{ident}::{}::ExtTagWrapper", name.value());
TypeOrInline::Inline(ParseData {
name: Some(struct_name.to_lit_str()),
doc: Vec::new(),
ty: ParseDataType::Struct {
fields: vec![ParseDataField {
name,
doc: Vec::new(),
ty: data,
flatten: false
}],
deny_unknown_fields: true
}
})
},
(Some(tag), None, false) => {
match &mut data {
TypeOrInline::Inline(ParseData {
ty: ParseDataType::Struct { fields, .. },
..
}) => fields.push(ParseDataField {
name: tag.clone(),
doc: Vec::new(),
ty: TypeOrInline::Inline(ParseData {
name: None,
doc: Vec::new(),
ty: ParseDataType::Enum { variants: vec![name] }
}),
flatten: false
}),
_ => return Err(syn::Error::new(
tag.span(),
"#[derive(OpenapiType)] does not support tuple variants on internally tagged enums"
))
};
data
},
(Some(tag), Some(content), false) => {
let struct_name = format!("{ident}::{}::AdjTagWrapper", name.value());
TypeOrInline::Inline(ParseData {
name: Some(struct_name.to_lit_str()),
doc: Vec::new(),
ty: ParseDataType::Struct {
fields: vec![
ParseDataField {
name: tag.clone(),
doc: Vec::new(),
ty: TypeOrInline::Inline(ParseData {
name: None,
doc: Vec::new(),
ty: ParseDataType::Enum { variants: vec![name] }
}),
flatten: false
},
ParseDataField {
name: content.clone(),
doc: Vec::new(),
ty: data,
flatten: false
},
],
deny_unknown_fields: true
}
})
},
(None, None, true) => data,
_ => return Err(syn::Error::new(Span::call_site(), "Unknown enum representation"))
})
})
.collect::<syn::Result<Vec<_>>>()?
}
})
};
match (data_strings, data_types) {
(Some(ty), None) => Ok(ParseData {
name: Some(ident.to_lit_str()),
doc: attrs.doc.clone(),
ty
}),
(
None,
Some(ParseData {
ty: ParseDataType::Alternatives { mut alts },
..
})
) if alts.len() == 1 && matches!(alts.first().unwrap(), TypeOrInline::Inline(..)) => Ok(ParseData {
name: Some(ident.to_lit_str()),
doc: attrs.doc.clone(),
ty: match alts.remove(0) {
TypeOrInline::Inline(data) => data.ty,
_ => unreachable!() }
}),
(None, Some(data)) => Ok(data),
(Some(data_strings), Some(mut data_types)) => {
let alts = match &mut data_types.ty {
ParseDataType::Alternatives { alts } => alts,
_ => unreachable!()
};
alts.push(TypeOrInline::Inline(ParseData {
name: None,
doc: Vec::new(),
ty: data_strings
}));
Ok(data_types)
},
(None, None) => Err(syn::Error::new(
inum.brace_token.span,
"#[derive(OpenapiType)] does not support enums with no variants"
))
}
}
pub(super) fn parse_union(union: &DataUnion) -> syn::Result<ParseData> {
Err(syn::Error::new(
union.union_token.span(),
"#[derive(OpenapiType)] cannot be used on unions"
))
}