lambda-appsync-proc 0.10.0

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