6 releases (breaking)
new 0.5.1 | Apr 21, 2025 |
---|---|
0.5.0 | Apr 21, 2025 |
0.4.2 | Apr 19, 2025 |
0.3.0 | Apr 4, 2025 |
0.1.0 | Mar 30, 2025 |
#2341 in Procedural macros
594 downloads per month
Used in lambda-appsync
72KB
1.5K
SLoC
lambda-appsync
A Rust framework for implementing AWS AppSync Direct Lambda resolvers with complete type safety and validation.
The lambda-appsync crate provides procedural macros that convert GraphQL schemas into type-safe Rust code and types for AWS AppSync event Lambda integration. This allows you to focus on implementing resolver logic while the framework handles all AWS AppSync integration details.
Features
- ✨ Type-safe GraphQL schema conversion to Rust types
- 🚀 Full AWS Lambda runtime integration
- 🔒 Built-in validation of resolver function signatures
- 🔌 Easy AWS SDK client initialization
- 📦 Performance-optimized batching support
- 🛡️ Flexible request validation hooks (e.g. for advanced authentication flows)
Known limitations
The framework currently has limited support for certain AWS AppSync and GraphQL schema features:
- GraphQL unions are not supported and will be ignored by the macro
- GraphQL interfaces are not directly supported, though concrete types that implement interfaces will work correctly
We actively track user needs around these features. If your project requires union or interface support, please open a GitHub issue detailing your use case and expected implementation. Your feedback helps us prioritize future development work and determine the best way to implement these features in a type-safe manner.
Installation
Add this dependency to your Cargo.toml
:
[dependencies]
lambda-appsync = "0.4.2"
Quick Start
- Create your GraphQL schema file (e.g.
graphql/schema.gql
). When in a workspace context, all relative paths are assumed to be relative to the workspace root directory:
type Query {
players: [Player!]!
gameStatus: GameStatus!
}
type Player {
id: ID!
name: String!
team: Team!
}
enum Team {
RUST
PYTHON
JS
}
enum GameStatus {
STARTED
STOPPED
}
- Configure the Lambda runtime with AWS SDK clients in
main.rs
:
use lambda_appsync::appsync_lambda_main;
// Generate types and runtime setup from schema
appsync_lambda_main!(
"graphql/schema.gql",
// Initialize DynamoDB client if needed
dynamodb() -> aws_sdk_dynamodb::Client,
);
- Implement resolver functions for GraphQL operations in your crate:
use lambda_appsync::{appsync_operation, AppsyncError};
use lambda_appsync::subscription_filters::{FieldPath, FilterGroup};
// The appsync_lambda_main! macro will have created the
// types declared in schema.gql at the crate root
use crate::{Player, GameStatus};
#[appsync_operation(query(players))]
async fn get_players() -> Result<Vec<Player>, AppsyncError> {
let client = dynamodb();
// Implement resolver logic
todo!()
}
#[appsync_operation(query(gameStatus))]
async fn get_game_status() -> Result<GameStatus, AppsyncError> {
let client = dynamodb();
// Implement resolver logic
todo!()
}
#[appsync_operation(subscription(onCreatePlayer))]
async fn on_create_player(name: String) -> Result<Option<FilterGroup>, AppsyncError> {
// Return a subscription filter to subscribe only
// to events where the player name contains the string `name`
Ok(Some(
FieldPath::new("name")?.contains(name).into()
))
}
The framework's macros verify function signatures match the GraphQL schema and automatically wire everything up to handle AWS AppSync requests.
Important Note
When using enhanced subscription filters (i.e., returning a FilterGroup from Subscribe operation handlers), you need to modify your Response mapping in AWS AppSync.
It must contain exactly the following:
#if($context.result.data)
$extensions.setSubscriptionFilter($context.result.data)
#end
null
Example project
Check out our complete sample project that demonstrates lambda-appsync in action! This full-featured demo implements a GraphQL API for a mini-game web application using AWS AppSync and Lambda, showcasing:
- 🎮 Real-world GraphQL schema
- 📊 DynamoDB integration
- 🏗️ Infrastructure as code with AWS CloudFormation
- 🚀 CI/CD pipeline configuration
Clone the repo to get started with a production-ready template that you can use as reference for your own projects. The demo includes detailed documentation and best practices for building serverless GraphQL APIs with Rust.
Additional Examples
Custom Type Overrides
Override generated Rust types for specific GraphQL fields:
appsync_lambda_main!(
"graphql/schema.gql",
// Override Player.id type to be String instead of ID on the Rust struct
field_type_override = Player.id: String
);
Preserving Original Function Names
Keep the original function name available while using it as an operation handler:
// Can still call fetch_user() directly
#[appsync_operation(query(getUser), keep_original_function_name)]
async fn fetch_user(id: ID) -> Result<User, AppsyncError> {
todo!()
}
Modular Type and Implementation Structure
For larger projects, share GraphQL types across multiple Lambda functions while keeping resolvers separate:
// In a shared library crate:
appsync_lambda_main!(
"graphql/schema.gql",
only_appsync_types = true,
);
// Then in each Lambda using this lib:
use shared_lib::*;
appsync_lambda_main!(
"graphql/schema.gql",
exclude_appsync_types = true,
dynamodb() -> aws_sdk_dynamodb::Client
);
This enables defining custom traits and methods on GraphQL types in one place while reusing them across multiple Lambda functions. The shared library contains type definitions, while each Lambda maintains its operation handlers and AWS SDK client initialization.
Error Handling
Combine multiple errors using the pipe operator:
let err = AppsyncError::new("ValidationError", "Invalid email")
| AppsyncError::new("DatabaseError", "User not found");
AWS SDK Error Support
Seamlessly handle AWS SDK errors with automatic conversion:
async fn store_item(item: Item, client: &aws_sdk_dynamodb::Client) -> Result<(), AppsyncError> {
// AWS SDK errors are automatically converted to AppsyncError
client.put_item()
.table_name("my-table")
.item("id", AttributeValue::S(item.id.to_string()))
.item("data", AttributeValue::S(item.data))
.send()
.await?;
Ok(())
}
Error types and messages are extracted from AWS SDK error metadata, allowing use of the ?
operator with AWS SDK calls for properly formatted AppSync response errors.
Minimum Supported Rust Version (MSRV)
This crate requires Rust version 1.81.0 or later.
Contributing
We welcome contributions! Here's how you can help:
- Report bugs by opening an issue
- Suggest new features or improvements
- Submit pull requests for bug fixes or features
- Improve documentation
- Share example code and use cases
Please review our contributing guidelines before submitting pull requests.
Development
This project uses git hooks to ensure code quality. The hooks are automatically installed when you enter the development shell using nix develop
or direnv allow
.
Git Hooks
The following checks are run before each commit:
- Code formatting (cargo fmt)
- Linting (clippy)
- Tests
If any of these checks fail, the commit will be aborted. Fix the issues and try committing again.
To manually install the hooks:
./scripts/install-hooks.sh
Issues
Before reporting issues, please check:
- Existing issues to avoid duplicates
- The documentation to ensure it's not a usage error
- The FAQ for common problems
When opening a new issue, include:
- A clear title and description
- Steps to reproduce bugs
- Expected vs actual behavior
- Code samples if relevant
License
This project is licensed under the MIT License - see the LICENSE file for details.
Authors
- Jérémie RODON (@JeremieRodon)
If you find this crate useful, please star the repository and share your feedback!
Dependencies
~1–1.5MB
~34K SLoC