Skip to main content

typhoon_syn/
errors.rs

1use syn::{
2    parse::Parse, punctuated::Punctuated, token::Comma, Attribute, Data, DeriveInput, Expr,
3    ExprLit, Ident, ItemEnum, Lit, LitStr, Variant,
4};
5
6fn parse_attribute(attributes: &[Attribute]) -> Option<String> {
7    attributes.iter().find_map(|attr| {
8        if !attr.path().is_ident("msg") {
9            return None;
10        }
11
12        let lit: LitStr = attr.parse_args().ok()?;
13        Some(lit.value())
14    })
15}
16
17pub struct ErrorVariant {
18    pub discriminant: u32,
19    pub name: Ident,
20    pub msg: String,
21}
22
23pub struct Errors {
24    pub name: Ident,
25    pub variants: Vec<ErrorVariant>,
26}
27
28impl TryFrom<&ItemEnum> for Errors {
29    type Error = syn::Error;
30
31    fn try_from(value: &ItemEnum) -> Result<Self, Self::Error> {
32        Ok(Errors {
33            name: value.ident.clone(),
34            variants: parse_variants(&value.variants)?,
35        })
36    }
37}
38
39impl Parse for Errors {
40    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
41        let derive_input: DeriveInput = input.parse()?;
42
43        let Data::Enum(data) = &derive_input.data else {
44            return Err(syn::Error::new_spanned(
45                &derive_input,
46                "TyphoonError can only be used on enums",
47            ));
48        };
49
50        Ok(Errors {
51            name: derive_input.ident,
52            variants: parse_variants(&data.variants)?,
53        })
54    }
55}
56
57fn parse_variants(data_variants: &Punctuated<Variant, Comma>) -> syn::Result<Vec<ErrorVariant>> {
58    let mut variants = Vec::with_capacity(data_variants.len());
59    let mut latest_dis: isize = -1;
60
61    for variant in data_variants {
62        let variant_name = &variant.ident;
63        let msg = parse_attribute(&variant.attrs)
64            .ok_or(syn::Error::new_spanned(variant, "No error msg set."))?;
65
66        if let Some((_, ref expr)) = variant.discriminant {
67            if let Expr::Lit(ExprLit {
68                lit: Lit::Int(val), ..
69            }) = expr
70            {
71                latest_dis = val.base10_parse::<isize>()?
72            } else {
73                return Err(syn::Error::new_spanned(expr, "Invalid discriminant."));
74            }
75        } else {
76            latest_dis += 1;
77        }
78
79        variants.push(ErrorVariant {
80            name: variant_name.to_owned(),
81            msg,
82            discriminant: latest_dis as u32,
83        });
84    }
85
86    Ok(variants)
87}