#![cfg(feature = "mock")]
#![allow(missing_docs)]
use std::future::pending;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use crate::meta::engines::error::EngineError;
use crate::meta::engines::models::SearchResult;
use crate::meta::engines::BoxFuture;
use crate::meta::engines::SearchEngine;
#[derive(Clone, Debug)]
pub struct MockResult {
pub title: String,
pub url: String,
pub snippet: Option<String>,
pub source_engine: String,
}
impl MockResult {
pub fn new(
title: impl Into<String>,
url: impl Into<String>,
source_engine: impl Into<String>,
) -> Self {
Self {
title: title.into(),
url: url.into(),
snippet: None,
source_engine: source_engine.into(),
}
}
pub fn with_snippet(mut self, snippet: impl Into<String>) -> Self {
self.snippet = Some(snippet.into());
self
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum MockFailure {
Timeout,
HttpStatus(u16),
Parse,
Network,
}
impl MockFailure {
fn to_engine_error(self, engine: &'static str) -> EngineError {
match self {
MockFailure::Timeout => EngineError::Timeout { engine },
MockFailure::HttpStatus(status) => EngineError::BadStatus { engine, status },
MockFailure::Parse => EngineError::ParseFailed {
engine,
reason: "mock parse failure".to_string(),
},
MockFailure::Network => EngineError::NetworkError {
engine,
reason: "mock network failure".to_string(),
},
}
}
}
pub struct MockEngine {
name: &'static str,
results: Vec<SearchResult>,
failure: Option<MockFailure>,
hang: bool,
timeout_sink: Option<Arc<Mutex<Option<Duration>>>>,
}
impl MockEngine {
pub fn success(name: &'static str, results: Vec<MockResult>) -> Self {
let rs = results
.into_iter()
.map(|r| SearchResult {
title: r.title,
url: r.url,
snippet: r.snippet,
source_engine: r.source_engine,
})
.collect();
Self {
name,
results: rs,
failure: None,
hang: false,
timeout_sink: None,
}
}
pub fn failure(name: &'static str, failure: MockFailure) -> Self {
Self {
name,
results: Vec::new(),
failure: Some(failure),
hang: false,
timeout_sink: None,
}
}
pub fn hang(name: &'static str) -> Self {
Self {
name,
results: Vec::new(),
failure: None,
hang: true,
timeout_sink: None,
}
}
pub fn record_timeout(
name: &'static str,
sink: Arc<Mutex<Option<Duration>>>,
) -> Self {
Self {
name,
results: Vec::new(),
failure: None,
hang: false,
timeout_sink: Some(sink),
}
}
}
impl SearchEngine for MockEngine {
fn name(&self) -> &'static str {
self.name
}
fn search<'a>(
&'a self,
_query: &'a str,
_max_results: usize,
timeout: Duration,
) -> BoxFuture<'a, Result<Vec<SearchResult>, EngineError>> {
if let Some(sink) = &self.timeout_sink {
if let Ok(mut g) = sink.lock() {
*g = Some(timeout);
}
}
Box::pin(async move {
if self.hang {
pending::<()>().await;
unreachable!("pending future resolved")
}
match self.failure {
Some(f) => Err(f.to_engine_error(self.name)),
None => Ok(self.results.clone()),
}
})
}
}
pub fn mock_engines(engines: Vec<MockEngine>) -> Vec<Arc<dyn SearchEngine>> {
engines
.into_iter()
.map(|e| Arc::new(e) as Arc<dyn SearchEngine>)
.collect()
}