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#![cfg_attr(docsrs, feature(doc_cfg))]
14#![doc(html_root_url = "https://docs.rs/ferogram-derive/0.6.3")]
15//! Procedural macros for ferogram.
16//!
17//! This crate is part of [ferogram](https://crates.io/crates/ferogram), an async Rust
18//! MTProto client built by [Ankit Chaubey](https://github.com/ankit-chaubey).
19//!
20//! - Channel: [t.me/Ferogram](https://t.me/Ferogram)
21//! - Chat: [t.me/FerogramChat](https://t.me/FerogramChat)
22//!
23//! You do not depend on this crate directly. It is re-exported through
24//! `ferogram` and `ferogram-fsm`. Add those crates to your `Cargo.toml`
25//! instead.
26//!
27//! # What's in here
28//!
29//! - **`#[derive(FsmState)]`**: Implements the `ferogram_fsm::FsmState`
30//!   trait for a unit-variant enum. Generates `as_key` (variant name →
31//!   `String`) and `from_key` (string → `Option<Self>`). Tuple and struct
32//!   variants are rejected at compile time.
33//!
34//! # Example
35//!
36//! ```rust,ignore
37//! use ferogram::FsmState;
38//!
39//! #[derive(FsmState, Clone, Debug, PartialEq)]
40//! enum CheckoutState {
41//!     Cart,
42//!     Address,
43//!     Payment,
44//!     Confirmation,
45//! }
46//! ```
47
48#![deny(unsafe_code)]
49
50use proc_macro::TokenStream;
51use proc_macro2::TokenStream as TokenStream2;
52use quote::quote;
53use syn::{Data, DeriveInput, Fields, parse_macro_input, spanned::Spanned};
54
55/// Derive the `ferogram_fsm::FsmState` trait for an enum.
56///
57/// Only **unit variants** (no fields) are supported. Tuple or struct variants
58/// are rejected with a compile error.
59///
60/// # What gets generated
61///
62/// - `as_key(&self) -> String` - returns the variant name as a `&'static str`-backed `String`.
63/// - `from_key(key: &str) -> Option<Self>` - parses the variant name back into the enum.
64///
65/// # Example
66///
67/// ```rust,ignore
68/// use ferogram::FsmState;
69///
70/// #[derive(FsmState, Clone, Debug, PartialEq)]
71/// enum RegistrationState {
72///     Start,
73///     WaitingName,
74///     WaitingPhone,
75///     WaitingCity,
76///     Done,
77/// }
78/// ```
79#[proc_macro_derive(FsmState)]
80pub fn derive_fsm_state(input: TokenStream) -> TokenStream {
81    let input = parse_macro_input!(input as DeriveInput);
82    match fsm_state_impl(input) {
83        Ok(ts) => ts.into(),
84        Err(e) => e.to_compile_error().into(),
85    }
86}
87
88fn fsm_state_impl(input: DeriveInput) -> syn::Result<TokenStream2> {
89    let name = &input.ident;
90    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
91
92    let data_enum = match &input.data {
93        Data::Enum(e) => e,
94        _ => {
95            return Err(syn::Error::new(
96                input.ident.span(),
97                "`#[derive(FsmState)]` can only be applied to enums",
98            ));
99        }
100    };
101
102    // Validate: only unit variants allowed.
103    for variant in &data_enum.variants {
104        match &variant.fields {
105            Fields::Unit => {}
106            _ => {
107                return Err(syn::Error::new(
108                    variant.span(),
109                    "`#[derive(FsmState)]` only supports unit variants (no fields). \
110                     Tuple and struct variants are not supported.",
111                ));
112            }
113        }
114    }
115
116    // Generate `as_key` match arms.
117    let as_key_arms = data_enum.variants.iter().map(|v| {
118        let ident = &v.ident;
119        let key = ident.to_string();
120        quote! { #name::#ident => #key }
121    });
122
123    // Generate `from_key` match arms.
124    let from_key_arms = data_enum.variants.iter().map(|v| {
125        let ident = &v.ident;
126        let key = ident.to_string();
127        quote! { #key => ::std::option::Option::Some(#name::#ident) }
128    });
129
130    Ok(quote! {
131        #[automatically_derived]
132        impl #impl_generics ::ferogram_fsm::FsmState
133            for #name #ty_generics
134            #where_clause
135        {
136            fn as_key(&self) -> ::std::string::String {
137                match self {
138                    #(#as_key_arms),*
139                }
140                .to_string()
141            }
142
143            fn from_key(key: &str) -> ::std::option::Option<Self> {
144                match key {
145                    #(#from_key_arms),*
146                    _ => ::std::option::Option::None,
147                }
148            }
149        }
150    })
151}