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}