momento_functions_host/aws/
lambda.rs

1//! Host interfaces for working with AWS Lambda
2use crate::encoding::{Encode, EncodeError, Extract, ExtractError};
3use momento_functions_wit::host::momento::host;
4use momento_functions_wit::host::momento::host::aws_lambda::LambdaError;
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
18/// An error occurred while invoking a Lambda function.
19#[derive(Debug, thiserror::Error)]
20pub enum InvokeError<E>
21where
22    E: EncodeError,
23{
24    /// An error occurred while encoding the provided payload.
25    #[error("Failed to encode payload.")]
26    EncodeFailed {
27        /// The underlying encode error.
28        cause: E,
29    },
30    /// An error occurred when calling the host invoke function.
31    #[error(transparent)]
32    LambdaError(#[from] LambdaError),
33}
34
35impl LambdaClient {
36    /// Create a new Lambda client.
37    ///
38    /// ```rust
39    /// # use momento_functions_host::aws::auth::AwsCredentialsProvider;
40    /// # use momento_functions_host::aws::lambda::LambdaClient;
41    /// # use momento_functions_host::build_environment_aws_credentials;    /// #
42    /// use momento_functions_wit::host::momento::host::aws_auth::AuthError;
43    ///
44    /// fn f() -> Result<(), AuthError> {
45    /// let client = LambdaClient::new(
46    ///     &AwsCredentialsProvider::new(
47    ///         "us-east-1",
48    ///         build_environment_aws_credentials!()
49    ///     )?
50    /// );
51    /// # Ok(())
52    /// # }
53    /// ```
54    pub fn new(credentials: &auth::AwsCredentialsProvider) -> Self {
55        Self {
56            client: host::aws_lambda::Client::new(credentials.resource()),
57        }
58    }
59
60    /// Invoke a lambda function.
61    ///
62    /// You can use strings, bytes, or structs that are Serializable.
63    ///
64    /// Examples:
65    /// ________
66    /// ```rust
67    /// use momento_functions_host::aws::lambda::{InvokeError, LambdaClient};
68    /// use momento_functions_host::encoding::Json;;
69    ///
70    /// # fn f(client: &LambdaClient) -> Result<(), InvokeError<&str>> {
71    /// // With a payload
72    /// client.invoke(
73    ///     "my_lambda_function",
74    ///     "hello world",
75    /// )?;
76    ///
77    /// // With a payload and a qualifier
78    /// client.invoke(
79    ///     ("my_lambda_function", "v1"),
80    ///     "hello world",
81    /// )?;
82    ///
83    /// // Without a payload
84    /// client.invoke(
85    ///     "my_lambda_function",
86    ///     (),
87    /// )?;
88    ///
89    /// // With literal bytes
90    /// client.invoke(
91    ///     "my_lambda_function",
92    ///     b"some literal bytes".to_vec(),
93    /// )?;
94    /// # Ok(())}
95    /// ```
96    /// ________
97    /// With json-encoded payloads
98    /// ```rust
99    /// use momento_functions_host::aws::lambda::{InvokeError, LambdaClient};
100    /// use momento_functions_host::encoding::Json;
101    ///
102    /// #[derive(serde::Serialize)]
103    /// struct MyStruct {
104    ///     hello: String
105    /// }
106    /// #[derive(serde::Deserialize)]
107    /// struct Reply {
108    ///     message: String
109    /// }
110    ///
111    /// # fn f(client: &LambdaClient) -> Result<(), InvokeError<Json<MyStruct>>> {
112    /// // Just a request payload, encoded as JSON
113    /// client.invoke(
114    ///     "my_lambda_function",
115    ///     Json(MyStruct { hello: "hello".to_string() }),
116    /// )?;
117    ///
118    /// // Request and response payload, both encoded as JSON
119    /// let Json(reply): Json<Reply> = client.invoke(
120    ///     "my_lambda_function",
121    ///     Json(MyStruct { hello: "hello".to_string() }),
122    /// )?
123    /// .extract()?;
124    ///
125    /// let message = reply.message;
126    /// # Ok(())}
127    /// ```
128    pub fn invoke<E: Encode>(
129        &self,
130        name: impl Into<LambdaName>,
131        payload: E,
132    ) -> Result<InvokeResponse, InvokeError<E::Error>> {
133        let (function_name, qualifier) = name.into().into_inner();
134        let request = host::aws_lambda::InvokeRequest {
135            function_name,
136            qualifier,
137            payload: Some(
138                payload
139                    .try_serialize()
140                    .map_err(|e| InvokeError::EncodeFailed { cause: e })?
141                    .into(),
142            ),
143            invocation_type: host::aws_lambda::InvocationType::RequestResponse(
144                host::aws_lambda::InvokeSynchronousParameters {
145                    log_type: None,
146                    client_context: None,
147                },
148            ),
149        };
150        let output = self.client.invoke(&request)?;
151
152        Ok(InvokeResponse {
153            status_code: output.status_code,
154            payload: output.payload,
155        })
156    }
157}
158
159/// Result from Lambda
160pub struct InvokeResponse {
161    /// The status code of the response
162    status_code: i32,
163    /// The payload of the response
164    payload: Option<Vec<u8>>,
165}
166
167/// An error occurred when extracting the Lambda response.
168#[derive(Debug, thiserror::Error)]
169pub enum ResponseExtractError<E: ExtractError> {
170    /// An error occurred when calling the provided extract method.
171    Extract {
172        /// The underlying error.
173        cause: E,
174    },
175    /// The response was missing a payload.
176    MissingPayload,
177}
178
179impl InvokeResponse {
180    /// Get the status code of the response
181    pub fn status_code(&self) -> i32 {
182        self.status_code
183    }
184
185    /// Take the payload of the response
186    ///
187    /// This consumes the payload; if you call it again, it will return None.
188    pub fn take_payload(&mut self) -> Option<Vec<u8>> {
189        self.payload.take()
190    }
191
192    /// Take the payload of the response and decode it.
193    ///
194    /// This consumes the payload; if you call it again, it will return an Error.
195    pub fn extract<E: Extract>(&mut self) -> Result<E, ResponseExtractError<E::Error>> {
196        let payload = self
197            .take_payload()
198            .ok_or_else(|| ResponseExtractError::MissingPayload)?;
199        E::extract(payload).map_err(|e| ResponseExtractError::Extract { cause: e })
200    }
201}
202
203/// Identifier for a Lambda function
204pub enum LambdaName {
205    /// Lambda function name
206    Name(String),
207    /// Lambda function ARN
208    Qualified {
209        /// Name of the lambda function
210        name: String,
211        /// Version or alias of the lambda function
212        qualifier: String,
213    },
214}
215impl LambdaName {
216    fn into_inner(self) -> (String, Option<String>) {
217        match self {
218            LambdaName::Name(name) => (name, None),
219            LambdaName::Qualified { name, qualifier } => (name, Some(qualifier)),
220        }
221    }
222}
223impl From<String> for LambdaName {
224    fn from(name: String) -> Self {
225        LambdaName::Name(name)
226    }
227}
228impl From<&str> for LambdaName {
229    fn from(name: &str) -> Self {
230        LambdaName::Name(name.to_string())
231    }
232}
233impl From<(String, String)> for LambdaName {
234    fn from((name, qualifier): (String, String)) -> Self {
235        LambdaName::Qualified { name, qualifier }
236    }
237}
238impl From<(&str, String)> for LambdaName {
239    fn from((name, qualifier): (&str, String)) -> Self {
240        LambdaName::Qualified {
241            name: name.to_string(),
242            qualifier,
243        }
244    }
245}
246impl From<(String, &str)> for LambdaName {
247    fn from((name, qualifier): (String, &str)) -> Self {
248        LambdaName::Qualified {
249            name,
250            qualifier: qualifier.to_string(),
251        }
252    }
253}
254impl From<(&str, &str)> for LambdaName {
255    fn from((name, qualifier): (&str, &str)) -> Self {
256        LambdaName::Qualified {
257            name: name.to_string(),
258            qualifier: qualifier.to_string(),
259        }
260    }
261}