azure_functions/bindings/
http_response.rs

1use crate::{
2    http::{Body, ResponseBuilder, Status},
3    rpc::{typed_data::Data, RpcHttp, TypedData},
4};
5use std::collections::HashMap;
6
7/// Represents a HTTP output binding.
8///
9/// The following binding attributes are supported:
10///
11/// | Name         | Description                                                                                                                                                                                       |
12/// |--------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
13/// | `name`       | The name of the parameter being bound.                                                                                                                                                            |
14///
15/// Responses can be created for any type that implements `Into<Body>`.
16///
17/// # Examples
18///
19/// Creating a response from a string:
20///
21/// ```rust
22/// use azure_functions::bindings::{HttpRequest, HttpResponse};
23/// use azure_functions::func;
24///
25/// #[func]
26/// pub fn example(_req: HttpRequest) -> HttpResponse {
27///     "Hello world!".into()
28/// }
29/// ```
30///
31/// Creating a response from a JSON value (see the [json! macro](https://docs.serde.rs/serde_json/macro.json.html) from the `serde_json` crate):
32///
33/// ```rust
34/// use azure_functions::bindings::{HttpRequest, HttpResponse};
35/// use azure_functions::func;
36/// use serde_json::json;
37///
38/// #[func]
39/// pub fn example(_req: HttpRequest) -> HttpResponse {
40///     json!({ "hello": "world" }).into()
41/// }
42/// ```
43///
44/// Creating a response from a sequence of bytes:
45///
46/// ```rust
47/// use azure_functions::bindings::{HttpRequest, HttpResponse};
48/// use azure_functions::func;
49///
50/// #[func]
51/// pub fn example(_req: HttpRequest) -> HttpResponse {
52///     [1, 2, 3][..].into()
53/// }
54/// ```
55///
56/// Building a custom response:
57///
58/// ```rust
59/// use azure_functions::bindings::{HttpRequest, HttpResponse};
60/// use azure_functions::func;
61/// use azure_functions::http::Status;
62///
63/// #[func]
64/// pub fn example(_req: HttpRequest) -> HttpResponse {
65///     HttpResponse::build()
66///         .status(Status::MovedPermanently)
67///         .header("Location", "http://example.com")
68///         .body("The requested resource has moved to: http://example.com")
69///         .finish()
70/// }
71/// ```
72#[derive(Default, Debug)]
73pub struct HttpResponse {
74    pub(crate) data: RpcHttp,
75    pub(crate) status: Status,
76}
77
78impl HttpResponse {
79    pub(crate) fn new() -> Self {
80        HttpResponse {
81            data: RpcHttp::default(),
82            status: Status::Ok,
83        }
84    }
85
86    /// Creates a new [ResponseBuilder](../http/struct.ResponseBuilder.html) for building a response.
87    ///
88    /// # Examples
89    ///
90    /// ```rust
91    /// # use azure_functions::bindings::HttpResponse;
92    /// use azure_functions::http::Status;
93    ///
94    /// let response = HttpResponse::build().status(Status::NotFound).finish();
95    /// assert_eq!(response.status(), Status::NotFound);
96    /// ```
97    pub fn build() -> ResponseBuilder {
98        ResponseBuilder::new()
99    }
100
101    /// Gets the status code for the response.
102    ///
103    /// # Examples
104    ///
105    /// ```rust
106    /// # use azure_functions::bindings::HttpResponse;
107    /// use azure_functions::http::Status;
108    ///
109    /// let response = HttpResponse::build().status(Status::BadRequest).finish();
110    /// assert_eq!(response.status(), Status::BadRequest);
111    /// ```
112    pub fn status(&self) -> Status {
113        self.status
114    }
115
116    /// Gets the body of the response.
117    ///
118    /// # Examples
119    ///
120    /// ```rust
121    /// # use azure_functions::bindings::HttpResponse;
122    /// let response = HttpResponse::build().body("example").finish();
123    ///
124    /// assert_eq!(response.body().as_str().unwrap(), "example");
125    /// ```
126    pub fn body(&self) -> Body {
127        self.data
128            .body
129            .as_ref()
130            .map(|b| Body::from(&**b))
131            .unwrap_or(Body::Empty)
132    }
133
134    /// Gets the headers of the response.
135    ///
136    /// # Examples
137    ///
138    /// ```rust
139    /// # use azure_functions::bindings::HttpResponse;
140    /// let response = HttpResponse::build().header("Content-Type", "text/plain").finish();
141    ///
142    /// assert_eq!(response.headers().get("Content-Type").unwrap(), "text/plain");
143    /// ```
144    pub fn headers(&self) -> &HashMap<String, String> {
145        &self.data.headers
146    }
147}
148
149impl<'a, T> From<T> for HttpResponse
150where
151    T: Into<Body<'a>>,
152{
153    fn from(data: T) -> Self {
154        HttpResponse::build().body(data).finish()
155    }
156}
157
158#[doc(hidden)]
159impl Into<TypedData> for HttpResponse {
160    fn into(mut self) -> TypedData {
161        self.data.status_code = self.status.to_string();
162
163        TypedData {
164            data: Some(Data::Http(Box::new(self.data))),
165        }
166    }
167}
168
169#[cfg(test)]
170mod tests {
171    use super::*;
172    use matches::matches;
173    use serde::{Deserialize, Serialize};
174
175    #[test]
176    fn it_is_empty_by_default() {
177        let response = HttpResponse::new();
178
179        assert_eq!(response.status(), Status::Ok);
180        assert!(matches!(response.body(), Body::Empty));
181    }
182
183    #[test]
184    fn it_is_empty_from_a_builder() {
185        let response: HttpResponse = HttpResponse::build().finish();
186
187        assert_eq!(response.status(), Status::Ok);
188        assert!(matches!(response.body(), Body::Empty));
189    }
190
191    #[test]
192    fn it_builds_with_a_status() {
193        let response: HttpResponse = HttpResponse::build().status(Status::Continue).finish();
194
195        assert_eq!(response.status(), Status::Continue);
196    }
197
198    #[test]
199    fn it_builds_with_a_string_body() {
200        const BODY: &'static str = "test body";
201
202        let response: HttpResponse = HttpResponse::build().body(BODY).finish();
203
204        assert_eq!(
205            response.headers().get("Content-Type").unwrap(),
206            "text/plain"
207        );
208        assert_eq!(response.body().as_str().unwrap(), BODY);
209    }
210
211    #[test]
212    fn it_builds_with_a_json_body() {
213        #[derive(Serialize, Deserialize)]
214        struct Data {
215            message: String,
216        };
217
218        const MESSAGE: &'static str = "test";
219
220        let data = Data {
221            message: MESSAGE.to_string(),
222        };
223
224        let response = HttpResponse::build()
225            .body(::serde_json::to_value(data).unwrap())
226            .finish();
227
228        assert_eq!(
229            response.headers().get("Content-Type").unwrap(),
230            "application/json"
231        );
232        assert_eq!(response.body().as_json::<Data>().unwrap().message, MESSAGE);
233    }
234
235    #[test]
236    fn it_builds_with_a_bytes_body() {
237        const BODY: &'static [u8] = &[1, 2, 3];
238
239        let response: HttpResponse = HttpResponse::build().body(BODY).finish();
240
241        assert_eq!(
242            response.headers().get("Content-Type").unwrap(),
243            "application/octet-stream"
244        );
245        assert_eq!(response.body().as_bytes(), BODY);
246    }
247
248    #[test]
249    fn it_builds_with_headers() {
250        let response: HttpResponse = HttpResponse::build()
251            .header("header1", "value1")
252            .header("header2", "value2")
253            .header("header3", "value3")
254            .finish();
255
256        assert_eq!(response.headers().get("header1").unwrap(), "value1");
257        assert_eq!(response.headers().get("header2").unwrap(), "value2");
258        assert_eq!(response.headers().get("header3").unwrap(), "value3");
259    }
260
261    #[test]
262    fn it_converts_to_typed_data() {
263        let response: HttpResponse = HttpResponse::build()
264            .status(Status::BadRequest)
265            .header("header", "value")
266            .body("body")
267            .finish();
268
269        let data: TypedData = response.into();
270        match data.data {
271            Some(Data::Http(http)) => {
272                assert_eq!(http.status_code, "400");
273                assert_eq!(http.headers.get("header").unwrap(), "value");
274                assert_eq!(
275                    http.body,
276                    Some(Box::new(TypedData {
277                        data: Some(Data::String("body".to_string()))
278                    }))
279                );
280            }
281            _ => assert!(false),
282        }
283    }
284}