1use serde::{Deserialize, Serialize};
2use std::path::PathBuf;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
10#[serde(rename_all = "lowercase")]
11pub enum SearchCascadeTier {
12 Headless,
14 Http,
16 Api,
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
22#[serde(rename_all = "camelCase")]
23pub struct SearchConfig {
24 #[serde(default = "default_search_timeout")]
26 pub timeout: u64,
27
28 #[serde(default, skip_serializing_if = "Option::is_none")]
30 pub health: Option<SearchHealthConfig>,
31
32 #[serde(
38 default,
39 alias = "cascade_order",
40 skip_serializing_if = "Option::is_none"
41 )]
42 pub cascade_order: Option<Vec<SearchCascadeTier>>,
43
44 #[serde(default, rename = "engine")]
46 pub engines: std::collections::HashMap<String, SearchEngineConfig>,
47
48 #[serde(default, skip_serializing_if = "Option::is_none")]
52 pub headless: Option<HeadlessConfig>,
53}
54
55#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
57#[serde(rename_all = "lowercase")]
58pub enum BrowserBackend {
59 #[default]
61 Moli,
62 #[serde(alias = "chromium")]
64 Chrome,
65 Lightpanda,
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
75#[serde(rename_all = "camelCase")]
76pub struct HeadlessConfig {
77 #[serde(default)]
79 pub backend: BrowserBackend,
80
81 #[serde(default = "default_headless_max_tabs")]
83 pub max_tabs: usize,
84
85 #[serde(
88 default,
89 alias = "chromePath",
90 alias = "lightpandaPath",
91 alias = "obscuraPath",
92 alias = "playwrightPath",
93 skip_serializing_if = "Option::is_none"
94 )]
95 pub browser_path: Option<String>,
96
97 #[serde(default = "default_auto_download_moli")]
100 pub auto_download_moli: bool,
101
102 #[serde(default, skip_serializing_if = "Option::is_none")]
105 pub moli_version: Option<String>,
106
107 #[serde(default, skip_serializing_if = "Option::is_none")]
110 pub moli_sha256: Option<String>,
111
112 #[serde(default, skip_serializing_if = "Option::is_none")]
114 pub moli_cache_dir: Option<PathBuf>,
115
116 #[serde(default = "default_moli_download_timeout_secs")]
120 pub moli_download_timeout_secs: u64,
121
122 #[serde(default, skip_serializing_if = "Vec::is_empty")]
124 pub launch_args: Vec<String>,
125
126 #[serde(default, skip_serializing_if = "Option::is_none")]
128 pub proxy_url: Option<String>,
129}
130
131impl BrowserBackend {
132 pub fn is_moli(self) -> bool {
133 matches!(self, Self::Moli)
134 }
135
136 pub fn is_lightpanda(self) -> bool {
137 matches!(self, Self::Lightpanda)
138 }
139}
140
141impl Default for HeadlessConfig {
142 fn default() -> Self {
143 Self {
144 backend: BrowserBackend::Moli,
145 max_tabs: 4,
146 browser_path: None,
147 auto_download_moli: true,
148 moli_version: None,
149 moli_sha256: None,
150 moli_cache_dir: None,
151 moli_download_timeout_secs: default_moli_download_timeout_secs(),
152 launch_args: Vec::new(),
153 proxy_url: None,
154 }
155 }
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize)]
160#[serde(rename_all = "camelCase")]
161pub struct DocumentParserConfig {
162 #[serde(default = "default_enabled")]
164 pub enabled: bool,
165
166 #[serde(default = "default_document_parser_max_file_size_mb")]
168 pub max_file_size_mb: u64,
169
170 #[serde(default, skip_serializing_if = "Option::is_none")]
172 pub cache: Option<DocumentCacheConfig>,
173}
174
175impl Default for DocumentParserConfig {
176 fn default() -> Self {
177 Self {
178 enabled: true,
179 max_file_size_mb: default_document_parser_max_file_size_mb(),
180 cache: Some(DocumentCacheConfig::default()),
181 }
182 }
183}
184
185impl DocumentParserConfig {
186 pub fn normalized(&self) -> Self {
187 Self {
188 enabled: self.enabled,
189 max_file_size_mb: self.max_file_size_mb.clamp(1, 1024),
190 cache: self.cache.as_ref().map(DocumentCacheConfig::normalized),
191 }
192 }
193}
194
195#[derive(Debug, Clone, Serialize, Deserialize)]
196#[serde(rename_all = "camelCase")]
197pub struct DocumentCacheConfig {
198 #[serde(default = "default_enabled")]
199 pub enabled: bool,
200
201 #[serde(default, skip_serializing_if = "Option::is_none")]
202 pub directory: Option<PathBuf>,
203}
204
205impl Default for DocumentCacheConfig {
206 fn default() -> Self {
207 Self {
208 enabled: true,
209 directory: None,
210 }
211 }
212}
213
214impl DocumentCacheConfig {
215 pub fn normalized(&self) -> Self {
216 Self {
217 enabled: self.enabled,
218 directory: self.directory.clone(),
219 }
220 }
221}
222
223#[derive(Debug, Clone, Serialize, Deserialize)]
225#[serde(rename_all = "camelCase")]
226pub struct SearchHealthConfig {
227 #[serde(default = "default_max_failures")]
229 pub max_failures: u32,
230
231 #[serde(default = "default_suspend_seconds")]
233 pub suspend_seconds: u64,
234}
235
236#[derive(Debug, Clone, Serialize, Deserialize)]
238#[serde(rename_all = "camelCase")]
239pub struct SearchEngineConfig {
240 #[serde(default = "default_enabled")]
242 pub enabled: bool,
243
244 #[serde(default = "default_weight")]
246 pub weight: f64,
247
248 #[serde(skip_serializing_if = "Option::is_none")]
250 pub timeout: Option<u64>,
251
252 #[serde(default, skip_serializing_if = "Option::is_none")]
256 pub api_key: Option<String>,
257
258 #[serde(default, skip_serializing_if = "Option::is_none")]
260 pub project: Option<String>,
261
262 #[serde(default, skip_serializing_if = "Option::is_none")]
268 pub endpoint: Option<String>,
269}
270
271pub(crate) fn default_search_timeout() -> u64 {
272 20
273}
274
275pub(crate) fn default_headless_max_tabs() -> usize {
276 4
277}
278
279fn default_auto_download_moli() -> bool {
280 true
281}
282
283pub(crate) fn default_moli_download_timeout_secs() -> u64 {
284 120
285}
286
287fn default_max_failures() -> u32 {
288 3
289}
290
291fn default_suspend_seconds() -> u64 {
292 60
293}
294
295pub(crate) fn default_enabled() -> bool {
296 true
297}
298
299fn default_weight() -> f64 {
300 1.0
301}
302
303pub(crate) fn default_document_parser_max_file_size_mb() -> u64 {
304 50
305}