Skip to content

Commit a5e76e6

Browse files
committed
Add builder pattern support for event response types
Add derived builders to events. Builders are conditionally compiled with the "builders" feature flag.
1 parent 0ebdbde commit a5e76e6

File tree

74 files changed

+1680
-2
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+1680
-2
lines changed

lambda-events/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ edition = "2021"
2020
base64 = { workspace = true }
2121
bytes = { workspace = true, features = ["serde"], optional = true }
2222
chrono = { workspace = true, optional = true }
23+
derive_builder = { version = "0.20", optional = true }
2324
flate2 = { version = "1.0.24", optional = true }
2425
http = { workspace = true, optional = true }
2526
http-body = { workspace = true, optional = true }
@@ -126,6 +127,7 @@ documentdb = []
126127
eventbridge = ["chrono", "serde_with"]
127128

128129
catch-all-fields = []
130+
builders = ["derive_builder"]
129131

130132
[package.metadata.docs.rs]
131133
all-features = true
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Example demonstrating builder pattern usage for AWS Lambda events
2+
#[cfg(feature = "builders")]
3+
use aws_lambda_events::event::{
4+
dynamodb::EventBuilder as DynamoDbEventBuilder,
5+
kinesis::KinesisEventBuilder,
6+
s3::S3EventBuilder,
7+
secretsmanager::SecretsManagerSecretRotationEventBuilder,
8+
sns::SnsEventBuilder,
9+
sqs::SqsEventBuilder,
10+
};
11+
12+
#[cfg(feature = "builders")]
13+
fn main() {
14+
15+
// S3 Event - Object storage notifications
16+
let s3_event = S3EventBuilder::default()
17+
.records(vec![])
18+
.build()
19+
.unwrap();
20+
21+
// Kinesis Event - Stream processing
22+
let kinesis_event = KinesisEventBuilder::default()
23+
.records(vec![])
24+
.build()
25+
.unwrap();
26+
27+
// DynamoDB Event - Database change streams
28+
let dynamodb_event = DynamoDbEventBuilder::default()
29+
.records(vec![])
30+
.build()
31+
.unwrap();
32+
33+
// SNS Event - Pub/sub messaging
34+
let sns_event = SnsEventBuilder::default()
35+
.records(vec![])
36+
.build()
37+
.unwrap();
38+
39+
// SQS Event - Queue messaging
40+
let sqs_event = SqsEventBuilder::default()
41+
.records(vec![])
42+
.build()
43+
.unwrap();
44+
45+
// Secrets Manager Event - Secret rotation
46+
let secrets_event = SecretsManagerSecretRotationEventBuilder::default()
47+
.step("createSecret")
48+
.secret_id("test-secret")
49+
.client_request_token("token-123")
50+
.build()
51+
.unwrap();
52+
53+
}
54+
55+
#[cfg(not(feature = "builders"))]
56+
fn main() {
57+
println!("This example requires the 'builders' feature to be enabled.");
58+
println!("Run with: cargo run --example comprehensive-builders --all-features");
59+
}
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
// Example showing how builders solve the Default trait requirement problem
2+
// when using lambda_runtime with API Gateway custom authorizers
3+
//
4+
// ❌ OLD WAY (with Default requirement):
5+
// #[derive(Default)]
6+
// struct MyContext {
7+
// // Had to use Option just for Default
8+
// some_thing: Option<ThirdPartyThing>,
9+
// }
10+
//
11+
// let mut output = Response::default();
12+
// output.is_authorized = true;
13+
// output.context = MyContext {
14+
// some_thing: Some(thing), // ❌ Unnecessary Some()
15+
// };
16+
//
17+
// ✅ NEW WAY (with Builder pattern):
18+
// struct MyContext {
19+
// // No Option needed!
20+
// some_thing: ThirdPartyThing,
21+
// }
22+
//
23+
// let output = ResponseBuilder::default()
24+
// .is_authorized(true)
25+
// .context(context)
26+
// .build()?;
27+
//
28+
// Benefits:
29+
// • No Option<T> wrapper for fields that always exist
30+
// • Type-safe construction
31+
// • Works seamlessly with lambda_runtime::LambdaEvent
32+
// • Cleaner, more idiomatic Rust code
33+
34+
#[cfg(feature = "builders")]
35+
use aws_lambda_events::event::apigw::{
36+
ApiGatewayV2CustomAuthorizerSimpleResponse,
37+
ApiGatewayV2CustomAuthorizerSimpleResponseBuilder,
38+
ApiGatewayV2CustomAuthorizerV2Request,
39+
};
40+
#[cfg(feature = "builders")]
41+
use lambda_runtime::{Error, LambdaEvent};
42+
#[cfg(feature = "builders")]
43+
use serde::{Deserialize, Serialize};
44+
45+
#[cfg(feature = "builders")]
46+
#[derive(Debug, Clone, Serialize, Deserialize)]
47+
pub struct SomeThirdPartyThingWithoutDefaultValue {
48+
pub api_key: String,
49+
pub endpoint: String,
50+
pub timeout_ms: u64,
51+
}
52+
53+
// ❌ OLD WAY: Had to use Option to satisfy Default requirement
54+
#[cfg(feature = "builders")]
55+
#[derive(Debug, Default, Serialize, Deserialize)]
56+
pub struct MyContextOldWay {
57+
// NOT IDEAL: Need to wrap with Option just for Default
58+
some_thing_always_exists: Option<SomeThirdPartyThingWithoutDefaultValue>,
59+
}
60+
61+
// ✅ NEW WAY: No Option needed with builder pattern!
62+
#[cfg(feature = "builders")]
63+
#[derive(Debug, Clone, Serialize, Deserialize)]
64+
pub struct MyContext {
65+
// IDEAL: Can use the actual type directly!
66+
some_thing_always_exists: SomeThirdPartyThingWithoutDefaultValue,
67+
user_id: String,
68+
permissions: Vec<String>,
69+
}
70+
71+
// ❌ OLD IMPLEMENTATION: Using Default
72+
#[cfg(feature = "builders")]
73+
pub async fn function_handler_old_way(
74+
_event: LambdaEvent<ApiGatewayV2CustomAuthorizerV2Request>,
75+
) -> Result<ApiGatewayV2CustomAuthorizerSimpleResponse<MyContextOldWay>, Error> {
76+
let mut output: ApiGatewayV2CustomAuthorizerSimpleResponse<MyContextOldWay> =
77+
ApiGatewayV2CustomAuthorizerSimpleResponse::default();
78+
79+
output.is_authorized = true;
80+
output.context = MyContextOldWay {
81+
// ❌ Had to wrap in Some() even though it always exists
82+
some_thing_always_exists: Some(SomeThirdPartyThingWithoutDefaultValue {
83+
api_key: "secret-key-123".to_string(),
84+
endpoint: "https://api.example.com".to_string(),
85+
timeout_ms: 5000,
86+
}),
87+
};
88+
89+
Ok(output)
90+
}
91+
92+
// ✅ NEW IMPLEMENTATION: Using Builder
93+
#[cfg(feature = "builders")]
94+
pub async fn function_handler(
95+
_event: LambdaEvent<ApiGatewayV2CustomAuthorizerV2Request>,
96+
) -> Result<ApiGatewayV2CustomAuthorizerSimpleResponse<MyContext>, Error> {
97+
let context = MyContext {
98+
// ✅ No Option wrapper needed!
99+
some_thing_always_exists: SomeThirdPartyThingWithoutDefaultValue {
100+
api_key: "secret-key-123".to_string(),
101+
endpoint: "https://api.example.com".to_string(),
102+
timeout_ms: 5000,
103+
},
104+
user_id: "user-123".to_string(),
105+
permissions: vec!["read".to_string(), "write".to_string()],
106+
};
107+
108+
// ✅ Clean builder pattern - no Default required!
109+
let output = ApiGatewayV2CustomAuthorizerSimpleResponseBuilder::default()
110+
.is_authorized(true)
111+
.context(context)
112+
.build()
113+
.map_err(|e| format!("Failed to build response: {}", e))?;
114+
115+
Ok(output)
116+
}
117+
118+
#[cfg(feature = "builders")]
119+
fn main() {
120+
let context = MyContext {
121+
some_thing_always_exists: SomeThirdPartyThingWithoutDefaultValue {
122+
api_key: "secret-key-123".to_string(),
123+
endpoint: "https://api.example.com".to_string(),
124+
timeout_ms: 5000,
125+
},
126+
user_id: "user-123".to_string(),
127+
permissions: vec!["read".to_string(), "write".to_string()],
128+
};
129+
130+
let response = ApiGatewayV2CustomAuthorizerSimpleResponseBuilder::<MyContext>::default()
131+
.is_authorized(true)
132+
.context(context)
133+
.build()
134+
.unwrap();
135+
136+
println!("✅ Built authorizer response for user: {}", response.context.user_id);
137+
println!(" Authorized: {}", response.is_authorized);
138+
println!(" Permissions: {:?}", response.context.permissions);
139+
println!(
140+
" Third-party endpoint: {}",
141+
response.context.some_thing_always_exists.endpoint
142+
);
143+
}
144+
145+
#[cfg(not(feature = "builders"))]
146+
fn main() {
147+
println!("This example requires the 'builders' feature to be enabled.");
148+
println!("Run with: cargo run --example lambda-runtime-authorizer-builder --features builders");
149+
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
// Example showing how builders simplify SQS batch response construction
2+
// when handling partial batch failures
3+
//
4+
// ❌ OLD WAY (with Default):
5+
// for record in event.payload.records {
6+
// match process_record(&record).await {
7+
// Err(_) => {
8+
// let mut item = BatchItemFailure::default();
9+
// item.item_identifier = record.message_id.unwrap();
10+
// batch_item_failures.push(item)
11+
// }
12+
// }
13+
// }
14+
// let mut response = SqsBatchResponse::default();
15+
// response.batch_item_failures = batch_item_failures;
16+
//
17+
// ✅ NEW WAY (with Builder):
18+
// for record in event.payload.records {
19+
// match process_record(&record).await {
20+
// Err(_) => {
21+
// let item = BatchItemFailureBuilder::default()
22+
// .item_identifier(record.message_id.unwrap())
23+
// .build()?;
24+
// batch_item_failures.push(item)
25+
// }
26+
// }
27+
// }
28+
// let response = SqsBatchResponseBuilder::default()
29+
// .batch_item_failures(batch_item_failures)
30+
// .build()?;
31+
//
32+
// Benefits:
33+
// • Immutable construction (no mut needed)
34+
// • Cleaner, more functional style
35+
// • Type-safe field assignment
36+
// • Works seamlessly with lambda_runtime::LambdaEvent
37+
38+
#[cfg(feature = "builders")]
39+
use aws_lambda_events::event::sqs::{
40+
BatchItemFailure, BatchItemFailureBuilder, SqsBatchResponse, SqsBatchResponseBuilder, SqsEvent,
41+
};
42+
#[cfg(feature = "builders")]
43+
use lambda_runtime::{Error, LambdaEvent};
44+
45+
// Simulate processing a record
46+
#[cfg(feature = "builders")]
47+
#[allow(dead_code)]
48+
async fn process_record(record: &aws_lambda_events::event::sqs::SqsMessage) -> Result<(), String> {
49+
// Simulate some processing logic
50+
if let Some(body) = &record.body {
51+
if body.contains("error") {
52+
return Err(format!("Failed to process message: {}", body));
53+
}
54+
}
55+
Ok(())
56+
}
57+
58+
// ❌ OLD WAY: Using Default and manual field assignment
59+
#[cfg(feature = "builders")]
60+
#[allow(dead_code)]
61+
async fn function_handler_old_way(event: LambdaEvent<SqsEvent>) -> Result<SqsBatchResponse, Error> {
62+
let mut batch_item_failures = Vec::new();
63+
64+
for record in event.payload.records {
65+
match process_record(&record).await {
66+
Ok(_) => (),
67+
Err(_) => {
68+
let mut item = BatchItemFailure::default();
69+
item.item_identifier = record.message_id.unwrap();
70+
71+
batch_item_failures.push(item)
72+
}
73+
}
74+
}
75+
76+
let mut response = SqsBatchResponse::default();
77+
response.batch_item_failures = batch_item_failures;
78+
79+
Ok(response)
80+
}
81+
82+
// ✅ NEW WAY: Using Builder pattern
83+
#[cfg(feature = "builders")]
84+
#[allow(dead_code)]
85+
async fn function_handler(event: LambdaEvent<SqsEvent>) -> Result<SqsBatchResponse, Error> {
86+
let mut batch_item_failures = Vec::new();
87+
88+
for record in event.payload.records {
89+
match process_record(&record).await {
90+
Ok(_) => (),
91+
Err(_) => {
92+
// ✅ Clean builder construction
93+
let item = BatchItemFailureBuilder::default()
94+
.item_identifier(record.message_id.unwrap())
95+
.build()
96+
.unwrap();
97+
98+
batch_item_failures.push(item)
99+
}
100+
}
101+
}
102+
103+
// ✅ Clean response construction with builder
104+
let response = SqsBatchResponseBuilder::default()
105+
.batch_item_failures(batch_item_failures)
106+
.build()
107+
.map_err(|e| format!("Failed to build response: {}", e))?;
108+
109+
Ok(response)
110+
}
111+
112+
#[cfg(feature = "builders")]
113+
fn main() {
114+
// Demonstrate builder usage with sample data
115+
let failures = vec![
116+
BatchItemFailureBuilder::default()
117+
.item_identifier("msg-123".to_string())
118+
.build()
119+
.unwrap(),
120+
BatchItemFailureBuilder::default()
121+
.item_identifier("msg-456".to_string())
122+
.build()
123+
.unwrap(),
124+
];
125+
126+
let response = SqsBatchResponseBuilder::default()
127+
.batch_item_failures(failures)
128+
.build()
129+
.unwrap();
130+
131+
println!("✅ Built SQS batch response with {} failed items", response.batch_item_failures.len());
132+
for failure in &response.batch_item_failures {
133+
println!(" Failed message: {}", failure.item_identifier);
134+
}
135+
}
136+
137+
#[cfg(not(feature = "builders"))]
138+
fn main() {
139+
println!("This example requires the 'builders' feature to be enabled.");
140+
println!("Run with: cargo run --example lambda-runtime-sqs-batch-builder --features builders");
141+
}

0 commit comments

Comments
 (0)