azure_functions/bindings/
http_request.rs

1use crate::{
2    http::Body,
3    rpc::{typed_data::Data, RpcHttp, TypedData},
4};
5use std::collections::HashMap;
6
7/// Represents a HTTP trigger binding.
8///
9/// The following binding attributes are supported:
10///
11/// | Name         | Description                                                                                                                                                                                       |
12/// |--------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
13/// | `name`       | The name of the parameter being bound.                                                                                                                                                            |
14/// | `auth_level` | Determines what keys, if any, need to be present on the request in order to invoke the function. The authorization level can be one of the following values: `anonymous`, `function`, or `admin`. |
15/// | `methods`    | A list of HTTP methods to which the function responds, separated by <code>&#124;</code> (e.g. <code>get&#124;post</code>). If not specified, the function responds to all HTTP methods.           |
16/// | `route`      | The URL route to which the function responds. The default value is the name of the function.                                                                                                      |
17///
18/// # Examples
19///
20/// A function that responds with a friendly greeting:
21///
22/// ```rust
23/// use azure_functions::{
24///     bindings::{HttpRequest, HttpResponse},
25///     func,
26/// };
27///
28/// #[func]
29/// pub fn greet(request: HttpRequest) -> HttpResponse {
30///     format!(
31///         "Hello, {}!",
32///         request.query_params().get("name").map_or("stranger", |x| x)
33///     ).into()
34/// }
35/// ```
36#[derive(Debug)]
37pub struct HttpRequest(RpcHttp);
38
39impl HttpRequest {
40    #[doc(hidden)]
41    pub fn new(data: TypedData, _: HashMap<String, TypedData>) -> Self {
42        match data.data {
43            Some(Data::Http(http)) => HttpRequest(*http),
44            _ => panic!("unexpected type data for HTTP request."),
45        }
46    }
47
48    /// Gets the HTTP method (e.g. "GET") for the request.
49    pub fn method(&self) -> &str {
50        &self.0.method
51    }
52
53    /// Gets the URL of the request.
54    pub fn url(&self) -> &str {
55        &self.0.url
56    }
57
58    /// Gets the headers of the request.
59    ///
60    /// The header keys are lower-cased.
61    pub fn headers(&self) -> &HashMap<String, String> {
62        &self.0.headers
63    }
64
65    /// Gets the route parameters of the request.
66    ///
67    /// Route parameters are specified through the `route` argument of a `HttpRequest` binding attribute.
68    ///
69    /// See [Route Containts](https://docs.microsoft.com/en-us/aspnet/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2#route-constraints) for syntax.
70    ///
71    /// # Examples
72    ///
73    /// ```rust
74    /// use azure_functions::func;
75    /// use azure_functions::bindings::{HttpRequest, HttpResponse};
76    ///
77    /// #[func]
78    /// #[binding(name = "request", route = "users/{id:int}")]
79    /// pub fn users(request: HttpRequest) -> HttpResponse {
80    ///     format!(
81    ///         "User ID requested: {}",
82    ///         request.route_params().get("id").unwrap()
83    ///     ).into()
84    /// }
85    /// ```
86    ///
87    /// Invoking the above function as `https://<app-name>.azurewebsites.net/api/users/1234`
88    /// would result in a response of `User ID requested: 1234`.
89    pub fn route_params(&self) -> &HashMap<String, String> {
90        &self.0.params
91    }
92
93    /// Gets the query parameters of the request.
94    ///
95    /// The query parameter keys are case-sensative.
96    ///
97    /// # Examples
98    ///
99    /// ```rust
100    /// use azure_functions::func;
101    /// use azure_functions::bindings::{HttpRequest, HttpResponse};
102    ///
103    /// #[func]
104    /// pub fn users(request: HttpRequest) -> HttpResponse {
105    ///     format!(
106    ///         "The 'name' query parameter is: {}",
107    ///         request.query_params().get("name").map_or("undefined", |x| x)
108    ///     ).into()
109    /// }
110    /// ```
111    pub fn query_params(&self) -> &HashMap<String, String> {
112        &self.0.query
113    }
114
115    /// Gets the body of the request.
116    pub fn body(&self) -> Body {
117        self.0
118            .body
119            .as_ref()
120            .map(|b| Body::from(&**b))
121            .unwrap_or(Body::Empty)
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128    use matches::matches;
129    use std::borrow::Cow;
130
131    #[test]
132    fn it_has_the_method() {
133        const METHOD: &'static str = "GET";
134
135        let mut http = RpcHttp::default();
136        http.method = METHOD.to_string();
137
138        let data = TypedData {
139            data: Some(Data::Http(Box::new(http))),
140        };
141
142        let request = HttpRequest::new(data, HashMap::new());
143        assert_eq!(request.method(), METHOD);
144    }
145
146    #[test]
147    fn it_has_the_url() {
148        const URL: &'static str = "http://example.com";
149
150        let mut http = RpcHttp::default();
151        http.url = URL.to_string();
152
153        let data = TypedData {
154            data: Some(Data::Http(Box::new(http))),
155        };
156
157        let request = HttpRequest::new(data, HashMap::new());
158        assert_eq!(request.url(), URL);
159    }
160
161    #[test]
162    fn it_has_a_header() {
163        const KEY: &'static str = "Accept";
164        const VALUE: &'static str = "application/json";
165
166        let mut http = RpcHttp::default();
167        http.headers.insert(KEY.to_string(), VALUE.to_string());
168
169        let data = TypedData {
170            data: Some(Data::Http(Box::new(http))),
171        };
172
173        let request = HttpRequest::new(data, HashMap::new());
174        assert_eq!(request.headers().get(KEY).unwrap(), VALUE);
175    }
176
177    #[test]
178    fn it_has_a_route_parameter() {
179        const KEY: &'static str = "id";
180        const VALUE: &'static str = "12345";
181
182        let mut http = RpcHttp::default();
183        http.params.insert(KEY.to_string(), VALUE.to_string());
184
185        let data = TypedData {
186            data: Some(Data::Http(Box::new(http))),
187        };
188
189        let request = HttpRequest::new(data, HashMap::new());
190        assert_eq!(request.route_params().get(KEY).unwrap(), VALUE);
191    }
192
193    #[test]
194    fn it_has_a_query_parameter() {
195        const KEY: &'static str = "name";
196        const VALUE: &'static str = "Peter";
197
198        let mut http = RpcHttp::default();
199        http.query.insert(KEY.to_string(), VALUE.to_string());
200
201        let data = TypedData {
202            data: Some(Data::Http(Box::new(http))),
203        };
204
205        let request = HttpRequest::new(data, HashMap::new());
206        assert_eq!(request.query_params().get(KEY).unwrap(), VALUE);
207    }
208
209    #[test]
210    fn it_has_an_empty_body() {
211        let data = TypedData {
212            data: Some(Data::Http(Box::new(RpcHttp::default()))),
213        };
214
215        let request = HttpRequest::new(data, HashMap::new());
216        assert!(matches!(request.body(), Body::Empty));
217    }
218
219    #[test]
220    fn it_has_a_string_body() {
221        const BODY: &'static str = "TEXT BODY";
222
223        let mut http = RpcHttp::default();
224        http.body = Some(Box::new(TypedData {
225            data: Some(Data::String(BODY.to_string())),
226        }));
227
228        let data = TypedData {
229            data: Some(Data::Http(Box::new(http))),
230        };
231
232        let request = HttpRequest::new(data, HashMap::new());
233        assert!(matches!(request.body(), Body::String(Cow::Borrowed(BODY))));
234    }
235
236    #[test]
237    fn it_has_a_json_body() {
238        const BODY: &'static str = r#"{ "json": "body" }"#;
239
240        let mut http = RpcHttp::default();
241        http.body = Some(Box::new(TypedData {
242            data: Some(Data::Json(BODY.to_string())),
243        }));
244
245        let data = TypedData {
246            data: Some(Data::Http(Box::new(http))),
247        };
248
249        let request = HttpRequest::new(data, HashMap::new());
250        assert!(matches!(request.body(), Body::Json(Cow::Borrowed(BODY))));
251    }
252
253    #[test]
254    fn it_has_a_bytes_body() {
255        const BODY: &'static [u8] = &[0, 1, 2];
256
257        let mut http = RpcHttp::default();
258        http.body = Some(Box::new(TypedData {
259            data: Some(Data::Bytes(BODY.to_vec())),
260        }));
261
262        let data = TypedData {
263            data: Some(Data::Http(Box::new(http))),
264        };
265
266        let request = HttpRequest::new(data, HashMap::new());
267        assert!(matches!(request.body(), Body::Bytes(Cow::Borrowed(BODY))));
268    }
269
270    #[test]
271    fn it_has_a_stream_body() {
272        const BODY: &'static [u8] = &[0, 1, 2];
273
274        let mut http = RpcHttp::default();
275        http.body = Some(Box::new(TypedData {
276            data: Some(Data::Stream(BODY.to_vec())),
277        }));
278
279        let data = TypedData {
280            data: Some(Data::Http(Box::new(http))),
281        };
282
283        let request = HttpRequest::new(data, HashMap::new());
284        assert!(matches!(request.body(), Body::Bytes(Cow::Borrowed(BODY))));
285    }
286}