Skip to main content

a3s_search/
query.rs

1//! Search query representation.
2
3use serde::{Deserialize, Serialize};
4
5use crate::EngineCategory;
6
7/// Safe search level.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
9pub enum SafeSearch {
10    /// No filtering.
11    #[default]
12    Off = 0,
13    /// Moderate filtering.
14    Moderate = 1,
15    /// Strict filtering.
16    Strict = 2,
17}
18
19/// Time range filter for search results.
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
21pub enum TimeRange {
22    Day,
23    Week,
24    Month,
25    Year,
26}
27
28/// A search query with all parameters.
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct SearchQuery {
31    /// The search terms.
32    pub query: String,
33    /// Target categories.
34    pub categories: Vec<EngineCategory>,
35    /// Language/locale (e.g., "en-US").
36    pub language: Option<String>,
37    /// Safe search level.
38    pub safesearch: SafeSearch,
39    /// Page number (1-indexed).
40    pub page: u32,
41    /// Time range filter.
42    pub time_range: Option<TimeRange>,
43    /// Specific engines to use (by shortcut).
44    pub engines: Vec<String>,
45}
46
47impl SearchQuery {
48    /// Creates a new search query with the given terms.
49    pub fn new(query: impl Into<String>) -> Self {
50        Self {
51            query: query.into(),
52            categories: vec![EngineCategory::General],
53            language: None,
54            safesearch: SafeSearch::Off,
55            page: 1,
56            time_range: None,
57            engines: Vec::new(),
58        }
59    }
60
61    /// Sets the categories to search.
62    pub fn with_categories(mut self, categories: Vec<EngineCategory>) -> Self {
63        self.categories = categories;
64        self
65    }
66
67    /// Sets the language/locale.
68    pub fn with_language(mut self, language: impl Into<String>) -> Self {
69        self.language = Some(language.into());
70        self
71    }
72
73    /// Sets the safe search level.
74    pub fn with_safesearch(mut self, level: SafeSearch) -> Self {
75        self.safesearch = level;
76        self
77    }
78
79    /// Sets the page number.
80    pub fn with_page(mut self, page: u32) -> Self {
81        self.page = page;
82        self
83    }
84
85    /// Sets the time range filter.
86    pub fn with_time_range(mut self, range: TimeRange) -> Self {
87        self.time_range = Some(range);
88        self
89    }
90
91    /// Sets specific engines to use.
92    pub fn with_engines(mut self, engines: Vec<String>) -> Self {
93        self.engines = engines;
94        self
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    #[test]
103    fn test_search_query_new() {
104        let query = SearchQuery::new("test query");
105        assert_eq!(query.query, "test query");
106        assert_eq!(query.categories, vec![EngineCategory::General]);
107        assert_eq!(query.safesearch, SafeSearch::Off);
108        assert_eq!(query.page, 1);
109        assert!(query.language.is_none());
110        assert!(query.time_range.is_none());
111        assert!(query.engines.is_empty());
112    }
113
114    #[test]
115    fn test_search_query_with_categories() {
116        let query = SearchQuery::new("test")
117            .with_categories(vec![EngineCategory::Images, EngineCategory::Videos]);
118        assert_eq!(
119            query.categories,
120            vec![EngineCategory::Images, EngineCategory::Videos]
121        );
122    }
123
124    #[test]
125    fn test_search_query_with_language() {
126        let query = SearchQuery::new("test").with_language("en-US");
127        assert_eq!(query.language, Some("en-US".to_string()));
128    }
129
130    #[test]
131    fn test_search_query_with_safesearch() {
132        let query = SearchQuery::new("test").with_safesearch(SafeSearch::Strict);
133        assert_eq!(query.safesearch, SafeSearch::Strict);
134    }
135
136    #[test]
137    fn test_search_query_with_page() {
138        let query = SearchQuery::new("test").with_page(5);
139        assert_eq!(query.page, 5);
140    }
141
142    #[test]
143    fn test_search_query_with_time_range() {
144        let query = SearchQuery::new("test").with_time_range(TimeRange::Week);
145        assert_eq!(query.time_range, Some(TimeRange::Week));
146    }
147
148    #[test]
149    fn test_search_query_with_engines() {
150        let query =
151            SearchQuery::new("test").with_engines(vec!["ddg".to_string(), "wiki".to_string()]);
152        assert_eq!(query.engines, vec!["ddg", "wiki"]);
153    }
154
155    #[test]
156    fn test_search_query_builder_chain() {
157        let query = SearchQuery::new("rust programming")
158            .with_categories(vec![EngineCategory::General])
159            .with_language("en")
160            .with_safesearch(SafeSearch::Moderate)
161            .with_page(2)
162            .with_time_range(TimeRange::Month)
163            .with_engines(vec!["ddg".to_string()]);
164
165        assert_eq!(query.query, "rust programming");
166        assert_eq!(query.language, Some("en".to_string()));
167        assert_eq!(query.safesearch, SafeSearch::Moderate);
168        assert_eq!(query.page, 2);
169        assert_eq!(query.time_range, Some(TimeRange::Month));
170        assert_eq!(query.engines, vec!["ddg"]);
171    }
172
173    #[test]
174    fn test_safe_search_default() {
175        let default: SafeSearch = Default::default();
176        assert_eq!(default, SafeSearch::Off);
177    }
178
179    #[test]
180    fn test_safe_search_values() {
181        assert_eq!(SafeSearch::Off as u8, 0);
182        assert_eq!(SafeSearch::Moderate as u8, 1);
183        assert_eq!(SafeSearch::Strict as u8, 2);
184    }
185
186    #[test]
187    fn test_time_range_variants() {
188        let day = TimeRange::Day;
189        let week = TimeRange::Week;
190        let month = TimeRange::Month;
191        let year = TimeRange::Year;
192
193        assert_ne!(day, week);
194        assert_ne!(month, year);
195    }
196
197    #[test]
198    fn test_search_query_serialization() {
199        let query = SearchQuery::new("test");
200        let json = serde_json::to_string(&query).unwrap();
201        assert!(json.contains("\"query\":\"test\""));
202    }
203
204    #[test]
205    fn test_search_query_deserialization() {
206        let json = r#"{"query":"test","categories":["general"],"language":null,"safesearch":"Off","page":1,"time_range":null,"engines":[]}"#;
207        let query: SearchQuery = serde_json::from_str(json).unwrap();
208        assert_eq!(query.query, "test");
209    }
210}