Skip to main content

momento_functions_http/
request.rs

1use momento_functions_bytes::{
2    Data,
3    encoding::{Encode, Json},
4};
5
6use crate::wit::momento::http::http;
7
8/// SigV4 credentials for signing an AWS request.
9pub struct AwsSigV4Secret {
10    /// AWS access key ID.
11    pub access_key_id: String,
12    /// AWS secret access key.
13    pub secret_access_key: String,
14    /// AWS region.
15    pub region: String,
16    /// AWS service name.
17    pub service: String,
18}
19
20impl From<AwsSigV4Secret> for http::AwsSigv4Secret {
21    fn from(s: AwsSigV4Secret) -> Self {
22        http::AwsSigv4Secret {
23            access_key_id: s.access_key_id,
24            secret_access_key: s.secret_access_key,
25            region: s.region,
26            service: s.service,
27        }
28    }
29}
30
31/// An IAM role for Momento to federate into when making the request.
32pub struct IamRole {
33    /// The ARN of the IAM role.
34    pub role_arn: String,
35    /// The AWS service name.
36    pub service: String,
37}
38
39impl From<IamRole> for http::IamRole {
40    fn from(r: IamRole) -> Self {
41        http::IamRole {
42            role_arn: r.role_arn,
43            service: r.service,
44        }
45    }
46}
47
48/// Authorization strategy for an HTTP request.
49pub enum Authorization {
50    /// No special authorization. You can still include an `Authorization` header manually.
51    None,
52    /// Sign the request with AWS SigV4 using explicit credentials.
53    AwsSigV4Secret(AwsSigV4Secret),
54    /// Federate into an IAM role for the request.
55    Federated(IamRole),
56}
57
58impl From<Authorization> for http::Authorization {
59    fn from(a: Authorization) -> Self {
60        match a {
61            Authorization::None => http::Authorization::None,
62            Authorization::AwsSigV4Secret(s) => http::Authorization::AwsSigv4Secret(s.into()),
63            Authorization::Federated(r) => http::Authorization::Federated(r.into()),
64        }
65    }
66}
67
68/// An HTTP request.
69///
70/// Construct with [`Request::new`] and configure using the builder methods.
71pub struct Request {
72    url: String,
73    verb: String,
74    headers: Vec<(String, String)>,
75    body: Data,
76    authorization: Authorization,
77}
78
79impl Request {
80    /// Create a new request with no body and no authorization.
81    ///
82    /// # Examples
83    /// ________
84    /// Build a GET request:
85    /// ```rust,no_run
86    /// use momento_functions_http::Request;
87    ///
88    /// let request = Request::new("https://example.com/api", "GET");
89    /// ```
90    pub fn new(url: impl Into<String>, verb: impl Into<String>) -> Self {
91        Self {
92            url: url.into(),
93            verb: verb.into(),
94            headers: Vec::new(),
95            body: Data::from(vec![]),
96            authorization: Authorization::None,
97        }
98    }
99
100    /// Add a header to the request.
101    pub fn with_header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
102        self.headers.push((name.into(), value.into()));
103        self
104    }
105
106    /// Add multiple headers to the request at once.
107    ///
108    /// # Examples
109    /// ________
110    /// ```rust,no_run
111    /// use momento_functions_http::Request;
112    ///
113    /// let request = Request::new("https://example.com/api", "POST")
114    ///     .with_headers([
115    ///         ("Content-Type", "application/json"),
116    ///         ("Accept", "application/json"),
117    ///     ]);
118    /// ```
119    pub fn with_headers<K, V>(mut self, headers: impl IntoIterator<Item = (K, V)>) -> Self
120    where
121        K: Into<String>,
122        V: Into<String>,
123    {
124        self.headers
125            .extend(headers.into_iter().map(|(k, v)| (k.into(), v.into())));
126        self
127    }
128
129    /// Set the request body to a JSON-serialized value, and set the
130    /// `content-type` header to `application/json`.
131    ///
132    /// # Examples
133    /// ________
134    /// ```rust,no_run
135    /// use momento_functions_http::Request;
136    /// use momento_functions_bytes::encoding::Json;
137    ///
138    /// #[derive(serde::Serialize)]
139    /// struct Payload { message: String }
140    ///
141    /// let request = match Request::new("https://example.com/api", "POST")
142    ///     .json(Json(Payload { message: "hello".to_string() }))
143    /// {
144    ///     Ok(request) => request,
145    ///     Err(e) => {
146    ///         eprintln!("failed to serialize body: {e}");
147    ///         return;
148    ///     }
149    /// };
150    /// ```
151    pub fn json<T: serde::Serialize>(mut self, body: Json<T>) -> Result<Self, serde_json::Error> {
152        self.body = body.try_serialize()?;
153        if let Some(entry) = self
154            .headers
155            .iter_mut()
156            .find(|(k, _)| k.eq_ignore_ascii_case("content-type"))
157        {
158            entry.1 = "application/json".to_string();
159        } else {
160            self.headers
161                .push(("content-type".to_string(), "application/json".to_string()));
162        }
163        Ok(self)
164    }
165
166    /// Set the request body.
167    pub fn with_body(mut self, body: impl Into<Data>) -> Self {
168        self.body = body.into();
169        self
170    }
171
172    /// Set the authorization strategy.
173    pub fn with_authorization(mut self, authorization: Authorization) -> Self {
174        self.authorization = authorization;
175        self
176    }
177}
178
179impl From<Request> for http::Request {
180    fn from(r: Request) -> Self {
181        http::Request {
182            url: r.url,
183            verb: r.verb,
184            headers: r.headers,
185            body: r.body.into(),
186            authorization: r.authorization.into(),
187        }
188    }
189}