1use async_trait::async_trait;
4use serde::{Deserialize, Serialize};
5
6use crate::{Result, SearchQuery, SearchResult};
7
8#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
10#[serde(rename_all = "lowercase")]
11pub enum EngineCategory {
12 #[default]
13 General,
14 Images,
15 Videos,
16 News,
17 Maps,
18 Music,
19 Files,
20 Science,
21 Social,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct EngineConfig {
27 pub name: String,
29 pub shortcut: String,
31 pub categories: Vec<EngineCategory>,
33 #[serde(default = "default_weight")]
35 pub weight: f64,
36 #[serde(default = "default_timeout")]
38 pub timeout: u64,
39 #[serde(default = "default_enabled")]
41 pub enabled: bool,
42 #[serde(default)]
44 pub paging: bool,
45 #[serde(default)]
47 pub safesearch: bool,
48}
49
50fn default_weight() -> f64 {
51 1.0
52}
53
54fn default_timeout() -> u64 {
55 5
56}
57
58fn default_enabled() -> bool {
59 true
60}
61
62impl Default for EngineConfig {
63 fn default() -> Self {
64 Self {
65 name: String::new(),
66 shortcut: String::new(),
67 categories: vec![EngineCategory::General],
68 weight: 1.0,
69 timeout: 5,
70 enabled: true,
71 paging: false,
72 safesearch: false,
73 }
74 }
75}
76
77#[async_trait]
81pub trait Engine: Send + Sync {
82 fn config(&self) -> &EngineConfig;
84
85 async fn search(&self, query: &SearchQuery) -> Result<Vec<SearchResult>>;
87
88 fn name(&self) -> &str {
90 &self.config().name
91 }
92
93 fn shortcut(&self) -> &str {
95 &self.config().shortcut
96 }
97
98 fn weight(&self) -> f64 {
100 self.config().weight
101 }
102
103 fn is_enabled(&self) -> bool {
105 self.config().enabled
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112
113 #[test]
114 fn test_engine_category_default() {
115 let default: EngineCategory = Default::default();
116 assert_eq!(default, EngineCategory::General);
117 }
118
119 #[test]
120 fn test_engine_category_variants() {
121 let categories = vec![
122 EngineCategory::General,
123 EngineCategory::Images,
124 EngineCategory::Videos,
125 EngineCategory::News,
126 EngineCategory::Maps,
127 EngineCategory::Music,
128 EngineCategory::Files,
129 EngineCategory::Science,
130 EngineCategory::Social,
131 ];
132 assert_eq!(categories.len(), 9);
133 }
134
135 #[test]
136 fn test_engine_config_default() {
137 let config = EngineConfig::default();
138 assert_eq!(config.name, "");
139 assert_eq!(config.shortcut, "");
140 assert_eq!(config.categories, vec![EngineCategory::General]);
141 assert_eq!(config.weight, 1.0);
142 assert_eq!(config.timeout, 5);
143 assert!(config.enabled);
144 assert!(!config.paging);
145 assert!(!config.safesearch);
146 }
147
148 #[test]
149 fn test_engine_config_custom() {
150 let config = EngineConfig {
151 name: "Test Engine".to_string(),
152 shortcut: "test".to_string(),
153 categories: vec![EngineCategory::Images, EngineCategory::Videos],
154 weight: 2.0,
155 timeout: 10,
156 enabled: false,
157 paging: true,
158 safesearch: true,
159 };
160 assert_eq!(config.name, "Test Engine");
161 assert_eq!(config.shortcut, "test");
162 assert_eq!(config.weight, 2.0);
163 assert_eq!(config.timeout, 10);
164 assert!(!config.enabled);
165 assert!(config.paging);
166 assert!(config.safesearch);
167 }
168
169 #[test]
170 fn test_engine_config_serialization() {
171 let config = EngineConfig {
172 name: "Test".to_string(),
173 shortcut: "t".to_string(),
174 ..Default::default()
175 };
176 let json = serde_json::to_string(&config).unwrap();
177 assert!(json.contains("\"name\":\"Test\""));
178 assert!(json.contains("\"shortcut\":\"t\""));
179 }
180
181 #[test]
182 fn test_engine_config_deserialization() {
183 let json = r#"{"name":"Test","shortcut":"t","categories":["general"]}"#;
184 let config: EngineConfig = serde_json::from_str(json).unwrap();
185 assert_eq!(config.name, "Test");
186 assert_eq!(config.shortcut, "t");
187 assert_eq!(config.weight, 1.0); assert_eq!(config.timeout, 5); assert!(config.enabled); }
191
192 #[test]
193 fn test_engine_category_serialization() {
194 let category = EngineCategory::Images;
195 let json = serde_json::to_string(&category).unwrap();
196 assert_eq!(json, "\"images\"");
197 }
198
199 #[test]
200 fn test_engine_category_deserialization() {
201 let json = "\"videos\"";
202 let category: EngineCategory = serde_json::from_str(json).unwrap();
203 assert_eq!(category, EngineCategory::Videos);
204 }
205
206 #[test]
207 fn test_engine_category_hash() {
208 use std::collections::HashSet;
209 let mut set = HashSet::new();
210 set.insert(EngineCategory::General);
211 set.insert(EngineCategory::Images);
212 set.insert(EngineCategory::General); assert_eq!(set.len(), 2);
214 }
215}