momento_functions_host/
http.rs

1//! Host interface utilities for HTTP
2
3use momento_functions_wit::host::momento::host::http;
4use thiserror::Error;
5
6use crate::encoding::EncodeError;
7use crate::{
8    aws,
9    encoding::{Encode, Extract},
10};
11
12/// HTTP Get response
13#[derive(Debug, serde::Deserialize, serde::Serialize)]
14pub struct Response {
15    /// HTTP status code
16    pub status: u16,
17    /// HTTP response headers
18    pub headers: Vec<(String, String)>,
19    /// HTTP response body
20    pub body: Vec<u8>,
21}
22impl Response {
23    /// Take the payload of the response and decode it.
24    ///
25    /// This consumes the payload; if you call it again, it will return an Error.
26    ///
27    /// ```rust
28    /// # use momento_functions_host::http;
29    /// use momento_functions_host::encoding::Json;
30    ///
31    /// # fn f() -> Result<(), serde_json::error::Error> {
32    /// #[derive(serde::Serialize)]
33    /// struct Request {
34    ///     message: String
35    /// }
36    /// #[derive(serde::Deserialize)]
37    /// struct Reply {
38    ///     message: String
39    /// }
40    ///
41    /// let Json(reply): Json<Reply> = http::post(
42    ///     "https://gomomento.com",
43    ///     [
44    ///         ("authorization".to_string(), "abc123".to_string()),
45    ///     ],
46    ///     Json(Request { message: "hello".to_string() })
47    /// )?
48    /// .extract()?;
49    /// # Ok(()) }
50    /// ```
51    pub fn extract<E: Extract>(&mut self) -> Result<E, E::Error> {
52        E::extract(std::mem::take(&mut self.body))
53    }
54}
55
56/// An error occurred while calling an HTTP Get method.
57#[derive(Debug, Error)]
58pub enum HttpGetError {
59    /// An error occurred while calling the host http function.
60    #[error(transparent)]
61    HttpError(#[from] http::Error),
62}
63
64/// HTTP GET
65///
66/// ```rust
67/// # use momento_functions_host::http;
68///
69/// # fn f() -> Result<(), http::HttpGetError> {
70/// http::get("https://gomomento.com", [])?;
71/// http::get(
72///     "https://gomomento.com",
73///     [
74///         ("authorization".to_string(), "abc123".to_string()),
75///     ]
76/// )?;
77/// # Ok(()) }
78/// ```
79pub fn get(
80    url: impl Into<String>,
81    headers: impl IntoIterator<Item = (String, String)>,
82) -> Result<Response, HttpGetError> {
83    let http::Response {
84        status,
85        headers,
86        body,
87    } = http::get(&http::Request {
88        url: url.into(),
89        headers: headers.into_iter().collect(),
90        body: Default::default(),
91        authorization: http::Authorization::None,
92    })?;
93    Ok(Response {
94        status,
95        headers,
96        body,
97    })
98}
99
100/// An error occurred while calling an HTTP Put method.
101#[derive(Debug, Error)]
102pub enum HttpPutError<E: EncodeError> {
103    /// An error occurred while calling the host http function.
104    #[error(transparent)]
105    HttpError(#[from] http::Error),
106    /// An error occurred while encoding the provided body.
107    #[error("Failed to encode body.")]
108    EncodeFailed {
109        /// The underlying encoding error.
110        cause: E,
111    },
112}
113
114/// HTTP PUT
115///
116/// ```rust
117/// # use momento_functions_host::http;
118/// # use momento_functions_host::http::HttpPutError;
119/// # fn f() -> Result<(), HttpPutError<&'static str>> {
120/// http::put("https://gomomento.com", [], b"hello".as_ref())?;
121/// # Ok(())}
122///
123/// use momento_functions_host::encoding::Json;
124/// #[derive(serde::Serialize)]
125/// struct MyStruct {
126///     message: String
127/// }
128///
129/// # fn g() -> Result<(), HttpPutError<Json<MyStruct>>> {
130/// http::put(
131///     "https://gomomento.com",
132///     [
133///         ("authorization".to_string(), "abc123".to_string()),
134///     ],
135///     Json(MyStruct { message: "hello".to_string() })
136/// )?;
137/// # Ok(())}
138/// ```
139pub fn put<E: Encode>(
140    url: impl Into<String>,
141    headers: impl IntoIterator<Item = (String, String)>,
142    body: E,
143) -> Result<Response, HttpPutError<E::Error>> {
144    let http::Response {
145        status,
146        headers,
147        body,
148    } = http::put(&http::Request {
149        url: url.into(),
150        headers: headers.into_iter().collect(),
151        body: body
152            .try_serialize()
153            .map_err(|e| HttpPutError::EncodeFailed { cause: e })?
154            .into(),
155        authorization: http::Authorization::None,
156    })?;
157    Ok(Response {
158        status,
159        headers,
160        body,
161    })
162}
163
164/// An error occurred while calling an HTTP Post method.
165#[derive(Debug, Error)]
166pub enum HttpPostError<E: EncodeError> {
167    /// An error occurred while calling the host http function.
168    #[error(transparent)]
169    HttpError(#[from] http::Error),
170    /// An error occurred while encoding the provided body.
171    #[error("Failed to encode body.")]
172    EncodeFailed {
173        /// The underlying encoding error.
174        cause: E,
175    },
176}
177
178/// HTTP POST
179///
180/// ```rust
181/// # use momento_functions_host::http;
182/// # use momento_functions_host::http::HttpPostError;
183///
184/// # fn f() -> Result<(), HttpPostError<&'static str>> {
185/// http::post("https://gomomento.com", [], b"hello".as_ref())?;
186/// # Ok(())}
187///
188/// use momento_functions_host::encoding::Json;
189///
190/// #[derive(serde::Serialize)]
191/// struct Request {
192///     message: String
193/// }
194/// #[derive(serde::Deserialize)]
195/// struct Reply {
196///     message: String
197/// }
198/// # fn g() -> Result<(), HttpPostError<Json<Request>>> {
199///
200/// let Json(reply): Json<Reply> = http::post(
201///     "https://gomomento.com",
202///     [
203///         ("authorization".to_string(), "abc123".to_string()),
204///     ],
205///     Json(Request { message: "hello".to_string() })
206/// )?
207/// .extract()?;
208/// # Ok(()) }
209/// ```
210pub fn post<E: Encode>(
211    url: impl Into<String>,
212    headers: impl IntoIterator<Item = (String, String)>,
213    body: E,
214) -> Result<Response, HttpPostError<E::Error>> {
215    let http::Response {
216        status,
217        headers,
218        body,
219    } = http::post(&http::Request {
220        url: url.into(),
221        headers: headers.into_iter().collect(),
222        body: body
223            .try_serialize()
224            .map_err(|e| HttpPostError::EncodeFailed { cause: e })?
225            .into(),
226        authorization: http::Authorization::None,
227    })?;
228    Ok(Response {
229        status,
230        headers,
231        body,
232    })
233}
234
235/// An error occurred while calling an HTTP Delete method.
236#[derive(Debug, Error)]
237pub enum HttpDeleteError {
238    /// An error occurred while calling the host http function.
239    #[error(transparent)]
240    HttpError(#[from] http::Error),
241}
242
243/// HTTP DELETE
244///
245/// ```rust
246/// # use momento_functions_host::http;
247/// # use momento_functions_host::http::HttpDeleteError;
248///
249/// fn f() -> Result<(), HttpDeleteError> {
250/// http::delete("https://gomomento.com", [])?;
251/// http::delete(
252///     "https://gomomento.com",
253///     [
254///         ("authorization".to_string(), "abc123".to_string()),
255///     ]
256/// )?;
257/// # Ok(()) }
258/// ```
259pub fn delete(
260    url: impl Into<String>,
261    headers: impl IntoIterator<Item = (String, String)>,
262) -> Result<Response, HttpDeleteError> {
263    let http::Response {
264        status,
265        headers,
266        body,
267    } = http::delete(&http::Request {
268        url: url.into(),
269        headers: headers.into_iter().collect(),
270        body: Default::default(),
271        authorization: http::Authorization::None,
272    })?;
273    Ok(Response {
274        status,
275        headers,
276        body,
277    })
278}
279
280impl aws::auth::Credentials {
281    fn into_http(
282        self,
283        region: impl Into<String>,
284        service: impl Into<String>,
285    ) -> http::Authorization {
286        match self {
287            aws::auth::Credentials::Hardcoded {
288                access_key_id,
289                secret_access_key,
290            } => http::Authorization::AwsSigv4Secret(http::AwsSigv4Secret {
291                access_key_id,
292                secret_access_key,
293                region: region.into(),
294                service: service.into(),
295            }),
296        }
297    }
298}
299
300/// HTTP GET with AWS SigV4 signing provided by the host
301///
302/// ```rust
303/// # use momento_functions_host::http;
304/// use momento_functions_host::build_environment_aws_credentials;
305///
306/// # fn f() -> Result<(), http::HttpGetError> {
307/// http::get_aws_sigv4(
308///     "https://bedrock-runtime.us-west-2.amazonaws.com/model/us.amazon.nova-pro-v1:0/invoke",
309///     [],
310///     build_environment_aws_credentials!(),
311///     "us-west-2",
312///     "bedrock",
313/// )?;
314/// http::get_aws_sigv4(
315///     "https://bedrock-runtime.us-west-2.amazonaws.com/model/us.amazon.nova-pro-v1:0/invoke",
316///     [
317///         ("other_header".to_string(), "abc123".to_string()),
318///     ],
319///     build_environment_aws_credentials!(),
320///     "us-west-2",
321///     "bedrock",
322/// )?;
323/// # Ok(()) }
324/// ```
325pub fn get_aws_sigv4(
326    url: impl Into<String>,
327    headers: impl IntoIterator<Item = (String, String)>,
328    aws_credentials: aws::auth::Credentials,
329    region: impl Into<String>,
330    service: impl Into<String>,
331) -> Result<Response, HttpGetError> {
332    let http::Response {
333        status,
334        headers,
335        body,
336    } = http::get(&http::Request {
337        url: url.into(),
338        headers: headers.into_iter().collect(),
339        body: Default::default(),
340        authorization: aws_credentials.into_http(region, service),
341    })?;
342    Ok(Response {
343        status,
344        headers,
345        body,
346    })
347}
348
349/// HTTP PUT with AWS SigV4 signing provided by the host
350///
351/// ```rust
352/// # use momento_functions_host::http;
353/// use momento_functions_host::encoding::Json;
354/// use momento_functions_host::build_environment_aws_credentials;
355///
356/// #[derive(serde::Serialize)]
357/// struct MyStruct {
358///     message: String
359/// }
360/// # fn f() -> Result<(), http::HttpPutError<Json<MyStruct>>> {
361///
362/// http::put_aws_sigv4(
363///     "https://gomomento.com",
364///     [
365///         ("authorization".to_string(), "abc123".to_string()),
366///     ],
367///     build_environment_aws_credentials!(),
368///     "us-west-2",
369///     "bedrock",
370///     Json(MyStruct { message: "hello".to_string() })
371/// )?;
372/// # Ok(()) }
373/// ```
374pub fn put_aws_sigv4<E: Encode>(
375    url: impl Into<String>,
376    headers: impl IntoIterator<Item = (String, String)>,
377    aws_credentials: aws::auth::Credentials,
378    region: impl Into<String>,
379    service: impl Into<String>,
380    body: E,
381) -> Result<Response, HttpPutError<E::Error>> {
382    let http::Response {
383        status,
384        headers,
385        body,
386    } = http::put(&http::Request {
387        url: url.into(),
388        headers: headers.into_iter().collect(),
389        body: body
390            .try_serialize()
391            .map_err(|e| HttpPutError::EncodeFailed { cause: e })?
392            .into(),
393        authorization: aws_credentials.into_http(region, service),
394    })?;
395    Ok(Response {
396        status,
397        headers,
398        body,
399    })
400}
401
402/// HTTP POST with AWS SigV4 signing provided by the host
403///
404/// ```rust
405/// # use momento_functions_host::http;
406/// use momento_functions_host::encoding::Json;
407/// use momento_functions_host::build_environment_aws_credentials;
408///
409/// #[derive(serde::Serialize)]
410/// struct MyStruct {
411///     message: String
412/// }
413/// # fn f() -> Result<(), http::HttpPostError<Json<MyStruct>>> {
414///
415/// http::post_aws_sigv4(
416///     "https://gomomento.com",
417///     [
418///         ("authorization".to_string(), "abc123".to_string()),
419///     ],
420///     build_environment_aws_credentials!(),
421///     "us-west-2",
422///     "bedrock",
423///     Json(MyStruct { message: "hello".to_string() })
424/// )?;
425/// # Ok(()) }
426/// ```
427pub fn post_aws_sigv4<E: Encode>(
428    url: impl Into<String>,
429    headers: impl IntoIterator<Item = (String, String)>,
430    aws_credentials: aws::auth::Credentials,
431    region: impl Into<String>,
432    service: impl Into<String>,
433    body: E,
434) -> Result<Response, HttpPostError<E::Error>> {
435    let http::Response {
436        status,
437        headers,
438        body,
439    } = http::post(&http::Request {
440        url: url.into(),
441        headers: headers.into_iter().collect(),
442        body: body
443            .try_serialize()
444            .map_err(|e| HttpPostError::EncodeFailed { cause: e })?
445            .into(),
446        authorization: aws_credentials.into_http(region, service),
447    })?;
448    Ok(Response {
449        status,
450        headers,
451        body,
452    })
453}
454
455/// HTTP DELETE with AWS SigV4 signing provided by the host
456///
457/// ```rust
458/// # use momento_functions_host::http;
459/// use momento_functions_host::build_environment_aws_credentials;
460///
461/// # fn f() -> Result<(), http::HttpDeleteError> {
462/// http::delete_aws_sigv4(
463///     "https://bedrock-runtime.us-west-2.amazonaws.com/model/us.amazon.nova-pro-v1:0/invoke",
464///     [],
465///     build_environment_aws_credentials!(),
466///     "us-west-2",
467///     "bedrock",
468/// )?;
469/// http::delete_aws_sigv4(
470///     "https://bedrock-runtime.us-west-2.amazonaws.com/model/us.amazon.nova-pro-v1:0/invoke",
471///     [
472///         ("other_header".to_string(), "abc123".to_string()),
473///     ],
474///     build_environment_aws_credentials!(),
475///     "us-west-2",
476///     "bedrock",
477/// )?;
478/// # Ok(()) }
479/// ```
480pub fn delete_aws_sigv4(
481    url: impl Into<String>,
482    headers: impl IntoIterator<Item = (String, String)>,
483    aws_credentials: aws::auth::Credentials,
484    region: impl Into<String>,
485    service: impl Into<String>,
486) -> Result<Response, HttpDeleteError> {
487    let http::Response {
488        status,
489        headers,
490        body,
491    } = http::delete(&http::Request {
492        url: url.into(),
493        headers: headers.into_iter().collect(),
494        body: Default::default(),
495        authorization: match aws_credentials {
496            aws::auth::Credentials::Hardcoded {
497                access_key_id,
498                secret_access_key,
499            } => http::Authorization::AwsSigv4Secret(http::AwsSigv4Secret {
500                access_key_id,
501                secret_access_key,
502                region: region.into(),
503                service: service.into(),
504            }),
505        },
506    })?;
507    Ok(Response {
508        status,
509        headers,
510        body,
511    })
512}