make_handlers!() { /* proc-macro */ }Expand description
Generates a Handlers trait and a DefaultHandlers struct for handling AWS AppSync Lambda events.
§Usage
make_handlers!();
// or with options:
make_handlers!(
batch = true, // enable/disable batch handling (default: true)
operation_type = my_crate::Operation, // custom path to the Operation type
);This macro is one of three composable macros for building an AppSync Direct Lambda resolver:
- make_types! — generates Rust types (structs and enums) from a GraphQL schema
- make_operation! — generates the
Operationenum and dispatch logic make_handlers!— generates the Lambda handler trait and default implementation
make_appsync! is a convenience macro that combines all three.
Unlike make_types! and make_operation!, this macro does not take a schema path as its first argument. It only generates the handler trait and struct.
§Prerequisites
The Operation type must be in scope when make_handlers! is invoked. This is typically
achieved by calling make_operation! (or make_appsync!) in the same scope beforehand.
Alternatively, use the operation_type parameter to specify a custom path.
§Parameters
§batch
batch = true(default): Generates bothappsync_handler(single event) andappsync_batch_handler(batch of events processed concurrently viatokio::spawn). Theservice_fnmethod deserializes the payload as aVecand callsappsync_batch_handler.batch = false: Only generatesappsync_handler. Theservice_fnmethod deserializes the payload as a single event and callsappsync_handler.
§operation_type
Specifies the type to use for the Operation enum. Defaults to Operation (expected to be
in scope).
Use this when the Operation type is defined in a different module or crate:
operation_type = crate::operations::Operationoperation_type = my_operations::Operation
§What Gets Generated
§The Handlers trait
trait Handlers {
// Handles a single deserialized AppSync event.
// Default: delegates to Operation::execute()
fn appsync_handler(
event: AppsyncEvent<Operation>
) -> impl Future<Output = AppsyncResponse> + Send + 'static;
// (Only when batch = true)
// Handles a batch of events concurrently using tokio::spawn.
// Default: spawns appsync_handler for each event
async fn appsync_batch_handler(
events: Vec<AppsyncEvent<Operation>>
) -> Vec<AppsyncResponse>;
// Top-level Lambda handler.
// Default: deserializes the raw JSON payload and calls the appropriate handler
async fn service_fn(
event: LambdaEvent<Value>
) -> Result<Vec<AppsyncResponse>, Error>;
// (returns Result<AppsyncResponse, Error> when batch = false)
}When the tracing feature is enabled, the default Handlers trait methods are instrumented for observability.
§The DefaultHandlers struct
struct DefaultHandlers;
impl Handlers for DefaultHandlers {}A zero-sized struct that uses all the default trait implementations. Use it directly if you don’t need to customize handler behavior.
§Customizing Handler Behavior
The key design principle: override specific trait methods while keeping defaults for the rest. The default implementations serve as a starting point — copy the default body and modify it.
This replaces the old hook, log_init, and event_logging options from [appsync_lambda_main!]:
| Old option | New approach |
|---|---|
hook = fn_name | Override appsync_handler to add pre/post processing |
event_logging = true | Add logging in your appsync_handler or service_fn override |
log_init = fn_name | Call your init function directly in main() |
| AWS SDK clients | Initialize clients directly in main() |
§Examples
§Basic usage with default handlers:
use lambda_appsync::{make_types, make_operation, make_handlers};
make_types!("schema.graphql");
make_operation!("schema.graphql");
make_handlers!();
#[tokio::main]
async fn main() -> Result<(), lambda_runtime::Error> {
lambda_runtime::run(
lambda_runtime::service_fn(DefaultHandlers::service_fn)
).await
}§Disable batch processing:
lambda_appsync::make_handlers!(batch = false);§Custom handler with authentication hook:
use lambda_appsync::{make_types, make_operation, make_handlers};
use lambda_appsync::{AppsyncEvent, AppsyncResponse, AppsyncIdentity};
make_types!("schema.graphql");
make_operation!("schema.graphql");
make_handlers!();
struct MyHandlers;
impl Handlers for MyHandlers {
fn appsync_handler(
event: AppsyncEvent<Operation>
) -> impl std::future::Future<Output = AppsyncResponse> + Send + 'static {
async move {
// Custom authentication check
if let AppsyncIdentity::ApiKey = &event.identity {
return AppsyncResponse::unauthorized();
}
// Delegate to the default operation dispatch
event.info.operation.execute(event).await
}
}
}
#[tokio::main]
async fn main() -> Result<(), lambda_runtime::Error> {
lambda_runtime::run(
lambda_runtime::service_fn(MyHandlers::service_fn)
).await
}§Using operation_type for cross-module Operation:
use lambda_appsync::make_handlers;
make_handlers!(
operation_type = crate::operations::Operation,
);§Important Notes
- This macro does not take a schema path. It only generates the handler trait and struct.
- The
Operationtype must be in scope (or specified viaoperation_type). It must have anexecutemethod, which is generated by make_operation!. - The generated
Handlerstrait is marked#[deny(dead_code)]— all methods must be used or you’ll get a compilation warning. - When
batch = true, the batch handler usestokio::spawnfor concurrent processing, which requires the handler future to beSend + 'static. This is whyappsync_handlerreturnsimpl Future<Output = AppsyncResponse> + Send + 'staticinstead of being a plainasync fnin batch mode.