pubmed_client/pubmed/query/
search.rs1use super::{ArticleType, Language, SearchQuery};
4
5impl SearchQuery {
6 pub fn title_contains<S: Into<String>>(mut self, title: S) -> Self {
21 self.filters.push(format!("{}[ti]", title.into()));
22 self
23 }
24
25 pub fn abstract_contains<S: Into<String>>(mut self, abstract_text: S) -> Self {
40 self.filters.push(format!("{}[tiab]", abstract_text.into()));
41 self
42 }
43
44 pub fn title_or_abstract<S: Into<String>>(mut self, text: S) -> Self {
59 self.filters.push(format!("{}[tiab]", text.into()));
60 self
61 }
62
63 pub fn journal<S: Into<String>>(mut self, journal: S) -> Self {
79 self.filters.push(format!("{}[ta]", journal.into()));
80 self
81 }
82
83 pub fn journal_abbreviation<S: Into<String>>(mut self, abbreviation: S) -> Self {
99 self.filters.push(format!("{}[ta]", abbreviation.into()));
100 self
101 }
102
103 pub fn grant_number<S: Into<String>>(mut self, grant_number: S) -> Self {
118 self.filters.push(format!("{}[gr]", grant_number.into()));
119 self
120 }
121
122 pub fn isbn<S: Into<String>>(mut self, isbn: S) -> Self {
137 self.filters.push(format!("{}[ISBN]", isbn.into()));
138 self
139 }
140
141 pub fn issn<S: Into<String>>(mut self, issn: S) -> Self {
156 self.filters.push(format!("{}[ISSN]", issn.into()));
157 self
158 }
159
160 pub fn free_full_text_only(mut self) -> Self {
174 self.filters.push("free full text[sb]".to_string());
175 self
176 }
177
178 pub fn full_text_only(mut self) -> Self {
190 self.filters.push("full text[sb]".to_string());
191 self
192 }
193
194 pub fn pmc_only(mut self) -> Self {
206 self.filters.push("pubmed pmc[sb]".to_string());
207 self
208 }
209
210 pub fn has_abstract(mut self) -> Self {
222 self.filters.push("hasabstract".to_string());
223 self
224 }
225
226 pub fn article_types(mut self, types: &[ArticleType]) -> Self {
242 if !types.is_empty() {
243 let type_filters: Vec<String> = types
244 .iter()
245 .map(|t| t.to_query_string().to_string())
246 .collect();
247
248 if type_filters.len() == 1 {
249 self.filters.push(type_filters[0].clone());
250 } else {
251 let combined = format!("({})", type_filters.join(" OR "));
253 self.filters.push(combined);
254 }
255 }
256 self
257 }
258
259 pub fn article_type(self, article_type: ArticleType) -> Self {
275 self.article_types(&[article_type])
276 }
277
278 pub fn language(mut self, language: Language) -> Self {
294 self.filters.push(language.to_query_string());
295 self
296 }
297}
298
299#[cfg(test)]
300mod tests {
301 use super::*;
302
303 #[test]
304 fn test_title_contains() {
305 let query = SearchQuery::new().title_contains("machine learning");
306 assert_eq!(query.build(), "machine learning[ti]");
307 }
308
309 #[test]
310 fn test_abstract_contains() {
311 let query = SearchQuery::new().abstract_contains("deep learning neural networks");
312 assert_eq!(query.build(), "deep learning neural networks[tiab]");
313 }
314
315 #[test]
316 fn test_title_or_abstract() {
317 let query = SearchQuery::new().title_or_abstract("CRISPR gene editing");
318 assert_eq!(query.build(), "CRISPR gene editing[tiab]");
319 }
320
321 #[test]
322 fn test_journal() {
323 let query = SearchQuery::new().journal("Nature");
324 assert_eq!(query.build(), "Nature[ta]");
325 }
326
327 #[test]
328 fn test_journal_abbreviation() {
329 let query = SearchQuery::new().journal_abbreviation("Nat Med");
330 assert_eq!(query.build(), "Nat Med[ta]");
331 }
332
333 #[test]
334 fn test_grant_number() {
335 let query = SearchQuery::new().grant_number("R01AI123456");
336 assert_eq!(query.build(), "R01AI123456[gr]");
337 }
338
339 #[test]
340 fn test_isbn() {
341 let query = SearchQuery::new().isbn("978-0123456789");
342 assert_eq!(query.build(), "978-0123456789[ISBN]");
343 }
344
345 #[test]
346 fn test_issn() {
347 let query = SearchQuery::new().issn("1234-5678");
348 assert_eq!(query.build(), "1234-5678[ISSN]");
349 }
350
351 #[test]
352 fn test_free_full_text_only() {
353 let query = SearchQuery::new().free_full_text_only();
354 assert_eq!(query.build(), "free full text[sb]");
355 }
356
357 #[test]
358 fn test_full_text_only() {
359 let query = SearchQuery::new().full_text_only();
360 assert_eq!(query.build(), "full text[sb]");
361 }
362
363 #[test]
364 fn test_pmc_only() {
365 let query = SearchQuery::new().pmc_only();
366 assert_eq!(query.build(), "pubmed pmc[sb]");
367 }
368
369 #[test]
370 fn test_has_abstract() {
371 let query = SearchQuery::new().has_abstract();
372 assert_eq!(query.build(), "hasabstract");
373 }
374
375 #[test]
376 fn test_single_article_type() {
377 let query = SearchQuery::new().article_type(ArticleType::ClinicalTrial);
378 assert_eq!(query.build(), "Clinical Trial[pt]");
379 }
380
381 #[test]
382 fn test_multiple_article_types() {
383 let types = [ArticleType::ClinicalTrial, ArticleType::Review];
384 let query = SearchQuery::new().article_types(&types);
385 assert_eq!(query.build(), "(Clinical Trial[pt] OR Review[pt])");
386 }
387
388 #[test]
389 fn test_empty_article_types() {
390 let types: &[ArticleType] = &[];
391 let query = SearchQuery::new().article_types(types);
392 assert_eq!(query.build(), "");
393 }
394
395 #[test]
396 fn test_single_article_type_via_array() {
397 let types = [ArticleType::Review];
398 let query = SearchQuery::new().article_types(&types);
399 assert_eq!(query.build(), "Review[pt]");
400 }
401
402 #[test]
403 fn test_language() {
404 let query = SearchQuery::new().language(Language::English);
405 assert_eq!(query.build(), "English[la]");
406 }
407
408 #[test]
409 fn test_language_other() {
410 let query = SearchQuery::new().language(Language::Other("Esperanto".to_string()));
411 assert_eq!(query.build(), "Esperanto[la]");
412 }
413
414 #[test]
415 fn test_combined_search_filters() {
416 let query = SearchQuery::new()
417 .query("cancer treatment")
418 .title_contains("immunotherapy")
419 .journal("Nature")
420 .free_full_text_only()
421 .article_type(ArticleType::ClinicalTrial)
422 .language(Language::English);
423
424 let expected = "cancer treatment AND immunotherapy[ti] AND Nature[ta] AND free full text[sb] AND Clinical Trial[pt] AND English[la]";
425 assert_eq!(query.build(), expected);
426 }
427
428 #[test]
429 fn test_multiple_journal_filters() {
430 let query = SearchQuery::new().journal("Nature").journal("Science");
431 assert_eq!(query.build(), "Nature[ta] AND Science[ta]");
432 }
433
434 #[test]
435 fn test_title_and_abstract_separate() {
436 let query = SearchQuery::new()
437 .title_contains("machine learning")
438 .abstract_contains("neural networks");
439 assert_eq!(
440 query.build(),
441 "machine learning[ti] AND neural networks[tiab]"
442 );
443 }
444
445 #[test]
446 fn test_all_text_availability_filters() {
447 let query = SearchQuery::new()
448 .query("research")
449 .has_abstract()
450 .full_text_only()
451 .free_full_text_only()
452 .pmc_only();
453 assert_eq!(
454 query.build(),
455 "research AND hasabstract AND full text[sb] AND free full text[sb] AND pubmed pmc[sb]"
456 );
457 }
458
459 #[test]
460 fn test_many_article_types() {
461 let types = [
462 ArticleType::ClinicalTrial,
463 ArticleType::Review,
464 ArticleType::MetaAnalysis,
465 ArticleType::SystematicReview,
466 ];
467 let query = SearchQuery::new().article_types(&types);
468 let expected =
469 "(Clinical Trial[pt] OR Review[pt] OR Meta-Analysis[pt] OR Systematic Review[pt])";
470 assert_eq!(query.build(), expected);
471 }
472
473 #[test]
474 fn test_identifier_fields() {
475 let query = SearchQuery::new()
476 .grant_number("R01CA123456")
477 .isbn("978-0123456789")
478 .issn("0028-0836");
479
480 let expected = "R01CA123456[gr] AND 978-0123456789[ISBN] AND 0028-0836[ISSN]";
481 assert_eq!(query.build(), expected);
482 }
483}