lambda-appsync-proc 0.10.0

Procedural macros for the lambda-appsync type-safe AWS AppSync resolver framework
Documentation
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

```rust,no_run
# lambda_appsync::make_appsync!("schema.graphql");
# mod sub {
# async fn dynamodb_get_players() -> Result<Vec<Player>, AppsyncError> {
#    todo!()
# }
# async fn dynamodb_create_player(name: String) -> Result<Player, AppsyncError> {
#    todo!()
# }
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?)
}
# }
# fn main() {}
```

## Using the AppSync event

You may need to explore the [AppsyncEvent](struct.AppsyncEvent.html) 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:
```rust,no_run
# lambda_appsync::make_appsync!("schema.graphql");
# mod sub {
# async fn dynamodb_create_player(name: String) -> Result<Player, AppsyncError> {
#    todo!()
# }
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?)
}
# }
# fn main() {}
```

Note that the `args` field of the [AppsyncEvent](struct.AppsyncEvent.html) will always contain
[Null](https://docs.rs/serde_json/latest/serde_json/enum.Value.html#variant.Null) at this stage because its initial content is taken to extract
the argument values for the operation.

## Original function preservation

By default the [macro@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:

```rust,no_run
# lambda_appsync::make_appsync!("schema.graphql");
# async fn dynamodb_get_players() -> Result<Vec<Player>, AppsyncError> {
#    todo!()
# }
# // Needed because compat is enabled
# async fn fetch_players() {}
# use lambda_appsync::{appsync_operation, AppsyncError};

#[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;
} 
# fn main() {}
```

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:

```rust,compile_fail
# lambda_appsync::make_appsync!("schema.graphql");
# use lambda_appsync::{appsync_operation, AppsyncError};

#[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;
} 

# fn main() {}
```

### `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

```rust,no_run
# lambda_appsync::make_appsync!("schema.graphql");

// (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)
        ])
    ])))
}
# fn main() {}
```

When using a single [FieldPath](subscription_filters/struct.FieldPath.html) you can turn it directly into a [FilterGroup](subscription_filters/struct.FilterGroup.html).
The following code is equivalent to the one above:
```rust,no_run
# lambda_appsync::make_appsync!("schema.graphql");
# 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(FieldPath::new("name")?.contains(name).into()))
}
# fn main() {}
```

### Important Note

When using enhanced subscription filters (i.e., returning a [FilterGroup](subscription_filters/struct.FilterGroup.html)
from Subscribe operation handlers), you need to modify your ***Response*** mapping in AWS AppSync.
It must contain the following:

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