alien_bindings/providers/function/
aws_lambda.rs1use crate::error::{ErrorData, Result};
2use crate::traits::{Binding, Function, FunctionInvokeRequest, FunctionInvokeResponse};
3use alien_aws_clients::lambda::{InvocationType, InvokeRequest, LambdaApi, LambdaClient};
4use alien_aws_clients::AwsCredentialProvider;
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#[derive(Debug)]
14pub struct LambdaFunction {
15 client: LambdaClient,
16 binding: LambdaFunctionBinding,
17}
18
19impl LambdaFunction {
20 pub fn new(
21 client: Client,
22 credentials: AwsCredentialProvider,
23 binding: LambdaFunctionBinding,
24 ) -> Self {
25 let lambda_client = LambdaClient::new(client, credentials);
26 Self {
27 client: lambda_client,
28 binding,
29 }
30 }
31
32 fn get_function_name(&self) -> Result<String> {
34 self.binding
35 .function_name
36 .clone()
37 .into_value("function", "function_name")
38 .context(ErrorData::BindingConfigInvalid {
39 binding_name: "function".to_string(),
40 reason: "Failed to resolve function_name from binding".to_string(),
41 })
42 }
43}
44
45impl Binding for LambdaFunction {}
46
47#[async_trait]
48impl Function for LambdaFunction {
49 async fn invoke(&self, request: FunctionInvokeRequest) -> Result<FunctionInvokeResponse> {
50 let function_name = self.get_function_name()?;
51
52 let payload = serde_json::json!({
55 "httpMethod": request.method.to_uppercase(),
56 "path": request.path,
57 "headers": request.headers,
58 "body": base64::engine::general_purpose::STANDARD.encode(&request.body),
59 "isBase64Encoded": true
60 });
61
62 let payload_bytes =
63 serde_json::to_vec(&payload)
64 .into_alien_error()
65 .context(ErrorData::Other {
66 message: "Failed to serialize Lambda invoke payload".to_string(),
67 })?;
68
69 let target_function = if !request.target_function.is_empty() {
71 request.target_function.clone()
72 } else {
73 function_name
74 };
75
76 let invoke_request = InvokeRequest::builder()
77 .function_name(target_function.clone())
78 .invocation_type(InvocationType::RequestResponse)
79 .payload(payload_bytes)
80 .build();
81
82 let response = self
83 .client
84 .invoke(invoke_request)
85 .await
86 .context(ErrorData::Other {
87 message: format!("Failed to invoke Lambda function '{}'", target_function),
88 })?;
89
90 if let Some(function_error) = response.function_error {
92 return Err(AlienError::new(ErrorData::Other {
93 message: format!(
94 "Lambda function '{}' returned error: {}",
95 target_function, function_error
96 ),
97 }));
98 }
99
100 let lambda_response: serde_json::Value = serde_json::from_slice(&response.payload)
102 .into_alien_error()
103 .context(ErrorData::Other {
104 message: "Failed to parse Lambda response payload".to_string(),
105 })?;
106
107 let status = lambda_response
109 .get("statusCode")
110 .and_then(|s| s.as_u64())
111 .unwrap_or(200) as u16;
112
113 let headers = lambda_response
114 .get("headers")
115 .and_then(|h| h.as_object())
116 .map(|obj| {
117 obj.iter()
118 .map(|(k, v)| (k.clone(), v.as_str().unwrap_or("").to_string()))
119 .collect::<BTreeMap<String, String>>()
120 })
121 .unwrap_or_default();
122
123 let body = if let Some(body_str) = lambda_response.get("body").and_then(|b| b.as_str()) {
124 let is_base64 = lambda_response
126 .get("isBase64Encoded")
127 .and_then(|b| b.as_bool())
128 .unwrap_or(false);
129
130 if is_base64 {
131 base64::engine::general_purpose::STANDARD
132 .decode(body_str)
133 .into_alien_error()
134 .context(ErrorData::Other {
135 message: "Failed to decode base64 response body".to_string(),
136 })?
137 } else {
138 body_str.as_bytes().to_vec()
139 }
140 } else {
141 Vec::new()
142 };
143
144 Ok(FunctionInvokeResponse {
145 status,
146 headers,
147 body,
148 })
149 }
150
151 async fn get_function_url(&self) -> Result<Option<String>> {
152 if let Some(url_binding) = &self.binding.url {
154 let url = url_binding.clone().into_value("function", "url").context(
155 ErrorData::BindingConfigInvalid {
156 binding_name: "function".to_string(),
157 reason: "Failed to resolve url from binding".to_string(),
158 },
159 )?;
160 return Ok(Some(url));
161 }
162
163 let function_name = self.get_function_name()?;
165 match self
166 .client
167 .get_function_url_config(&function_name, None)
168 .await
169 {
170 Ok(url_config) => Ok(Some(url_config.function_url)),
171 Err(_) => Ok(None), }
173 }
174
175 fn as_any(&self) -> &dyn std::any::Any {
176 self
177 }
178}