Skip to content

Commit 6261e5b

Browse files
committed
Will squash and merge
1 parent 174e501 commit 6261e5b

9 files changed

Lines changed: 211 additions & 21 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2929
- **Breaking**: The crate no longer generates `main()`, log initialization, or AWS SDK client singletons. Users write their own `main()` and initialize these concerns directly. This is a deliberate design change: generating `main()` and instantiating SDK clients are application-level concerns that don't belong in a type-generation / operation-dispatch crate. The monolithic approach led to unsustainable option proliferation (every new logging framework or instrumentation pattern required another macro option and feature flag) and made it impossible to compose with external middleware such as Tower layers. See [PR #12](https://github.com/RustyServerless/lambda-appsync/pull/12) for the full rationale
3030
- **Breaking**: Default features are now empty. `env_logger` is no longer enabled by default. Add `features = ["env_logger"]` to restore the previous behavior
3131
- **Breaking**: The `hook`, `log_init`, `event_logging`, `only_appsync_types`, `exclude_appsync_types`, `only_appsync_operations`, `exclude_appsync_operations`, `only_lambda_handler`, `exclude_lambda_handler`, and AWS SDK client definition options are no longer available in the default API. They remain available through `appsync_lambda_main!` behind the `compat` feature
32+
- **Breaking**: `#[appsync_operation]` now **preserves the original function** by default (it used to inline the body and remove the function). A new `inline_and_remove` parameter is available when you want the old behavior — the function body is inlined into the generated `impl Operation` method and the original function is removed. The `keep_original_function_name` parameter is moved behind the `compat` feature. Enabling `compat` also restores the old default behavior (inline and remove)
3233
- Repository migrated to the [RustyServerless](https://github.com/RustyServerless) organization
3334

3435
### Deprecated

README.md

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -324,19 +324,39 @@ async fn create_player(
324324
}
325325
```
326326
327-
### Preserving Original Function Names
327+
### Original Function Preservation
328328

329-
Keep the original function name available while using it as an operation handler:
329+
By default, `#[appsync_operation]` preserves the original function alongside the generated `impl Operation` method. You can call it directly elsewhere in your code:
330330

331331
```rust
332-
#[appsync_operation(query(players), keep_original_function_name)]
332+
#[appsync_operation(query(players))]
333333
async fn fetch_players() -> Result<Vec<Player>, AppsyncError> {
334334
todo!()
335335
}
336336

337-
// Can still call fetch_players() directly elsewhere
337+
// fetch_players() is still available as a regular function
338+
```
339+
340+
If you want the function to be removed (its body is inlined into the generated method), use `inline_and_remove`. This can be handy when generating handlers with `macro_rules!`, where you don't want the function name to collide with itself:
341+
342+
```rust
343+
macro_rules! game_status_mut {
344+
($mut_name:ident, $status:path) => {
345+
#[appsync_operation(mutation($mut_name), inline_and_remove)]
346+
pub async fn _discarded() -> Result<GameStatus, AppsyncError> {
347+
dynamodb_set_game_status($status).await?;
348+
Ok($status)
349+
}
350+
};
351+
}
352+
353+
game_status_mut!(startGame, GameStatus::Started);
354+
game_status_mut!(stopGame, GameStatus::Stopped);
355+
game_status_mut!(resetGame, GameStatus::Reset);
338356
```
339357

358+
> **Note:** When the `compat` feature is enabled, the old default behavior is restored (inline and remove), and the `keep_original_function_name` parameter is available to opt back into preservation.
359+
340360
### AWS SDK Error Support
341361

342362
AWS SDK errors are automatically converted to `AppsyncError`, allowing use of the `?` operator:

lambda-appsync-proc/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,15 @@ proc-macro2 = { workspace = true }
2121
graphql-parser = { workspace = true }
2222

2323
[dev-dependencies]
24-
lambda-appsync = { path = "../lambda-appsync", default-features = false, features = ["log", "env_logger", "tracing", "compat"] }
24+
lambda-appsync = { path = "../lambda-appsync", default-features = false, features = ["compat", "log", "env_logger", "tracing"] }
2525
tracing = { workspace = true }
2626
aws-sdk-s3 = { workspace = true }
2727
aws-sdk-dynamodb = { workspace = true }
2828
serde = { workspace = true }
2929
trybuild = { workspace = true }
3030

3131
[features]
32-
default = ["log", "env_logger", "tracing", "compat"]
32+
default = ["compat", "log", "env_logger", "tracing"]
3333
compat = []
3434
log = []
3535
env_logger = []

lambda-appsync-proc/doc/appsync_operation.md

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -78,34 +78,58 @@ Note that the `args` field of the [AppsyncEvent](struct.AppsyncEvent.html) will
7878
[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
7979
the argument values for the operation.
8080

81-
## Preserve original function name
81+
## Original function preservation
82+
83+
By default the [macro@appsync_operation] macro preserves your original function in addition to
84+
generating the `impl Operation` method. This means you can call the function directly elsewhere
85+
in your code:
8286

83-
By default the [macro@appsync_operation] macro will discard your function's name but
84-
you can also keep it available by adding the `keep_original_function_name` flag:
8587
```rust,no_run
8688
# lambda_appsync::make_appsync!("schema.graphql");
87-
# mod sub {
88-
use lambda_appsync::{appsync_operation, AppsyncError};
89-
90-
// Your types are declared at the crate level by the make_appsync! macro
91-
use crate::Player;
92-
9389
# async fn dynamodb_get_players() -> Result<Vec<Player>, AppsyncError> {
9490
# todo!()
9591
# }
96-
// Keep the original function name available separately
97-
#[appsync_operation(query(players), keep_original_function_name)]
92+
# // Needed because compat is enabled
93+
# async fn fetch_players() {}
94+
# use lambda_appsync::{appsync_operation, AppsyncError};
95+
96+
#[appsync_operation(query(players))]
9897
async fn fetch_players() -> Result<Vec<Player>, AppsyncError> {
9998
Ok(dynamodb_get_players().await?)
10099
}
101100
async fn other_stuff() {
102-
// Can still call fetch_players() directly
101+
// fetch_players() is still available as a regular function
103102
fetch_players().await;
103+
}
104+
# fn main() {}
105+
```
106+
107+
If you want the original function to be removed (its body is inlined into the generated
108+
`impl Operation` method), use the `inline_and_remove` flag:
109+
110+
```rust,compile_fail
111+
# lambda_appsync::make_appsync!("schema.graphql");
112+
# use lambda_appsync::{appsync_operation, AppsyncError};
113+
114+
#[appsync_operation(query(players), inline_and_remove)]
115+
async fn get_players() -> Result<Vec<Player>, AppsyncError> {
116+
Ok(vec![])
104117
}
105-
# }
118+
119+
async fn other_stuff() {
120+
// get_players() is NOT available anymore.
121+
get_players().await;
122+
}
123+
106124
# fn main() {}
107125
```
108126

127+
### `keep_original_function_name` (compat only)
128+
129+
When the `compat` feature is enabled, the old default behavior is restored: the original function
130+
is inlined and removed by default. In that mode, the `keep_original_function_name` flag is available to
131+
explicitly preserve the function.
132+
109133
## Using enhanced subscription filters
110134

111135
```rust,no_run

lambda-appsync-proc/src/internal/appsync_operation/mod.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,22 @@ use super::common::{Name, OperationKind};
77

88
/// Optional flags accepted by the `appsync_operation` macro attribute.
99
enum ArgsOption {
10-
/// Preserve the original function name in addition to generating the operation method.
10+
/// Preserve the original function name in addition to generating the operation method
11+
#[cfg(feature = "compat")]
1112
KeepOriginalFunctionName,
1213
/// Pass the full `AppsyncEvent` as an extra argument to the handler.
1314
WithAppsyncEvent,
15+
/// Remove the function and inline its body inside the implemented method on Operation
16+
InlineAndRemove,
1417
}
1518
impl Parse for ArgsOption {
1619
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
1720
let ident = input.parse::<Ident>()?;
1821
match ident.to_string().as_str() {
22+
#[cfg(feature = "compat")]
1923
"keep_original_function_name" => Ok(Self::KeepOriginalFunctionName),
2024
"with_appsync_event" => Ok(Self::WithAppsyncEvent),
25+
"inline_and_remove" => Ok(Self::InlineAndRemove),
2126
_ => Err(syn::Error::new(
2227
ident.span(),
2328
format!("Unknown option `{ident}`",),
@@ -33,6 +38,7 @@ struct Args {
3338
keep_original_function_name: bool,
3439
with_appsync_event: bool,
3540
}
41+
3642
impl Parse for Args {
3743
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
3844
let op_kind = input.parse::<Ident>()?;
@@ -58,7 +64,7 @@ impl Parse for Args {
5864
let mut args = Self {
5965
op_kind,
6066
op_name,
61-
keep_original_function_name: false,
67+
keep_original_function_name: !cfg!(feature = "compat"),
6268
with_appsync_event: false,
6369
};
6470

@@ -70,8 +76,10 @@ impl Parse for Args {
7076
// We got an option
7177
let option = input.parse::<ArgsOption>()?;
7278
match option {
79+
#[cfg(feature = "compat")]
7380
ArgsOption::KeepOriginalFunctionName => args.keep_original_function_name = true,
7481
ArgsOption::WithAppsyncEvent => args.with_appsync_event = true,
82+
ArgsOption::InlineAndRemove => args.keep_original_function_name = false,
7583
}
7684
}
7785
Ok(args)

lambda-appsync-proc/src/internal/make_appsync/graphql/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ pub(super) struct GraphQLSchema {
7070
subscriptions: Operations,
7171
structures: Vec<Structure>,
7272
enums: Vec<Enum>,
73+
#[cfg(feature = "log")]
7374
make_operation_parameters: MakeOperationParameters,
7475
}
7576
impl GraphQLSchema {
@@ -270,6 +271,7 @@ impl GraphQLSchema {
270271
subscriptions: subscriptions.unwrap_or_default(),
271272
structures,
272273
enums,
274+
#[cfg(feature = "log")]
273275
make_operation_parameters,
274276
})
275277
} else {

lambda-appsync-proc/src/internal/make_appsync/make_operation.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ impl Default for MakeOperationParameters {
4444
fn default() -> Self {
4545
Self {
4646
type_module: None,
47+
#[cfg(feature = "log")]
4748
error_logging: true,
4849
}
4950
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
use lambda_appsync::{
2+
appsync_operation, make_appsync,
3+
subscription_filters::{FieldPath, FilterGroup},
4+
AppsyncError, ID,
5+
};
6+
7+
make_appsync!("../../../../schema.graphql");
8+
fn main() {}
9+
10+
// Query operations
11+
#[appsync_operation(query(players), inline_and_remove)]
12+
async fn get_players() -> Result<Vec<Player>, AppsyncError> {
13+
Ok(vec![])
14+
}
15+
16+
#[appsync_operation(query(gameStatus), inline_and_remove)]
17+
async fn get_game_status() -> Result<GameStatus, AppsyncError> {
18+
Ok(GameStatus::Started)
19+
}
20+
21+
#[appsync_operation(query(player), inline_and_remove)]
22+
async fn get_player(id: ID) -> Result<Option<Player>, AppsyncError> {
23+
Ok(Some(Player {
24+
id,
25+
name: "Test".into(),
26+
team: Team::Rust,
27+
}))
28+
}
29+
30+
// Mutation operations
31+
#[appsync_operation(mutation(createPlayer), inline_and_remove)]
32+
async fn create_player(name: String) -> Result<Player, AppsyncError> {
33+
Ok(Player {
34+
id: ID::new(),
35+
name,
36+
team: Team::Rust,
37+
})
38+
}
39+
40+
#[appsync_operation(mutation(deletePlayer), inline_and_remove)]
41+
async fn delete_player(id: ID) -> Result<Player, AppsyncError> {
42+
Ok(Player {
43+
id,
44+
name: "Deleted".into(),
45+
team: Team::MultiWordsTeam,
46+
})
47+
}
48+
49+
#[appsync_operation(mutation(setGameStatus), inline_and_remove)]
50+
async fn set_game_status() -> Result<GameStatus, AppsyncError> {
51+
Ok(GameStatus::Started)
52+
}
53+
54+
// Subscription operations
55+
#[appsync_operation(subscription(onCreatePlayer), inline_and_remove)]
56+
async fn on_create_player(name: String) -> Result<Option<FilterGroup>, AppsyncError> {
57+
Ok(Some(FieldPath::new("name")?.contains(name).into()))
58+
}
59+
60+
#[appsync_operation(subscription(onDeletePlayer), inline_and_remove)]
61+
async fn on_delete_player(id: ID) -> Result<Option<FilterGroup>, AppsyncError> {
62+
Ok(Some(FieldPath::new("id")?.eq(id).into()))
63+
}
64+
65+
#[appsync_operation(subscription(onGameStatusChange), inline_and_remove)]
66+
async fn on_game_status_change() -> Result<Option<FilterGroup>, AppsyncError> {
67+
Ok(None)
68+
}
69+
70+
// Test that we cannot call the original functions
71+
async fn test_original_fn() {
72+
let _ = create_player("test".to_string()).await;
73+
let _ = get_players().await;
74+
let _ = get_game_status().await;
75+
let _ = get_player(ID::new()).await;
76+
let _ = delete_player(ID::new()).await;
77+
let _ = set_game_status().await;
78+
let _ = on_create_player("test".to_string()).await;
79+
let _ = on_delete_player(ID::new()).await;
80+
let _ = on_game_status_change().await;
81+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
error[E0425]: cannot find function `create_player` in this scope
2+
--> tests/fail/operation_inline.rs:72:13
3+
|
4+
72 | let _ = create_player("test".to_string()).await;
5+
| ^^^^^^^^^^^^^ not found in this scope
6+
7+
error[E0425]: cannot find function `get_players` in this scope
8+
--> tests/fail/operation_inline.rs:73:13
9+
|
10+
73 | let _ = get_players().await;
11+
| ^^^^^^^^^^^ not found in this scope
12+
13+
error[E0425]: cannot find function `get_game_status` in this scope
14+
--> tests/fail/operation_inline.rs:74:13
15+
|
16+
74 | let _ = get_game_status().await;
17+
| ^^^^^^^^^^^^^^^ not found in this scope
18+
19+
error[E0425]: cannot find function `get_player` in this scope
20+
--> tests/fail/operation_inline.rs:75:13
21+
|
22+
75 | let _ = get_player(ID::new()).await;
23+
| ^^^^^^^^^^ not found in this scope
24+
25+
error[E0425]: cannot find function `delete_player` in this scope
26+
--> tests/fail/operation_inline.rs:76:13
27+
|
28+
76 | let _ = delete_player(ID::new()).await;
29+
| ^^^^^^^^^^^^^ not found in this scope
30+
31+
error[E0425]: cannot find function `set_game_status` in this scope
32+
--> tests/fail/operation_inline.rs:77:13
33+
|
34+
77 | let _ = set_game_status().await;
35+
| ^^^^^^^^^^^^^^^ not found in this scope
36+
37+
error[E0425]: cannot find function `on_create_player` in this scope
38+
--> tests/fail/operation_inline.rs:78:13
39+
|
40+
78 | let _ = on_create_player("test".to_string()).await;
41+
| ^^^^^^^^^^^^^^^^ not found in this scope
42+
43+
error[E0425]: cannot find function `on_delete_player` in this scope
44+
--> tests/fail/operation_inline.rs:79:13
45+
|
46+
79 | let _ = on_delete_player(ID::new()).await;
47+
| ^^^^^^^^^^^^^^^^ not found in this scope
48+
49+
error[E0425]: cannot find function `on_game_status_change` in this scope
50+
--> tests/fail/operation_inline.rs:80:13
51+
|
52+
80 | let _ = on_game_status_change().await;
53+
| ^^^^^^^^^^^^^^^^^^^^^ not found in this scope

0 commit comments

Comments
 (0)