| Crates.io | easy-macros-attributes |
| lib.rs | easy-macros-attributes |
| version | 0.1.1 |
| created_at | 2025-11-15 19:29:31.973409+00 |
| updated_at | 2025-11-17 06:23:42.553833+00 |
| description | Easy Macros support library |
| homepage | https://github.com/LimitLost/easy-macros |
| repository | https://github.com/LimitLost/easy-macros |
| max_upload_size | |
| id | 1934684 |
| size | 105,673 |
Use the parent crate instead.
This crate provides procedural macros for working with Rust attributes. It enables pattern matching, extraction, and filtering based on attributes in your code, with support for unknown placeholders and flexible matching patterns.
has_attributes! - Check if an item has all specified attributesget_attributes! - Extract dynamic values from attributes using __unknown__ placeholdersfields_with_attributes! - Filter struct/enum fields by their attributesfields_get_attributes! - Extract dynamic values from field attributesAttrWithUnknown - Advanced attribute parsing with unknown placeholder supporthas_attributes!use syn::parse_quote;
// Check for a single attribute
let input: syn::ItemStruct = parse_quote! {
#[derive(Debug)]
#[serde(rename_all = "camelCase")]
struct User {
name: String,
}
};
let has_debug = has_attributes!(input, #[derive(Debug)]);
assert!(has_debug);
// Check for multiple attributes (all must be present)
let has_both = has_attributes!(
input,
#[derive(Debug)] #[serde(rename_all = "camelCase")]
);
assert!(has_both);
// This returns false since #[derive(Clone)] is not present
let has_clone = has_attributes!(input, #[derive(Clone)]);
assert!(!has_clone);
get_attributes! and __unknown__The __unknown__ placeholder allows you to extract dynamic parts from attributes:
use syn::parse_quote;
let input: syn::ItemStruct = parse_quote! {
#[serde(rename = "custom_name")]
#[serde(rename = "other_name")]
struct User {
name: String,
}
};
// Extract rename values
let renames: Vec<proc_macro2::TokenStream> = get_attributes!(
input,
#[serde(rename = __unknown__)]
);
assert_eq!(renames.len(), 2);
assert_eq!(renames[0].to_string(), "\"custom_name\"");
assert_eq!(renames[1].to_string(), "\"other_name\"");
Ok(())
use syn::parse_quote;
let input: syn::ItemStruct = parse_quote! {
#[test_case_one]
#[test_case_two]
#[test_case_foo]
#[other_attr]
struct TestSuite;
};
// Extract the suffix after "test_case_"
let test_cases: Vec<proc_macro2::TokenStream> = get_attributes!(
input,
#[test_case___unknown__]
);
assert_eq!(test_cases.len(), 3);
assert_eq!(test_cases[0].to_string(), "one");
assert_eq!(test_cases[1].to_string(), "two");
assert_eq!(test_cases[2].to_string(), "foo");
Ok(())
fields_with_attributes!Filter struct fields based on their attributes:
use syn::parse_quote;
let input: syn::ItemStruct = parse_quote! {
struct User {
#[serde(skip)]
id: u64,
#[validate]
#[serde(rename = "user_name")]
name: String,
#[validate]
email: String,
created_at: String,
}
};
// Get fields with validation attributes
let validated_fields: Vec<(usize, syn::Field)> = fields_with_attributes!(
input,
#[validate]
)
.collect();
assert_eq!(validated_fields.len(), 2); // name and email fields
assert_eq!(validated_fields[0].0, 1); // name is at index 1
assert_eq!(validated_fields[1].0, 2);
use syn::parse_quote;
let input: syn::ItemStruct = parse_quote! {
struct User {
#[serde(skip)]
id: u64,
#[validate]
#[serde(rename = "user_name")]
name: String,
#[validate]
email: String,
}
};
// Get fields that have BOTH validate AND the exact serde attribute
let validated_serde_fields: Vec<(usize, syn::Field)> = fields_with_attributes!(
input,
#[validate] #[serde(rename = "user_name")]
)
.collect();
assert_eq!(validated_serde_fields.len(), 1);
Note: Exact matching means #[serde(rename = "user_name", skip_serializing_if = "Option::is_none")]
won't match #[serde(rename = "user_name")] because it has additional content.
fields_get_attributes!Extract dynamic values from field attributes:
use syn::parse_quote;
let input: syn::ItemStruct = parse_quote! {
struct ApiEndpoints {
#[route(GET, "/users")]
get_users: String,
#[route(POST, "/users")]
create_user: String,
#[route(GET, "/users/{id}")]
get_user: String,
#[route(DELETE, "/users/{id}")]
delete_user: String,
#[other_attr]
non_route_field: String,
}
};
// Extract HTTP methods for all route fields
let methods: Vec<(usize, syn::Field, Vec<proc_macro2::TokenStream>)> =
fields_get_attributes!(input, #[route(__unknown__, "/users")]);
assert_eq!(methods.len(), 2); // get_users and create_user
assert_eq!(methods[0].2[0].to_string(), "GET"); // get_users method
assert_eq!(methods[1].2[0].to_string(), "POST"); // create_user method
Ok(())
use syn::parse_quote;
let input: syn::ItemStruct = parse_quote! {
struct UserTable {
#[column(id, primary_key)]
#[column(id, auto_increment)]
id: i32,
#[column(varchar, length = 255)]
#[unique]
email: String,
#[column(varchar, length = 100)]
#[nullable]
name: Option<String>,
#[column(timestamp)]
created_at: String,
}
};
// Extract column types
let column_types: Vec<(usize, syn::Field, Vec<proc_macro2::TokenStream>)> =
fields_get_attributes!(input, #[column(__unknown__, length = 255)]);
assert_eq!(column_types.len(), 1); // only email field
assert_eq!(column_types[0].2[0].to_string(), "varchar");
Ok(())
use syn::parse_quote;
// Docify has trouble parsing parse_quote! with attributes inside,
// so we use a string literal workaround
let input: syn::ItemStruct = syn::parse_str(
r#"
#[config(database(url = "postgres://localhost"))]
#[config(redis(url = "redis://localhost"))]
struct AppConfig;
"#,
)?;
// Extract database URL
let db_urls: Vec<proc_macro2::TokenStream> = get_attributes!(
input,
#[config(database(url = __unknown__))]
);
assert_eq!(db_urls[0].to_string(), "\"postgres:
use syn::parse_quote;
// **KEY CONCEPT**: A single field can have MULTIPLE matching attributes!
// Each matching attribute on the same field adds to the Vec<TokenStream>
let input: syn::ItemStruct = parse_quote! {
struct Multi {
#[tag(v1)] // ← All three of these match #[tag(__unknown__)]
#[tag(v2)] // ←
#[tag(v3)] // ←
field: String,
}
};
let versions: Vec<(usize, syn::Field, Vec<proc_macro2::TokenStream>)> =
fields_get_attributes!(input, #[tag(__unknown__)]);
assert_eq!(versions.len(), 1); // ONE field in results
assert_eq!(versions[0].2.len(), 3); // THREE extracted values from that field
assert_eq!(versions[0].2[0].to_string(), "v1");
assert_eq!(versions[0].2[1].to_string(), "v2");
assert_eq!(versions[0].2[2].to_string(), "v3");
Ok(())
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
use easy_macros_attributes::{fields_with_attributes, fields_get_attributes};
#[proc_macro_derive(ApiEndpoints)]
pub fn derive_api_endpoints(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
// Get all fields with route attributes, extracting HTTP methods
let routes: Vec<(usize, syn::Field, Vec<proc_macro2::TokenStream>)> =
fields_get_attributes!(input, #[route(__unknown__)]);
let route_implementations = routes.iter().map(|(_, field, methods)| {
let field_name = &field.ident;
// Note: A field might have multiple route attributes with different methods
let method_impls = methods.iter().map(|method| {
quote! {
pub fn #field_name() -> Route {
Route::new(stringify!(#method), "/path")
}
}
});
quote! {
#(#method_impls)*
}
});
quote! {
impl ApiEndpoints for YourStruct {
#(#route_implementations)*
}
}.into()
}
__unknown__ PlaceholdersThe __unknown__ placeholder is a powerful feature that allows pattern matching in attributes:
__unknown__ is allowed per attribute patternprefix___unknown___suffix)proc_macro2::TokenStream// Method extraction
#[route(__unknown__, "/path")] // Captures HTTP method
// Path parameter extraction
#[route(GET, __unknown__)] // Captures full path
// Partial identifier matching
#[test___unknown__] // Captures suffix of test attributes
// Value extraction from key-value pairs
#[config(key = __unknown__)] // Captures configuration values
// Nested structure matching
#[outer(inner(__unknown__))] // Captures inner content
All macros return empty results or compile errors with detailed messages:
use syn::parse_quote;
let input: syn::ItemStruct = parse_quote! {
#[derive(Debug)]
struct User;
};
// No #[route(...)] attributes exist, so this returns empty
let no_routes: Vec<proc_macro2::TokenStream> = get_attributes!(
input,
#[route(__unknown__)]
);
assert_eq!(no_routes.len(), 0);
Ok(())
use syn::parse_quote;
// Missing the required #[derive(Debug)] attribute
let input_no_debug: syn::ItemStruct = parse_quote! {
#[api_version(v1)]
struct User;
};
// Without derive(Debug), this returns empty vec![]
let no_versions: Vec<proc_macro2::TokenStream> = get_attributes!(
input_no_debug,
#[derive(Debug)] #[api_version(__unknown__)]
);
assert_eq!(no_versions.len(), 0); // Empty because derive(Debug) is missing
Ok(())
For more error handling examples, see the documentation.
This crate is part of the larger Easy Macros ecosystem and integrates seamlessly with other components:
context!TokensBuilderNote: All examples in this README are tested as part of the test suite. See src/examples.rs for the full, runnable code.