Skip to main content

rustack_cloudfront_http/
response.rs

1//! HTTP response helpers.
2
3use bytes::Bytes;
4use http::{HeaderValue, Response, StatusCode};
5use rustack_cloudfront_model::CloudFrontError;
6
7use crate::{service::HttpBody, xml::ser::error_xml};
8
9/// Turn a `CloudFrontError` into an HTTP response.
10pub fn error_response(err: &CloudFrontError, request_id: &str) -> Response<HttpBody> {
11    let status =
12        StatusCode::from_u16(err.http_status()).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
13    let xml = error_xml(err.code(), &err.message(), request_id);
14    let mut resp = Response::builder()
15        .status(status)
16        .header(http::header::CONTENT_TYPE, "text/xml")
17        .body(HttpBody::from(xml))
18        .unwrap_or_else(|_| Response::new(HttpBody::from(String::new())));
19    if let Ok(hv) = HeaderValue::from_str(err.code()) {
20        resp.headers_mut().insert("x-amzn-errortype", hv);
21    }
22    if let Ok(hv) = HeaderValue::from_str(request_id) {
23        resp.headers_mut().insert("x-amzn-requestid", hv);
24    }
25    resp
26}
27
28/// Build an XML response with `ETag`, `Content-Type`, and optional `Location` headers.
29pub fn xml_response(status: StatusCode, body: String, etag: Option<&str>) -> Response<HttpBody> {
30    let mut builder = Response::builder()
31        .status(status)
32        .header(http::header::CONTENT_TYPE, "application/xml");
33    if let Some(tag) = etag {
34        builder = builder.header(http::header::ETAG, tag);
35    }
36    builder
37        .body(HttpBody::from(body))
38        .unwrap_or_else(|_| Response::new(HttpBody::from(String::new())))
39}
40
41/// Build a byte response with `ETag` and `Content-Type` headers.
42pub fn bytes_response(
43    status: StatusCode,
44    body: Bytes,
45    content_type: &'static str,
46    etag: Option<&str>,
47) -> Response<HttpBody> {
48    let mut builder = Response::builder()
49        .status(status)
50        .header(http::header::CONTENT_TYPE, content_type);
51    if let Some(tag) = etag {
52        builder = builder.header(http::header::ETAG, tag);
53    }
54    builder
55        .body(HttpBody::from(body))
56        .unwrap_or_else(|_| Response::new(HttpBody::default()))
57}
58
59/// Empty 204 response.
60pub fn empty_204() -> Response<HttpBody> {
61    Response::builder()
62        .status(StatusCode::NO_CONTENT)
63        .body(HttpBody::default())
64        .unwrap_or_else(|_| Response::new(HttpBody::default()))
65}
66
67#[cfg(test)]
68mod tests {
69    use bytes::Bytes;
70    use http_body_util::BodyExt;
71
72    use super::*;
73
74    #[tokio::test]
75    async fn test_should_build_bytes_response_with_etag() {
76        let response = bytes_response(
77            StatusCode::OK,
78            Bytes::from_static(b"function code"),
79            "application/octet-stream",
80            Some("etag-1"),
81        );
82
83        assert_eq!(response.status(), StatusCode::OK);
84        assert_eq!(
85            response.headers().get(http::header::CONTENT_TYPE),
86            Some(&HeaderValue::from_static("application/octet-stream")),
87        );
88        assert_eq!(
89            response.headers().get(http::header::ETAG),
90            Some(&HeaderValue::from_static("etag-1")),
91        );
92        let body = response.into_body().collect().await.unwrap().to_bytes();
93        assert_eq!(body, Bytes::from_static(b"function code"));
94    }
95}