| @@ -93,7 +93,7 @@ async fn search( |
| .and_then(|v| v.to_str().ok()) |
| .unwrap_or(USER_AGENT); |
| |
| - let mut external_results: Vec<ResultEntry> = Vec::new(); |
| + let mut all_external: Vec<Vec<ResultEntry>> = Vec::new(); |
| let mut provider_count: usize = 0; |
| if has_query { |
| let client = reqwest::Client::builder() |
| @@ -119,14 +119,14 @@ async fn search( |
| if let Ok((name, result, elapsed)) = handle.await { |
| provider_count += 1; |
| match result { |
| - Ok(mut results) => { |
| + Ok(results) => { |
| info!( |
| "external {} ({} results) [{:.2?}]", |
| name, |
| results.len(), |
| elapsed |
| ); |
| - external_results.append(&mut results); |
| + all_external.push(results); |
| } |
| Err(e) => { |
| info!("external {} (error) [{:.2?}]: {}", name, elapsed, e); |
| @@ -137,6 +137,8 @@ async fn search( |
| } |
| } |
| |
| + let external_results = interleave(&all_external); |
| + |
| let page_elapsed = start.elapsed(); |
| let page_time_ms = format!("{:.1}", page_elapsed.as_secs_f64() * 1000.0); |
| info!( |
| @@ -210,6 +212,19 @@ pub async fn run_server( |
| .await |
| } |
| |
| +fn interleave(per_engine: &[Vec<ResultEntry>]) -> Vec<ResultEntry> { |
| + let max_len = per_engine.iter().map(|r| r.len()).max().unwrap_or(0); |
| + let mut out = Vec::with_capacity(max_len * per_engine.len()); |
| + for i in 0..max_len { |
| + for results in per_engine { |
| + if let Some(entry) = results.get(i) { |
| + out.push(entry.clone()); |
| + } |
| + } |
| + } |
| + out |
| +} |
| + |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| @@ -272,4 +287,58 @@ mod tests { |
| let rendered = test_tera().render("index.html", &ctx).expect("render"); |
| assert!(rendered.contains("no bookmarks found")); |
| } |
| + |
| + #[test] |
| + fn test_interleave_empty() { |
| + let result = interleave(&[]); |
| + assert!(result.is_empty()); |
| + } |
| + |
| + #[test] |
| + fn test_interleave_single_engine() { |
| + let e = vec![ |
| + ResultEntry { title: "A".into(), url: "http://a".into(), description: None, engine: "e1".into() }, |
| + ResultEntry { title: "B".into(), url: "http://b".into(), description: None, engine: "e1".into() }, |
| + ]; |
| + let result = interleave(&[e]); |
| + assert_eq!(result.len(), 2); |
| + assert_eq!(result[0].title, "A"); |
| + assert_eq!(result[1].title, "B"); |
| + } |
| + |
| + #[test] |
| + fn test_interleave_two_engines_equal_length() { |
| + let e1 = vec![ |
| + ResultEntry { title: "A1".into(), url: "http://a1".into(), description: None, engine: "e1".into() }, |
| + ResultEntry { title: "A2".into(), url: "http://a2".into(), description: None, engine: "e1".into() }, |
| + ]; |
| + let e2 = vec![ |
| + ResultEntry { title: "B1".into(), url: "http://b1".into(), description: None, engine: "e2".into() }, |
| + ResultEntry { title: "B2".into(), url: "http://b2".into(), description: None, engine: "e2".into() }, |
| + ]; |
| + let result = interleave(&[e1, e2]); |
| + assert_eq!(result.len(), 4); |
| + assert_eq!(result[0].title, "A1"); |
| + assert_eq!(result[1].title, "B1"); |
| + assert_eq!(result[2].title, "A2"); |
| + assert_eq!(result[3].title, "B2"); |
| + } |
| + |
| + #[test] |
| + fn test_interleave_uneven_length() { |
| + let e1 = vec![ |
| + ResultEntry { title: "A1".into(), url: "http://a1".into(), description: None, engine: "e1".into() }, |
| + ResultEntry { title: "A2".into(), url: "http://a2".into(), description: None, engine: "e1".into() }, |
| + ResultEntry { title: "A3".into(), url: "http://a3".into(), description: None, engine: "e1".into() }, |
| + ]; |
| + let e2 = vec![ |
| + ResultEntry { title: "B1".into(), url: "http://b1".into(), description: None, engine: "e2".into() }, |
| + ]; |
| + let result = interleave(&[e1, e2]); |
| + assert_eq!(result.len(), 4); |
| + assert_eq!(result[0].title, "A1"); |
| + assert_eq!(result[1].title, "B1"); |
| + assert_eq!(result[2].title, "A2"); |
| + assert_eq!(result[3].title, "A3"); |
| + } |
| } |