Skip to main content

make_operation

Macro make_operation 

Source
make_operation!() { /* proc-macro */ }
Expand description

Generates the Operation enum and its dispatch logic from a GraphQL schema file.

§Usage

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:


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


#[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 appsync_operation attribute macro on your handler functions.

§The execute method


impl Operation {
    async fn execute(self, event: AppsyncEvent<Self>) -> AppsyncResponse { 
        /* ... */ 
    }
}

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)

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");

§Using type_module for cross-module types

use lambda_appsync::make_operation;

// Types are in a separate module
make_operation!(
    "schema.graphql",
    type_module = crate::types,
);

§Override operation return types and argument types

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,
);

§Disable error logging

use lambda_appsync::{make_types, make_operation};

make_types!("schema.graphql");

make_operation!(
    "schema.graphql",
    error_logging = false,
);

§With name overrides

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,
);

§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 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.