momento_functions_host/aws/
lambda.rs

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