1pub fn validate_year(year: u32) -> Result<(), String> {
8 if !(1800..=3000).contains(&year) {
9 Err(format!("Year must be between 1800 and 3000, got: {}", year))
10 } else {
11 Ok(())
12 }
13}
14
15#[derive(Debug, Clone, PartialEq, Eq)]
22pub enum SortOrder {
23 Relevance,
25 PublicationDate,
27 FirstAuthor,
29 JournalName,
31}
32
33impl SortOrder {
34 pub fn from_str_insensitive(s: &str) -> Result<Self, String> {
41 match s.trim().to_lowercase().as_str() {
42 "relevance" => Ok(SortOrder::Relevance),
43 "pub_date" | "publication_date" | "date" => Ok(SortOrder::PublicationDate),
44 "author" | "first_author" => Ok(SortOrder::FirstAuthor),
45 "journal" | "journal_name" => Ok(SortOrder::JournalName),
46 _ => Err(format!(
47 "Invalid sort order: '{}'. Supported values: relevance, pub_date, author, journal",
48 s
49 )),
50 }
51 }
52
53 pub(crate) fn as_api_param(&self) -> &str {
55 match self {
56 SortOrder::Relevance => "relevance",
57 SortOrder::PublicationDate => "pub_date",
58 SortOrder::FirstAuthor => "Author",
59 SortOrder::JournalName => "JournalName",
60 }
61 }
62}
63
64#[derive(Debug, Clone, PartialEq)]
66pub enum ArticleType {
67 ClinicalTrial,
69 Review,
71 SystematicReview,
73 MetaAnalysis,
75 CaseReport,
77 RandomizedControlledTrial,
79 ObservationalStudy,
81}
82
83impl ArticleType {
84 pub fn from_str_insensitive(s: &str) -> Result<Self, String> {
92 match s.trim().to_lowercase().as_str() {
93 "clinical trial" => Ok(ArticleType::ClinicalTrial),
94 "review" => Ok(ArticleType::Review),
95 "systematic review" => Ok(ArticleType::SystematicReview),
96 "meta-analysis" | "meta analysis" => Ok(ArticleType::MetaAnalysis),
97 "case reports" | "case report" => Ok(ArticleType::CaseReport),
98 "randomized controlled trial" | "rct" => Ok(ArticleType::RandomizedControlledTrial),
99 "observational study" => Ok(ArticleType::ObservationalStudy),
100 _ => Err(format!(
101 "Invalid article type: '{}'. Supported types: Clinical Trial, Review, Systematic Review, Meta-Analysis, Case Reports, Randomized Controlled Trial, Observational Study",
102 s
103 )),
104 }
105 }
106
107 pub(crate) fn to_query_string(&self) -> &'static str {
108 match self {
109 ArticleType::ClinicalTrial => "Clinical Trial[pt]",
110 ArticleType::Review => "Review[pt]",
111 ArticleType::SystematicReview => "Systematic Review[pt]",
112 ArticleType::MetaAnalysis => "Meta-Analysis[pt]",
113 ArticleType::CaseReport => "Case Reports[pt]",
114 ArticleType::RandomizedControlledTrial => "Randomized Controlled Trial[pt]",
115 ArticleType::ObservationalStudy => "Observational Study[pt]",
116 }
117 }
118}
119
120#[derive(Debug, Clone, PartialEq)]
122pub enum Language {
123 English,
124 Japanese,
125 German,
126 French,
127 Spanish,
128 Italian,
129 Chinese,
130 Russian,
131 Portuguese,
132 Arabic,
133 Dutch,
134 Korean,
135 Polish,
136 Swedish,
137 Danish,
138 Norwegian,
139 Finnish,
140 Turkish,
141 Hebrew,
142 Czech,
143 Hungarian,
144 Greek,
145 Other(String),
146}
147
148impl Language {
149 pub fn from_str_insensitive(s: &str) -> Self {
155 match s.trim().to_lowercase().as_str() {
156 "english" | "eng" => Language::English,
157 "japanese" | "jpn" => Language::Japanese,
158 "german" | "ger" | "deu" => Language::German,
159 "french" | "fre" | "fra" => Language::French,
160 "spanish" | "spa" => Language::Spanish,
161 "italian" | "ita" => Language::Italian,
162 "chinese" | "chi" | "zho" => Language::Chinese,
163 "russian" | "rus" => Language::Russian,
164 "portuguese" | "por" => Language::Portuguese,
165 "arabic" | "ara" => Language::Arabic,
166 "dutch" | "dut" | "nld" => Language::Dutch,
167 "korean" | "kor" => Language::Korean,
168 "polish" | "pol" => Language::Polish,
169 "swedish" | "swe" => Language::Swedish,
170 "danish" | "dan" => Language::Danish,
171 "norwegian" | "nor" => Language::Norwegian,
172 "finnish" | "fin" => Language::Finnish,
173 "turkish" | "tur" => Language::Turkish,
174 "hebrew" | "heb" => Language::Hebrew,
175 "czech" | "cze" | "ces" => Language::Czech,
176 "hungarian" | "hun" => Language::Hungarian,
177 "greek" | "gre" | "ell" => Language::Greek,
178 _ => Language::Other(s.trim().to_string()),
179 }
180 }
181
182 pub(crate) fn to_query_string(&self) -> String {
183 match self {
184 Language::English => "English[la]".to_string(),
185 Language::Japanese => "Japanese[la]".to_string(),
186 Language::German => "German[la]".to_string(),
187 Language::French => "French[la]".to_string(),
188 Language::Spanish => "Spanish[la]".to_string(),
189 Language::Italian => "Italian[la]".to_string(),
190 Language::Chinese => "Chinese[la]".to_string(),
191 Language::Russian => "Russian[la]".to_string(),
192 Language::Portuguese => "Portuguese[la]".to_string(),
193 Language::Arabic => "Arabic[la]".to_string(),
194 Language::Dutch => "Dutch[la]".to_string(),
195 Language::Korean => "Korean[la]".to_string(),
196 Language::Polish => "Polish[la]".to_string(),
197 Language::Swedish => "Swedish[la]".to_string(),
198 Language::Danish => "Danish[la]".to_string(),
199 Language::Norwegian => "Norwegian[la]".to_string(),
200 Language::Finnish => "Finnish[la]".to_string(),
201 Language::Turkish => "Turkish[la]".to_string(),
202 Language::Hebrew => "Hebrew[la]".to_string(),
203 Language::Czech => "Czech[la]".to_string(),
204 Language::Hungarian => "Hungarian[la]".to_string(),
205 Language::Greek => "Greek[la]".to_string(),
206 Language::Other(lang) => format!("{lang}[la]"),
207 }
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214
215 #[test]
216 fn test_article_type_to_query_string() {
217 let test_cases = vec![
218 (ArticleType::ClinicalTrial, "Clinical Trial[pt]"),
219 (ArticleType::Review, "Review[pt]"),
220 (ArticleType::SystematicReview, "Systematic Review[pt]"),
221 (ArticleType::MetaAnalysis, "Meta-Analysis[pt]"),
222 (ArticleType::CaseReport, "Case Reports[pt]"),
223 (
224 ArticleType::RandomizedControlledTrial,
225 "Randomized Controlled Trial[pt]",
226 ),
227 (ArticleType::ObservationalStudy, "Observational Study[pt]"),
228 ];
229
230 for (article_type, expected) in test_cases {
231 assert_eq!(article_type.to_query_string(), expected);
232 }
233 }
234
235 #[test]
236 fn test_language_to_query_string() {
237 let test_cases = vec![
238 (Language::English, "English[la]"),
239 (Language::Japanese, "Japanese[la]"),
240 (Language::German, "German[la]"),
241 (Language::French, "French[la]"),
242 (Language::Spanish, "Spanish[la]"),
243 (Language::Italian, "Italian[la]"),
244 (Language::Chinese, "Chinese[la]"),
245 (Language::Russian, "Russian[la]"),
246 (Language::Portuguese, "Portuguese[la]"),
247 (Language::Arabic, "Arabic[la]"),
248 (Language::Dutch, "Dutch[la]"),
249 (Language::Korean, "Korean[la]"),
250 (Language::Polish, "Polish[la]"),
251 (Language::Swedish, "Swedish[la]"),
252 (Language::Danish, "Danish[la]"),
253 (Language::Norwegian, "Norwegian[la]"),
254 (Language::Finnish, "Finnish[la]"),
255 (Language::Turkish, "Turkish[la]"),
256 (Language::Hebrew, "Hebrew[la]"),
257 (Language::Czech, "Czech[la]"),
258 (Language::Hungarian, "Hungarian[la]"),
259 (Language::Greek, "Greek[la]"),
260 ];
261
262 for (language, expected) in test_cases {
263 assert_eq!(language.to_query_string(), expected);
264 }
265 }
266
267 #[test]
268 fn test_language_other_variant() {
269 let custom_lang = Language::Other("Esperanto".to_string());
270 assert_eq!(custom_lang.to_query_string(), "Esperanto[la]");
271
272 let another_custom = Language::Other("Klingon".to_string());
273 assert_eq!(another_custom.to_query_string(), "Klingon[la]");
274 }
275
276 #[test]
277 fn test_article_type_equality() {
278 assert_eq!(ArticleType::Review, ArticleType::Review);
279 assert_ne!(ArticleType::Review, ArticleType::ClinicalTrial);
280 assert_ne!(ArticleType::MetaAnalysis, ArticleType::SystematicReview);
281 }
282
283 #[test]
284 fn test_language_equality() {
285 assert_eq!(Language::English, Language::English);
286 assert_ne!(Language::English, Language::Japanese);
287
288 let other1 = Language::Other("Custom".to_string());
289 let other2 = Language::Other("Custom".to_string());
290 let other3 = Language::Other("Different".to_string());
291
292 assert_eq!(other1, other2);
293 assert_ne!(other1, other3);
294 assert_ne!(Language::English, other1);
295 }
296
297 #[test]
298 fn test_debug_formatting() {
299 let article_type = ArticleType::Review;
300 let debug_str = format!("{:?}", article_type);
301 assert!(debug_str.contains("Review"));
302
303 let language = Language::English;
304 let debug_str = format!("{:?}", language);
305 assert!(debug_str.contains("English"));
306
307 let custom_lang = Language::Other("Test".to_string());
308 let debug_str = format!("{:?}", custom_lang);
309 assert!(debug_str.contains("Other"));
310 assert!(debug_str.contains("Test"));
311 }
312
313 #[test]
314 fn test_clone_functionality() {
315 let original_type = ArticleType::MetaAnalysis;
316 let cloned_type = original_type.clone();
317 assert_eq!(original_type, cloned_type);
318 assert_eq!(
319 original_type.to_query_string(),
320 cloned_type.to_query_string()
321 );
322
323 let original_lang = Language::German;
324 let cloned_lang = original_lang.clone();
325 assert_eq!(original_lang, cloned_lang);
326 assert_eq!(
327 original_lang.to_query_string(),
328 cloned_lang.to_query_string()
329 );
330
331 let original_other = Language::Other("Custom".to_string());
332 let cloned_other = original_other.clone();
333 assert_eq!(original_other, cloned_other);
334 assert_eq!(
335 original_other.to_query_string(),
336 cloned_other.to_query_string()
337 );
338 }
339
340 #[test]
341 fn test_language_other_empty_string() {
342 let empty_lang = Language::Other("".to_string());
343 assert_eq!(empty_lang.to_query_string(), "[la]");
344 }
345
346 #[test]
347 fn test_language_other_special_characters() {
348 let special_lang = Language::Other("中文-汉语".to_string());
349 assert_eq!(special_lang.to_query_string(), "中文-汉语[la]");
350
351 let symbol_lang = Language::Other("Lang@#$%".to_string());
352 assert_eq!(symbol_lang.to_query_string(), "Lang@#$%[la]");
353 }
354
355 #[test]
356 fn test_all_article_types_unique() {
357 let all_types = vec![
358 ArticleType::ClinicalTrial,
359 ArticleType::Review,
360 ArticleType::SystematicReview,
361 ArticleType::MetaAnalysis,
362 ArticleType::CaseReport,
363 ArticleType::RandomizedControlledTrial,
364 ArticleType::ObservationalStudy,
365 ];
366
367 let mut query_strings = Vec::new();
368 for article_type in all_types {
369 let query_string = article_type.to_query_string();
370 assert!(
371 !query_strings.contains(&query_string),
372 "Duplicate query string found: {}",
373 query_string
374 );
375 query_strings.push(query_string);
376 }
377 }
378
379 #[test]
380 fn test_sort_order_as_api_param() {
381 assert_eq!(SortOrder::Relevance.as_api_param(), "relevance");
382 assert_eq!(SortOrder::PublicationDate.as_api_param(), "pub_date");
383 assert_eq!(SortOrder::FirstAuthor.as_api_param(), "Author");
384 assert_eq!(SortOrder::JournalName.as_api_param(), "JournalName");
385 }
386
387 #[test]
388 fn test_sort_order_equality() {
389 assert_eq!(SortOrder::Relevance, SortOrder::Relevance);
390 assert_ne!(SortOrder::Relevance, SortOrder::PublicationDate);
391 assert_ne!(SortOrder::FirstAuthor, SortOrder::JournalName);
392 }
393
394 #[test]
395 fn test_sort_order_clone() {
396 let original = SortOrder::PublicationDate;
397 let cloned = original.clone();
398 assert_eq!(original, cloned);
399 assert_eq!(original.as_api_param(), cloned.as_api_param());
400 }
401
402 #[test]
403 fn test_all_standard_languages_unique() {
404 let standard_languages = vec![
405 Language::English,
406 Language::Japanese,
407 Language::German,
408 Language::French,
409 Language::Spanish,
410 Language::Italian,
411 Language::Chinese,
412 Language::Russian,
413 Language::Portuguese,
414 Language::Arabic,
415 Language::Dutch,
416 Language::Korean,
417 Language::Polish,
418 Language::Swedish,
419 Language::Danish,
420 Language::Norwegian,
421 Language::Finnish,
422 Language::Turkish,
423 Language::Hebrew,
424 Language::Czech,
425 Language::Hungarian,
426 Language::Greek,
427 ];
428
429 let mut query_strings = Vec::new();
430 for language in standard_languages {
431 let query_string = language.to_query_string();
432 assert!(
433 !query_strings.contains(&query_string),
434 "Duplicate query string found: {}",
435 query_string
436 );
437 query_strings.push(query_string);
438 }
439 }
440}