mae_macros
Procedural macros for Mae-Technologies services.
For development rules, see DEVELOPMENT.md.
Macros
#[run_app]— Actix-Web server setup wrapper#[schema]— Postgres schema binding with standard audit columns#[schema_root]— Likeschemabut withoutsys_clientFK#[derive(MaeRepo)]— SQL helper type generation#[mae_test]— Async journey test harness
#[run_app]
Rewrites a single-expression function body into a complete async fn run<Context>(…) Actix-Web server setup.
What it does
Place #[run_app] on a function containing exactly one statement — typically a route configuration call. The macro expands it into a full server function that:
- Creates a Redis session store via
app::redis_session - Attaches
TracingLogger, session middleware, andweb::Dataextractors forPgPool,ApplicationBaseUrl,HmacSecret, and the custom context type - Binds the HTTP server to a
TcpListenerand returns aResult<Server, anyhow::Error>
When to use it
Use #[run_app] in your service's main.rs or startup.rs to replace boilerplate Actix-Web server wiring. The annotated function's body becomes the .service(…) / .configure(…) call chain appended to the generated app.
Example
use run_app;
// Expands to:
// async fn run<Context: Clone + Send + 'static>(
// listener: TcpListener, db_pool: PgPool, base_url: String,
// hmac_secret: SecretString, redis_uri: SecretString, custom_context: Context,
// ) -> Result<Server, anyhow::Error> { … }
#[schema]
Binds a struct to a Postgres schema and injects standard Mae repository columns.
What it does
#[schema(CtxType, "schema_name")] transforms the annotated struct into a full Mae repository by injecting standard audit columns (id, sys_client, status, comment, tags, sys_detail, created_by, updated_by, created_at, updated_at) and deriving MaeRepo, sqlx::FromRow, serde::Serialize, serde::Deserialize, Debug, and Clone. It also implements mae::repo::__private__::Build with the given schema name.
When to use it
Use #[schema] on every domain repository struct in Mae services. Define only the domain-specific fields; all standard columns are injected automatically.
Example
use schema;
// Expands to a struct with all standard Mae columns plus `username` and `email`.
// Also generates InsertRow, UpdateRow, Field, PatchField in the same module.
#[schema_root]
Like #[schema] but omits the auto-injected sys_client foreign-key column.
What it does
Identical to #[schema] in every respect, except the generated struct does not include the sys_client: i32 field. All other standard audit columns are still injected.
When to use it
Use #[schema_root] for the sys_client table itself (or any root entity that has no foreign-key back to sys_client).
Example
use schema_root;
// Like #[schema] but without `sys_client: i32`.
#[derive(MaeRepo)]
Generates InsertRow, UpdateRow, Field, and PatchField types for a repository struct.
What it does
For each named field in the struct, MaeRepo emits:
InsertRow— plain struct of all non-#[locked], non-#[update_only]fields (used for SQLINSERT)UpdateRow— struct of all non-#[locked], non-#[insert_only]fields wrapped inOption<T>(used for SQLUPDATE;Nonefields are omitted)Field— enum with one variant per field plusAll;Displayimpl emits the column name or a comma-separated list forSELECTPatchField— typed enum carrying field values; convertible toFilterOp<Field>for partial-update WHERE clauses
Field attributes
| Attribute | Effect |
|---|---|
#[locked] |
Excluded from both InsertRow and UpdateRow (server-managed: id, timestamps) |
#[insert_only] |
Excluded from UpdateRow only (e.g. sys_client) |
#[update_only] |
Excluded from InsertRow only |
When to use it
MaeRepo is normally applied indirectly via #[schema] or #[schema_root]. Use it directly only when you need the generated types on a struct that doesn't follow the standard schema layout.
Example
use MaeRepo;
// Generates: InsertRow { customer_id, total }
// UpdateRow { customer_id: Option<i32>, total: Option<i64> }
// Field { All, id, customer_id, total }
// PatchField { customer_id(i32), total(i64) }
#[mae_test]
The standard test harness macro for async journey tests in Mae services.
What it does
#[mae_test] wraps an async fn test in a dedicated multi-threaded Tokio runtime and
enforces Mae test-hygiene rules at compile time:
- Forbids raw
.unwrap(),.expect(),assert!,assert_eq!, andassert_ne!in test bodies. Usemust::*helpers or?-propagation instead. - Drives the async test body synchronously so it integrates cleanly with the standard
#[test]harness (no#[tokio::test]needed). - Supports optional teardown (called after the test body, even on panic).
- Supports optional docker gating — skip the test at compile time unless
MAE_TESTCONTAINERS=1was set in the environment.
When to use #[mae_test] vs plain #[test]
| Scenario | Use |
|---|---|
| Async test that hits a real DB / HTTP endpoint | #[mae_test] |
Test that needs a TestContext / Must helpers |
#[mae_test] |
| Pure synchronous unit test with no I/O | #[test] |
| Quick in-process logic check | #[test] |
Rule of thumb: if your test is async, use #[mae_test].
Attribute arguments
| Argument | Effect |
|---|---|
| (none) | Basic multi-threaded async test |
docker |
Skip unless compiled with MAE_TESTCONTAINERS=1 cargo test |
teardown = <path> |
Call the given async fn after the test body, even on panic |
Arguments can be combined: #[mae_test(docker, teardown = crate::common::context::teardown)]
Examples
Good journey test — uses TestContext, Must, and ?
use mae_test;
use crate;
async
Good docker-gated test with teardown
use mae_test;
async
Bad journey test — will not compile
use mae_test;
async
Running docker tests
# Run all tests (non-docker tests only):
# Run all tests including docker-gated tests:
MAE_TESTCONTAINERS=1
# Run a specific test:
MAE_TESTCONTAINERS=1
Note:
MAE_TESTCONTAINERS=1is evaluated at compile time viaoption_env!. You must set it before / during thecargo testinvocation, not just at runtime.
#[ignore] compatibility
#[mae_test] preserves all other attributes on the function, including #[ignore]:
async
Run ignored tests with cargo test -- --ignored.
Re-export path
#[mae_test] is re-exported from the mae crate's testing module (see
mae::testing::mae_test). Services should import it via:
use mae_test;
or directly from mae_macros:
use mae_test;