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::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#[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 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 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 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 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 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 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 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 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 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), }
169 }
170
171 fn as_any(&self) -> &dyn std::any::Any {
172 self
173 }
174}