Skip to main content

ferogram_derive/
lib.rs

1// Copyright (c) Ankit Chaubey <ankitchaubey.dev@gmail.com>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3//
4// ferogram: async Telegram MTProto client in Rust
5// https://github.com/ankit-chaubey/ferogram
6//
7// If you use or modify this code, keep this notice at the top of your file
8// and include the LICENSE-MIT or LICENSE-APACHE file from this repository:
9// https://github.com/ankit-chaubey/ferogram
10
11use proc_macro::TokenStream;
12use proc_macro2::TokenStream as TokenStream2;
13use quote::quote;
14use syn::{Data, DeriveInput, Fields, parse_macro_input, spanned::Spanned};
15
16/// Derive the [`ferogram::fsm::FsmState`] trait for an enum.
17///
18/// Only **unit variants** (no fields) are supported. Tuple or struct variants
19/// are rejected with a compile error.
20///
21/// # What gets generated
22///
23/// - `as_key(&self) -> String` - returns the variant name as a `&'static str`-backed `String`.
24/// - `from_key(key: &str) -> Option<Self>` - parses the variant name back into the enum.
25///
26/// # Example
27///
28/// ```rust,no_run
29/// use ferogram::FsmState;
30///
31/// #[derive(FsmState, Clone, Debug, PartialEq)]
32/// enum RegistrationState {
33///     Start,
34///     WaitingName,
35///     WaitingPhone,
36///     WaitingCity,
37///     Done,
38/// }
39/// ```
40#[proc_macro_derive(FsmState)]
41pub fn derive_fsm_state(input: TokenStream) -> TokenStream {
42    let input = parse_macro_input!(input as DeriveInput);
43    match fsm_state_impl(input) {
44        Ok(ts) => ts.into(),
45        Err(e) => e.to_compile_error().into(),
46    }
47}
48
49fn fsm_state_impl(input: DeriveInput) -> syn::Result<TokenStream2> {
50    let name = &input.ident;
51    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
52
53    let data_enum = match &input.data {
54        Data::Enum(e) => e,
55        _ => {
56            return Err(syn::Error::new(
57                input.ident.span(),
58                "`#[derive(FsmState)]` can only be applied to enums",
59            ));
60        }
61    };
62
63    // Validate: only unit variants allowed.
64    for variant in &data_enum.variants {
65        match &variant.fields {
66            Fields::Unit => {}
67            _ => {
68                return Err(syn::Error::new(
69                    variant.span(),
70                    "`#[derive(FsmState)]` only supports unit variants (no fields). \
71                     Tuple and struct variants are not supported.",
72                ));
73            }
74        }
75    }
76
77    // Generate `as_key` match arms.
78    let as_key_arms = data_enum.variants.iter().map(|v| {
79        let ident = &v.ident;
80        let key = ident.to_string();
81        quote! { #name::#ident => #key }
82    });
83
84    // Generate `from_key` match arms.
85    let from_key_arms = data_enum.variants.iter().map(|v| {
86        let ident = &v.ident;
87        let key = ident.to_string();
88        quote! { #key => ::std::option::Option::Some(#name::#ident) }
89    });
90
91    Ok(quote! {
92        #[automatically_derived]
93        impl #impl_generics ::ferogram::fsm::FsmState
94            for #name #ty_generics
95            #where_clause
96        {
97            fn as_key(&self) -> ::std::string::String {
98                match self {
99                    #(#as_key_arms),*
100                }
101                .to_string()
102            }
103
104            fn from_key(key: &str) -> ::std::option::Option<Self> {
105                match key {
106                    #(#from_key_arms),*
107                    _ => ::std::option::Option::None,
108                }
109            }
110        }
111    })
112}