Skip to content

Commit 907c946

Browse files
committed
csv streaming fixes and performance improvements
1 parent 0f4656c commit 907c946

File tree

3 files changed

+58
-10
lines changed

3 files changed

+58
-10
lines changed

src/render.rs

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ impl HeaderContext {
230230

231231
async fn csv(mut self, options: &JsonValue) -> anyhow::Result<PageContext> {
232232
self.response
233-
.insert_header((header::CONTENT_TYPE, "text/csv"));
233+
.insert_header((header::CONTENT_TYPE, "text/csv; charset=utf-8"));
234234
if let Some(filename) = get_object_str(options, "filename") {
235235
self.response.insert_header((
236236
header::CONTENT_DISPOSITION,
@@ -461,7 +461,7 @@ impl<W: std::io::Write> JsonBodyRenderer<W> {
461461

462462
pub struct CsvBodyRenderer {
463463
writer: csv_async::AsyncWriter<AsyncResponseWriter>,
464-
is_first: bool,
464+
columns: Vec<String>,
465465
}
466466

467467
impl CsvBodyRenderer {
@@ -501,29 +501,55 @@ impl CsvBodyRenderer {
501501
let writer = builder.create_writer(async_writer);
502502
Ok(CsvBodyRenderer {
503503
writer,
504-
is_first: true,
504+
columns: vec![],
505505
})
506506
}
507507

508508
pub async fn handle_row(&mut self, data: &JsonValue) -> anyhow::Result<()> {
509-
if self.is_first {
510-
self.is_first = false;
509+
if self.columns.is_empty() {
511510
if let Some(obj) = data.as_object() {
512-
let headers: Vec<_> = obj.keys().collect();
513-
self.writer.write_record(&headers).await?;
511+
let headers: Vec<String> = obj.keys().map(String::to_owned).collect();
512+
self.columns = headers;
513+
self.writer.write_record(&self.columns).await?;
514514
}
515515
}
516516

517517
if let Some(obj) = data.as_object() {
518-
let values: Vec<_> = obj.values().map(std::string::ToString::to_string).collect();
519-
self.writer.write_record(&values).await?;
518+
self.writer
519+
.write_record(
520+
self.columns
521+
.iter()
522+
.map(|s| {
523+
let val = obj.get(s);
524+
if let Some(val) = val {
525+
if let Some(s) = val.as_str() {
526+
Cow::Borrowed(s.as_bytes())
527+
} else {
528+
Cow::Owned(val.to_string().into_bytes())
529+
}
530+
} else {
531+
Cow::Borrowed(&b""[..])
532+
}
533+
})
534+
.collect::<Vec<_>>(),
535+
)
536+
.await?;
520537
}
521538

522539
Ok(())
523540
}
524541

525542
pub async fn handle_error(&mut self, error: &anyhow::Error) -> anyhow::Result<()> {
526-
self.writer.write_record(&[error.to_string()]).await?;
543+
let err_str = error.to_string();
544+
self.writer
545+
.write_record(
546+
self.columns
547+
.iter()
548+
.enumerate()
549+
.map(|(i, _)| if i == 0 { &err_str } else { "" })
550+
.collect::<Vec<_>>(),
551+
)
552+
.await?;
527553
Ok(())
528554
}
529555

tests/csv_data.sql

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
select 'csv' as component;
2+
select 0 as id, 'Hello World 😊!' as msg
3+
union all
4+
select 1 as id, 'Goodbye World 😔!' as msg;

tests/index.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,24 @@ async fn test_json_body() -> actix_web::Result<()> {
256256
Ok(())
257257
}
258258

259+
#[actix_web::test]
260+
async fn test_csv_body() -> actix_web::Result<()> {
261+
let req = get_request_to("/tests/csv_data.sql")
262+
.await?
263+
.to_srv_request();
264+
let resp = main_handler(req).await?;
265+
266+
assert_eq!(resp.status(), StatusCode::OK);
267+
assert_eq!(
268+
resp.headers().get(header::CONTENT_TYPE).unwrap(),
269+
"text/csv; charset=utf-8"
270+
);
271+
let body = test::read_body(resp).await;
272+
let body_str = String::from_utf8(body.to_vec()).unwrap();
273+
assert_eq!(body_str, "id,msg\n0,Hello World 😊!\n1,Goodbye World 😔!\n");
274+
Ok(())
275+
}
276+
259277
async fn test_file_upload(target: &str) -> actix_web::Result<()> {
260278
let req = get_request_to(target)
261279
.await?

0 commit comments

Comments
 (0)