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