use alloc::{boxed::Box, vec::Vec};
use proc_macro2::TokenStream;
use syn::{Ident, Path};
use crate::{
enumerate,
field::{FieldRef, Fields},
format::Format,
resolve::Resolve as _,
semantics::{
Conversion, Display, Enumeration as ValidEnumeration, ErrorSource, Header, Structure as ValidStructure, Target as ValidTarget,
Variant as ValidVariant,
},
source::{Source, SourceShape},
structure,
syntax::Transparent,
target::Kind,
};
#[derive(Clone, Debug)]
struct Validated<ValueType>(ValueType);
impl<ValueType> Validated<ValueType> {
const fn new(value: ValueType) -> Self {
Self(value)
}
#[inline]
#[must_use]
fn into_inner(self) -> ValueType {
let Self(value) = self;
value
}
}
trait Validate {
type Output;
fn validate(self) -> syn::Result<Self::Output>;
}
#[derive(Clone, Debug)]
pub struct ValidatedTarget(ValidTarget);
impl ValidatedTarget {
#[inline]
pub fn expand(self) -> syn::Result<TokenStream> {
let Self(target) = self;
crate::expand::target(target)
}
}
pub fn target(target: Kind) -> syn::Result<ValidatedTarget> {
let target = match target {
Kind::Struct(structure) => {
let structure = structure.validate()?.into_inner();
ValidTarget::Struct(Box::new(structure))
}
Kind::Enum(enumeration) => {
let enumeration = enumeration.validate()?.into_inner();
ValidTarget::Enum(enumeration)
}
};
Ok(ValidatedTarget(target))
}
impl Validate for structure::Structure {
type Output = Validated<ValidStructure>;
#[inline]
fn validate(self) -> syn::Result<Self::Output> {
let (config, name, generics, fields, declaration) = self.parts();
let (inline, import) = config.parts();
let (format, display, source, transparent, from) = declaration.parts();
let declaration = Declaration {
subject: name.clone(),
fields: &fields,
format,
display,
source,
transparent,
from,
};
let (display, source, conversion) = declaration.validate()?;
let header = Header::new(inline, import, name, generics);
let structure = ValidStructure::new(header, fields, display, source, conversion);
Ok(Validated::new(structure))
}
}
impl Validate for enumerate::Enumeration {
type Output = Validated<ValidEnumeration>;
#[inline]
fn validate(self) -> syn::Result<Self::Output> {
let (config, name, generics, variants) = self.parts();
let (inline, import) = config.parts();
let mut validated = Vec::with_capacity(variants.len());
for variant in variants {
validated.push(variant.validate()?.into_inner());
}
let header = Header::new(inline, import, name, generics);
let enumeration = ValidEnumeration::new(header, validated);
Ok(Validated::new(enumeration))
}
}
impl Validate for enumerate::Variant {
type Output = Validated<ValidVariant>;
#[inline]
fn validate(self) -> syn::Result<Self::Output> {
let (name, fields, declaration) = self.parts();
let (format, display, source, transparent, from) = declaration.parts();
let declaration = Declaration {
subject: name.clone(),
fields: &fields,
format,
display,
source,
transparent,
from,
};
let (display, source, conversion) = declaration.validate()?;
let variant = ValidVariant::new(name, fields, display, source, conversion);
Ok(Validated::new(variant))
}
}
struct Declaration<'fields> {
subject: Ident,
fields: &'fields Fields,
format: Option<Format>,
display: Option<Path>,
source: Option<FieldRef>,
transparent: Option<Transparent>,
from: bool,
}
impl Declaration<'_> {
fn validate(self) -> syn::Result<(Display, ErrorSource, Option<Conversion>)> {
let Self {
subject,
fields,
format,
display,
source,
transparent,
from,
} = self;
if format.is_some() && display.is_some() {
return Err(syn::Error::new_spanned(
subject,
"error cannot declare both a format string and `display(...)`",
));
}
if transparent.is_some() && (format.is_some() || display.is_some()) {
return Err(syn::Error::new_spanned(
subject,
"transparent error cannot also declare display formatting",
));
}
if transparent.is_some() && source.is_some() {
return Err(syn::Error::new_spanned(
subject,
"transparent error cannot also declare an ordinary source",
));
}
if transparent.is_some() && from {
return Err(syn::Error::new_spanned(subject, "transparent error cannot also derive `From`"));
}
if let Some(Transparent(field_ref)) = transparent {
let field = field_ref.resolve(fields)?;
let sole = fields.sole()?;
if field != sole {
return Err(syn::Error::new_spanned(subject, "transparent error must target its sole field"));
}
let source = Source::new(fields, field);
if matches!(source.shape(), SourceShape::Optional | SourceShape::OptionalBoxed) {
return Err(syn::Error::new_spanned(subject, "transparent error source cannot be optional"));
}
let display = Display::Transparent(field);
let source = ErrorSource::Transparent(source);
return Ok((display, source, None));
}
let display = match (format, display) {
(Some(format), None) => Display::Format(format.resolve(fields)?),
(None, Some(path)) => Display::Custom(path),
(None, None) => {
return Err(syn::Error::new_spanned(
subject,
"error requires a format string, `display(...)`, or `transparent(...)`",
));
}
(Some(_), Some(_)) => {
return Err(syn::Error::new_spanned(subject, "error has conflicting display declarations"));
}
};
let conversion = if from {
let field = fields.sole()?;
let field_type = fields.ty(field).clone();
Some(Conversion::new(field, field_type))
} else {
None
};
let source = match (source, conversion.as_ref()) {
(Some(field_ref), Some(conversion)) => {
let field = field_ref.resolve(fields)?;
if field != conversion.field() {
return Err(syn::Error::new_spanned(
subject,
"`from` conversion and explicit source must refer to the same field",
));
}
ErrorSource::Field(Source::new(fields, field))
}
(Some(field_ref), None) => {
let field = field_ref.resolve(fields)?;
ErrorSource::Field(Source::new(fields, field))
}
(None, Some(conversion)) => ErrorSource::Field(Source::new(fields, conversion.field())),
(None, None) => ErrorSource::None,
};
Ok((display, source, conversion))
}
}