use darling::{FromDeriveInput, FromMeta};
use syn::{DeriveInput, Path};
#[derive(Clone, Debug)]
pub(crate) struct ConversionMeta {
pub(crate) source_name: Path,
pub(crate) target_name: Path,
pub(crate) method: ConversionMethod,
pub(crate) default_allowed: bool,
pub(crate) validate: Option<Path>,
}
impl ConversionMeta {
pub(crate) fn other_type(&self) -> Path {
if self.method.is_from() {
self.source_name.clone()
} else {
self.target_name.clone()
}
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) enum ConversionMethod {
Into,
TryInto,
From,
TryFrom,
}
impl ConversionMethod {
pub(crate) fn is_from(&self) -> bool {
matches!(self, ConversionMethod::From | ConversionMethod::TryFrom)
}
pub(crate) fn is_falliable(&self) -> bool {
matches!(self, ConversionMethod::TryInto | ConversionMethod::TryFrom)
}
}
fn ident_to_path(ident: &syn::Ident) -> syn::Path {
syn::Path {
leading_colon: None,
segments: std::iter::once(syn::PathSegment {
ident: ident.clone(),
arguments: syn::PathArguments::None,
})
.collect(),
}
}
#[derive(FromMeta, Debug)]
struct ConvAttrs {
path: Path,
#[darling(default)]
default: bool,
#[darling(default)]
validate: Option<Path>,
}
#[derive(FromDeriveInput)]
#[darling(attributes(convert))]
struct Conversions {
ident: syn::Ident,
#[darling(default, multiple)]
into: Vec<ConvAttrs>,
#[darling(default, multiple)]
try_into: Vec<ConvAttrs>,
#[darling(default, multiple)]
from: Vec<ConvAttrs>,
#[darling(default, multiple)]
try_from: Vec<ConvAttrs>,
}
pub(crate) fn extract_conversions(ast: &DeriveInput) -> Vec<ConversionMeta> {
let conversions_data = match Conversions::from_derive_input(ast) {
Ok(v) => v,
Err(e) => {
panic!("Error parsing conversion attributes: {}", e);
}
};
let mut result = Vec::new();
for attr in conversions_data.into {
if attr.validate.is_some() {
panic!("`validate` is only supported on fallible conversions (`try_from`/`try_into`)");
}
result.push(ConversionMeta {
source_name: ident_to_path(&conversions_data.ident),
target_name: attr.path,
method: ConversionMethod::Into,
default_allowed: attr.default,
validate: None,
});
}
for attr in conversions_data.try_into {
result.push(ConversionMeta {
source_name: ident_to_path(&conversions_data.ident),
target_name: attr.path,
method: ConversionMethod::TryInto,
default_allowed: attr.default,
validate: attr.validate,
});
}
for attr in conversions_data.from {
if attr.validate.is_some() {
panic!("`validate` is only supported on fallible conversions (`try_from`/`try_into`)");
}
result.push(ConversionMeta {
source_name: attr.path,
target_name: ident_to_path(&conversions_data.ident),
method: ConversionMethod::From,
default_allowed: attr.default,
validate: None,
});
}
for attr in conversions_data.try_from {
result.push(ConversionMeta {
source_name: attr.path,
target_name: ident_to_path(&conversions_data.ident),
method: ConversionMethod::TryFrom,
default_allowed: attr.default,
validate: attr.validate,
});
}
result
}