momento_functions_host/aws/lambda.rs
1//! Host interfaces for working with AWS Lambda
2use momento_functions_wit::host::momento::host;
3
4use crate::FunctionResult;
5
6use super::auth;
7
8/// Lambda client for host interfaces.
9///
10/// This client uses Momento's host-provided AWS communication channel, which
11/// is kept hot at all times. When your Function has not run in several days or more,
12/// the channel is still hot and ready, keeping your Function invocations predictable
13/// even when your demand is unpredictable.
14pub struct LambdaClient {
15 client: host::aws_lambda::Client,
16}
17
18impl LambdaClient {
19 /// Create a new Lambda client.
20 ///
21 /// ```rust
22 /// # use momento_functions_host::aws::auth::AwsCredentialsProvider;
23 /// # use momento_functions_host::aws::lambda::LambdaClient;
24 /// # use momento_functions_host::build_environment_aws_credentials;
25 /// # use momento_functions_host::FunctionResult;
26 /// # fn f() -> FunctionResult<()> {
27 /// let client = LambdaClient::new(
28 /// &AwsCredentialsProvider::new(
29 /// "us-east-1",
30 /// build_environment_aws_credentials!()
31 /// )?
32 /// );
33 /// # Ok(())
34 /// # }
35 /// ```
36 pub fn new(credentials: &auth::AwsCredentialsProvider) -> Self {
37 Self {
38 client: host::aws_lambda::Client::new(credentials.resource()),
39 }
40 }
41
42 /// Invoke a lambda function.
43 ///
44 /// You can use strings, bytes, or structs that are Serializable.
45 ///
46 /// Examples:
47 /// ________
48 /// ```rust
49 /// use momento_functions_host::aws::lambda::{EmptyInvokePayload, InvokePayload, LambdaClient};
50 /// use momento_functions_host::{FunctionResult, Error};
51 ///
52 /// # fn f(client: &LambdaClient) -> FunctionResult<()> {
53 /// // With a payload
54 /// client.invoke(
55 /// "my_lambda_function",
56 /// "hello world",
57 /// )?;
58 ///
59 /// // With a payload and a qualifier
60 /// client.invoke(
61 /// ("my_lambda_function", "v1"),
62 /// "hello world",
63 /// )?;
64 ///
65 /// // Without a payload
66 /// client.invoke(
67 /// "my_lambda_function",
68 /// EmptyInvokePayload,
69 /// )?;
70 ///
71 /// // With literal bytes
72 /// client.invoke(
73 /// "my_lambda_function",
74 /// b"some literal bytes".to_vec(),
75 /// )?;
76 /// # Ok(())}
77 /// ```
78 /// ________
79 /// With json-encoded payloads
80 /// ```rust
81 /// use momento_functions_host::aws::lambda::{InvokePayload, Json, LambdaClient};
82 /// use momento_functions_host::{FunctionResult, Error};
83 ///
84 /// #[derive(serde::Serialize)]
85 /// struct MyStruct {
86 /// hello: String
87 /// }
88 /// #[derive(serde::Deserialize)]
89 /// struct Reply {
90 /// message: String
91 /// }
92 ///
93 /// # fn f(client: &LambdaClient) -> FunctionResult<()> {
94 /// // Just a request payload, encoded as JSON
95 /// client.invoke(
96 /// "my_lambda_function",
97 /// Json(MyStruct { hello: "hello".to_string() }),
98 /// )?;
99 ///
100 /// // Request and response payload, both encoded as JSON
101 /// let Json(reply): Json<Reply> = client.invoke(
102 /// "my_lambda_function",
103 /// Json(MyStruct { hello: "hello".to_string() }),
104 /// )?
105 /// .extract()?;
106 ///
107 /// let message = reply.message;
108 /// # Ok(())}
109 /// ```
110 pub fn invoke(
111 &self,
112 name: impl Into<LambdaName>,
113 payload: impl InvokePayload,
114 ) -> FunctionResult<InvokeResponse> {
115 let (function_name, qualifier) = name.into().into_inner();
116 let request = host::aws_lambda::InvokeRequest {
117 function_name,
118 qualifier,
119 payload: payload.try_into()?,
120 invocation_type: host::aws_lambda::InvocationType::RequestResponse(
121 host::aws_lambda::InvokeSynchronousParameters {
122 log_type: None,
123 client_context: None,
124 },
125 ),
126 };
127 let output = self.client.invoke(&request)?;
128
129 Ok(InvokeResponse {
130 status_code: output.status_code,
131 payload: output.payload,
132 })
133 }
134}
135
136/// Request payload to a Lambda function
137pub trait InvokePayload {
138 /// Convert the payload to a vector of bytes
139 fn try_into(self) -> FunctionResult<Option<Vec<u8>>>;
140}
141
142impl InvokePayload for Vec<u8> {
143 fn try_into(self) -> FunctionResult<Option<Vec<u8>>> {
144 Ok(Some(self))
145 }
146}
147impl InvokePayload for String {
148 fn try_into(self) -> FunctionResult<Option<Vec<u8>>> {
149 Ok(Some(self.into_bytes()))
150 }
151}
152impl InvokePayload for &str {
153 fn try_into(self) -> FunctionResult<Option<Vec<u8>>> {
154 Ok(Some(self.as_bytes().to_vec()))
155 }
156}
157impl InvokePayload for Option<Vec<u8>> {
158 fn try_into(self) -> FunctionResult<Option<Vec<u8>>> {
159 Ok(self)
160 }
161}
162
163/// No payload to a Lambda function
164pub struct EmptyInvokePayload;
165impl InvokePayload for EmptyInvokePayload {
166 fn try_into(self) -> FunctionResult<Option<Vec<u8>>> {
167 Ok(None)
168 }
169}
170
171/// Payload extractor for encodings
172pub trait Extract: Sized {
173 /// Convert from a payload to a value
174 fn extract(payload: Vec<u8>) -> FunctionResult<Self>;
175}
176
177/// Payload to a Lambda function, to be encoded as JSON
178pub struct Json<T>(pub T);
179impl<T: serde::de::DeserializeOwned> Extract for Json<T> {
180 fn extract(payload: Vec<u8>) -> FunctionResult<Self> {
181 Ok(Json(serde_json::from_slice(&payload).map_err(|e| {
182 crate::Error::MessageError(format!("failed to deserialize json: {e}"))
183 })?))
184 }
185}
186impl<T: serde::Serialize> InvokePayload for Json<T> {
187 fn try_into(self) -> FunctionResult<Option<Vec<u8>>> {
188 let value = serde_json::to_vec(&self.0)
189 .map_err(|e| crate::Error::MessageError(format!("failed to serialize json: {e}")))?;
190 Ok(Some(value))
191 }
192}
193
194/// Result from Lambda
195pub struct InvokeResponse {
196 /// The status code of the response
197 status_code: i32,
198 /// The payload of the response
199 payload: Option<Vec<u8>>,
200}
201impl InvokeResponse {
202 /// Get the status code of the response
203 pub fn status_code(&self) -> i32 {
204 self.status_code
205 }
206
207 /// Take the payload of the response
208 ///
209 /// This consumes the payload; if you call it again, it will return None.
210 pub fn take_payload(&mut self) -> Option<Vec<u8>> {
211 self.payload.take()
212 }
213
214 /// Take the payload of the response and decode it.
215 ///
216 /// This consumes the payload; if you call it again, it will return an Error.
217 pub fn extract<E: Extract>(&mut self) -> FunctionResult<E> {
218 let payload = self
219 .take_payload()
220 .ok_or_else(|| crate::Error::MessageError("no payload in response".to_string()))?;
221 E::extract(payload)
222 }
223}
224
225/// Identifier for a Lambda function
226pub enum LambdaName {
227 /// Lambda function name
228 Name(String),
229 /// Lambda function ARN
230 Qualified {
231 /// Name of the lambda function
232 name: String,
233 /// Version or alias of the lambda function
234 qualifier: String,
235 },
236}
237impl LambdaName {
238 fn into_inner(self) -> (String, Option<String>) {
239 match self {
240 LambdaName::Name(name) => (name, None),
241 LambdaName::Qualified { name, qualifier } => (name, Some(qualifier)),
242 }
243 }
244}
245impl From<String> for LambdaName {
246 fn from(name: String) -> Self {
247 LambdaName::Name(name)
248 }
249}
250impl From<&str> for LambdaName {
251 fn from(name: &str) -> Self {
252 LambdaName::Name(name.to_string())
253 }
254}
255impl From<(String, String)> for LambdaName {
256 fn from((name, qualifier): (String, String)) -> Self {
257 LambdaName::Qualified { name, qualifier }
258 }
259}
260impl From<(&str, String)> for LambdaName {
261 fn from((name, qualifier): (&str, String)) -> Self {
262 LambdaName::Qualified {
263 name: name.to_string(),
264 qualifier,
265 }
266 }
267}
268impl From<(String, &str)> for LambdaName {
269 fn from((name, qualifier): (String, &str)) -> Self {
270 LambdaName::Qualified {
271 name,
272 qualifier: qualifier.to_string(),
273 }
274 }
275}
276impl From<(&str, &str)> for LambdaName {
277 fn from((name, qualifier): (&str, &str)) -> Self {
278 LambdaName::Qualified {
279 name: name.to_string(),
280 qualifier: qualifier.to_string(),
281 }
282 }
283}
284
285impl From<host::aws_lambda::LambdaError> for crate::Error {
286 fn from(e: host::aws_lambda::LambdaError) -> Self {
287 match e {
288 host::aws_lambda::LambdaError::Unauthorized(u) => Self::MessageError(u),
289 host::aws_lambda::LambdaError::Malformed(s) => Self::MessageError(s),
290 host::aws_lambda::LambdaError::Other(o) => Self::MessageError(o),
291 }
292 }
293}