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