Generate chained like string with enums and struct

I’m experimenting with proc-macro, the goal is that I can chain functions like a struct that contain fields of enums,

Look at the test, the response will be
permission::auth::moderator::execute::conditional

main.rs:

use ::enum_display::RbacBuilder;

#[derive(Debug, RbacBuilder)]
pub struct PermissionBuilder {
    #[enum_def(Object)]
    object: Option<Object>,
    #[enum_def(Subject)]
    subject: Option<Subject>,
    #[enum_def(Action)]
    action: Option<Action>,
    #[enum_def(Permission)]
    permission: Option<Permission>,
}

#[derive(Debug)]
pub enum Subject {
    User,
    Admin,
    Guest,
    Moderator,
}

#[derive(Debug)]
pub enum Object {
    Auth,
    Role,
    Resource,
    Policy,
}

#[derive(Debug)]
pub enum Action {
    Create,
    Read,
    Update,
    Delete,
    List,
    Execute,
}

#[derive(Debug)]
pub enum Permission {
    Allow,
    Deny,
    Conditional,
}
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_permission() {
        let result = PermissionBuilder::new()
            .auth()
            .moderator() // New method from added enum variant
            .execute() // New method from added enum variant
            .conditional() // New method from added enum variant
            .finish();

        println!("perm: {:?}", result);
    }
}

my proc macro library (enum_display/src/lib.rs) look like this:

extern crate proc_macro;
extern crate proc_macro2;
extern crate quote;
extern crate syn;

use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{parse_macro_input, Data, DeriveInput, Fields, Type};

#[proc_macro_derive(RbacBuilder, attributes(enum_def))]
pub fn rbac_builder_derive(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = input.ident;

    let mut methods = vec![];

    if let Data::Struct(data_struct) = &input.data {
        if let Fields::Named(fields_named) = &data_struct.fields {
            for field in fields_named.named.iter() {
                if let Some(ident) = &field.ident {
                    let ty = &field.ty;

                    // Extract the enum name and its variants
                    let enum_name = match ty {
                        Type::Path(type_path) => {
                            if let Some(segment) = type_path.path.segments.last() {
                                // Check if it's an Option<T>
                                if segment.ident == "Option" {
                                    if let syn::PathArguments::AngleBracketed(args) =
                                        &segment.arguments
                                    {
                                        if let Some(syn::GenericArgument::Type(Type::Path(
                                            inner_type_path,
                                        ))) = args.args.first()
                                        {
                                            if let Some(inner_segment) =
                                                inner_type_path.path.segments.last()
                                            {
                                                Some(inner_segment.ident.clone())
                                            } else {
                                                None
                                            }
                                        } else {
                                            None
                                        }
                                    } else {
                                        None
                                    }
                                } else {
                                    Some(segment.ident.clone())
                                }
                            } else {
                                None
                            }
                        }
                        _ => None,
                    };

                    if let Some(enum_name) = enum_name {
                        eprintln!("Detected enum: {:?}", enum_name);

                        // Directly reference the enums instead of finding them in the AST
                        let enum_variants = get_enum_variants(&enum_name.to_string());

                        if let Some(variants) = enum_variants {
                            for variant in variants {
                                let method_name = format_ident!("{}", variant.to_lowercase());
                                let variant_ident = format_ident!("{}", variant);

                                methods.push(quote! {
                                    pub fn #method_name(mut self) -> Self {
                                        self.#ident = Some(#enum_name::#variant_ident);
                                        self
                                    }
                                });
                            }
                        } else {
                            eprintln!("Enum definition not found for {:?}", enum_name);
                        }
                    } else {
                        eprintln!("Field type is not a recognized path: {:?}", ty);
                    }
                } else {
                    eprintln!("Field without an identifier");
                }
            }
        } else {
            eprintln!("Struct does not have named fields");
        }
    } else {
        eprintln!("Not a struct: {:?}", input.data);
    }

    let expanded = quote! {
        impl #name {
            pub fn new() -> Self {
                Self {
                    object: None,
                    subject: None,
                    action: None,
                    permission: None,
                }
            }

            #(#methods)*

            pub fn finish(self) -> String {
                format!(
                    "permission::{:?}::{:?}::{:?}::{:?}",
                    self.object.unwrap(),
                    self.subject.unwrap(),
                    self.action.unwrap(),
                    self.permission.unwrap()
                ).to_lowercase().replace("::", "::")
            }
        }
    };

    TokenStream::from(expanded)
}

fn get_enum_variants(enum_name: &str) -> Option<Vec<&'static str>> {
    match enum_name {
        "Subject" => Some(vec!["User", "Admin", "Guest", "Moderator"]),
        "Object" => Some(vec!["Auth", "Role", "Resource", "Policy"]),
        "Action" => Some(vec![
            "Create", "Read", "Update", "Delete", "List", "Execute",
        ]),
        "Permission" => Some(vec!["Allow", "Deny", "Conditional"]),
        _ => None,
    }
}

