rama_http/service/web/endpoint/response/
csv.rs

1use super::IntoResponse;
2use crate::{Body, Response, dep::http::StatusCode};
3use bytes::buf::Writer;
4use bytes::{BufMut, BytesMut};
5use csv;
6use rama_core::error::OpaqueError;
7use rama_http_headers::ContentType;
8use rama_utils::macros::impl_deref;
9use serde::Serialize;
10use std::fmt;
11
12use super::Headers;
13
14/// Wrapper used to create Csv Http [`Response`]s,
15/// as well as to extract Csv from Http [`Request`] bodies.
16///
17/// [`Request`]: crate::Request
18/// [`Response`]: crate::Response
19///
20/// # Examples
21///
22/// ## Creating a Csv Response
23///
24/// ```
25/// use serde_json::json;
26/// use rama_http::service::web::response::{IntoResponse, Csv};
27///
28/// async fn handler() -> impl IntoResponse {
29///     Csv(
30///         vec![
31///             json!({
32///                 "name": "john",
33///                 "age": 30,
34///                 "is_student": false
35///             })
36///         ]
37///     )
38/// }
39/// ```
40///
41/// ## Extracting Csv from a Request
42///
43/// ```
44/// use serde_json::json;
45/// use rama_http::service::web::response::Csv;
46///
47/// #[derive(Debug, serde::Deserialize)]
48/// struct Input {
49///     name: String,
50///     age: u8,
51///     alive: Option<bool>,
52/// }
53///
54/// # fn bury(name: impl AsRef<str>) {}
55///
56/// async fn handler(Csv(input): Csv<Vec<Input>>) {
57///     if !input[0].alive.unwrap_or_default() {
58///         bury(&input[0].name);
59///     }
60/// }
61/// ```
62pub struct Csv<T>(pub T);
63
64impl<T: fmt::Debug> fmt::Debug for Csv<T> {
65    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66        f.debug_tuple("Csv").field(&self.0).finish()
67    }
68}
69
70impl<T: Clone> Clone for Csv<T> {
71    fn clone(&self) -> Self {
72        Self(self.0.clone())
73    }
74}
75
76impl_deref!(Csv);
77
78impl<T> From<T> for Csv<T> {
79    fn from(inner: T) -> Self {
80        Self(inner)
81    }
82}
83
84impl<T> IntoResponse for Csv<T>
85where
86    T: IntoIterator<Item: Serialize> + std::fmt::Debug,
87{
88    fn into_response(self) -> Response {
89        // Extracted into separate fn so it's only compiled once for all T.
90        fn make_respone(
91            res: csv::Result<Vec<()>>,
92            mut wtr: csv::Writer<Writer<BytesMut>>,
93        ) -> Response {
94            if let Err(err) = res {
95                return (
96                    StatusCode::INTERNAL_SERVER_ERROR,
97                    Headers::single(ContentType::text_utf8()),
98                    err.to_string(),
99                )
100                    .into_response();
101            }
102            if let Err(err) = wtr.flush() {
103                return (
104                    StatusCode::INTERNAL_SERVER_ERROR,
105                    Headers::single(ContentType::text_utf8()),
106                    err.to_string(),
107                )
108                    .into_response();
109            }
110
111            let bw = match wtr.into_inner() {
112                Ok(bw) => bw,
113                Err(err) => {
114                    return (
115                        StatusCode::INTERNAL_SERVER_ERROR,
116                        Headers::single(ContentType::text_utf8()),
117                        err.to_string(),
118                    )
119                        .into_response();
120                }
121            };
122
123            (
124                Headers::single(ContentType::csv_utf8()),
125                bw.into_inner().freeze(),
126            )
127                .into_response()
128        }
129
130        // Use a small initial capacity of 128 bytes like serde_json::to_vec
131        // https://docs.rs/serde_json/1.0.82/src/serde_json/ser.rs.html#2189
132        let buf = BytesMut::with_capacity(128).writer();
133
134        let mut wtr = csv::Writer::from_writer(buf);
135        let res: Result<Vec<_>, _> = self.0.into_iter().map(|rec| wtr.serialize(rec)).collect();
136
137        make_respone(res, wtr)
138    }
139}
140
141impl<T> TryInto<Body> for Csv<T>
142where
143    T: IntoIterator<Item: Serialize>,
144{
145    type Error = OpaqueError;
146
147    fn try_into(self) -> Result<Body, Self::Error> {
148        // Use a small initial capacity of 128 bytes like serde_json::to_vec
149        // https://docs.rs/serde_json/1.0.82/src/serde_json/ser.rs.html#2189
150        let mut buf = BytesMut::with_capacity(128).writer();
151        {
152            let mut wtr = csv::Writer::from_writer(&mut buf);
153            let res: Result<Vec<_>, _> = self.0.into_iter().map(|rec| wtr.serialize(rec)).collect();
154            if let Err(err) = res {
155                return Err(OpaqueError::from_std(err));
156            }
157            if let Err(err) = wtr.flush() {
158                return Err(OpaqueError::from_std(err));
159            }
160        }
161
162        Ok(buf.into_inner().freeze().into())
163    }
164}