htmxology-macros
Procedural macros for the htmxology framework.
Architecture Guidelines
This crate follows a consistent organizational pattern for all derive macros to maintain clarity and scalability.
Directory Structure
Each derive macro should be organized in its own subdirectory with the following structure:
src/
├── macro_name/
│ ├── mod.rs # Main derive function and exports
│ ├── config.rs # Data structures for parsed configuration
│ ├── codegen.rs # Code generation helpers
│ ├── snapshots/ # Insta snapshot test files
│ └── *.rs # Other supporting modules as needed
├── utils.rs # Shared utilities across all macros
└── lib.rs # Crate entry point
Module Responsibilities
mod.rs - Main Entry Point
- Contains the public
derive()function - High-level orchestration of parsing and code generation
- Should be relatively thin (~300-500 lines)
- Contains snapshot tests
Example structure:
config.rs - Configuration Structures
- Purpose: Separate parsing from code generation
- Contains intermediate data structures representing parsed configuration
- Implements parsing logic from
syntypes into config types - Should be focused on what the configuration is, not how to generate code
Key principles:
- Use descriptive struct/enum names (e.g.,
VariantConfig,FieldConfig,FieldRole) - Include
Fromimplementations or parsing functions forsyntypes - Add validation logic here
- Keep structures
Clonefor flexibility - Avoid
TokenStream- this module should be pure data
Example:
codegen.rs - Code Generation Helpers
- Purpose: Reusable code generation functions
- Contains functions that generate
TokenStreamfrom config structures - Each function should be focused and composable
- Should handle both Named and Unnamed variants uniformly when possible
Key principles:
- Functions take
&Configtypes and returnTokenStreamorsyn::Result<TokenStream> - Use descriptive function names (e.g.,
generate_pattern,generate_url_format) - Document expected output with examples in docstrings
- Keep functions pure and testable
- Use helper functions to avoid duplication
Example:
/// Generates a match pattern for a variant.
///
/// # Example Output
///
/// ```ignore
/// Self::Variant { field1, field2 }
/// ```
snapshots/ - Test Snapshots
- One
.snapfile per test case - Named consistently:
{crate}__{module}__snapshot_tests__{test_name}.snap - Snapshots capture the generated code to prevent regressions
- Review snapshots carefully when they change - don't blindly accept
Shared Utilities (utils.rs)
Common functionality shared across all derive macros:
expect_enum()- Validates input is an enum (not struct/union)testing::test_derive()- Wrapper for snapshot tests
When adding new shared utilities, consider:
- Will this be used by multiple derive macros?
- Does it eliminate meaningful duplication?
- Is it generic enough to be reusable?
Testing Guidelines
All derive macros must have comprehensive snapshot tests:
- Coverage: Test all supported patterns and edge cases
- Naming: Use descriptive test names (e.g.,
unit_variant_get,named_body_param) - Organization: Keep tests in
mod snapshot_testswithinmod.rs - Verification: Run
cargo insta reviewafter changes to inspect diffs - Regression Prevention: Never delete snapshots unless removing functionality
Test structure:
Code Quality Standards
Before committing changes:
- Format:
cargo fmt --all - Test:
cargo test -p htmxology-macros(all tests must pass) - Lint:
cargo clippy -p htmxology-macros(zero warnings) - Review Snapshots:
cargo insta review(verify changes are correct)
Migration Guide
When refactoring an existing derive macro to follow this pattern:
- Create subdirectory:
src/macro_name/ - Move main file to
src/macro_name/mod.rs - Extract config structures to
config.rs - Extract code generation to
codegen.rs - Move (don't regenerate) snapshots to
snapshots/subdirectory - Update imports in
lib.rs - Verify all tests still pass with
cargo test - Verify snapshots are identical with
git diff
Critical: When moving snapshots, use git mv or ensure file contents are identical to prevent false regressions.
Benefits of This Pattern
- Separation of Concerns: Parsing vs. code generation
- Testability: Small, focused functions are easier to test
- Maintainability: Clear structure makes code easy to navigate
- Reusability: Helpers can be shared between variants
- Scalability: Easy to add new features without increasing complexity
- Documentation: Self-documenting through module organization
Examples
See src/route/ for a complete example following this pattern.