The problem here is that I cannot find a way to infer the enum values and must hardcode them in my library.

How I can get rid of the hardcoded lib.rs?

9

After i did try and error, I’m following @Jmb comment, which turn out working really well:

// lib.rs

extern crate proc_macro;
extern crate proc_macro2;
extern crate quote;
extern crate syn;

use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use quote::ToTokens;
use syn::{parse_macro_input, Attribute, DeriveInput, Ident};

#[proc_macro_derive(RbacEnum, attributes(enum_field))]
pub fn rbac_enum_derive(input: TokenStream) -> TokenStream {
  let input = parse_macro_input!(input as DeriveInput);
  let name = &input.ident;

  // Extract the enum variants
  let data = match input.data {
    syn::Data::Enum(data) => data,
    _ => {
      return TokenStream::from(quote! {
          compile_error!("#[derive(RbacEnum)] can only be used with enums");
      })
    }
  };

  let field_name = match extract_field_name(&input.attrs) {
    Ok(field) => field,
    Err(err) => {
      return TokenStream::from(quote! {
          compile_error!(#err);
      })
    }
  };

  let mut methods = Vec::new();
  for variant in data.variants.iter() {
    let variant_name = &variant.ident;
    let method_name = Ident::new(
      &variant_name.to_string().to_lowercase(),
      variant_name.span(),
    );

    methods.push(quote! {
        impl #name {
            pub fn #method_name() -> Self {
                #name::#variant_name
            }
        }

        impl PermissionBuilder {
            pub fn #method_name(mut self) -> Self {
                self.#field_name = Some(#name::#variant_name);
                self
            }
        }
    });
  }

  let expanded = quote! {
      #(#methods)*
  };

  TokenStream::from(expanded)
}

fn extract_field_name(attrs: &[Attribute]) -> Result<Ident, String> {
  for attr in attrs {
    if attr.path.is_ident("enum_field") {
      if let Ok(meta) = attr.parse_meta() {
        if let syn::Meta::List(list) = meta {
          if let Some(syn::NestedMeta::Meta(syn::Meta::Path(path))) =
            list.nested.first()
          {
            let path_string = path.to_token_stream().to_string();
            let parts: Vec<&str> = path_string.split("::").collect();
            if parts.len() == 2 {
              let field_name = parts[1].trim();
              if !field_name.is_empty() {
                return Ok(Ident::new(field_name, Span::call_site()));
              }
            }
          }
        }
      }
    }
  }
  Err("Missing or invalid enum_field attribute".to_string())
}

the lib.rs now can infer the enums value and expand the methods using impl from #[enum_field(…)]

// main.rs

use ::enum_display::RbacEnum;

#[derive(Debug)]
pub struct PermissionBuilder {
  object: Option<Object>,
  subject: Option<Subject>,
  action: Option<Action>,
  permission: Option<Permission>,
}
impl PermissionBuilder {
  pub fn new() -> Self {
    Self {
      object: None,
      subject: None,
      action: None,
      permission: None,
    }
  }

  pub fn finish(self) -> String {
    format!(
      "permission::{:?}::{:?}::{:?}::{:?}",
      self.object.unwrap(),
      self.subject.unwrap(),
      self.action.unwrap(),
      self.permission.unwrap()
    )
    .to_lowercase()
    .replace("::", "::")
  }
}

#[derive(Debug, RbacEnum)]
#[enum_field(PermissionBuilder::subject)]
pub enum Subject {
  User,
  Admin,
  Guest,
  Moderator,
}

#[derive(Debug, RbacEnum)]
#[enum_field(PermissionBuilder::object)]
pub enum Object {
  Auth,
  Role,
  Resource,
  Policy,
}

#[derive(Debug, RbacEnum)]
#[enum_field(PermissionBuilder::action)]
pub enum Action {
  Create,
  Read,
  Update,
  Delete,
  List,
  Execute,
}

#[derive(Debug, RbacEnum)]
#[enum_field(PermissionBuilder::permission)]
pub enum Permission {
  Allow,
  Deny,
  Conditional,
}

#[cfg(test)]
mod tests {
  use super::*;

  #[test]
  fn test_permission() {
    let result = PermissionBuilder::new()
      .auth()
      .moderator() // New method from added enum variant
      .execute() // New method from added enum variant
      .conditional() // New method from added enum variant
      .finish();

    println!("perm: {:?}", result); // print: perm: "permission::auth::moderator::execute::conditional"
  }
}

to use it we can call the proc_macro RbacEnum derive with enum_field attributes.
which will target the struct PermissionBuilder because of enum_field attributes.

Thanks again @Jmb for leading the way, and @cafce25 for making me have faith in this experiments!

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật