Skip to main content

alien_bindings/providers/function/
aws_lambda.rs

1use crate::error::{ErrorData, Result};
2use crate::traits::{Binding, Function, FunctionInvokeRequest, FunctionInvokeResponse};
3use alien_aws_clients::lambda::{InvocationType, InvokeRequest, LambdaApi, LambdaClient};
4use alien_aws_clients::AwsClientConfig;
5use alien_core::bindings::LambdaFunctionBinding;
6use alien_error::{AlienError, Context, IntoAlienError};
7use async_trait::async_trait;
8use base64::Engine;
9use reqwest::Client;
10use std::collections::BTreeMap;
11
12/// AWS Lambda function binding implementation
13#[derive(Debug)]
14pub struct LambdaFunction {
15    client: LambdaClient,
16    binding: LambdaFunctionBinding,
17}
18
19impl LambdaFunction {
20    pub fn new(client: Client, config: AwsClientConfig, binding: LambdaFunctionBinding) -> Self {
21        let lambda_client = LambdaClient::new(client, config);
22        Self {
23            client: lambda_client,
24            binding,
25        }
26    }
27
28    /// Get the function name from the binding, resolving template expressions if needed
29    fn get_function_name(&self) -> Result<String> {
30        self.binding
31            .function_name
32            .clone()
33            .into_value("function", "function_name")
34            .context(ErrorData::BindingConfigInvalid {
35                binding_name: "function".to_string(),
36                reason: "Failed to resolve function_name from binding".to_string(),
37            })
38    }
39}
40
41impl Binding for LambdaFunction {}
42
43#[async_trait]
44impl Function for LambdaFunction {
45    async fn invoke(&self, request: FunctionInvokeRequest) -> Result<FunctionInvokeResponse> {
46        let function_name = self.get_function_name()?;
47
48        // Create the invoke request payload
49        // For Lambda, we need to construct an HTTP-like payload that the runtime can understand
50        let payload = serde_json::json!({
51            "httpMethod": request.method.to_uppercase(),
52            "path": request.path,
53            "headers": request.headers,
54            "body": base64::engine::general_purpose::STANDARD.encode(&request.body),
55            "isBase64Encoded": true
56        });
57
58        let payload_bytes =
59            serde_json::to_vec(&payload)
60                .into_alien_error()
61                .context(ErrorData::Other {
62                    message: "Failed to serialize Lambda invoke payload".to_string(),
63                })?;
64
65        // Use the target_function if provided, otherwise use the bound function
66        let target_function = if !request.target_function.is_empty() {
67            request.target_function.clone()
68        } else {
69            function_name
70        };
71
72        let invoke_request = InvokeRequest::builder()
73            .function_name(target_function.clone())
74            .invocation_type(InvocationType::RequestResponse)
75            .payload(payload_bytes)
76            .build();
77
78        let response = self
79            .client
80            .invoke(invoke_request)
81            .await
82            .context(ErrorData::Other {
83                message: format!("Failed to invoke Lambda function '{}'", target_function),
84            })?;
85
86        // Check for function error
87        if let Some(function_error) = response.function_error {
88            return Err(AlienError::new(ErrorData::Other {
89                message: format!(
90                    "Lambda function '{}' returned error: {}",
91                    target_function, function_error
92                ),
93            }));
94        }
95
96        // Parse the response payload
97        let lambda_response: serde_json::Value = serde_json::from_slice(&response.payload)
98            .into_alien_error()
99            .context(ErrorData::Other {
100                message: "Failed to parse Lambda response payload".to_string(),
101            })?;
102
103        // Extract HTTP response components
104        let status = lambda_response
105            .get("statusCode")
106            .and_then(|s| s.as_u64())
107            .unwrap_or(200) as u16;
108
109        let headers = lambda_response
110            .get("headers")
111            .and_then(|h| h.as_object())
112            .map(|obj| {
113                obj.iter()
114                    .map(|(k, v)| (k.clone(), v.as_str().unwrap_or("").to_string()))
115                    .collect::<BTreeMap<String, String>>()
116            })
117            .unwrap_or_default();
118
119        let body = if let Some(body_str) = lambda_response.get("body").and_then(|b| b.as_str()) {
120            // Check if body is base64 encoded
121            let is_base64 = lambda_response
122                .get("isBase64Encoded")
123                .and_then(|b| b.as_bool())
124                .unwrap_or(false);
125
126            if is_base64 {
127                base64::engine::general_purpose::STANDARD
128                    .decode(body_str)
129                    .into_alien_error()
130                    .context(ErrorData::Other {
131                        message: "Failed to decode base64 response body".to_string(),
132                    })?
133            } else {
134                body_str.as_bytes().to_vec()
135            }
136        } else {
137            Vec::new()
138        };
139
140        Ok(FunctionInvokeResponse {
141            status,
142            headers,
143            body,
144        })
145    }
146
147    async fn get_function_url(&self) -> Result<Option<String>> {
148        // First check if we have it in the binding
149        if let Some(url_binding) = &self.binding.url {
150            let url = url_binding.clone().into_value("function", "url").context(
151                ErrorData::BindingConfigInvalid {
152                    binding_name: "function".to_string(),
153                    reason: "Failed to resolve url from binding".to_string(),
154                },
155            )?;
156            return Ok(Some(url));
157        }
158
159        // If not in binding, try to fetch it from AWS
160        let function_name = self.get_function_name()?;
161        match self
162            .client
163            .get_function_url_config(&function_name, None)
164            .await
165        {
166            Ok(url_config) => Ok(Some(url_config.function_url)),
167            Err(_) => Ok(None), // Function URL doesn't exist
168        }
169    }
170
171    fn as_any(&self) -> &dyn std::any::Any {
172        self
173    }
174}