use syn::{Data, DeriveInput, Field, Fields, Generics, Ident, Index, Member, Path, Type, Variant};
pub(crate) struct VersionBoundary {
pub(crate) latest: Ident,
pub(crate) model: Path,
pub(crate) generics: Generics,
pub(crate) data: VersionBoundaryData,
pub(crate) error: Option<Path>,
}
pub(crate) enum VersionBoundaryData {
Struct(LatestFields),
Enum(Vec<LatestVariant>),
}
pub(crate) struct LatestVariant {
pub(crate) ident: Ident,
pub(crate) fields: LatestFields,
}
pub(crate) enum LatestFields {
Named(Vec<LatestField>),
Unnamed(Vec<LatestField>),
Unit,
}
pub(crate) struct LatestField {
pub(crate) member: Member,
pub(crate) ty: Type,
pub(crate) nested: bool,
pub(crate) family: Option<Path>,
}
impl VersionBoundaryData {
pub(crate) fn fields(&self) -> impl Iterator<Item = &LatestField> {
match self {
Self::Struct(fields) => EitherFields::Struct(fields.iter()),
Self::Enum(variants) => {
EitherFields::Enum(variants.iter().flat_map(|variant| variant.fields.iter()))
}
}
}
}
impl LatestFields {
pub(crate) fn iter(&self) -> std::slice::Iter<'_, LatestField> {
match self {
Self::Named(fields) | Self::Unnamed(fields) => fields.iter(),
Self::Unit => [].iter(),
}
}
}
enum EitherFields<A, B> {
Struct(A),
Enum(B),
}
impl<'a, A, B> Iterator for EitherFields<A, B>
where
A: Iterator<Item = &'a LatestField>,
B: Iterator<Item = &'a LatestField>,
{
type Item = &'a LatestField;
fn next(&mut self) -> Option<Self::Item> {
match self {
Self::Struct(iter) => iter.next(),
Self::Enum(iter) => iter.next(),
}
}
}
pub(crate) fn parse(input: DeriveInput) -> syn::Result<VersionBoundary> {
let (model, error) = parse_options(&input)?;
let model = model.ok_or_else(|| {
syn::Error::new_spanned(
&input,
"model conversion requires `#[thisversion(model = Model)]`",
)
})?;
let DeriveInput {
ident,
generics,
data,
..
} = input;
let data = match data {
Data::Struct(data) => VersionBoundaryData::Struct(parse_fields(data.fields)?),
Data::Enum(data) => VersionBoundaryData::Enum(
data.variants
.into_iter()
.map(parse_variant)
.collect::<syn::Result<_>>()?,
),
Data::Union(data) => {
return Err(syn::Error::new_spanned(
data.union_token,
"model boundaries do not support unions",
));
}
};
Ok(VersionBoundary {
latest: ident,
model,
generics,
data,
error,
})
}
fn parse_options(input: &DeriveInput) -> syn::Result<(Option<Path>, Option<Path>)> {
let mut model = None;
let mut error = None;
for attr in &input.attrs {
if !attr.path().is_ident("thisversion") {
continue;
}
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("model") {
if model.is_some() {
return Err(meta.error("duplicate `model`"));
}
model = Some(meta.value()?.parse()?);
Ok(())
} else if meta.path.is_ident("error") {
if error.is_some() {
return Err(meta.error("duplicate `error`"));
}
error = Some(meta.value()?.parse()?);
Ok(())
} else {
Err(meta.error("unsupported `thisversion` attribute"))
}
})?;
}
Ok((model, error))
}
fn parse_variant(variant: Variant) -> syn::Result<LatestVariant> {
Ok(LatestVariant {
ident: variant.ident,
fields: parse_fields(variant.fields)?,
})
}
fn parse_fields(fields: Fields) -> syn::Result<LatestFields> {
match fields {
Fields::Named(fields) => {
let fields = fields
.named
.into_iter()
.map(|field| {
let Field {
ident, ty, attrs, ..
} = field;
parse_field(attrs, ty, Member::Named(ident.expect("named field")))
})
.collect::<syn::Result<_>>()?;
Ok(LatestFields::Named(fields))
}
Fields::Unnamed(fields) => {
let fields = fields
.unnamed
.into_iter()
.enumerate()
.map(|(index, field)| {
let Field { ty, attrs, .. } = field;
parse_field(attrs, ty, Member::Unnamed(Index::from(index)))
})
.collect::<syn::Result<_>>()?;
Ok(LatestFields::Unnamed(fields))
}
Fields::Unit => Ok(LatestFields::Unit),
}
}
fn parse_field(attrs: Vec<syn::Attribute>, ty: Type, member: Member) -> syn::Result<LatestField> {
let mut nested = false;
let mut family = None;
for attr in &attrs {
if !attr.path().is_ident("thisversion") {
continue;
}
attr.parse_nested_meta(|meta| {
if !meta.path.is_ident("nested") {
return Err(meta.error("expected `nested` on a field"));
}
if nested {
return Err(meta.error("duplicate `nested`"));
}
nested = true;
if meta.input.peek(syn::Token![=]) {
family = Some(meta.value()?.parse()?);
}
Ok(())
})?;
}
Ok(LatestField {
member,
ty,
nested,
family,
})
}