Skip to main content

depyler_lambda/
lambda_codegen.rs

1use anyhow::Result;
2use depyler_annotations::{Architecture, LambdaAnnotations, LambdaEventType};
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6/// Lambda-specific code generation for Rust output
7#[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        // Basic handler template
71        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    /// Generate complete Lambda Rust project from Python handler
110    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        // Generate deployment templates if needed
128        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    /// Generate the main handler Rust code
137    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        // Replace template variables
154        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        // Handle event type specific replacements
159        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        // Handle tracing
169        if context.annotations.tracing_enabled {
170            code = code.replace("{{tracing_enabled}}", "true");
171        } else {
172            code = code.replace("{{tracing_enabled}}", "false");
173        }
174
175        // Add imports
176        let imports_section = context.imports.join("\n");
177        code = code.replace("{{imports}}", &imports_section);
178
179        Ok(code)
180    }
181
182    /// Generate Cargo.toml for Lambda project
183    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        // Add event-specific dependencies
193        let mut dependencies = context.dependencies.clone();
194
195        // Core Lambda dependencies
196        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        // Event-specific dependencies
203        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        // Add optimization profile
221        let profile_section = self.generate_optimization_profile();
222        cargo_toml = cargo_toml.replace("{{profile}}", &profile_section);
223
224        // Add Lambda metadata
225        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    /// Generate build script for cargo-lambda
232    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    /// Generate SAM template
250    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    /// Generate CDK construct
276    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
429// Template constants
430const 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    // === LambdaTemplate tests ===
651
652    #[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    // === OptimizationProfile tests ===
696
697    #[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    // === LambdaGenerationContext tests ===
739
740    #[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    // === LambdaCodeGenerator tests ===
799
800    #[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        // Should not include aws-lambda-events when no event type
888        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    // === Build script tests ===
956
957    #[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    // === SAM template tests ===
978
979    #[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    // === CDK construct tests ===
1011
1012    #[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    // === Event type mapping tests ===
1034
1035    #[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    // === LambdaProject tests ===
1139
1140    #[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    // === Readme generation test ===
1183
1184    #[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}