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}