use std::collections::HashMap; use async_stream::stream; use axum::{ body::Body, extract::Query, http::{header, StatusCode}, response::IntoResponse, }; use bytes::Bytes; use html_escape::{encode_text, encode_unquoted_attribute}; use crate::engines; fn render_beginning_of_html(query: &str) -> String { format!( r#" {} - metasearch
"#, encode_text(query), encode_unquoted_attribute(query) ) } fn render_end_of_html() -> String { r#"
"#.to_string() } fn render_search_result(result: &engines::SearchResult) -> String { let engines_html = result .engines .iter() .map(|engine| { format!( r#"{}"#, encode_text(&engine.name()) ) }) .collect::>() .join(""); format!( r#"
{url}

{title}

{desc}

{engines_html}
"#, url_attr = encode_unquoted_attribute(&result.url), url = encode_text(&result.url), title = encode_text(&result.title), desc = encode_text(&result.description) ) } pub async fn route(Query(params): Query>) -> impl IntoResponse { let query = params .get("q") .cloned() .unwrap_or_default() .trim() .replace('\n', " "); if query.is_empty() { // redirect to index return ( StatusCode::FOUND, [ (header::LOCATION, "/"), (header::CONTENT_TYPE, "text/html; charset=utf-8"), ], Body::from("No query provided, click here to go back to index"), ); } let s = stream! { type R = Result; yield R::Ok(Bytes::from(render_beginning_of_html(&query))); let (progress_tx, mut progress_rx) = tokio::sync::mpsc::unbounded_channel(); let search_future = tokio::spawn(async move { engines::search(&query, progress_tx).await }); while let Some(progress_update) = progress_rx.recv().await { let progress_html = format!( r#"

{}

"#, encode_text(&progress_update.to_string()) ); yield R::Ok(Bytes::from(progress_html)); } let results = match search_future.await? { Ok(results) => results, Err(e) => { let error_html = format!( r#"

Error: {}

"#, encode_text(&e.to_string()) ); yield R::Ok(Bytes::from(error_html)); return; } }; let mut second_half = String::new(); second_half.push_str(""); // close progress-updates second_half.push_str(""); for result in results.search_results { second_half.push_str(&render_search_result(&result)); } second_half.push_str(&render_end_of_html()); yield Ok(Bytes::from(second_half)); }; let stream = Body::from_stream(s); ( StatusCode::OK, [ (header::CONTENT_TYPE, "text/html; charset=utf-8"), (header::TRANSFER_ENCODING, "chunked"), ], stream, ) }