Skip to main content

appsync_operation

Attribute Macro appsync_operation 

Source
#[appsync_operation]
Expand description

Marks an async function as an AWS AppSync resolver operation, binding it to a specific Query, Mutation or Subscription operation defined in the GraphQL schema.

The marked function must match the signature of the GraphQL operation, with parameters and return type matching what is defined in the schema. The function will be wired up to handle requests for that operation through the AWS AppSync Direct Lambda resolver.

§Important

This macro can only be used in a crate where make_operation! (or make_appsync!) has been invoked at the root level (typically in main.rs). The code generated by this macro depends on types and implementations that are created by make_operation! (or make_appsync!, which calls it internally). The legacy [appsync_lambda_main!] macro (behind the compat feature) also works.

§Example Usage

use lambda_appsync::{appsync_operation, AppsyncError};

// Your types are declared at the crate level by the make_appsync! macro
use crate::Player;

// Execute when a 'players' query is received
#[appsync_operation(query(players))]
async fn get_players() -> Result<Vec<Player>, AppsyncError> {
    // Implement resolver logic
    Ok(dynamodb_get_players().await?)
}

// Handle a 'createPlayer' mutation
#[appsync_operation(mutation(createPlayer))]
async fn create_player(name: String) -> Result<Player, AppsyncError> {
    Ok(dynamodb_create_player(name).await?)
}

§Using the AppSync event

You may need to explore the AppsyncEvent received by the lambda in your operation handler. You can make it available by adding the with_appsync_event flag and adding a reference to it in your operation handler signature (must be the last argument), like so:

use lambda_appsync::{appsync_operation, AppsyncError, AppsyncEvent, AppsyncIdentity};

// Your types are declared at the crate level by the make_appsync! macro
use crate::{Operation, Player};

// Use the AppsyncEvent
#[appsync_operation(mutation(createPlayer), with_appsync_event)]
async fn create_player(name: String, event: &AppsyncEvent<Operation>) -> Result<Player, AppsyncError> {
    // Example: extract the cognito user ID
    let user_id = if let AppsyncIdentity::Cognito(cognito_id) = &event.identity {
        cognito_id.sub.clone()
    } else {
        return Err(AppsyncError::new("Unauthorized", "Must be Cognito authenticated"))
    };
    Ok(dynamodb_create_player(name).await?)
}

Note that the args field of the AppsyncEvent will always contain Null at this stage because its initial content is taken to extract the argument values for the operation.

§Original function preservation

By default the appsync_operation macro preserves your original function in addition to generating the impl Operation method. This means you can call the function directly elsewhere in your code:


#[appsync_operation(query(players))]
async fn fetch_players() -> Result<Vec<Player>, AppsyncError> {
    Ok(dynamodb_get_players().await?)
}
async fn other_stuff() {
    // fetch_players() is still available as a regular function
    fetch_players().await;
} 

If you want the original function to be removed (its body is inlined into the generated impl Operation method), use the inline_and_remove flag:


#[appsync_operation(query(players), inline_and_remove)]
async fn get_players() -> Result<Vec<Player>, AppsyncError> {
    Ok(vec![])
}

async fn other_stuff() {
    // get_players() is NOT available anymore.
    get_players().await;
} 

§keep_original_function_name (compat only)

When the compat feature is enabled, the old default behavior is restored: the original function is inlined and removed by default. In that mode, the keep_original_function_name flag is available to explicitly preserve the function.

§Using enhanced subscription filters


// (Optional) Use an enhanced subscription filter for onCreatePlayer
use lambda_appsync::{appsync_operation, AppsyncError};
use lambda_appsync::subscription_filters::{FilterGroup, Filter, FieldPath};

#[appsync_operation(subscription(onCreatePlayer))]
async fn on_create_player(name: String) -> Result<Option<FilterGroup>, AppsyncError> {
    Ok(Some(FilterGroup::from([
        Filter::from([
            FieldPath::new("name")?.contains(name)
        ])
    ])))
}

When using a single FieldPath you can turn it directly into a FilterGroup. The following code is equivalent to the one above:


#[appsync_operation(subscription(onCreatePlayer))]
async fn on_create_player(name: String) -> Result<Option<FilterGroup>, AppsyncError> {
    Ok(Some(FieldPath::new("name")?.contains(name).into()))
}

§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 the following:

#if($context.result.data)
$extensions.setSubscriptionFilter($context.result.data)
#end
null