11use crate :: templates:: SplitTemplate ;
2- use crate :: webserver:: http:: RequestContext ;
2+ use crate :: webserver:: http:: { RequestContext , ResponseWriter } ;
33use crate :: webserver:: ErrorWithStatus ;
44use crate :: AppState ;
55use actix_web:: cookie:: time:: format_description:: well_known:: Rfc3339 ;
@@ -15,31 +15,35 @@ use serde_json::{json, Value};
1515use std:: borrow:: Cow ;
1616use std:: sync:: Arc ;
1717
18- pub enum PageContext < W : std :: io :: Write > {
18+ pub enum PageContext {
1919 /// Indicates that we should stay in the header context
20- Header ( HeaderContext < W > ) ,
20+ Header ( HeaderContext ) ,
2121
2222 /// Indicates that we should start rendering the body
2323 Body {
2424 http_response : HttpResponseBuilder ,
25- renderer : AnyRenderBodyContext < W > ,
25+ renderer : AnyRenderBodyContext ,
2626 } ,
2727
2828 /// The response is ready, and should be sent as is. No further statements should be executed
2929 Close ( HttpResponse ) ,
3030}
3131
3232/// Handles the first SQL statements, before the headers have been sent to
33- pub struct HeaderContext < W : std :: io :: Write > {
33+ pub struct HeaderContext {
3434 app_state : Arc < AppState > ,
3535 request_context : RequestContext ,
36- pub writer : W ,
36+ pub writer : ResponseWriter ,
3737 response : HttpResponseBuilder ,
3838 has_status : bool ,
3939}
4040
41- impl < ' a , W : std:: io:: Write > HeaderContext < W > {
42- pub fn new ( app_state : Arc < AppState > , request_context : RequestContext , writer : W ) -> Self {
41+ impl HeaderContext {
42+ pub fn new (
43+ app_state : Arc < AppState > ,
44+ request_context : RequestContext ,
45+ writer : ResponseWriter ,
46+ ) -> Self {
4347 let mut response = HttpResponseBuilder :: new ( StatusCode :: OK ) ;
4448 response. content_type ( "text/html; charset=utf-8" ) ;
4549 if app_state. config . content_security_policy . is_none ( ) {
@@ -53,20 +57,21 @@ impl<'a, W: std::io::Write> HeaderContext<W> {
5357 has_status : false ,
5458 }
5559 }
56- pub async fn handle_row ( self , data : JsonValue ) -> anyhow:: Result < PageContext < W > > {
60+ pub async fn handle_row ( self , data : JsonValue ) -> anyhow:: Result < PageContext > {
5761 log:: debug!( "Handling header row: {data}" ) ;
5862 match get_object_str ( & data, "component" ) {
5963 Some ( "status_code" ) => self . status_code ( & data) . map ( PageContext :: Header ) ,
6064 Some ( "http_header" ) => self . add_http_header ( & data) . map ( PageContext :: Header ) ,
6165 Some ( "redirect" ) => self . redirect ( & data) . map ( PageContext :: Close ) ,
6266 Some ( "json" ) => self . json ( & data) ,
67+ Some ( "csv" ) => self . csv ( & data) ,
6368 Some ( "cookie" ) => self . add_cookie ( & data) . map ( PageContext :: Header ) ,
6469 Some ( "authentication" ) => self . authentication ( data) . await ,
6570 _ => self . start_body ( data) . await ,
6671 }
6772 }
6873
69- pub async fn handle_error ( self , err : anyhow:: Error ) -> anyhow:: Result < PageContext < W > > {
74+ pub async fn handle_error ( self , err : anyhow:: Error ) -> anyhow:: Result < PageContext > {
7075 if self . app_state . config . environment . is_prod ( ) {
7176 return Err ( err) ;
7277 }
@@ -187,7 +192,7 @@ impl<'a, W: std::io::Write> HeaderContext<W> {
187192 }
188193
189194 /// Answers to the HTTP request with a single json object
190- fn json ( mut self , data : & JsonValue ) -> anyhow:: Result < PageContext < W > > {
195+ fn json ( mut self , data : & JsonValue ) -> anyhow:: Result < PageContext > {
191196 self . response
192197 . insert_header ( ( header:: CONTENT_TYPE , "application/json" ) ) ;
193198 if let Some ( contents) = data. get ( "contents" ) {
@@ -220,7 +225,19 @@ impl<'a, W: std::io::Write> HeaderContext<W> {
220225 }
221226 }
222227
223- async fn authentication ( mut self , mut data : JsonValue ) -> anyhow:: Result < PageContext < W > > {
228+ fn csv ( mut self , data : & JsonValue ) -> anyhow:: Result < PageContext > {
229+ self . response
230+ . insert_header ( ( header:: CONTENT_TYPE , "text/csv" ) ) ;
231+ let csv_renderer = CsvBodyRenderer :: new ( self . writer ) ;
232+ let renderer = AnyRenderBodyContext :: Csv ( csv_renderer) ;
233+ let http_response = self . response . take ( ) ;
234+ Ok ( PageContext :: Body {
235+ renderer,
236+ http_response,
237+ } )
238+ }
239+
240+ async fn authentication ( mut self , mut data : JsonValue ) -> anyhow:: Result < PageContext > {
224241 let password_hash = take_object_str ( & mut data, "password_hash" ) ;
225242 let password = take_object_str ( & mut data, "password" ) ;
226243 if let ( Some ( password) , Some ( password_hash) ) = ( password, password_hash) {
@@ -250,7 +267,7 @@ impl<'a, W: std::io::Write> HeaderContext<W> {
250267 Ok ( PageContext :: Close ( http_response) )
251268 }
252269
253- async fn start_body ( self , data : JsonValue ) -> anyhow:: Result < PageContext < W > > {
270+ async fn start_body ( self , data : JsonValue ) -> anyhow:: Result < PageContext > {
254271 let html_renderer =
255272 HtmlRenderContext :: new ( self . app_state , self . request_context , self . writer , data)
256273 . await
@@ -307,15 +324,16 @@ fn take_object_str(json: &mut JsonValue, key: &str) -> Option<String> {
307324/**
308325 * Can receive rows, and write them in a given format to an `io::Write`
309326 */
310- pub enum AnyRenderBodyContext < W : std:: io:: Write > {
311- Html ( HtmlRenderContext < W > ) ,
312- Json ( JsonBodyRenderer < W > ) ,
327+ pub enum AnyRenderBodyContext {
328+ Html ( HtmlRenderContext < ResponseWriter > ) ,
329+ Json ( JsonBodyRenderer < ResponseWriter > ) ,
330+ Csv ( CsvBodyRenderer ) ,
313331}
314332
315333/**
316334 * Dummy impl to dispatch method calls to the underlying renderer
317335 */
318- impl < W : std :: io :: Write > AnyRenderBodyContext < W > {
336+ impl AnyRenderBodyContext {
319337 pub async fn handle_row ( & mut self , data : & JsonValue ) -> anyhow:: Result < ( ) > {
320338 log:: debug!(
321339 "<- Rendering properties: {}" ,
@@ -324,6 +342,7 @@ impl<W: std::io::Write> AnyRenderBodyContext<W> {
324342 match self {
325343 AnyRenderBodyContext :: Html ( render_context) => render_context. handle_row ( data) . await ,
326344 AnyRenderBodyContext :: Json ( json_body_renderer) => json_body_renderer. handle_row ( data) ,
345+ AnyRenderBodyContext :: Csv ( csv_renderer) => csv_renderer. handle_row ( data) . await ,
327346 }
328347 }
329348 pub async fn handle_error ( & mut self , error : & anyhow:: Error ) -> anyhow:: Result < ( ) > {
@@ -333,26 +352,33 @@ impl<W: std::io::Write> AnyRenderBodyContext<W> {
333352 AnyRenderBodyContext :: Json ( json_body_renderer) => {
334353 json_body_renderer. handle_error ( error)
335354 }
355+ AnyRenderBodyContext :: Csv ( csv_renderer) => csv_renderer. handle_error ( error) . await ,
336356 }
337357 }
338358 pub async fn finish_query ( & mut self ) -> anyhow:: Result < ( ) > {
339359 match self {
340360 AnyRenderBodyContext :: Html ( render_context) => render_context. finish_query ( ) . await ,
341361 AnyRenderBodyContext :: Json ( _json_body_renderer) => Ok ( ( ) ) ,
362+ AnyRenderBodyContext :: Csv ( _csv_renderer) => Ok ( ( ) ) ,
342363 }
343364 }
344365
345- pub fn writer_mut ( & mut self ) -> & mut W {
366+ pub async fn flush ( & mut self ) -> anyhow :: Result < ( ) > {
346367 match self {
347368 AnyRenderBodyContext :: Html ( HtmlRenderContext { writer, .. } )
348- | AnyRenderBodyContext :: Json ( JsonBodyRenderer { writer, .. } ) => writer,
369+ | AnyRenderBodyContext :: Json ( JsonBodyRenderer { writer, .. } ) => {
370+ writer. async_flush ( ) . await ?
371+ }
372+ AnyRenderBodyContext :: Csv ( csv_renderer) => csv_renderer. flush ( ) . await ?,
349373 }
374+ Ok ( ( ) )
350375 }
351376
352- pub async fn close ( self ) -> W {
377+ pub async fn close ( self ) -> ResponseWriter {
353378 match self {
354379 AnyRenderBodyContext :: Html ( render_context) => render_context. close ( ) . await ,
355380 AnyRenderBodyContext :: Json ( json_body_renderer) => json_body_renderer. close ( ) ,
381+ AnyRenderBodyContext :: Csv ( csv_renderer) => csv_renderer. close ( ) . await ,
356382 }
357383 }
358384}
@@ -424,6 +450,54 @@ impl<W: std::io::Write> JsonBodyRenderer<W> {
424450 }
425451}
426452
453+ pub struct CsvBodyRenderer {
454+ writer : csv_async:: AsyncWriter < ResponseWriter > ,
455+ is_first : bool ,
456+ }
457+
458+ impl CsvBodyRenderer {
459+ pub fn new ( writer : ResponseWriter ) -> CsvBodyRenderer {
460+ CsvBodyRenderer {
461+ writer : csv_async:: AsyncWriter :: from_writer ( writer) ,
462+ is_first : true ,
463+ }
464+ }
465+
466+ pub async fn handle_row ( & mut self , data : & JsonValue ) -> anyhow:: Result < ( ) > {
467+ if self . is_first {
468+ self . is_first = false ;
469+ if let Some ( obj) = data. as_object ( ) {
470+ let headers: Vec < _ > = obj. keys ( ) . collect ( ) ;
471+ self . writer . write_record ( & headers) . await ?;
472+ }
473+ }
474+
475+ if let Some ( obj) = data. as_object ( ) {
476+ let values: Vec < _ > = obj. values ( ) . map ( |v| v. to_string ( ) ) . collect ( ) ;
477+ self . writer . write_record ( & values) . await ?;
478+ }
479+
480+ Ok ( ( ) )
481+ }
482+
483+ pub async fn handle_error ( & mut self , error : & anyhow:: Error ) -> anyhow:: Result < ( ) > {
484+ self . writer . write_record ( & [ error. to_string ( ) ] ) . await ?;
485+ Ok ( ( ) )
486+ }
487+
488+ pub async fn flush ( & mut self ) -> anyhow:: Result < ( ) > {
489+ self . writer . flush ( ) . await ?;
490+ Ok ( ( ) )
491+ }
492+
493+ pub async fn close ( self ) -> ResponseWriter {
494+ self . writer
495+ . into_inner ( )
496+ . await
497+ . expect ( "Failed to get inner writer" )
498+ }
499+ }
500+
427501#[ allow( clippy:: module_name_repetitions) ]
428502pub struct HtmlRenderContext < W : std:: io:: Write > {
429503 app_state : Arc < AppState > ,
0 commit comments