Skip to main content

ferogram_derive/
lib.rs

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