Skip to main content

depyler_lambda/
lambda_types.rs

1use anyhow::Result;
2use depyler_annotations::LambdaEventType;
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6/// Lambda-specific type mappings and conversions
7#[derive(Debug, Clone)]
8pub struct LambdaTypeMapper {
9    event_mappings: HashMap<LambdaEventType, EventTypeMapping>,
10    response_mappings: HashMap<LambdaEventType, ResponseTypeMapping>,
11}
12
13#[derive(Debug, Clone)]
14pub struct EventTypeMapping {
15    pub rust_type: String,
16    pub aws_events_module: String,
17    pub imports: Vec<String>,
18    pub serde_attributes: Vec<String>,
19}
20
21#[derive(Debug, Clone)]
22pub struct ResponseTypeMapping {
23    pub rust_type: String,
24    pub conversion_impl: Option<String>,
25    pub imports: Vec<String>,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct TypeConversionRule {
30    pub python_pattern: String,
31    pub rust_type: String,
32    pub lambda_context: LambdaContext,
33    pub serde_attribute: Option<String>,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
37pub enum LambdaContext {
38    EventRoot,
39    HeaderValue,
40    StatusCode,
41    Timestamp,
42    Records,
43    RequestContext,
44    PathParameters,
45    QueryStringParameters,
46    Body,
47}
48
49impl Default for LambdaTypeMapper {
50    fn default() -> Self {
51        Self::new()
52    }
53}
54
55impl LambdaTypeMapper {
56    pub fn new() -> Self {
57        let mut event_mappings = HashMap::new();
58        let mut response_mappings = HashMap::new();
59
60        // S3 Event mappings
61        event_mappings.insert(
62            LambdaEventType::S3Event,
63            EventTypeMapping {
64                rust_type: "S3Event".to_string(),
65                aws_events_module: "s3".to_string(),
66                imports: vec!["use aws_lambda_events::s3::S3Event;".to_string()],
67                serde_attributes: vec![],
68            },
69        );
70
71        response_mappings.insert(
72            LambdaEventType::S3Event,
73            ResponseTypeMapping {
74                rust_type: "serde_json::Value".to_string(),
75                conversion_impl: None,
76                imports: vec!["use serde_json;".to_string()],
77            },
78        );
79
80        // API Gateway v1 mappings
81        event_mappings.insert(
82            LambdaEventType::ApiGatewayProxyRequest,
83            EventTypeMapping {
84                rust_type: "ApiGatewayProxyRequest".to_string(),
85                aws_events_module: "apigw".to_string(),
86                imports: vec![
87                    "use aws_lambda_events::apigw::{ApiGatewayProxyRequest, ApiGatewayProxyResponse};".to_string(),
88                    "use std::collections::HashMap;".to_string(),
89                ],
90                serde_attributes: vec![],
91            },
92        );
93
94        response_mappings.insert(
95            LambdaEventType::ApiGatewayProxyRequest,
96            ResponseTypeMapping {
97                rust_type: "ApiGatewayProxyResponse".to_string(),
98                conversion_impl: Some(APIGW_RESPONSE_IMPL.to_string()),
99                imports: vec![
100                    "use aws_lambda_events::apigw::ApiGatewayProxyResponse;".to_string(),
101                    "use std::collections::HashMap;".to_string(),
102                ],
103            },
104        );
105
106        // API Gateway v2 mappings
107        event_mappings.insert(
108            LambdaEventType::ApiGatewayV2HttpRequest,
109            EventTypeMapping {
110                rust_type: "ApiGatewayV2httpRequest".to_string(),
111                aws_events_module: "apigw".to_string(),
112                imports: vec![
113                    "use aws_lambda_events::apigw::{ApiGatewayV2httpRequest, ApiGatewayV2httpResponse};".to_string(),
114                    "use std::collections::HashMap;".to_string(),
115                ],
116                serde_attributes: vec![],
117            },
118        );
119
120        response_mappings.insert(
121            LambdaEventType::ApiGatewayV2HttpRequest,
122            ResponseTypeMapping {
123                rust_type: "ApiGatewayV2httpResponse".to_string(),
124                conversion_impl: Some(APIGW_V2_RESPONSE_IMPL.to_string()),
125                imports: vec![
126                    "use aws_lambda_events::apigw::ApiGatewayV2httpResponse;".to_string(),
127                    "use std::collections::HashMap;".to_string(),
128                ],
129            },
130        );
131
132        // SQS Event mappings
133        event_mappings.insert(
134            LambdaEventType::SqsEvent,
135            EventTypeMapping {
136                rust_type: "SqsEvent".to_string(),
137                aws_events_module: "sqs".to_string(),
138                imports: vec![
139                    "use aws_lambda_events::sqs::{SqsEvent, SqsBatchResponse, SqsBatchItemFailure};".to_string(),
140                ],
141                serde_attributes: vec![],
142            },
143        );
144
145        response_mappings.insert(
146            LambdaEventType::SqsEvent,
147            ResponseTypeMapping {
148                rust_type: "SqsBatchResponse".to_string(),
149                conversion_impl: None,
150                imports: vec!["use aws_lambda_events::sqs::SqsBatchResponse;".to_string()],
151            },
152        );
153
154        // SNS Event mappings
155        event_mappings.insert(
156            LambdaEventType::SnsEvent,
157            EventTypeMapping {
158                rust_type: "SnsEvent".to_string(),
159                aws_events_module: "sns".to_string(),
160                imports: vec!["use aws_lambda_events::sns::SnsEvent;".to_string()],
161                serde_attributes: vec![],
162            },
163        );
164
165        response_mappings.insert(
166            LambdaEventType::SnsEvent,
167            ResponseTypeMapping {
168                rust_type: "serde_json::Value".to_string(),
169                conversion_impl: None,
170                imports: vec!["use serde_json;".to_string()],
171            },
172        );
173
174        // DynamoDB Event mappings
175        event_mappings.insert(
176            LambdaEventType::DynamodbEvent,
177            EventTypeMapping {
178                rust_type: "DynamodbEvent".to_string(),
179                aws_events_module: "dynamodb".to_string(),
180                imports: vec!["use aws_lambda_events::dynamodb::DynamodbEvent;".to_string()],
181                serde_attributes: vec![],
182            },
183        );
184
185        response_mappings.insert(
186            LambdaEventType::DynamodbEvent,
187            ResponseTypeMapping {
188                rust_type: "serde_json::Value".to_string(),
189                conversion_impl: None,
190                imports: vec!["use serde_json;".to_string()],
191            },
192        );
193
194        // EventBridge mappings
195        event_mappings.insert(
196            LambdaEventType::EventBridgeEvent(None),
197            EventTypeMapping {
198                rust_type: "EventBridgeEvent<serde_json::Value>".to_string(),
199                aws_events_module: "eventbridge".to_string(),
200                imports: vec![
201                    "use aws_lambda_events::eventbridge::EventBridgeEvent;".to_string(),
202                    "use serde_json;".to_string(),
203                ],
204                serde_attributes: vec![],
205            },
206        );
207
208        response_mappings.insert(
209            LambdaEventType::EventBridgeEvent(None),
210            ResponseTypeMapping {
211                rust_type: "()".to_string(),
212                conversion_impl: None,
213                imports: vec![],
214            },
215        );
216
217        Self {
218            event_mappings,
219            response_mappings,
220        }
221    }
222
223    /// Get event type mapping for a Lambda event type
224    pub fn get_event_mapping(&self, event_type: &LambdaEventType) -> Option<&EventTypeMapping> {
225        // Handle EventBridge with custom types
226        if let LambdaEventType::EventBridgeEvent(Some(_)) = event_type {
227            return self
228                .event_mappings
229                .get(&LambdaEventType::EventBridgeEvent(None));
230        }
231
232        self.event_mappings.get(event_type)
233    }
234
235    /// Get response type mapping for a Lambda event type
236    pub fn get_response_mapping(
237        &self,
238        event_type: &LambdaEventType,
239    ) -> Option<&ResponseTypeMapping> {
240        // Handle EventBridge with custom types
241        if let LambdaEventType::EventBridgeEvent(Some(_)) = event_type {
242            return self
243                .response_mappings
244                .get(&LambdaEventType::EventBridgeEvent(None));
245        }
246
247        self.response_mappings.get(event_type)
248    }
249
250    /// Generate Python to Rust type conversion rules for Lambda context
251    pub fn get_type_conversion_rules(&self) -> Vec<TypeConversionRule> {
252        vec![
253            TypeConversionRule {
254                python_pattern: "dict".to_string(),
255                rust_type: "T: Deserialize".to_string(),
256                lambda_context: LambdaContext::EventRoot,
257                serde_attribute: Some("#[serde(rename_all = \"camelCase\")]".to_string()),
258            },
259            TypeConversionRule {
260                python_pattern: "str".to_string(),
261                rust_type: "HeaderValue".to_string(),
262                lambda_context: LambdaContext::HeaderValue,
263                serde_attribute: Some("#[serde(with = \"http_serde::header_value\")]".to_string()),
264            },
265            TypeConversionRule {
266                python_pattern: "int".to_string(),
267                rust_type: "u16".to_string(),
268                lambda_context: LambdaContext::StatusCode,
269                serde_attribute: None,
270            },
271            TypeConversionRule {
272                python_pattern: "float".to_string(),
273                rust_type: "f64".to_string(),
274                lambda_context: LambdaContext::Timestamp,
275                serde_attribute: Some(
276                    "#[serde(with = \"aws_lambda_events::time::float_unix_epoch\")]".to_string(),
277                ),
278            },
279            TypeConversionRule {
280                python_pattern: "List[dict]".to_string(),
281                rust_type: "Vec<Record>".to_string(),
282                lambda_context: LambdaContext::Records,
283                serde_attribute: None,
284            },
285            TypeConversionRule {
286                python_pattern: "Dict[str, str]".to_string(),
287                rust_type: "HashMap<String, String>".to_string(),
288                lambda_context: LambdaContext::PathParameters,
289                serde_attribute: Some("#[serde(default)]".to_string()),
290            },
291            TypeConversionRule {
292                python_pattern: "Dict[str, List[str]]".to_string(),
293                rust_type: "HashMap<String, Vec<String>>".to_string(),
294                lambda_context: LambdaContext::QueryStringParameters,
295                serde_attribute: Some("#[serde(default)]".to_string()),
296            },
297            TypeConversionRule {
298                python_pattern: "str".to_string(),
299                rust_type: "Option<String>".to_string(),
300                lambda_context: LambdaContext::Body,
301                serde_attribute: None,
302            },
303        ]
304    }
305
306    /// Generate custom structs for EventBridge events with custom types
307    pub fn generate_custom_eventbridge_types(&self, custom_type: &str) -> Result<String> {
308        Ok(format!(
309            r#"#[derive(Debug, Deserialize, Serialize)]
310#[serde(rename_all = "camelCase")]
311pub struct {custom_type} {{
312    // Define your custom event fields here
313    // Example:
314    // pub event_id: String,
315    // pub timestamp: String,
316    // pub data: serde_json::Value,
317}}
318
319#[derive(Debug, Deserialize)]
320#[serde(tag = "detail-type", content = "detail")]
321pub enum EventType {{
322    #[serde(rename = "Custom Event")]
323    CustomEvent({custom_type}),
324    // Add more event variants as needed
325}}
326"#
327        ))
328    }
329
330    /// Generate type-safe response builders
331    pub fn generate_response_builders(&self, event_type: &LambdaEventType) -> Result<String> {
332        match event_type {
333            LambdaEventType::ApiGatewayProxyRequest => Ok(APIGW_RESPONSE_BUILDER.to_string()),
334            LambdaEventType::ApiGatewayV2HttpRequest => Ok(APIGW_V2_RESPONSE_BUILDER.to_string()),
335            LambdaEventType::SqsEvent => Ok(SQS_RESPONSE_BUILDER.to_string()),
336            _ => Ok(String::new()),
337        }
338    }
339
340    /// Add custom event type mapping
341    pub fn add_custom_event_mapping(
342        &mut self,
343        event_type: LambdaEventType,
344        mapping: EventTypeMapping,
345    ) {
346        self.event_mappings.insert(event_type, mapping);
347    }
348
349    /// Add custom response type mapping
350    pub fn add_custom_response_mapping(
351        &mut self,
352        event_type: LambdaEventType,
353        mapping: ResponseTypeMapping,
354    ) {
355        self.response_mappings.insert(event_type, mapping);
356    }
357
358    /// Generate all required imports for an event type
359    pub fn get_required_imports(&self, event_type: &LambdaEventType) -> Vec<String> {
360        let mut imports = Vec::new();
361
362        if let Some(event_mapping) = self.get_event_mapping(event_type) {
363            imports.extend(event_mapping.imports.clone());
364        }
365
366        if let Some(response_mapping) = self.get_response_mapping(event_type) {
367            imports.extend(response_mapping.imports.clone());
368        }
369
370        // Remove duplicates
371        imports.sort();
372        imports.dedup();
373        imports
374    }
375
376    /// Generate error handling conversion for Lambda-specific errors
377    pub fn generate_error_conversions(&self) -> String {
378        LAMBDA_ERROR_CONVERSIONS.to_string()
379    }
380}
381
382// Implementation templates
383const APIGW_RESPONSE_IMPL: &str = r#"impl From<HandlerOutput> for ApiGatewayProxyResponse {
384    fn from(output: HandlerOutput) -> Self {
385        ApiGatewayProxyResponse {
386            status_code: output.status_code,
387            body: Some(serde_json::to_string(&output.body).unwrap()),
388            headers: output.headers.into_iter().collect(),
389            multi_value_headers: Default::default(),
390            is_base64_encoded: false,
391        }
392    }
393}"#;
394
395const APIGW_V2_RESPONSE_IMPL: &str = r#"impl From<HandlerOutput> for ApiGatewayV2httpResponse {
396    fn from(output: HandlerOutput) -> Self {
397        ApiGatewayV2httpResponse {
398            status_code: output.status_code,
399            body: Some(serde_json::to_string(&output.body).unwrap()),
400            headers: output.headers.into_iter().collect(),
401            is_base64_encoded: Some(false),
402            cookies: vec![],
403        }
404    }
405}"#;
406
407const APIGW_RESPONSE_BUILDER: &str = r#"pub struct ResponseBuilder {
408    status_code: u16,
409    headers: HashMap<String, String>,
410    body: Option<String>,
411}
412
413impl ResponseBuilder {
414    pub fn new() -> Self {
415        Self {
416            status_code: 200,
417            headers: HashMap::new(),
418            body: None,
419        }
420    }
421
422    pub fn status(mut self, status: u16) -> Self {
423        self.status_code = status;
424        self
425    }
426
427    pub fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
428        self.headers.insert(key.into(), value.into());
429        self
430    }
431
432    pub fn json<T: serde::Serialize>(mut self, data: &T) -> Result<Self, serde_json::Error> {
433        self.body = Some(serde_json::to_string(data)?);
434        self.headers.insert("Content-Type".to_string(), "application/json".to_string());
435        Ok(self)
436    }
437
438    pub fn build(self) -> ApiGatewayProxyResponse {
439        ApiGatewayProxyResponse {
440            status_code: self.status_code,
441            headers: self.headers,
442            body: self.body,
443            multi_value_headers: Default::default(),
444            is_base64_encoded: false,
445        }
446    }
447}"#;
448
449const APIGW_V2_RESPONSE_BUILDER: &str = r#"pub struct ResponseBuilderV2 {
450    status_code: u16,
451    headers: HashMap<String, String>,
452    body: Option<String>,
453}
454
455impl ResponseBuilderV2 {
456    pub fn new() -> Self {
457        Self {
458            status_code: 200,
459            headers: HashMap::new(),
460            body: None,
461        }
462    }
463
464    pub fn status(mut self, status: u16) -> Self {
465        self.status_code = status;
466        self
467    }
468
469    pub fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
470        self.headers.insert(key.into(), value.into());
471        self
472    }
473
474    pub fn json<T: serde::Serialize>(mut self, data: &T) -> Result<Self, serde_json::Error> {
475        self.body = Some(serde_json::to_string(data)?);
476        self.headers.insert("Content-Type".to_string(), "application/json".to_string());
477        Ok(self)
478    }
479
480    pub fn build(self) -> ApiGatewayV2httpResponse {
481        ApiGatewayV2httpResponse {
482            status_code: self.status_code,
483            headers: self.headers,
484            body: self.body,
485            is_base64_encoded: Some(false),
486            cookies: vec![],
487        }
488    }
489}"#;
490
491const SQS_RESPONSE_BUILDER: &str = r#"pub struct SqsResponseBuilder {
492    batch_item_failures: Vec<SqsBatchItemFailure>,
493}
494
495impl SqsResponseBuilder {
496    pub fn new() -> Self {
497        Self {
498            batch_item_failures: Vec::new(),
499        }
500    }
501
502    pub fn add_failure(mut self, item_identifier: String) -> Self {
503        self.batch_item_failures.push(SqsBatchItemFailure {
504            item_identifier,
505        });
506        self
507    }
508
509    pub fn build(self) -> SqsBatchResponse {
510        SqsBatchResponse {
511            batch_item_failures: self.batch_item_failures,
512        }
513    }
514}"#;
515
516const LAMBDA_ERROR_CONVERSIONS: &str = r#"#[derive(Debug, thiserror::Error)]
517pub enum LambdaError {
518    #[error("Serialization failed: {0}")]
519    Serialization(#[from] serde_json::Error),
520    
521    #[error("Handler error: {0}")]
522    Handler(String),
523    
524    #[error("Runtime error: {0}")]
525    Runtime(#[from] lambda_runtime::Error),
526    
527    #[error("HTTP error: {0}")]
528    Http(String),
529    
530    #[error("Missing parameter: {0}")]
531    MissingParameter(String),
532}
533
534// Note: lambda_runtime::Error conversion would be available when using the actual lambda_runtime crate
535// impl From<LambdaError> for lambda_runtime::Error {
536//     fn from(err: LambdaError) -> Self {
537//         lambda_runtime::Error::from(err.to_string())
538//     }
539// }
540
541// Automatic error chain generation for common Python patterns
542impl From<&str> for LambdaError {
543    fn from(msg: &str) -> Self {
544        if msg.contains("KeyError") {
545            LambdaError::MissingParameter(msg.to_string())
546        } else if msg.contains("ValueError") {
547            LambdaError::Handler(msg.to_string())
548        } else {
549            LambdaError::Handler(msg.to_string())
550        }
551    }
552}"#;
553
554#[cfg(test)]
555mod tests {
556    use super::*;
557
558    #[test]
559    fn test_event_mapping_retrieval() {
560        let mapper = LambdaTypeMapper::new();
561
562        let s3_mapping = mapper.get_event_mapping(&LambdaEventType::S3Event).unwrap();
563        assert_eq!(s3_mapping.rust_type, "S3Event");
564        assert_eq!(s3_mapping.aws_events_module, "s3");
565    }
566
567    #[test]
568    fn test_response_mapping_retrieval() {
569        let mapper = LambdaTypeMapper::new();
570
571        let apigw_response = mapper
572            .get_response_mapping(&LambdaEventType::ApiGatewayProxyRequest)
573            .unwrap();
574        assert_eq!(apigw_response.rust_type, "ApiGatewayProxyResponse");
575        assert!(apigw_response.conversion_impl.is_some());
576    }
577
578    #[test]
579    fn test_eventbridge_custom_type() {
580        let mapper = LambdaTypeMapper::new();
581
582        let custom_event = LambdaEventType::EventBridgeEvent(Some("OrderEvent".to_string()));
583        let mapping = mapper.get_event_mapping(&custom_event).unwrap();
584
585        assert_eq!(mapping.rust_type, "EventBridgeEvent<serde_json::Value>");
586    }
587
588    #[test]
589    fn test_type_conversion_rules() {
590        let mapper = LambdaTypeMapper::new();
591        let rules = mapper.get_type_conversion_rules();
592
593        assert!(!rules.is_empty());
594
595        let dict_rule = rules.iter().find(|r| r.python_pattern == "dict").unwrap();
596        assert_eq!(dict_rule.rust_type, "T: Deserialize");
597        assert!(dict_rule.serde_attribute.is_some());
598    }
599
600    #[test]
601    fn test_custom_eventbridge_types_generation() {
602        let mapper = LambdaTypeMapper::new();
603        let generated = mapper
604            .generate_custom_eventbridge_types("OrderEvent")
605            .unwrap();
606
607        assert!(generated.contains("struct OrderEvent"));
608        assert!(generated.contains("enum EventType"));
609    }
610
611    #[test]
612    fn test_required_imports() {
613        let mapper = LambdaTypeMapper::new();
614        let imports = mapper.get_required_imports(&LambdaEventType::ApiGatewayProxyRequest);
615
616        assert!(imports.iter().any(|i| i.contains("ApiGatewayProxyRequest")));
617        assert!(imports.iter().any(|i| i.contains("HashMap")));
618    }
619
620    #[test]
621    fn test_response_builders() {
622        let mapper = LambdaTypeMapper::new();
623        let builder = mapper
624            .generate_response_builders(&LambdaEventType::ApiGatewayProxyRequest)
625            .unwrap();
626
627        assert!(builder.contains("ResponseBuilder"));
628        assert!(builder.contains("fn status"));
629        assert!(builder.contains("fn json"));
630    }
631
632    #[test]
633    fn test_error_conversions() {
634        let mapper = LambdaTypeMapper::new();
635        let errors = mapper.generate_error_conversions();
636
637        assert!(errors.contains("enum LambdaError"));
638        assert!(errors.contains("MissingParameter"));
639        assert!(errors.contains("thiserror::Error"));
640    }
641
642    #[test]
643    fn test_custom_mapping_addition() {
644        let mut mapper = LambdaTypeMapper::new();
645
646        let custom_event = LambdaEventType::Custom("MyEvent".to_string());
647        let custom_mapping = EventTypeMapping {
648            rust_type: "MyCustomEvent".to_string(),
649            aws_events_module: "custom".to_string(),
650            imports: vec!["use my_crate::MyCustomEvent;".to_string()],
651            serde_attributes: vec![],
652        };
653
654        mapper.add_custom_event_mapping(custom_event.clone(), custom_mapping);
655
656        let retrieved = mapper.get_event_mapping(&custom_event).unwrap();
657        assert_eq!(retrieved.rust_type, "MyCustomEvent");
658    }
659
660    // ============================================================
661    // DEPYLER-COVERAGE-95: Additional comprehensive tests
662    // ============================================================
663
664    #[test]
665    fn test_lambda_type_mapper_default() {
666        let mapper = LambdaTypeMapper::default();
667        // Default impl calls new()
668        assert!(mapper
669            .get_event_mapping(&LambdaEventType::S3Event)
670            .is_some());
671    }
672
673    #[test]
674    fn test_sqs_event_mapping() {
675        let mapper = LambdaTypeMapper::new();
676        let mapping = mapper
677            .get_event_mapping(&LambdaEventType::SqsEvent)
678            .unwrap();
679        assert_eq!(mapping.rust_type, "SqsEvent");
680        assert_eq!(mapping.aws_events_module, "sqs");
681    }
682
683    #[test]
684    fn test_sqs_response_mapping() {
685        let mapper = LambdaTypeMapper::new();
686        let mapping = mapper
687            .get_response_mapping(&LambdaEventType::SqsEvent)
688            .unwrap();
689        assert_eq!(mapping.rust_type, "SqsBatchResponse");
690    }
691
692    #[test]
693    fn test_sns_event_mapping() {
694        let mapper = LambdaTypeMapper::new();
695        let mapping = mapper
696            .get_event_mapping(&LambdaEventType::SnsEvent)
697            .unwrap();
698        assert_eq!(mapping.rust_type, "SnsEvent");
699        assert_eq!(mapping.aws_events_module, "sns");
700    }
701
702    #[test]
703    fn test_sns_response_mapping() {
704        let mapper = LambdaTypeMapper::new();
705        let mapping = mapper
706            .get_response_mapping(&LambdaEventType::SnsEvent)
707            .unwrap();
708        assert_eq!(mapping.rust_type, "serde_json::Value");
709    }
710
711    #[test]
712    fn test_dynamodb_event_mapping() {
713        let mapper = LambdaTypeMapper::new();
714        let mapping = mapper
715            .get_event_mapping(&LambdaEventType::DynamodbEvent)
716            .unwrap();
717        assert_eq!(mapping.rust_type, "DynamodbEvent");
718        assert_eq!(mapping.aws_events_module, "dynamodb");
719    }
720
721    #[test]
722    fn test_dynamodb_response_mapping() {
723        let mapper = LambdaTypeMapper::new();
724        let mapping = mapper
725            .get_response_mapping(&LambdaEventType::DynamodbEvent)
726            .unwrap();
727        assert_eq!(mapping.rust_type, "serde_json::Value");
728    }
729
730    #[test]
731    fn test_eventbridge_base_event_mapping() {
732        let mapper = LambdaTypeMapper::new();
733        let mapping = mapper
734            .get_event_mapping(&LambdaEventType::EventBridgeEvent(None))
735            .unwrap();
736        assert_eq!(mapping.rust_type, "EventBridgeEvent<serde_json::Value>");
737        assert_eq!(mapping.aws_events_module, "eventbridge");
738    }
739
740    #[test]
741    fn test_eventbridge_base_response_mapping() {
742        let mapper = LambdaTypeMapper::new();
743        let mapping = mapper
744            .get_response_mapping(&LambdaEventType::EventBridgeEvent(None))
745            .unwrap();
746        assert_eq!(mapping.rust_type, "()");
747    }
748
749    #[test]
750    fn test_eventbridge_custom_response_mapping() {
751        let mapper = LambdaTypeMapper::new();
752        let custom_event = LambdaEventType::EventBridgeEvent(Some("MyEvent".to_string()));
753        let mapping = mapper.get_response_mapping(&custom_event).unwrap();
754        assert_eq!(mapping.rust_type, "()");
755    }
756
757    #[test]
758    fn test_s3_response_mapping() {
759        let mapper = LambdaTypeMapper::new();
760        let mapping = mapper
761            .get_response_mapping(&LambdaEventType::S3Event)
762            .unwrap();
763        assert_eq!(mapping.rust_type, "serde_json::Value");
764    }
765
766    #[test]
767    fn test_apigw_v1_event_mapping() {
768        let mapper = LambdaTypeMapper::new();
769        let mapping = mapper
770            .get_event_mapping(&LambdaEventType::ApiGatewayProxyRequest)
771            .unwrap();
772        assert_eq!(mapping.rust_type, "ApiGatewayProxyRequest");
773        assert_eq!(mapping.aws_events_module, "apigw");
774        assert!(!mapping.imports.is_empty());
775    }
776
777    #[test]
778    fn test_apigw_v2_event_mapping() {
779        let mapper = LambdaTypeMapper::new();
780        let mapping = mapper
781            .get_event_mapping(&LambdaEventType::ApiGatewayV2HttpRequest)
782            .unwrap();
783        assert_eq!(mapping.rust_type, "ApiGatewayV2httpRequest");
784        assert_eq!(mapping.aws_events_module, "apigw");
785    }
786
787    #[test]
788    fn test_apigw_v2_response_mapping() {
789        let mapper = LambdaTypeMapper::new();
790        let mapping = mapper
791            .get_response_mapping(&LambdaEventType::ApiGatewayV2HttpRequest)
792            .unwrap();
793        assert_eq!(mapping.rust_type, "ApiGatewayV2httpResponse");
794        assert!(mapping.conversion_impl.is_some());
795    }
796
797    #[test]
798    fn test_get_event_mapping_unknown() {
799        let mapper = LambdaTypeMapper::new();
800        let mapping = mapper.get_event_mapping(&LambdaEventType::Custom("Unknown".to_string()));
801        assert!(mapping.is_none());
802    }
803
804    #[test]
805    fn test_get_response_mapping_unknown() {
806        let mapper = LambdaTypeMapper::new();
807        let mapping = mapper.get_response_mapping(&LambdaEventType::Custom("Unknown".to_string()));
808        assert!(mapping.is_none());
809    }
810
811    #[test]
812    fn test_type_conversion_rules_count() {
813        let mapper = LambdaTypeMapper::new();
814        let rules = mapper.get_type_conversion_rules();
815        // We define 8 conversion rules
816        assert_eq!(rules.len(), 8);
817    }
818
819    #[test]
820    fn test_type_conversion_rules_status_code() {
821        let mapper = LambdaTypeMapper::new();
822        let rules = mapper.get_type_conversion_rules();
823        let status_rule = rules
824            .iter()
825            .find(|r| matches!(r.lambda_context, LambdaContext::StatusCode))
826            .unwrap();
827        assert_eq!(status_rule.python_pattern, "int");
828        assert_eq!(status_rule.rust_type, "u16");
829    }
830
831    #[test]
832    fn test_type_conversion_rules_timestamp() {
833        let mapper = LambdaTypeMapper::new();
834        let rules = mapper.get_type_conversion_rules();
835        let timestamp_rule = rules
836            .iter()
837            .find(|r| matches!(r.lambda_context, LambdaContext::Timestamp))
838            .unwrap();
839        assert_eq!(timestamp_rule.python_pattern, "float");
840        assert_eq!(timestamp_rule.rust_type, "f64");
841        assert!(timestamp_rule.serde_attribute.is_some());
842    }
843
844    #[test]
845    fn test_type_conversion_rules_records() {
846        let mapper = LambdaTypeMapper::new();
847        let rules = mapper.get_type_conversion_rules();
848        let records_rule = rules
849            .iter()
850            .find(|r| matches!(r.lambda_context, LambdaContext::Records))
851            .unwrap();
852        assert_eq!(records_rule.python_pattern, "List[dict]");
853        assert_eq!(records_rule.rust_type, "Vec<Record>");
854    }
855
856    #[test]
857    fn test_type_conversion_rules_path_params() {
858        let mapper = LambdaTypeMapper::new();
859        let rules = mapper.get_type_conversion_rules();
860        let path_rule = rules
861            .iter()
862            .find(|r| matches!(r.lambda_context, LambdaContext::PathParameters))
863            .unwrap();
864        assert_eq!(path_rule.rust_type, "HashMap<String, String>");
865    }
866
867    #[test]
868    fn test_type_conversion_rules_query_params() {
869        let mapper = LambdaTypeMapper::new();
870        let rules = mapper.get_type_conversion_rules();
871        let query_rule = rules
872            .iter()
873            .find(|r| matches!(r.lambda_context, LambdaContext::QueryStringParameters))
874            .unwrap();
875        assert_eq!(query_rule.rust_type, "HashMap<String, Vec<String>>");
876    }
877
878    #[test]
879    fn test_type_conversion_rules_body() {
880        let mapper = LambdaTypeMapper::new();
881        let rules = mapper.get_type_conversion_rules();
882        let body_rule = rules
883            .iter()
884            .find(|r| matches!(r.lambda_context, LambdaContext::Body))
885            .unwrap();
886        assert_eq!(body_rule.rust_type, "Option<String>");
887    }
888
889    #[test]
890    fn test_type_conversion_rules_header_value() {
891        let mapper = LambdaTypeMapper::new();
892        let rules = mapper.get_type_conversion_rules();
893        let header_rule = rules
894            .iter()
895            .find(|r| matches!(r.lambda_context, LambdaContext::HeaderValue))
896            .unwrap();
897        assert_eq!(header_rule.rust_type, "HeaderValue");
898    }
899
900    #[test]
901    fn test_generate_response_builders_apigw_v2() {
902        let mapper = LambdaTypeMapper::new();
903        let builder = mapper
904            .generate_response_builders(&LambdaEventType::ApiGatewayV2HttpRequest)
905            .unwrap();
906        assert!(builder.contains("ResponseBuilderV2"));
907        assert!(builder.contains("fn status"));
908        assert!(builder.contains("fn json"));
909    }
910
911    #[test]
912    fn test_generate_response_builders_sqs() {
913        let mapper = LambdaTypeMapper::new();
914        let builder = mapper
915            .generate_response_builders(&LambdaEventType::SqsEvent)
916            .unwrap();
917        assert!(builder.contains("SqsResponseBuilder"));
918        assert!(builder.contains("add_failure"));
919    }
920
921    #[test]
922    fn test_generate_response_builders_s3() {
923        let mapper = LambdaTypeMapper::new();
924        let builder = mapper
925            .generate_response_builders(&LambdaEventType::S3Event)
926            .unwrap();
927        assert!(builder.is_empty()); // S3 has no custom builder
928    }
929
930    #[test]
931    fn test_generate_response_builders_sns() {
932        let mapper = LambdaTypeMapper::new();
933        let builder = mapper
934            .generate_response_builders(&LambdaEventType::SnsEvent)
935            .unwrap();
936        assert!(builder.is_empty());
937    }
938
939    #[test]
940    fn test_generate_response_builders_dynamodb() {
941        let mapper = LambdaTypeMapper::new();
942        let builder = mapper
943            .generate_response_builders(&LambdaEventType::DynamodbEvent)
944            .unwrap();
945        assert!(builder.is_empty());
946    }
947
948    #[test]
949    fn test_generate_response_builders_eventbridge() {
950        let mapper = LambdaTypeMapper::new();
951        let builder = mapper
952            .generate_response_builders(&LambdaEventType::EventBridgeEvent(None))
953            .unwrap();
954        assert!(builder.is_empty());
955    }
956
957    #[test]
958    fn test_add_custom_response_mapping() {
959        let mut mapper = LambdaTypeMapper::new();
960        let custom_event = LambdaEventType::Custom("MyEvent".to_string());
961        let custom_mapping = ResponseTypeMapping {
962            rust_type: "MyResponse".to_string(),
963            conversion_impl: Some("impl From ...".to_string()),
964            imports: vec!["use my_crate::MyResponse;".to_string()],
965        };
966
967        mapper.add_custom_response_mapping(custom_event.clone(), custom_mapping);
968
969        let retrieved = mapper.get_response_mapping(&custom_event).unwrap();
970        assert_eq!(retrieved.rust_type, "MyResponse");
971        assert!(retrieved.conversion_impl.is_some());
972    }
973
974    #[test]
975    fn test_required_imports_deduplication() {
976        let mapper = LambdaTypeMapper::new();
977        let imports = mapper.get_required_imports(&LambdaEventType::ApiGatewayProxyRequest);
978        // Check no duplicates (sorted + dedup in implementation)
979        let mut sorted = imports.clone();
980        sorted.sort();
981        sorted.dedup();
982        assert_eq!(imports.len(), sorted.len());
983    }
984
985    #[test]
986    fn test_required_imports_s3() {
987        let mapper = LambdaTypeMapper::new();
988        let imports = mapper.get_required_imports(&LambdaEventType::S3Event);
989        assert!(imports.iter().any(|i| i.contains("S3Event")));
990        assert!(imports.iter().any(|i| i.contains("serde_json")));
991    }
992
993    #[test]
994    fn test_required_imports_sqs() {
995        let mapper = LambdaTypeMapper::new();
996        let imports = mapper.get_required_imports(&LambdaEventType::SqsEvent);
997        assert!(imports.iter().any(|i| i.contains("SqsEvent")));
998        assert!(imports.iter().any(|i| i.contains("SqsBatchResponse")));
999    }
1000
1001    #[test]
1002    fn test_error_conversions_content() {
1003        let mapper = LambdaTypeMapper::new();
1004        let errors = mapper.generate_error_conversions();
1005        assert!(errors.contains("Serialization"));
1006        assert!(errors.contains("Handler"));
1007        assert!(errors.contains("Http"));
1008        assert!(errors.contains("KeyError"));
1009        assert!(errors.contains("ValueError"));
1010    }
1011
1012    #[test]
1013    fn test_event_type_mapping_fields() {
1014        let mapping = EventTypeMapping {
1015            rust_type: "Test".to_string(),
1016            aws_events_module: "test".to_string(),
1017            imports: vec!["use test::Test;".to_string()],
1018            serde_attributes: vec!["#[serde(default)]".to_string()],
1019        };
1020        assert_eq!(mapping.rust_type, "Test");
1021        assert_eq!(mapping.aws_events_module, "test");
1022        assert_eq!(mapping.imports.len(), 1);
1023        assert_eq!(mapping.serde_attributes.len(), 1);
1024    }
1025
1026    #[test]
1027    fn test_response_type_mapping_fields() {
1028        let mapping = ResponseTypeMapping {
1029            rust_type: "TestResponse".to_string(),
1030            conversion_impl: None,
1031            imports: vec![],
1032        };
1033        assert_eq!(mapping.rust_type, "TestResponse");
1034        assert!(mapping.conversion_impl.is_none());
1035        assert!(mapping.imports.is_empty());
1036    }
1037
1038    #[test]
1039    fn test_type_conversion_rule_fields() {
1040        let rule = TypeConversionRule {
1041            python_pattern: "str".to_string(),
1042            rust_type: "String".to_string(),
1043            lambda_context: LambdaContext::Body,
1044            serde_attribute: None,
1045        };
1046        assert_eq!(rule.python_pattern, "str");
1047        assert_eq!(rule.rust_type, "String");
1048        assert!(matches!(rule.lambda_context, LambdaContext::Body));
1049        assert!(rule.serde_attribute.is_none());
1050    }
1051
1052    #[test]
1053    fn test_lambda_context_variants() {
1054        // Ensure all variants are usable
1055        let contexts = [
1056            LambdaContext::EventRoot,
1057            LambdaContext::HeaderValue,
1058            LambdaContext::StatusCode,
1059            LambdaContext::Timestamp,
1060            LambdaContext::Records,
1061            LambdaContext::RequestContext,
1062            LambdaContext::PathParameters,
1063            LambdaContext::QueryStringParameters,
1064            LambdaContext::Body,
1065        ];
1066        assert_eq!(contexts.len(), 9);
1067    }
1068
1069    #[test]
1070    fn test_apigw_response_impl_content() {
1071        assert!(APIGW_RESPONSE_IMPL.contains("impl From<HandlerOutput>"));
1072        assert!(APIGW_RESPONSE_IMPL.contains("ApiGatewayProxyResponse"));
1073        assert!(APIGW_RESPONSE_IMPL.contains("status_code"));
1074    }
1075
1076    #[test]
1077    fn test_apigw_v2_response_impl_content() {
1078        assert!(APIGW_V2_RESPONSE_IMPL.contains("impl From<HandlerOutput>"));
1079        assert!(APIGW_V2_RESPONSE_IMPL.contains("ApiGatewayV2httpResponse"));
1080        assert!(APIGW_V2_RESPONSE_IMPL.contains("cookies"));
1081    }
1082
1083    #[test]
1084    fn test_custom_eventbridge_types_struct_definition() {
1085        let mapper = LambdaTypeMapper::new();
1086        let generated = mapper
1087            .generate_custom_eventbridge_types("TestEvent")
1088            .unwrap();
1089        assert!(generated.contains("pub struct TestEvent"));
1090        assert!(generated.contains("#[derive(Debug, Deserialize, Serialize)]"));
1091    }
1092
1093    #[test]
1094    fn test_custom_eventbridge_types_enum_definition() {
1095        let mapper = LambdaTypeMapper::new();
1096        let generated = mapper
1097            .generate_custom_eventbridge_types("MyCustom")
1098            .unwrap();
1099        assert!(generated.contains("pub enum EventType"));
1100        assert!(generated.contains("CustomEvent(MyCustom)"));
1101    }
1102
1103    #[test]
1104    fn test_s3_event_imports() {
1105        let mapper = LambdaTypeMapper::new();
1106        let mapping = mapper.get_event_mapping(&LambdaEventType::S3Event).unwrap();
1107        assert!(mapping
1108            .imports
1109            .iter()
1110            .any(|i| i.contains("aws_lambda_events::s3")));
1111    }
1112
1113    #[test]
1114    fn test_apigw_v1_response_has_conversion() {
1115        let mapper = LambdaTypeMapper::new();
1116        let mapping = mapper
1117            .get_response_mapping(&LambdaEventType::ApiGatewayProxyRequest)
1118            .unwrap();
1119        let impl_str = mapping.conversion_impl.as_ref().unwrap();
1120        assert!(impl_str.contains("From<HandlerOutput>"));
1121    }
1122
1123    #[test]
1124    fn test_eventbridge_imports() {
1125        let mapper = LambdaTypeMapper::new();
1126        let mapping = mapper
1127            .get_event_mapping(&LambdaEventType::EventBridgeEvent(None))
1128            .unwrap();
1129        assert!(mapping
1130            .imports
1131            .iter()
1132            .any(|i| i.contains("EventBridgeEvent")));
1133        assert!(mapping.imports.iter().any(|i| i.contains("serde_json")));
1134    }
1135
1136    #[test]
1137    fn test_empty_serde_attributes() {
1138        let mapper = LambdaTypeMapper::new();
1139        let mapping = mapper.get_event_mapping(&LambdaEventType::S3Event).unwrap();
1140        assert!(mapping.serde_attributes.is_empty());
1141    }
1142
1143    #[test]
1144    fn test_response_builder_contains_build_method() {
1145        let mapper = LambdaTypeMapper::new();
1146        let builder = mapper
1147            .generate_response_builders(&LambdaEventType::ApiGatewayProxyRequest)
1148            .unwrap();
1149        assert!(builder.contains("fn build(self)"));
1150    }
1151
1152    #[test]
1153    fn test_sqs_response_builder_batch_failures() {
1154        let mapper = LambdaTypeMapper::new();
1155        let builder = mapper
1156            .generate_response_builders(&LambdaEventType::SqsEvent)
1157            .unwrap();
1158        assert!(builder.contains("batch_item_failures"));
1159        assert!(builder.contains("SqsBatchItemFailure"));
1160    }
1161
1162    #[test]
1163    fn test_lambda_error_conversions_from_str() {
1164        let errors = LAMBDA_ERROR_CONVERSIONS;
1165        assert!(errors.contains("impl From<&str> for LambdaError"));
1166    }
1167}