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}