1use anyhow::Result;
2use depyler_annotations::{Architecture, LambdaAnnotations, LambdaEventType};
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6#[derive(Debug, Clone)]
8pub struct LambdaCodeGenerator {
9 templates: HashMap<LambdaTemplate, String>,
10 optimization_profile: OptimizationProfile,
11}
12
13#[derive(Debug, Clone, PartialEq, Eq, Hash)]
14pub enum LambdaTemplate {
15 BasicHandler,
16 StreamingHandler,
17 BatchProcessor,
18 EventBridgeHandler,
19 CargoToml,
20 BuildScript,
21 SamTemplate,
22 CdkConstruct,
23}
24
25#[derive(Debug, Clone)]
26pub struct OptimizationProfile {
27 pub lto: bool,
28 pub panic_abort: bool,
29 pub codegen_units: u8,
30 pub opt_level: String,
31 pub strip: bool,
32 pub mimalloc: bool,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct LambdaGenerationContext {
37 pub event_type: Option<LambdaEventType>,
38 pub response_type: String,
39 pub handler_body: String,
40 pub imports: Vec<String>,
41 pub dependencies: Vec<String>,
42 pub annotations: LambdaAnnotations,
43 pub function_name: String,
44 pub module_name: String,
45}
46
47impl Default for OptimizationProfile {
48 fn default() -> Self {
49 Self {
50 lto: true,
51 panic_abort: true,
52 codegen_units: 1,
53 opt_level: "z".to_string(),
54 strip: true,
55 mimalloc: true,
56 }
57 }
58}
59
60impl Default for LambdaCodeGenerator {
61 fn default() -> Self {
62 Self::new()
63 }
64}
65
66impl LambdaCodeGenerator {
67 pub fn new() -> Self {
68 let mut templates = HashMap::new();
69
70 templates.insert(
72 LambdaTemplate::BasicHandler,
73 BASIC_HANDLER_TEMPLATE.to_string(),
74 );
75 templates.insert(
76 LambdaTemplate::StreamingHandler,
77 STREAMING_HANDLER_TEMPLATE.to_string(),
78 );
79 templates.insert(
80 LambdaTemplate::BatchProcessor,
81 BATCH_PROCESSOR_TEMPLATE.to_string(),
82 );
83 templates.insert(
84 LambdaTemplate::EventBridgeHandler,
85 EVENTBRIDGE_HANDLER_TEMPLATE.to_string(),
86 );
87 templates.insert(LambdaTemplate::CargoToml, CARGO_TOML_TEMPLATE.to_string());
88 templates.insert(
89 LambdaTemplate::BuildScript,
90 BUILD_SCRIPT_TEMPLATE.to_string(),
91 );
92 templates.insert(LambdaTemplate::SamTemplate, SAM_TEMPLATE.to_string());
93 templates.insert(
94 LambdaTemplate::CdkConstruct,
95 CDK_CONSTRUCT_TEMPLATE.to_string(),
96 );
97
98 Self {
99 templates,
100 optimization_profile: OptimizationProfile::default(),
101 }
102 }
103
104 pub fn with_optimization_profile(mut self, profile: OptimizationProfile) -> Self {
105 self.optimization_profile = profile;
106 self
107 }
108
109 pub fn generate_lambda_project(
111 &self,
112 context: &LambdaGenerationContext,
113 ) -> Result<LambdaProject> {
114 let handler_code = self.generate_handler(context)?;
115 let cargo_toml = self.generate_cargo_toml(context)?;
116 let build_script = self.generate_build_script(context)?;
117
118 let mut project = LambdaProject {
119 handler_code,
120 cargo_toml,
121 build_script,
122 sam_template: None,
123 cdk_construct: None,
124 readme: self.generate_readme(context)?,
125 };
126
127 if !context.annotations.pre_warm_paths.is_empty() {
129 project.sam_template = Some(self.generate_sam_template(context)?);
130 project.cdk_construct = Some(self.generate_cdk_construct(context)?);
131 }
132
133 Ok(project)
134 }
135
136 pub fn generate_handler(&self, context: &LambdaGenerationContext) -> Result<String> {
138 let template = match &context.event_type {
139 Some(LambdaEventType::SqsEvent) if context.annotations.batch_failure_reporting => {
140 self.templates.get(&LambdaTemplate::BatchProcessor)
141 }
142 Some(LambdaEventType::EventBridgeEvent(_))
143 if context.annotations.custom_serialization =>
144 {
145 self.templates.get(&LambdaTemplate::EventBridgeHandler)
146 }
147 _ => self.templates.get(&LambdaTemplate::BasicHandler),
148 }
149 .ok_or_else(|| anyhow::anyhow!("Template not found"))?;
150
151 let mut code = template.clone();
152
153 code = code.replace("{{function_name}}", &context.function_name);
155 code = code.replace("{{handler_body}}", &context.handler_body);
156 code = code.replace("{{response_type}}", &context.response_type);
157
158 if let Some(event_type) = &context.event_type {
160 let (event_module, event_rust_type) = self.get_event_type_mapping(event_type);
161 code = code.replace("{{event_module}}", &event_module);
162 code = code.replace("{{event_type}}", &event_rust_type);
163 } else {
164 code = code.replace("{{event_type}}", "serde_json::Value");
165 code = code.replace("{{event_module}}", "");
166 }
167
168 if context.annotations.tracing_enabled {
170 code = code.replace("{{tracing_enabled}}", "true");
171 } else {
172 code = code.replace("{{tracing_enabled}}", "false");
173 }
174
175 let imports_section = context.imports.join("\n");
177 code = code.replace("{{imports}}", &imports_section);
178
179 Ok(code)
180 }
181
182 pub fn generate_cargo_toml(&self, context: &LambdaGenerationContext) -> Result<String> {
184 let template = self
185 .templates
186 .get(&LambdaTemplate::CargoToml)
187 .ok_or_else(|| anyhow::anyhow!("Cargo.toml template not found"))?;
188
189 let mut cargo_toml = template.clone();
190 cargo_toml = cargo_toml.replace("{{package_name}}", &context.module_name);
191
192 let mut dependencies = context.dependencies.clone();
194
195 dependencies.push("lambda_runtime = \"0.8\"".to_string());
197 dependencies.push("tokio = { version = \"1\", features = [\"macros\"] }".to_string());
198 dependencies.push("serde = { version = \"1.0\", features = [\"derive\"] }".to_string());
199 dependencies.push("serde_json = \"1.0\"".to_string());
200 dependencies.push("anyhow = \"1.0\"".to_string());
201
202 if context.event_type.is_some() {
204 dependencies.push("aws-lambda-events = \"0.10\"".to_string());
205 }
206
207 if context.annotations.tracing_enabled {
208 dependencies.push("tracing = \"0.1\"".to_string());
209 dependencies.push("tracing-subscriber = \"0.3\"".to_string());
210 }
211
212 if self.optimization_profile.mimalloc {
213 dependencies
214 .push("mimalloc = { version = \"0.1\", default-features = false }".to_string());
215 }
216
217 let deps_section = dependencies.join("\n");
218 cargo_toml = cargo_toml.replace("{{dependencies}}", &deps_section);
219
220 let profile_section = self.generate_optimization_profile();
222 cargo_toml = cargo_toml.replace("{{profile}}", &profile_section);
223
224 let metadata_section = self.generate_lambda_metadata(context);
226 cargo_toml = cargo_toml.replace("{{lambda_metadata}}", &metadata_section);
227
228 Ok(cargo_toml)
229 }
230
231 pub fn generate_build_script(&self, context: &LambdaGenerationContext) -> Result<String> {
233 let template = self
234 .templates
235 .get(&LambdaTemplate::BuildScript)
236 .ok_or_else(|| anyhow::anyhow!("Build script template not found"))?;
237
238 let mut script = template.clone();
239
240 let arch_flag = match context.annotations.architecture {
241 Architecture::Arm64 => "--arm64",
242 Architecture::X86_64 => "--x86-64",
243 };
244 script = script.replace("{{architecture}}", arch_flag);
245
246 Ok(script)
247 }
248
249 pub fn generate_sam_template(&self, context: &LambdaGenerationContext) -> Result<String> {
251 let template = self
252 .templates
253 .get(&LambdaTemplate::SamTemplate)
254 .ok_or_else(|| anyhow::anyhow!("SAM template not found"))?;
255
256 let mut sam = template.clone();
257 sam = sam.replace("{{function_name}}", &context.function_name);
258 sam = sam.replace(
259 "{{memory_size}}",
260 &context.annotations.memory_size.to_string(),
261 );
262
263 let timeout = context.annotations.timeout.unwrap_or(15);
264 sam = sam.replace("{{timeout}}", &timeout.to_string());
265
266 let arch = match context.annotations.architecture {
267 Architecture::Arm64 => "arm64",
268 Architecture::X86_64 => "x86_64",
269 };
270 sam = sam.replace("{{architecture}}", arch);
271
272 Ok(sam)
273 }
274
275 pub fn generate_cdk_construct(&self, context: &LambdaGenerationContext) -> Result<String> {
277 let template = self
278 .templates
279 .get(&LambdaTemplate::CdkConstruct)
280 .ok_or_else(|| anyhow::anyhow!("CDK template not found"))?;
281
282 let mut cdk = template.clone();
283 cdk = cdk.replace("{{function_name}}", &context.function_name);
284 cdk = cdk.replace(
285 "{{memory_size}}",
286 &context.annotations.memory_size.to_string(),
287 );
288
289 let timeout = context.annotations.timeout.unwrap_or(15);
290 cdk = cdk.replace("{{timeout}}", &timeout.to_string());
291
292 Ok(cdk)
293 }
294
295 fn generate_readme(&self, context: &LambdaGenerationContext) -> Result<String> {
296 Ok(format!(
297 r#"# {} Lambda Function
298
299Generated Rust Lambda function from Python source.
300
301## Build
302
303```bash
304cargo lambda build --release
305```
306
307## Test
308
309```bash
310cargo lambda test
311```
312
313## Deploy
314
315```bash
316cargo lambda deploy
317```
318
319## Configuration
320
321- Memory: {}MB
322- Timeout: {}s
323- Architecture: {:?}
324- Event Type: {:?}
325"#,
326 context.function_name,
327 context.annotations.memory_size,
328 context.annotations.timeout.unwrap_or(15),
329 context.annotations.architecture,
330 context.event_type
331 ))
332 }
333
334 fn get_event_type_mapping(&self, event_type: &LambdaEventType) -> (String, String) {
335 match event_type {
336 LambdaEventType::S3Event => ("s3".to_string(), "S3Event".to_string()),
337 LambdaEventType::ApiGatewayProxyRequest => {
338 ("apigw".to_string(), "ApiGatewayProxyRequest".to_string())
339 }
340 LambdaEventType::ApiGatewayV2HttpRequest => {
341 ("apigw".to_string(), "ApiGatewayV2httpRequest".to_string())
342 }
343 LambdaEventType::SqsEvent => ("sqs".to_string(), "SqsEvent".to_string()),
344 LambdaEventType::SnsEvent => ("sns".to_string(), "SnsEvent".to_string()),
345 LambdaEventType::DynamodbEvent => ("dynamodb".to_string(), "DynamodbEvent".to_string()),
346 LambdaEventType::EventBridgeEvent(custom_type) => {
347 if let Some(custom) = custom_type {
348 (
349 "eventbridge".to_string(),
350 format!("EventBridgeEvent<{custom}>"),
351 )
352 } else {
353 (
354 "eventbridge".to_string(),
355 "EventBridgeEvent<serde_json::Value>".to_string(),
356 )
357 }
358 }
359 LambdaEventType::CloudwatchEvent => (
360 "cloudwatch_events".to_string(),
361 "CloudWatchEvent".to_string(),
362 ),
363 LambdaEventType::KinesisEvent => ("kinesis".to_string(), "KinesisEvent".to_string()),
364 LambdaEventType::Custom(name) => ("".to_string(), name.clone()),
365 LambdaEventType::Auto => ("".to_string(), "serde_json::Value".to_string()),
366 }
367 }
368
369 fn generate_optimization_profile(&self) -> String {
370 format!(
371 r#"
372[profile.lambda]
373inherits = "release"
374opt-level = "{}"
375lto = {}
376codegen-units = {}
377panic = "{}"
378strip = {}
379overflow-checks = false
380incremental = false
381"#,
382 self.optimization_profile.opt_level,
383 self.optimization_profile.lto,
384 self.optimization_profile.codegen_units,
385 if self.optimization_profile.panic_abort {
386 "abort"
387 } else {
388 "unwind"
389 },
390 self.optimization_profile.strip
391 )
392 }
393
394 fn generate_lambda_metadata(&self, context: &LambdaGenerationContext) -> String {
395 let arch = match context.annotations.architecture {
396 Architecture::Arm64 => "aarch64-unknown-linux-musl",
397 Architecture::X86_64 => "x86_64-unknown-linux-musl",
398 };
399
400 format!(
401 r#"
402[package.metadata.lambda]
403compile = "{}"
404memory = {}
405timeout = {}
406architecture = "{}"
407"#,
408 arch,
409 context.annotations.memory_size,
410 context.annotations.timeout.unwrap_or(15),
411 match context.annotations.architecture {
412 Architecture::Arm64 => "arm64",
413 Architecture::X86_64 => "x86_64",
414 }
415 )
416 }
417}
418
419#[derive(Debug, Clone)]
420pub struct LambdaProject {
421 pub handler_code: String,
422 pub cargo_toml: String,
423 pub build_script: String,
424 pub sam_template: Option<String>,
425 pub cdk_construct: Option<String>,
426 pub readme: String,
427}
428
429const BASIC_HANDLER_TEMPLATE: &str = r#"{{imports}}
431use lambda_runtime::{service_fn, LambdaEvent, Error};
432{% if event_type %}
433use aws_lambda_events::{{event_module}}::{{event_type}};
434{% endif %}
435
436{% if mimalloc %}
437#[global_allocator]
438static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc;
439{% endif %}
440
441#[tokio::main(flavor = "current_thread")]
442async fn main() -> Result<(), Error> {
443 {% if tracing_enabled %}
444 tracing_subscriber::fmt()
445 .json()
446 .with_target(false)
447 .init();
448 {% endif %}
449
450 {% if cold_start_optimize %}
451 // Pre-warm critical paths
452 let _ = serde_json::Value::Null;
453 {% endif %}
454
455 lambda_runtime::run(service_fn({{function_name}})).await
456}
457
458async fn {{function_name}}(
459 event: LambdaEvent<{% if event_type %}{{event_type}}{% else %}serde_json::Value{% endif %}>
460) -> Result<{{response_type}}, Error> {
461 {{handler_body}}
462}
463"#;
464
465const STREAMING_HANDLER_TEMPLATE: &str = r#"{{imports}}
466use lambda_runtime::{service_fn, LambdaEvent, Error, StreamResponse};
467use bytes::Bytes;
468use futures::stream::Stream;
469
470async fn {{function_name}}(
471 event: LambdaEvent<{{event_type}}>
472) -> Result<StreamResponse<impl Stream<Item = Result<Bytes, Error>>>, Error> {
473 let stream = futures::stream::iter(vec![
474 Ok(Bytes::from("data: ")),
475 Ok(Bytes::from({{handler_body}})),
476 ]);
477
478 Ok(StreamResponse::new(stream))
479}
480"#;
481
482const BATCH_PROCESSOR_TEMPLATE: &str = r#"{{imports}}
483use aws_lambda_events::sqs::{SqsBatchResponse, SqsBatchItemFailure, SqsEvent};
484use lambda_runtime::{service_fn, Error, LambdaEvent};
485
486#[tokio::main(flavor = "current_thread")]
487async fn main() -> Result<(), Error> {
488 lambda_runtime::run(service_fn({{function_name}})).await
489}
490
491async fn {{function_name}}(event: LambdaEvent<SqsEvent>) -> Result<SqsBatchResponse, Error> {
492 let mut batch_item_failures = Vec::new();
493
494 for record in event.payload.records {
495 let message_id = record.message_id.clone().unwrap_or_default();
496
497 match process_record(&record).await {
498 Ok(_) => {},
499 Err(_) => {
500 batch_item_failures.push(SqsBatchItemFailure {
501 item_identifier: message_id,
502 });
503 }
504 }
505 }
506
507 Ok(SqsBatchResponse {
508 batch_item_failures,
509 })
510}
511
512async fn process_record(record: &aws_lambda_events::sqs::SqsMessage) -> Result<(), Error> {
513 {{handler_body}}
514}
515"#;
516
517const EVENTBRIDGE_HANDLER_TEMPLATE: &str = r#"{{imports}}
518use aws_lambda_events::eventbridge::EventBridgeEvent;
519use lambda_runtime::{service_fn, Error, LambdaEvent};
520
521#[tokio::main(flavor = "current_thread")]
522async fn main() -> Result<(), Error> {
523 lambda_runtime::run(service_fn({{function_name}})).await
524}
525
526async fn {{function_name}}(
527 event: LambdaEvent<EventBridgeEvent<serde_json::Value>>,
528) -> Result<(), Error> {
529 {{handler_body}}
530}
531"#;
532
533const CARGO_TOML_TEMPLATE: &str = r#"[package]
534name = "{{package_name}}"
535version = "0.1.0"
536edition = "2021"
537
538[[bin]]
539name = "bootstrap"
540path = "src/main.rs"
541
542[dependencies]
543{{dependencies}}
544
545{{profile}}
546
547{{lambda_metadata}}
548"#;
549
550const BUILD_SCRIPT_TEMPLATE: &str = r#"#!/bin/bash
551# Generated build script for cargo-lambda
552
553set -e
554
555# Set optimization flags
556export RUSTFLAGS="-C link-arg=-s -C opt-level=z -C codegen-units=1"
557export CARGO_PROFILE_RELEASE_LTO=true
558export CARGO_PROFILE_RELEASE_PANIC="abort"
559
560# Build with cargo-lambda
561cargo lambda build \
562 --release \
563 {{architecture}} \
564 --output-format zip \
565 --lambda-dir ./target/lambda
566
567# Additional optimization
568if command -v upx > /dev/null; then
569 echo "Compressing binary with UPX..."
570 upx --best target/lambda/*/bootstrap
571fi
572
573# Generate deployment package
574HANDLER_NAME=$(basename $(pwd))
575cp target/lambda/${HANDLER_NAME}/bootstrap.zip ${HANDLER_NAME}.zip
576echo "Lambda package: ${HANDLER_NAME}.zip ($(du -h ${HANDLER_NAME}.zip | cut -f1))"
577"#;
578
579const SAM_TEMPLATE: &str = r#"AWSTemplateFormatVersion: '2010-09-09'
580Transform: AWS::Serverless-2016-10-31
581
582Globals:
583 Function:
584 Runtime: provided.al2
585 Architectures:
586 - {{architecture}}
587 MemorySize: {{memory_size}}
588 Timeout: {{timeout}}
589
590Resources:
591 {{function_name}}Function:
592 Type: AWS::Serverless::Function
593 Properties:
594 CodeUri: target/lambda/{{function_name}}/
595 Handler: bootstrap
596 Environment:
597 Variables:
598 RUST_LOG: info
599 Events:
600 Api:
601 Type: Api
602 Properties:
603 Path: /{proxy+}
604 Method: ANY
605"#;
606
607const CDK_CONSTRUCT_TEMPLATE: &str = r#"import * as lambda from 'aws-cdk-lib/aws-lambda';
608import * as cdk from 'aws-cdk-lib';
609
610export class {{function_name}}Lambda extends cdk.Construct {
611 public readonly function: lambda.Function;
612
613 constructor(scope: cdk.Construct, id: string) {
614 super(scope, id);
615
616 this.function = new lambda.Function(this, '{{function_name}}', {
617 runtime: lambda.Runtime.PROVIDED_AL2,
618 architecture: lambda.Architecture.ARM_64,
619 handler: 'bootstrap',
620 code: lambda.Code.fromAsset('target/lambda/{{function_name}}'),
621 memorySize: {{memory_size}},
622 timeout: cdk.Duration.seconds({{timeout}}),
623 environment: {
624 RUST_LOG: 'info',
625 },
626 });
627 }
628}
629"#;
630
631#[cfg(test)]
632mod tests {
633 use super::*;
634 use depyler_annotations::LambdaAnnotations;
635 use std::collections::HashSet;
636
637 fn create_test_context() -> LambdaGenerationContext {
638 LambdaGenerationContext {
639 event_type: Some(LambdaEventType::ApiGatewayProxyRequest),
640 response_type: "ApiGatewayProxyResponse".to_string(),
641 handler_body: "Ok(ApiGatewayProxyResponse::default())".to_string(),
642 imports: vec!["use serde_json;".to_string()],
643 dependencies: vec![],
644 annotations: LambdaAnnotations::default(),
645 function_name: "handler".to_string(),
646 module_name: "my_lambda".to_string(),
647 }
648 }
649
650 #[test]
653 fn test_lambda_template_variants() {
654 let templates = [
655 LambdaTemplate::BasicHandler,
656 LambdaTemplate::StreamingHandler,
657 LambdaTemplate::BatchProcessor,
658 LambdaTemplate::EventBridgeHandler,
659 LambdaTemplate::CargoToml,
660 LambdaTemplate::BuildScript,
661 LambdaTemplate::SamTemplate,
662 LambdaTemplate::CdkConstruct,
663 ];
664 assert_eq!(templates.len(), 8);
665 }
666
667 #[test]
668 fn test_lambda_template_eq() {
669 assert_eq!(LambdaTemplate::BasicHandler, LambdaTemplate::BasicHandler);
670 assert_ne!(LambdaTemplate::BasicHandler, LambdaTemplate::CargoToml);
671 }
672
673 #[test]
674 fn test_lambda_template_hash() {
675 let mut set = HashSet::new();
676 set.insert(LambdaTemplate::BasicHandler);
677 set.insert(LambdaTemplate::CargoToml);
678 assert_eq!(set.len(), 2);
679 assert!(set.contains(&LambdaTemplate::BasicHandler));
680 }
681
682 #[test]
683 fn test_lambda_template_clone() {
684 let template = LambdaTemplate::BasicHandler;
685 let cloned = template.clone();
686 assert_eq!(cloned, template);
687 }
688
689 #[test]
690 fn test_lambda_template_debug() {
691 let debug = format!("{:?}", LambdaTemplate::BasicHandler);
692 assert!(debug.contains("BasicHandler"));
693 }
694
695 #[test]
698 fn test_optimization_profile_default() {
699 let profile = OptimizationProfile::default();
700 assert!(profile.lto);
701 assert!(profile.panic_abort);
702 assert_eq!(profile.codegen_units, 1);
703 assert_eq!(profile.opt_level, "z");
704 assert!(profile.strip);
705 assert!(profile.mimalloc);
706 }
707
708 #[test]
709 fn test_optimization_profile_clone() {
710 let profile = OptimizationProfile::default();
711 let cloned = profile.clone();
712 assert_eq!(cloned.opt_level, profile.opt_level);
713 assert_eq!(cloned.lto, profile.lto);
714 }
715
716 #[test]
717 fn test_optimization_profile_debug() {
718 let profile = OptimizationProfile::default();
719 let debug = format!("{:?}", profile);
720 assert!(debug.contains("OptimizationProfile"));
721 }
722
723 #[test]
724 fn test_optimization_profile_custom() {
725 let profile = OptimizationProfile {
726 lto: false,
727 panic_abort: false,
728 codegen_units: 4,
729 opt_level: "3".to_string(),
730 strip: false,
731 mimalloc: false,
732 };
733 assert!(!profile.lto);
734 assert!(!profile.mimalloc);
735 assert_eq!(profile.opt_level, "3");
736 }
737
738 #[test]
741 fn test_lambda_generation_context_fields() {
742 let context = create_test_context();
743 assert!(context.event_type.is_some());
744 assert_eq!(context.function_name, "handler");
745 assert_eq!(context.module_name, "my_lambda");
746 }
747
748 #[test]
749 fn test_lambda_generation_context_clone() {
750 let context = create_test_context();
751 let cloned = context.clone();
752 assert_eq!(cloned.function_name, context.function_name);
753 }
754
755 #[test]
756 fn test_lambda_generation_context_debug() {
757 let context = create_test_context();
758 let debug = format!("{:?}", context);
759 assert!(debug.contains("LambdaGenerationContext"));
760 }
761
762 #[test]
763 fn test_lambda_generation_context_serialize() {
764 let context = create_test_context();
765 let json = serde_json::to_string(&context).unwrap();
766 assert!(json.contains("handler"));
767 assert!(json.contains("my_lambda"));
768 }
769
770 #[test]
771 fn test_lambda_generation_context_deserialize() {
772 let json = r#"{
773 "event_type": null,
774 "response_type": "String",
775 "handler_body": "Ok(String::new())",
776 "imports": [],
777 "dependencies": [],
778 "annotations": {
779 "runtime": "ProvidedAl2",
780 "event_type": null,
781 "cold_start_optimize": true,
782 "memory_size": 128,
783 "architecture": "Arm64",
784 "pre_warm_paths": [],
785 "custom_serialization": false,
786 "batch_failure_reporting": false,
787 "timeout": null,
788 "tracing_enabled": false,
789 "environment_variables": []
790 },
791 "function_name": "test",
792 "module_name": "test_mod"
793 }"#;
794 let context: LambdaGenerationContext = serde_json::from_str(json).unwrap();
795 assert_eq!(context.function_name, "test");
796 }
797
798 #[test]
801 fn test_lambda_code_generator_new() {
802 let generator = LambdaCodeGenerator::new();
803 assert!(!generator.templates.is_empty());
804 assert_eq!(generator.templates.len(), 8);
805 }
806
807 #[test]
808 fn test_lambda_code_generator_default() {
809 let generator = LambdaCodeGenerator::default();
810 assert!(!generator.templates.is_empty());
811 }
812
813 #[test]
814 fn test_lambda_code_generator_clone() {
815 let generator = LambdaCodeGenerator::new();
816 let cloned = generator.clone();
817 assert_eq!(cloned.templates.len(), generator.templates.len());
818 }
819
820 #[test]
821 fn test_lambda_code_generator_debug() {
822 let generator = LambdaCodeGenerator::new();
823 let debug = format!("{:?}", generator);
824 assert!(debug.contains("LambdaCodeGenerator"));
825 }
826
827 #[test]
828 fn test_basic_handler_generation() {
829 let generator = LambdaCodeGenerator::new();
830 let context = create_test_context();
831
832 let handler = generator.generate_handler(&context).unwrap();
833 assert!(handler.contains("async fn handler"));
834 assert!(handler.contains("ApiGatewayProxyRequest"));
835 assert!(handler.contains("LambdaEvent"));
836 }
837
838 #[test]
839 fn test_handler_without_event_type() {
840 let generator = LambdaCodeGenerator::new();
841 let mut context = create_test_context();
842 context.event_type = None;
843
844 let handler = generator.generate_handler(&context).unwrap();
845 assert!(handler.contains("serde_json::Value"));
846 }
847
848 #[test]
849 fn test_handler_with_tracing() {
850 let generator = LambdaCodeGenerator::new();
851 let mut context = create_test_context();
852 context.annotations.tracing_enabled = true;
853
854 let handler = generator.generate_handler(&context).unwrap();
855 assert!(handler.contains("true") || handler.contains("tracing"));
856 }
857
858 #[test]
859 fn test_cargo_toml_generation() {
860 let generator = LambdaCodeGenerator::new();
861 let context = create_test_context();
862
863 let cargo_toml = generator.generate_cargo_toml(&context).unwrap();
864 assert!(cargo_toml.contains("lambda_runtime"));
865 assert!(cargo_toml.contains("aws-lambda-events"));
866 assert!(cargo_toml.contains("[profile.lambda]"));
867 }
868
869 #[test]
870 fn test_cargo_toml_with_tracing() {
871 let generator = LambdaCodeGenerator::new();
872 let mut context = create_test_context();
873 context.annotations.tracing_enabled = true;
874
875 let cargo_toml = generator.generate_cargo_toml(&context).unwrap();
876 assert!(cargo_toml.contains("tracing"));
877 assert!(cargo_toml.contains("tracing-subscriber"));
878 }
879
880 #[test]
881 fn test_cargo_toml_without_event_type() {
882 let generator = LambdaCodeGenerator::new();
883 let mut context = create_test_context();
884 context.event_type = None;
885
886 let cargo_toml = generator.generate_cargo_toml(&context).unwrap();
887 assert!(!cargo_toml.contains("aws-lambda-events"));
889 }
890
891 #[test]
892 fn test_sqs_batch_processor() {
893 let generator = LambdaCodeGenerator::new();
894 let mut context = create_test_context();
895 context.event_type = Some(LambdaEventType::SqsEvent);
896 context.annotations.batch_failure_reporting = true;
897
898 let handler = generator.generate_handler(&context).unwrap();
899 assert!(handler.contains("SqsBatchResponse"));
900 assert!(handler.contains("batch_item_failures"));
901 }
902
903 #[test]
904 fn test_eventbridge_handler() {
905 let generator = LambdaCodeGenerator::new();
906 let mut context = create_test_context();
907 context.event_type = Some(LambdaEventType::EventBridgeEvent(Some(
908 "OrderEvent".to_string(),
909 )));
910 context.annotations.custom_serialization = true;
911
912 let handler = generator.generate_handler(&context).unwrap();
913 assert!(handler.contains("EventBridgeEvent"));
914 }
915
916 #[test]
917 fn test_full_project_generation() {
918 let generator = LambdaCodeGenerator::new();
919 let context = create_test_context();
920
921 let project = generator.generate_lambda_project(&context).unwrap();
922 assert!(!project.handler_code.is_empty());
923 assert!(!project.cargo_toml.is_empty());
924 assert!(!project.build_script.is_empty());
925 assert!(!project.readme.is_empty());
926 }
927
928 #[test]
929 fn test_project_with_pre_warm_paths() {
930 let generator = LambdaCodeGenerator::new();
931 let mut context = create_test_context();
932 context.annotations.pre_warm_paths = vec!["/api".to_string()];
933
934 let project = generator.generate_lambda_project(&context).unwrap();
935 assert!(project.sam_template.is_some());
936 assert!(project.cdk_construct.is_some());
937 }
938
939 #[test]
940 fn test_optimization_profile() {
941 let profile = OptimizationProfile {
942 opt_level: "s".to_string(),
943 lto: false,
944 ..OptimizationProfile::default()
945 };
946
947 let generator = LambdaCodeGenerator::new().with_optimization_profile(profile);
948 let context = create_test_context();
949
950 let cargo_toml = generator.generate_cargo_toml(&context).unwrap();
951 assert!(cargo_toml.contains("opt-level = \"s\""));
952 assert!(cargo_toml.contains("lto = false"));
953 }
954
955 #[test]
958 fn test_build_script_arm64() {
959 let generator = LambdaCodeGenerator::new();
960 let mut context = create_test_context();
961 context.annotations.architecture = Architecture::Arm64;
962
963 let script = generator.generate_build_script(&context).unwrap();
964 assert!(script.contains("--arm64"));
965 }
966
967 #[test]
968 fn test_build_script_x86_64() {
969 let generator = LambdaCodeGenerator::new();
970 let mut context = create_test_context();
971 context.annotations.architecture = Architecture::X86_64;
972
973 let script = generator.generate_build_script(&context).unwrap();
974 assert!(script.contains("--x86-64"));
975 }
976
977 #[test]
980 fn test_sam_template_generation() {
981 let generator = LambdaCodeGenerator::new();
982 let context = create_test_context();
983
984 let sam = generator.generate_sam_template(&context).unwrap();
985 assert!(sam.contains("AWSTemplateFormatVersion"));
986 assert!(sam.contains("AWS::Serverless::Function"));
987 assert!(sam.contains("handler"));
988 }
989
990 #[test]
991 fn test_sam_template_memory_size() {
992 let generator = LambdaCodeGenerator::new();
993 let mut context = create_test_context();
994 context.annotations.memory_size = 512;
995
996 let sam = generator.generate_sam_template(&context).unwrap();
997 assert!(sam.contains("512"));
998 }
999
1000 #[test]
1001 fn test_sam_template_timeout() {
1002 let generator = LambdaCodeGenerator::new();
1003 let mut context = create_test_context();
1004 context.annotations.timeout = Some(30);
1005
1006 let sam = generator.generate_sam_template(&context).unwrap();
1007 assert!(sam.contains("30"));
1008 }
1009
1010 #[test]
1013 fn test_cdk_construct_generation() {
1014 let generator = LambdaCodeGenerator::new();
1015 let context = create_test_context();
1016
1017 let cdk = generator.generate_cdk_construct(&context).unwrap();
1018 assert!(cdk.contains("aws-cdk-lib"));
1019 assert!(cdk.contains("lambda.Function"));
1020 assert!(cdk.contains("handler"));
1021 }
1022
1023 #[test]
1024 fn test_cdk_construct_memory_size() {
1025 let generator = LambdaCodeGenerator::new();
1026 let mut context = create_test_context();
1027 context.annotations.memory_size = 256;
1028
1029 let cdk = generator.generate_cdk_construct(&context).unwrap();
1030 assert!(cdk.contains("256"));
1031 }
1032
1033 #[test]
1036 fn test_event_type_mapping_s3() {
1037 let generator = LambdaCodeGenerator::new();
1038 let (module, rust_type) = generator.get_event_type_mapping(&LambdaEventType::S3Event);
1039 assert_eq!(module, "s3");
1040 assert_eq!(rust_type, "S3Event");
1041 }
1042
1043 #[test]
1044 fn test_event_type_mapping_api_gateway() {
1045 let generator = LambdaCodeGenerator::new();
1046 let (module, rust_type) =
1047 generator.get_event_type_mapping(&LambdaEventType::ApiGatewayProxyRequest);
1048 assert_eq!(module, "apigw");
1049 assert_eq!(rust_type, "ApiGatewayProxyRequest");
1050 }
1051
1052 #[test]
1053 fn test_event_type_mapping_api_gateway_v2() {
1054 let generator = LambdaCodeGenerator::new();
1055 let (module, rust_type) =
1056 generator.get_event_type_mapping(&LambdaEventType::ApiGatewayV2HttpRequest);
1057 assert_eq!(module, "apigw");
1058 assert_eq!(rust_type, "ApiGatewayV2httpRequest");
1059 }
1060
1061 #[test]
1062 fn test_event_type_mapping_sqs() {
1063 let generator = LambdaCodeGenerator::new();
1064 let (module, rust_type) = generator.get_event_type_mapping(&LambdaEventType::SqsEvent);
1065 assert_eq!(module, "sqs");
1066 assert_eq!(rust_type, "SqsEvent");
1067 }
1068
1069 #[test]
1070 fn test_event_type_mapping_sns() {
1071 let generator = LambdaCodeGenerator::new();
1072 let (module, rust_type) = generator.get_event_type_mapping(&LambdaEventType::SnsEvent);
1073 assert_eq!(module, "sns");
1074 assert_eq!(rust_type, "SnsEvent");
1075 }
1076
1077 #[test]
1078 fn test_event_type_mapping_dynamodb() {
1079 let generator = LambdaCodeGenerator::new();
1080 let (module, rust_type) = generator.get_event_type_mapping(&LambdaEventType::DynamodbEvent);
1081 assert_eq!(module, "dynamodb");
1082 assert_eq!(rust_type, "DynamodbEvent");
1083 }
1084
1085 #[test]
1086 fn test_event_type_mapping_eventbridge_custom() {
1087 let generator = LambdaCodeGenerator::new();
1088 let (module, rust_type) = generator.get_event_type_mapping(
1089 &LambdaEventType::EventBridgeEvent(Some("OrderEvent".to_string())),
1090 );
1091 assert_eq!(module, "eventbridge");
1092 assert_eq!(rust_type, "EventBridgeEvent<OrderEvent>");
1093 }
1094
1095 #[test]
1096 fn test_event_type_mapping_eventbridge_default() {
1097 let generator = LambdaCodeGenerator::new();
1098 let (module, rust_type) =
1099 generator.get_event_type_mapping(&LambdaEventType::EventBridgeEvent(None));
1100 assert_eq!(module, "eventbridge");
1101 assert_eq!(rust_type, "EventBridgeEvent<serde_json::Value>");
1102 }
1103
1104 #[test]
1105 fn test_event_type_mapping_cloudwatch() {
1106 let generator = LambdaCodeGenerator::new();
1107 let (module, rust_type) =
1108 generator.get_event_type_mapping(&LambdaEventType::CloudwatchEvent);
1109 assert_eq!(module, "cloudwatch_events");
1110 assert_eq!(rust_type, "CloudWatchEvent");
1111 }
1112
1113 #[test]
1114 fn test_event_type_mapping_kinesis() {
1115 let generator = LambdaCodeGenerator::new();
1116 let (module, rust_type) = generator.get_event_type_mapping(&LambdaEventType::KinesisEvent);
1117 assert_eq!(module, "kinesis");
1118 assert_eq!(rust_type, "KinesisEvent");
1119 }
1120
1121 #[test]
1122 fn test_event_type_mapping_custom() {
1123 let generator = LambdaCodeGenerator::new();
1124 let (module, rust_type) =
1125 generator.get_event_type_mapping(&LambdaEventType::Custom("MyEvent".to_string()));
1126 assert_eq!(module, "");
1127 assert_eq!(rust_type, "MyEvent");
1128 }
1129
1130 #[test]
1131 fn test_event_type_mapping_auto() {
1132 let generator = LambdaCodeGenerator::new();
1133 let (module, rust_type) = generator.get_event_type_mapping(&LambdaEventType::Auto);
1134 assert_eq!(module, "");
1135 assert_eq!(rust_type, "serde_json::Value");
1136 }
1137
1138 #[test]
1141 fn test_lambda_project_fields() {
1142 let project = LambdaProject {
1143 handler_code: "code".to_string(),
1144 cargo_toml: "toml".to_string(),
1145 build_script: "script".to_string(),
1146 sam_template: Some("sam".to_string()),
1147 cdk_construct: Some("cdk".to_string()),
1148 readme: "readme".to_string(),
1149 };
1150 assert_eq!(project.handler_code, "code");
1151 assert!(project.sam_template.is_some());
1152 }
1153
1154 #[test]
1155 fn test_lambda_project_clone() {
1156 let project = LambdaProject {
1157 handler_code: "code".to_string(),
1158 cargo_toml: "toml".to_string(),
1159 build_script: "script".to_string(),
1160 sam_template: None,
1161 cdk_construct: None,
1162 readme: "readme".to_string(),
1163 };
1164 let cloned = project.clone();
1165 assert_eq!(cloned.handler_code, project.handler_code);
1166 }
1167
1168 #[test]
1169 fn test_lambda_project_debug() {
1170 let project = LambdaProject {
1171 handler_code: String::new(),
1172 cargo_toml: String::new(),
1173 build_script: String::new(),
1174 sam_template: None,
1175 cdk_construct: None,
1176 readme: String::new(),
1177 };
1178 let debug = format!("{:?}", project);
1179 assert!(debug.contains("LambdaProject"));
1180 }
1181
1182 #[test]
1185 fn test_readme_generation() {
1186 let generator = LambdaCodeGenerator::new();
1187 let context = create_test_context();
1188
1189 let readme = generator.generate_readme(&context).unwrap();
1190 assert!(readme.contains("# handler Lambda Function"));
1191 assert!(readme.contains("cargo lambda build"));
1192 assert!(readme.contains("Memory:"));
1193 assert!(readme.contains("Timeout:"));
1194 }
1195}