Generates the `Operation` enum and its dispatch logic from a GraphQL schema file.
# Usage
```text
make_operation!("path/to/schema.graphql");
// or with options:
make_operation!(
"path/to/schema.graphql",
type_module = crate::types, // module path to types from make_types!
type_override = Query.operation: CustomType, // override an operation return type
type_override = Query.operation.arg: CustomType, // override an operation argument type
name_override = TypeName: NewTypeName, // rename a type (must match make_types!)
error_logging = false, // disable error logging (feature: log)
);
```
This is one of three composable macros that together replace [appsync_lambda_main!]:
- [make_types!] — generates Rust types (structs and enums) from the schema
- `make_operation!` — generates the `Operation` enum, sub-enums (`QueryField`, `MutationField`,
`SubscriptionField`), argument extraction, default operation implementations, and the `execute`
dispatch method
- [make_handlers!] — generates the Lambda handler and runtime setup
[make_appsync!] is a convenience macro that combines all three.
# Schema Path Argument
The first argument to this macro must be a string literal containing the path to your GraphQL
schema file. The schema path can be:
- An absolute filesystem path (e.g. "/home/user/project/schema.graphql")
- A relative path, that will be relative to your crate's root directory (e.g. "schema.graphql",
"graphql/schema.gql")
- When in a workspace context, the relative path will be relative to the workspace root directory
# Important Prerequisite
The types referenced by operations (return types, argument types) must be in scope when
`make_operation!` is invoked. There are two ways to achieve this:
1. **Same scope**: Call [make_types!] (or [make_appsync!]) in the same scope before `make_operation!`
2. **Different module/crate**: Use the `type_module` parameter to specify the path to the module
containing the types generated by [make_types!]
# Options
- `type_module = path::to::types`: Prefix all custom type references with the given module path
- (feature: `log`) `error_logging = bool`: Log errors via `log::error!` before converting them
to an `AppsyncResponse` (default: `true`)
- `type_override` — see section below for details
- `name_override` — see section below for details
## Type Overrides
The `type_override` option allows overriding Rust types for operation return types and argument
types:
- Operation return types (Query/Mutation): `type_override = OpType.operation: CustomType`
- Operation arguments (Query/Mutation/Subscription): `type_override = OpType.operation.arg: CustomType`
These overrides are only for the Rust code and must be compatible for serialization/deserialization
purposes, i.e. you can use `String` for a GraphQL `ID` but you cannot use a `u32` for a GraphQL
`Float`.
## Name Overrides
The `name_override` option supports renaming various schema elements:
- Type/input/enum names: `name_override = TypeName: NewTypeName`
- Field names: `name_override = Type.field: new_field_name`
- Enum variants: `name_override = Enum.VARIANT: NewVariant`
When using `make_operation!` separately from [make_types!], you **must** repeat the same
`name_override` entries so that operation signatures reference the correct renamed types.
## type_module
Specifies the module path where the GraphQL types (generated by [make_types!]) are located.
When set, all custom type references in operation signatures will be prefixed with this path.
This is the alternative to bringing all types into scope with `use types::*;`.
# What Gets Generated
## Sub-enums for each operation kind
For each of Query, Mutation and Subscription defined in the schema, a field enum is generated:
```rust
# lambda_appsync::make_types!("schema.graphql");
# use serde::Deserialize;
#[derive(Debug, Clone, Copy, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum QueryField { Players, GameStatus, Player }
#[derive(Debug, Clone, Copy, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum MutationField { CreatePlayer, DeletePlayer, SetGameStatus }
#[derive(Debug, Clone, Copy, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum SubscriptionField { OnCreatePlayer, OnDeletePlayer, OnGameStatusChange }
```
## The Operation enum
```rust
# use serde::Deserialize;
# #[derive(Debug, Clone, Copy, Deserialize)]
# pub struct QueryField;
# #[derive(Debug, Clone, Copy, Deserialize)]
# pub struct MutationField;
# #[derive(Debug, Clone, Copy, Deserialize)]
# pub struct SubscriptionField;
#[derive(Debug, Clone, Copy, Deserialize)]
#[serde(tag = "parentTypeName", content = "fieldName")]
pub enum Operation {
Query(QueryField),
Mutation(MutationField),
Subscription(SubscriptionField),
}
```
## Default operation implementations
For each operation, a default async function is generated as a trait method:
- Queries and mutations return `Err(AppsyncError::new("Unimplemented", "..."))` by default
- Subscriptions return `Ok(None)` by default (no filter)
These defaults are overridden when you use the [macro@appsync_operation] attribute macro on your
handler functions.
## The execute method
```rust
# use lambda_appsync::{AppsyncEvent, AppsyncResponse};
# pub struct Operation;
impl Operation {
async fn execute(self, event: AppsyncEvent<Self>) -> AppsyncResponse {
/* ... */
# todo!()
}
}
```
This method dispatches to the appropriate operation handler, serializes the result, and (if
`error_logging` is enabled) logs errors via `log::error!`.
# Examples
## Basic usage (types in same scope)
```rust,no_run
# mod sub {
use lambda_appsync::{make_types, make_operation};
// Generate types first
make_types!("schema.graphql");
// Then generate operations (types are in scope)
make_operation!("schema.graphql");
# }
# fn main() {}
```
## Using type_module for cross-module types
```rust,no_run
# mod types {
# lambda_appsync::make_types!("schema.graphql");
# }
# mod sub {
use lambda_appsync::make_operation;
// Types are in a separate module
make_operation!(
"schema.graphql",
type_module = crate::types,
);
# }
# fn main() {}
```
## Override operation return types and argument types
```rust,no_run
# mod sub {
use lambda_appsync::{make_types, make_operation};
make_types!(
"schema.graphql",
type_override = Player.id: String,
);
make_operation!(
"schema.graphql",
// Override argument types to match the type overrides
type_override = Query.player.id: String,
type_override = Mutation.deletePlayer.id: String,
type_override = Subscription.onDeletePlayer.id: String,
);
# }
# fn main() {}
```
## Disable error logging
```rust,no_run
# mod sub {
use lambda_appsync::{make_types, make_operation};
make_types!("schema.graphql");
# #[cfg(feature = "log")]
make_operation!(
"schema.graphql",
error_logging = false,
);
# }
# fn main() {}
```
## With name overrides
```rust,no_run
# mod sub {
use lambda_appsync::{make_types, make_operation};
make_types!(
"schema.graphql",
name_override = Player: NewPlayer,
);
// MUST repeat the same name_override entries
make_operation!(
"schema.graphql",
name_override = Player: NewPlayer,
// MUST also override the operations return type
type_override = Query.players: NewPlayer,
type_override = Query.player: NewPlayer,
type_override = Mutation.createPlayer: NewPlayer,
type_override = Mutation.deletePlayer: NewPlayer,
);
# }
# fn main() {}
```
# Important
- This macro requires that the GraphQL types (structs and enums) are already in scope, either via
[make_types!] in the same scope or via `type_module`.
- The [macro@appsync_operation] attribute macro depends on being able to add impl Operation {...}
blocks. You must use `make_operation!` (or [make_appsync!]) in a parent module in order to use
`#[appsync_operation(...)]`.
- When using `type_override` or `name_override`, ensure consistency between [make_types!] and
`make_operation!` invocations — mismatched overrides will cause compilation errors.