pubmed_client/pubmed/query/
filters.rs

1//! Filter types and enums for PubMed query filtering
2
3/// Sort order for PubMed search results
4///
5/// Controls how ESearch results are ordered. The default sort (when not specified)
6/// is by relevance for most queries.
7///
8/// See: <https://www.ncbi.nlm.nih.gov/books/NBK25499/#chapter4.ESearch>
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub enum SortOrder {
11    /// Sort by relevance (default PubMed behavior)
12    Relevance,
13    /// Sort by publication date (newest first)
14    PublicationDate,
15    /// Sort by first author name (alphabetical)
16    FirstAuthor,
17    /// Sort by journal name (alphabetical)
18    JournalName,
19}
20
21impl SortOrder {
22    /// Get the API parameter value for this sort order
23    pub(crate) fn as_api_param(&self) -> &str {
24        match self {
25            SortOrder::Relevance => "relevance",
26            SortOrder::PublicationDate => "pub_date",
27            SortOrder::FirstAuthor => "Author",
28            SortOrder::JournalName => "JournalName",
29        }
30    }
31}
32
33/// Article types that can be filtered in PubMed searches
34#[derive(Debug, Clone, PartialEq)]
35pub enum ArticleType {
36    /// Clinical trials
37    ClinicalTrial,
38    /// Review articles
39    Review,
40    /// Systematic reviews
41    SystematicReview,
42    /// Meta-analysis
43    MetaAnalysis,
44    /// Case reports
45    CaseReport,
46    /// Randomized controlled trials
47    RandomizedControlledTrial,
48    /// Observational studies
49    ObservationalStudy,
50}
51
52impl ArticleType {
53    pub(crate) fn to_query_string(&self) -> &'static str {
54        match self {
55            ArticleType::ClinicalTrial => "Clinical Trial[pt]",
56            ArticleType::Review => "Review[pt]",
57            ArticleType::SystematicReview => "Systematic Review[pt]",
58            ArticleType::MetaAnalysis => "Meta-Analysis[pt]",
59            ArticleType::CaseReport => "Case Reports[pt]",
60            ArticleType::RandomizedControlledTrial => "Randomized Controlled Trial[pt]",
61            ArticleType::ObservationalStudy => "Observational Study[pt]",
62        }
63    }
64}
65
66/// Language options for filtering articles
67#[derive(Debug, Clone, PartialEq)]
68pub enum Language {
69    English,
70    Japanese,
71    German,
72    French,
73    Spanish,
74    Italian,
75    Chinese,
76    Russian,
77    Portuguese,
78    Arabic,
79    Dutch,
80    Korean,
81    Polish,
82    Swedish,
83    Danish,
84    Norwegian,
85    Finnish,
86    Turkish,
87    Hebrew,
88    Czech,
89    Hungarian,
90    Greek,
91    Other(String),
92}
93
94impl Language {
95    pub(crate) fn to_query_string(&self) -> String {
96        match self {
97            Language::English => "English[la]".to_string(),
98            Language::Japanese => "Japanese[la]".to_string(),
99            Language::German => "German[la]".to_string(),
100            Language::French => "French[la]".to_string(),
101            Language::Spanish => "Spanish[la]".to_string(),
102            Language::Italian => "Italian[la]".to_string(),
103            Language::Chinese => "Chinese[la]".to_string(),
104            Language::Russian => "Russian[la]".to_string(),
105            Language::Portuguese => "Portuguese[la]".to_string(),
106            Language::Arabic => "Arabic[la]".to_string(),
107            Language::Dutch => "Dutch[la]".to_string(),
108            Language::Korean => "Korean[la]".to_string(),
109            Language::Polish => "Polish[la]".to_string(),
110            Language::Swedish => "Swedish[la]".to_string(),
111            Language::Danish => "Danish[la]".to_string(),
112            Language::Norwegian => "Norwegian[la]".to_string(),
113            Language::Finnish => "Finnish[la]".to_string(),
114            Language::Turkish => "Turkish[la]".to_string(),
115            Language::Hebrew => "Hebrew[la]".to_string(),
116            Language::Czech => "Czech[la]".to_string(),
117            Language::Hungarian => "Hungarian[la]".to_string(),
118            Language::Greek => "Greek[la]".to_string(),
119            Language::Other(lang) => format!("{lang}[la]"),
120        }
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    #[test]
129    fn test_article_type_to_query_string() {
130        let test_cases = vec![
131            (ArticleType::ClinicalTrial, "Clinical Trial[pt]"),
132            (ArticleType::Review, "Review[pt]"),
133            (ArticleType::SystematicReview, "Systematic Review[pt]"),
134            (ArticleType::MetaAnalysis, "Meta-Analysis[pt]"),
135            (ArticleType::CaseReport, "Case Reports[pt]"),
136            (
137                ArticleType::RandomizedControlledTrial,
138                "Randomized Controlled Trial[pt]",
139            ),
140            (ArticleType::ObservationalStudy, "Observational Study[pt]"),
141        ];
142
143        for (article_type, expected) in test_cases {
144            assert_eq!(article_type.to_query_string(), expected);
145        }
146    }
147
148    #[test]
149    fn test_language_to_query_string() {
150        let test_cases = vec![
151            (Language::English, "English[la]"),
152            (Language::Japanese, "Japanese[la]"),
153            (Language::German, "German[la]"),
154            (Language::French, "French[la]"),
155            (Language::Spanish, "Spanish[la]"),
156            (Language::Italian, "Italian[la]"),
157            (Language::Chinese, "Chinese[la]"),
158            (Language::Russian, "Russian[la]"),
159            (Language::Portuguese, "Portuguese[la]"),
160            (Language::Arabic, "Arabic[la]"),
161            (Language::Dutch, "Dutch[la]"),
162            (Language::Korean, "Korean[la]"),
163            (Language::Polish, "Polish[la]"),
164            (Language::Swedish, "Swedish[la]"),
165            (Language::Danish, "Danish[la]"),
166            (Language::Norwegian, "Norwegian[la]"),
167            (Language::Finnish, "Finnish[la]"),
168            (Language::Turkish, "Turkish[la]"),
169            (Language::Hebrew, "Hebrew[la]"),
170            (Language::Czech, "Czech[la]"),
171            (Language::Hungarian, "Hungarian[la]"),
172            (Language::Greek, "Greek[la]"),
173        ];
174
175        for (language, expected) in test_cases {
176            assert_eq!(language.to_query_string(), expected);
177        }
178    }
179
180    #[test]
181    fn test_language_other_variant() {
182        let custom_lang = Language::Other("Esperanto".to_string());
183        assert_eq!(custom_lang.to_query_string(), "Esperanto[la]");
184
185        let another_custom = Language::Other("Klingon".to_string());
186        assert_eq!(another_custom.to_query_string(), "Klingon[la]");
187    }
188
189    #[test]
190    fn test_article_type_equality() {
191        assert_eq!(ArticleType::Review, ArticleType::Review);
192        assert_ne!(ArticleType::Review, ArticleType::ClinicalTrial);
193        assert_ne!(ArticleType::MetaAnalysis, ArticleType::SystematicReview);
194    }
195
196    #[test]
197    fn test_language_equality() {
198        assert_eq!(Language::English, Language::English);
199        assert_ne!(Language::English, Language::Japanese);
200
201        let other1 = Language::Other("Custom".to_string());
202        let other2 = Language::Other("Custom".to_string());
203        let other3 = Language::Other("Different".to_string());
204
205        assert_eq!(other1, other2);
206        assert_ne!(other1, other3);
207        assert_ne!(Language::English, other1);
208    }
209
210    #[test]
211    fn test_debug_formatting() {
212        let article_type = ArticleType::Review;
213        let debug_str = format!("{:?}", article_type);
214        assert!(debug_str.contains("Review"));
215
216        let language = Language::English;
217        let debug_str = format!("{:?}", language);
218        assert!(debug_str.contains("English"));
219
220        let custom_lang = Language::Other("Test".to_string());
221        let debug_str = format!("{:?}", custom_lang);
222        assert!(debug_str.contains("Other"));
223        assert!(debug_str.contains("Test"));
224    }
225
226    #[test]
227    fn test_clone_functionality() {
228        let original_type = ArticleType::MetaAnalysis;
229        let cloned_type = original_type.clone();
230        assert_eq!(original_type, cloned_type);
231        assert_eq!(
232            original_type.to_query_string(),
233            cloned_type.to_query_string()
234        );
235
236        let original_lang = Language::German;
237        let cloned_lang = original_lang.clone();
238        assert_eq!(original_lang, cloned_lang);
239        assert_eq!(
240            original_lang.to_query_string(),
241            cloned_lang.to_query_string()
242        );
243
244        let original_other = Language::Other("Custom".to_string());
245        let cloned_other = original_other.clone();
246        assert_eq!(original_other, cloned_other);
247        assert_eq!(
248            original_other.to_query_string(),
249            cloned_other.to_query_string()
250        );
251    }
252
253    #[test]
254    fn test_language_other_empty_string() {
255        let empty_lang = Language::Other("".to_string());
256        assert_eq!(empty_lang.to_query_string(), "[la]");
257    }
258
259    #[test]
260    fn test_language_other_special_characters() {
261        let special_lang = Language::Other("中文-汉语".to_string());
262        assert_eq!(special_lang.to_query_string(), "中文-汉语[la]");
263
264        let symbol_lang = Language::Other("Lang@#$%".to_string());
265        assert_eq!(symbol_lang.to_query_string(), "Lang@#$%[la]");
266    }
267
268    #[test]
269    fn test_all_article_types_unique() {
270        let all_types = vec![
271            ArticleType::ClinicalTrial,
272            ArticleType::Review,
273            ArticleType::SystematicReview,
274            ArticleType::MetaAnalysis,
275            ArticleType::CaseReport,
276            ArticleType::RandomizedControlledTrial,
277            ArticleType::ObservationalStudy,
278        ];
279
280        let mut query_strings = Vec::new();
281        for article_type in all_types {
282            let query_string = article_type.to_query_string();
283            assert!(
284                !query_strings.contains(&query_string),
285                "Duplicate query string found: {}",
286                query_string
287            );
288            query_strings.push(query_string);
289        }
290    }
291
292    #[test]
293    fn test_sort_order_as_api_param() {
294        assert_eq!(SortOrder::Relevance.as_api_param(), "relevance");
295        assert_eq!(SortOrder::PublicationDate.as_api_param(), "pub_date");
296        assert_eq!(SortOrder::FirstAuthor.as_api_param(), "Author");
297        assert_eq!(SortOrder::JournalName.as_api_param(), "JournalName");
298    }
299
300    #[test]
301    fn test_sort_order_equality() {
302        assert_eq!(SortOrder::Relevance, SortOrder::Relevance);
303        assert_ne!(SortOrder::Relevance, SortOrder::PublicationDate);
304        assert_ne!(SortOrder::FirstAuthor, SortOrder::JournalName);
305    }
306
307    #[test]
308    fn test_sort_order_clone() {
309        let original = SortOrder::PublicationDate;
310        let cloned = original.clone();
311        assert_eq!(original, cloned);
312        assert_eq!(original.as_api_param(), cloned.as_api_param());
313    }
314
315    #[test]
316    fn test_all_standard_languages_unique() {
317        let standard_languages = vec![
318            Language::English,
319            Language::Japanese,
320            Language::German,
321            Language::French,
322            Language::Spanish,
323            Language::Italian,
324            Language::Chinese,
325            Language::Russian,
326            Language::Portuguese,
327            Language::Arabic,
328            Language::Dutch,
329            Language::Korean,
330            Language::Polish,
331            Language::Swedish,
332            Language::Danish,
333            Language::Norwegian,
334            Language::Finnish,
335            Language::Turkish,
336            Language::Hebrew,
337            Language::Czech,
338            Language::Hungarian,
339            Language::Greek,
340        ];
341
342        let mut query_strings = Vec::new();
343        for language in standard_languages {
344            let query_string = language.to_query_string();
345            assert!(
346                !query_strings.contains(&query_string),
347                "Duplicate query string found: {}",
348                query_string
349            );
350            query_strings.push(query_string);
351        }
352    }
353}