ModKit Macros
Procedural macros for the ModKit framework, focused on generating strongly-typed gRPC clients with built-in SecurityCtx propagation.
Overview
ModKit provides two macros for generating gRPC client implementations:
#[generate_clients](RECOMMENDED) - Generate a gRPC client from an API trait definition with automatic SecurityCtx propagation#[grpc_client]- Generate a gRPC client with manual trait implementation
Quick Start
Recommended: Using generate_clients
The generate_clients macro is applied to your API trait and automatically generates a strongly-typed gRPC client with full method delegation and automatic SecurityCtx propagation:
use generate_clients;
use SecurityCtx;
This generates:
- The original
UsersApitrait (unchanged) UsersApiGrpcClient- wraps the tonic client with:- Automatic proto ↔ domain type conversions
- Automatic SecurityCtx propagation via gRPC metadata
- Standard transport stack (timeouts, retries, metrics, tracing)
The client fully implements the UsersApi trait with automatic method delegation.
Usage
// Connect to gRPC service
let client = connect.await?;
// SecurityCtx is automatically propagated via gRPC metadata
let ctx = for_user;
let user = client.get_user.await?;
// Or with custom configuration
let config = new
.with_connect_timeout
.with_rpc_timeout;
let client = connect_with_config.await?;
Alternative: Manual #[grpc_client]
If you need more control, you can use the grpc_client macro which generates the struct and helpers, but requires manual trait implementation:
use grpc_client;
;
// You must manually implement the trait
API Requirements
All API traits used with these macros must follow strict signature rules:
- Async methods: All trait methods must be
async - Standard receiver: Methods must use
&self(not&mut selforself) - Result return type: Methods must return
Result<T, E>with two type parameters - Parameter patterns: Methods must use one of two patterns:
Pattern 1: Secured API (with SecurityCtx)
For APIs that require authorization and access control:
async ;
The SecurityCtx parameter:
- Must be the first parameter after
&self - Must be an immutable reference (
&SecurityCtx, not&mut SecurityCtx) - The type must be named
SecurityCtx(frommodkit_security::SecurityCtxor aliased)
Pattern 2: Unsecured API (without SecurityCtx)
For system-internal APIs that don't require user authorization:
async ;
Valid Secured API Trait
use SecurityCtx;
Valid Unsecured API Trait
How SecurityCtx Propagates
For secured APIs (with ctx: &SecurityCtx), the generated gRPC client:
- Client-side: Serializes the
SecurityCtxinto gRPC metadata headers before sending the request - Server-side: The gRPC server extracts the
SecurityCtxfrom metadata and passes it to your service - Automatic: No manual header management required
Example generated code:
async
Invalid API Traits
// ❌ NOT async
;
// ❌ Multiple parameters after request
async ;
// ❌ Wrong parameter order (request before ctx)
async ;
// ❌ Mutable SecurityCtx reference
async ;
// ❌ Not returning Result
async ;
// ❌ Mutable receiver
async ;
Generated Code Structure
Given a trait UsersApi, the generate_clients macro generates:
// Original trait (unchanged)
// gRPC client struct
Transport Stack
All generated gRPC clients automatically use the standardized transport stack from modkit-transport-grpc, which provides:
- Configurable timeouts: Separate timeouts for connection establishment and individual RPC calls
- Retry logic: Automatic retry with exponential backoff for transient failures
- Metrics collection: Built-in Prometheus metrics for monitoring
- Distributed tracing: OpenTelemetry integration for request tracing
Default Configuration
- Connect timeout: 10 seconds
- RPC timeout: 30 seconds
- Max retries: 3 attempts
- Base backoff: 100ms
- Max backoff: 5 seconds
- Metrics and tracing: Enabled
Custom Configuration
use GrpcClientConfig;
let config = new
.with_connect_timeout
.with_rpc_timeout
.with_max_retries
.without_metrics;
let client = connect_with_config.await?;
Bypassing the Transport Stack
For testing or custom channel setup:
let channel = from_static
.connect
.await?;
let client = from_channel;
Type Conversions
The generated gRPC client requires:
- Each request type
ReqimplementsInto<ProtoReq>whereProtoReqis the corresponding protobuf message - Each response type
RespimplementsFrom<ProtoResp>whereProtoRespis the tonic response message
Example:
// Domain type
// Conversion to protobuf
// Response conversion
If these conversions are missing, the code will not compile (by design).
Best Practices
- Use
generate_clientswhen possible - It provides the most automated experience - Keep API traits focused - Each trait should represent a cohesive set of operations
- Use descriptive names - Client structs are named after your trait (e.g.,
UsersApi→UsersApiGrpcClient) - Implement type conversions - Ensure domain types convert to/from protobuf
- Leverage trait objects - Enables polymorphism via
Arc<dyn YourTrait>
Troubleshooting
"generate_clients requires grpc_client parameter"
Ensure you provide the grpc_client parameter:
"API methods must be async"
All trait methods must be marked async.
"API methods must have exactly one parameter (besides &self)"
If you have multiple parameters, wrap them in a request struct:
// Instead of this:
async ;
// Use this:
async ;
Missing Into/From implementations
Ensure you implement the required conversions between domain and proto types.