pubmed_client/lib.rs
1#![deny(
2 clippy::panic,
3 clippy::absolute_paths,
4 clippy::print_stderr,
5 clippy::print_stdout
6)]
7
8//! # PubMed Client
9//!
10//! A Rust client library for accessing PubMed and PMC (PubMed Central) APIs.
11//! This crate provides easy-to-use interfaces for searching, fetching, and parsing
12//! biomedical research articles.
13//!
14//! ## Features
15//!
16//! - **PubMed API Integration**: Search and fetch article metadata
17//! - **PMC Full Text**: Retrieve and parse structured full-text articles
18//! - **Markdown Export**: Convert PMC articles to well-formatted Markdown
19//! - **Response Caching**: Reduce API quota usage with intelligent caching
20//! - **Async Support**: Built on tokio for async/await support
21//! - **Error Handling**: Comprehensive error types for robust error handling
22//! - **Type Safety**: Strongly typed data structures for all API responses
23//!
24//! ## Quick Start
25//!
26//! ### Searching for Articles
27//!
28//! ```no_run
29//! use pubmed_client::PubMedClient;
30//!
31//! #[tokio::main]
32//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
33//! let client = PubMedClient::new();
34//!
35//! // Search for articles with query builder
36//! let articles = client
37//! .search()
38//! .query("covid-19 treatment")
39//! .free_full_text_only()
40//! .published_after(2020)
41//! .limit(10)
42//! .search_and_fetch(&client)
43//! .await?;
44//!
45//! for article in articles {
46//! println!("Title: {}", article.title);
47//! let author_names: Vec<&str> = article.authors.iter().map(|a| a.full_name.as_str()).collect();
48//! println!("Authors: {}", author_names.join(", "));
49//! }
50//!
51//! Ok(())
52//! }
53//! ```
54//!
55//! ### Fetching Full Text from PMC
56//!
57//! ```no_run
58//! use pubmed_client::PmcClient;
59//!
60//! #[tokio::main]
61//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
62//! let client = PmcClient::new();
63//!
64//! // Check if PMC full text is available
65//! if let Some(pmcid) = client.check_pmc_availability("33515491").await? {
66//! // Fetch structured full text
67//! let full_text = client.fetch_full_text(&pmcid).await?;
68//!
69//! println!("Title: {}", full_text.title().unwrap_or("Untitled"));
70//! println!("Sections: {}", full_text.sections().len());
71//! println!("References: {}", full_text.references().len());
72//! }
73//!
74//! Ok(())
75//! }
76//! ```
77//!
78//! ### Converting PMC Articles to Markdown
79//!
80//! ```no_run
81//! use pubmed_client::{PmcClient, PmcMarkdownConverter, HeadingStyle, ReferenceStyle};
82//!
83//! #[tokio::main]
84//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
85//! let client = PmcClient::new();
86//!
87//! // Fetch and parse a PMC article
88//! if let Ok(full_text) = client.fetch_full_text("PMC1234567").await {
89//! // Create a markdown converter with custom configuration
90//! let converter = PmcMarkdownConverter::new()
91//! .with_include_metadata(true)
92//! .with_include_toc(true)
93//! .with_heading_style(HeadingStyle::ATX)
94//! .with_reference_style(ReferenceStyle::Numbered);
95//!
96//! // Convert to markdown
97//! let markdown = converter.convert(&full_text);
98//! println!("{}", markdown);
99//!
100//! // Or save to file
101//! std::fs::write("article.md", markdown)?;
102//! }
103//!
104//! Ok(())
105//! }
106//! ```
107//!
108//! ### Downloading and Extracting PMC Articles as TAR files
109//!
110//! ```no_run
111//! use pubmed_client::PmcClient;
112//! use std::path::Path;
113//!
114//! #[tokio::main]
115//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
116//! let client = PmcClient::new();
117//! let output_dir = Path::new("./extracted_articles");
118//!
119//! // Download a PMC article's files from the PMC OA Cloud (AWS S3) service
120//! let files = client.download_files("PMC7906746", output_dir).await?;
121//!
122//! println!("Downloaded {} files:", files.len());
123//! for file in files {
124//! println!(" - {}", file);
125//! }
126//!
127//! Ok(())
128//! }
129//! ```
130//!
131//! ### Extracting Figures with Captions
132//!
133//! ```no_run
134//! use pubmed_client::PmcClient;
135//! use std::path::Path;
136//!
137//! #[tokio::main]
138//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
139//! let client = PmcClient::new();
140//! let output_dir = Path::new("./extracted_articles");
141//!
142//! // Extract figures and match them with captions from XML
143//! let figures = client.extract_figures_with_captions("PMC7906746", output_dir).await?;
144//!
145//! for figure in figures {
146//! println!("Figure {}: {:?}", figure.figure.id, figure.figure.caption);
147//! println!("File: {}", figure.extracted_file_path);
148//! if let Some(dimensions) = figure.dimensions {
149//! println!("Dimensions: {}x{}", dimensions.0, dimensions.1);
150//! }
151//! }
152//!
153//! Ok(())
154//! }
155//! ```
156//!
157//! ## Response Caching
158//!
159//! The library supports intelligent caching to reduce API quota usage and improve performance.
160//!
161//! ### Basic Caching
162//!
163//! ```no_run
164//! use pubmed_client::{PmcClient, ClientConfig};
165//!
166//! #[tokio::main]
167//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
168//! // Enable default memory caching
169//! let config = ClientConfig::new().with_cache();
170//! let client = PmcClient::with_config(config);
171//!
172//! // First fetch - hits the API
173//! let article1 = client.fetch_full_text("PMC7906746").await?;
174//!
175//! // Second fetch - served from cache
176//! let article2 = client.fetch_full_text("PMC7906746").await?;
177//!
178//! Ok(())
179//! }
180//! ```
181//!
182//! ### Advanced Caching Options
183//!
184//! ```no_run
185//! use pubmed_client::{PmcClient, ClientConfig};
186//! use pubmed_client::cache::CacheConfig;
187//! use std::time::Duration;
188//!
189//! #[tokio::main]
190//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
191//! // Memory cache with custom settings
192//! let cache_config = CacheConfig {
193//! max_capacity: 5000,
194//! time_to_live: Duration::from_secs(24 * 60 * 60), // 24 hours
195//! ..Default::default()
196//! };
197//!
198//! let config = ClientConfig::new()
199//! .with_cache_config(cache_config);
200//! let client = PmcClient::with_config(config);
201//!
202//! // Use the client normally - caching happens automatically
203//! let article = client.fetch_full_text("PMC7906746").await?;
204//!
205//! Ok(())
206//! }
207//! ```
208//!
209//! ### Hybrid Cache with Disk Persistence
210//!
211//! ```no_run
212//! #[cfg(not(target_arch = "wasm32"))]
213//! {
214//! use pubmed_client::{PmcClient, ClientConfig};
215//! use pubmed_client::cache::CacheConfig;
216//! use std::time::Duration;
217//! use std::path::PathBuf;
218//!
219//! #[tokio::main]
220//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
221//! // Memory cache configuration
222//! let cache_config = CacheConfig {
223//! max_capacity: 1000,
224//! time_to_live: Duration::from_secs(24 * 60 * 60),
225//! ..Default::default()
226//! };
227//!
228//! let config = ClientConfig::new()
229//! .with_cache_config(cache_config);
230//! let client = PmcClient::with_config(config);
231//!
232//! // Articles are cached in memory
233//! let article = client.fetch_full_text("PMC7906746").await?;
234//!
235//! Ok(())
236//! }
237//! }
238//! ```
239//!
240//! ### Cache Management
241//!
242//! ```no_run
243//! use pubmed_client::{PmcClient, ClientConfig};
244//!
245//! #[tokio::main]
246//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
247//! let config = ClientConfig::new().with_cache();
248//! let client = PmcClient::with_config(config);
249//!
250//! // Fetch some articles
251//! client.fetch_full_text("PMC7906746").await?;
252//! client.fetch_full_text("PMC10618641").await?;
253//!
254//! // Check cache statistics
255//! let count = client.cache_entry_count();
256//! println!("Cached items: {}", count);
257//!
258//! // Clear the cache when needed
259//! client.clear_cache().await;
260//!
261//! Ok(())
262//! }
263//! ```
264
265pub mod cache;
266pub mod config;
267pub mod error;
268pub mod europe_pmc;
269pub mod pmc;
270pub mod pubmed;
271pub mod rate_limit;
272pub(crate) mod request;
273pub mod retry;
274pub mod time;
275pub(crate) mod tls;
276
277// Common types from pubmed-parser are surfaced only at the crate root (below);
278// the module itself is kept crate-internal to avoid duplicate public paths.
279pub(crate) use pubmed_parser::common;
280
281// Re-export main types for convenience
282pub use common::{Affiliation, Author, PmcId, PubMedId};
283pub use config::ClientConfig;
284pub use error::{ParseError, PubMedError, Result};
285pub use europe_pmc::{
286 EuropePmcCitation, EuropePmcCitationList, EuropePmcClient, EuropePmcDatabaseLink,
287 EuropePmcDatabaseLinkList, EuropePmcDbCrossReferenceInfo, EuropePmcId, EuropePmcReference,
288 EuropePmcReferenceList, EuropePmcResult, EuropePmcSearchOptions, EuropePmcSearchResponse,
289 EuropePmcSource, ResultType,
290};
291pub use pmc::{
292 Abstract, ArticleMeta, Back, Body, ExtractedFigure, Figure, FigureOptions, Front, FundingInfo,
293 HeadingStyle, JournalMeta, KeywordGroup, License, MarkdownConfig, MetadataOptions,
294 OaSubsetInfo, Permissions, PmcArticle, PmcClient, PmcCloudClient, PmcMarkdownConverter,
295 Reference, ReferenceStyle, RelatedArticle, Section, SectionKind, SubjectGroup,
296 SupplementaryMaterial, Table, TitleGroup, parse_pmc_xml,
297};
298pub use pubmed::{
299 AbstractSection, ArticleSummary, ArticleType, CitationMatch, CitationMatchStatus,
300 CitationMatches, CitationQuery, Citations, DatabaseCount, DatabaseInfo, EPostResult,
301 ExportFormat, FieldInfo, GlobalQueryResults, HistorySession, Language, LinkInfo, PmcLinks,
302 PubMedArticle, PubMedClient, RelatedArticles, SearchQuery, SearchResult, SortOrder,
303 SpellCheckResult, SpelledQuerySegment, export, parse_article_from_xml, validate_year,
304};
305pub use rate_limit::RateLimiter;
306pub use time::{Duration, Instant, sleep};
307
308/// Convenience client that combines both PubMed and PMC functionality
309#[derive(Clone)]
310pub struct Client {
311 /// PubMed client for metadata
312 pub pubmed: PubMedClient,
313 /// PMC client for full text
314 pub pmc: PmcClient,
315 /// Europe PMC client for cross-source search, full text, and citation graphs
316 pub europe_pmc: EuropePmcClient,
317}
318
319impl Client {
320 /// Create a new combined client with default configuration
321 ///
322 /// Uses default NCBI rate limiting (3 requests/second) and no API key.
323 /// For production use, consider using `with_config()` to set an API key.
324 ///
325 /// # Example
326 ///
327 /// ```
328 /// use pubmed_client::Client;
329 ///
330 /// let client = Client::new();
331 /// ```
332 pub fn new() -> Self {
333 let config = ClientConfig::new();
334 Self::with_config(config)
335 }
336
337 /// Create a new combined client with custom configuration
338 ///
339 /// Both PubMed and PMC clients will use the same configuration
340 /// for consistent rate limiting and API key usage.
341 ///
342 /// # Arguments
343 ///
344 /// * `config` - Client configuration including rate limits, API key, etc.
345 ///
346 /// # Example
347 ///
348 /// ```
349 /// use pubmed_client::{Client, ClientConfig};
350 ///
351 /// let config = ClientConfig::new()
352 /// .with_api_key("your_api_key_here")
353 /// .with_email("researcher@university.edu");
354 ///
355 /// let client = Client::with_config(config);
356 /// ```
357 pub fn with_config(config: ClientConfig) -> Self {
358 Self {
359 pubmed: PubMedClient::with_config(config.clone()),
360 pmc: PmcClient::with_config(config.clone()),
361 europe_pmc: EuropePmcClient::with_config(config),
362 }
363 }
364
365 /// Create a new combined client with custom HTTP client
366 ///
367 /// # Arguments
368 ///
369 /// * `http_client` - Custom reqwest client with specific configuration
370 ///
371 /// # Example
372 ///
373 /// ```
374 /// use pubmed_client::Client;
375 /// use reqwest::ClientBuilder;
376 /// use std::time::Duration;
377 ///
378 /// let http_client = ClientBuilder::new()
379 /// .timeout(Duration::from_secs(30))
380 /// .build()
381 /// .unwrap();
382 ///
383 /// let client = Client::with_http_client(http_client);
384 /// ```
385 pub fn with_http_client(http_client: reqwest::Client) -> Self {
386 Self {
387 pubmed: PubMedClient::with_client(http_client.clone()),
388 pmc: PmcClient::with_client(http_client.clone()),
389 europe_pmc: EuropePmcClient::with_client(http_client),
390 }
391 }
392
393 /// Search for articles and attempt to fetch full text for each
394 ///
395 /// # Arguments
396 ///
397 /// * `query` - Search query string
398 /// * `limit` - Maximum number of articles to process
399 ///
400 /// # Returns
401 ///
402 /// Returns a vector of tuples containing (`PubMedArticle`, `Option<PmcArticle>`)
403 ///
404 /// # Example
405 ///
406 /// ```no_run
407 /// use pubmed_client::Client;
408 ///
409 /// #[tokio::main]
410 /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
411 /// let client = Client::new();
412 /// let results = client.search_with_full_text("covid-19", 5).await?;
413 ///
414 /// for (article, full_text) in results {
415 /// println!("Article: {}", article.title);
416 /// if let Some(ft) = full_text {
417 /// println!(" Full text available with {} sections", ft.sections().len());
418 /// } else {
419 /// println!(" Full text not available");
420 /// }
421 /// }
422 ///
423 /// Ok(())
424 /// }
425 /// ```
426 pub async fn search_with_full_text(
427 &self,
428 query: &str,
429 limit: usize,
430 ) -> Result<Vec<(PubMedArticle, Option<PmcArticle>)>> {
431 let articles = self.pubmed.search_and_fetch(query, limit, None).await?;
432 let mut results = Vec::new();
433
434 for article in articles {
435 let full_text = match self.pmc.check_pmc_availability(&article.pmid).await? {
436 Some(pmcid) => self.pmc.fetch_full_text(&pmcid).await.ok(),
437 None => None,
438 };
439 results.push((article, full_text));
440 }
441
442 Ok(results)
443 }
444
445 /// Fetch multiple articles by PMIDs in a single batch request
446 ///
447 /// This is significantly more efficient than fetching articles one by one,
448 /// as it sends fewer HTTP requests to the NCBI API.
449 ///
450 /// # Arguments
451 ///
452 /// * `pmids` - Slice of PubMed IDs as strings
453 ///
454 /// # Returns
455 ///
456 /// Returns a `Result<Vec<PubMedArticle>>` containing articles with metadata
457 ///
458 /// # Example
459 ///
460 /// ```no_run
461 /// use pubmed_client::Client;
462 ///
463 /// #[tokio::main]
464 /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
465 /// let client = Client::new();
466 /// let articles = client.fetch_articles(&["31978945", "33515491"]).await?;
467 /// for article in &articles {
468 /// println!("{}: {}", article.pmid, article.title);
469 /// }
470 /// Ok(())
471 /// }
472 /// ```
473 pub async fn fetch_articles(&self, pmids: &[&str]) -> Result<Vec<PubMedArticle>> {
474 self.pubmed.fetch_articles(pmids).await
475 }
476
477 /// Fetch lightweight article summaries by PMIDs using the ESummary API
478 ///
479 /// Returns basic metadata (title, authors, journal, dates, DOI) without
480 /// abstracts, MeSH terms, or chemical lists. Faster than `fetch_articles()`
481 /// when you only need bibliographic overview data.
482 ///
483 /// # Arguments
484 ///
485 /// * `pmids` - Slice of PubMed IDs as strings
486 ///
487 /// # Returns
488 ///
489 /// Returns a `Result<Vec<ArticleSummary>>` containing lightweight article metadata
490 ///
491 /// # Example
492 ///
493 /// ```no_run
494 /// use pubmed_client::Client;
495 ///
496 /// #[tokio::main]
497 /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
498 /// let client = Client::new();
499 /// let summaries = client.fetch_summaries(&["31978945", "33515491"]).await?;
500 /// for summary in &summaries {
501 /// println!("{}: {}", summary.pmid, summary.title);
502 /// }
503 /// Ok(())
504 /// }
505 /// ```
506 pub async fn fetch_summaries(&self, pmids: &[&str]) -> Result<Vec<ArticleSummary>> {
507 self.pubmed.fetch_summaries(pmids).await
508 }
509
510 /// Search and fetch lightweight summaries in a single operation
511 ///
512 /// Combines search and ESummary fetch. Use this when you only need basic
513 /// metadata (title, authors, journal, dates) and want faster retrieval
514 /// than `search_and_fetch()` which uses EFetch.
515 ///
516 /// # Arguments
517 ///
518 /// * `query` - Search query string
519 /// * `limit` - Maximum number of articles
520 ///
521 /// # Returns
522 ///
523 /// Returns a `Result<Vec<ArticleSummary>>` containing lightweight article metadata
524 ///
525 /// # Example
526 ///
527 /// ```no_run
528 /// use pubmed_client::Client;
529 ///
530 /// #[tokio::main]
531 /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
532 /// let client = Client::new();
533 /// let summaries = client.search_and_fetch_summaries("covid-19", 20).await?;
534 /// for summary in &summaries {
535 /// println!("{}: {}", summary.pmid, summary.title);
536 /// }
537 /// Ok(())
538 /// }
539 /// ```
540 pub async fn search_and_fetch_summaries(
541 &self,
542 query: &str,
543 limit: usize,
544 ) -> Result<Vec<ArticleSummary>> {
545 self.pubmed
546 .search_and_fetch_summaries(query, limit, None)
547 .await
548 }
549
550 /// Get list of all available NCBI databases
551 ///
552 /// # Returns
553 ///
554 /// Returns a `Result<Vec<String>>` containing names of all available databases
555 ///
556 /// # Example
557 ///
558 /// ```no_run
559 /// use pubmed_client::Client;
560 ///
561 /// #[tokio::main]
562 /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
563 /// let client = Client::new();
564 /// let databases = client.get_database_list().await?;
565 /// println!("Available databases: {:?}", databases);
566 /// Ok(())
567 /// }
568 /// ```
569 pub async fn get_database_list(&self) -> Result<Vec<String>> {
570 self.pubmed.get_database_list().await
571 }
572
573 /// Get detailed information about a specific database
574 ///
575 /// # Arguments
576 ///
577 /// * `database` - Name of the database (e.g., "pubmed", "pmc", "books")
578 ///
579 /// # Returns
580 ///
581 /// Returns a `Result<DatabaseInfo>` containing detailed database information
582 ///
583 /// # Example
584 ///
585 /// ```no_run
586 /// use pubmed_client::Client;
587 ///
588 /// #[tokio::main]
589 /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
590 /// let client = Client::new();
591 /// let db_info = client.get_database_info("pubmed").await?;
592 /// println!("Database: {}", db_info.name);
593 /// println!("Description: {}", db_info.description);
594 /// println!("Fields: {}", db_info.fields.len());
595 /// Ok(())
596 /// }
597 /// ```
598 pub async fn get_database_info(&self, database: &str) -> Result<DatabaseInfo> {
599 self.pubmed.get_database_info(database).await
600 }
601
602 /// Get related articles for given PMIDs
603 ///
604 /// # Arguments
605 ///
606 /// * `pmids` - List of PubMed IDs to find related articles for
607 ///
608 /// # Returns
609 ///
610 /// Returns a `Result<RelatedArticles>` containing related article information
611 ///
612 /// # Example
613 ///
614 /// ```no_run
615 /// use pubmed_client::Client;
616 ///
617 /// #[tokio::main]
618 /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
619 /// let client = Client::new();
620 /// let related = client.get_related_articles(&[31978945]).await?;
621 /// println!("Found {} related articles", related.related_pmids.len());
622 /// Ok(())
623 /// }
624 /// ```
625 pub async fn get_related_articles(&self, pmids: &[u32]) -> Result<RelatedArticles> {
626 self.pubmed.get_related_articles(pmids).await
627 }
628
629 /// Get PMC links for given PMIDs (full-text availability)
630 ///
631 /// # Arguments
632 ///
633 /// * `pmids` - List of PubMed IDs to check for PMC availability
634 ///
635 /// # Returns
636 ///
637 /// Returns a `Result<PmcLinks>` containing PMC IDs with full text available
638 ///
639 /// # Example
640 ///
641 /// ```no_run
642 /// use pubmed_client::Client;
643 ///
644 /// #[tokio::main]
645 /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
646 /// let client = Client::new();
647 /// let pmc_links = client.get_pmc_links(&[31978945]).await?;
648 /// println!("Found {} PMC articles", pmc_links.pmc_ids.len());
649 /// Ok(())
650 /// }
651 /// ```
652 pub async fn get_pmc_links(&self, pmids: &[u32]) -> Result<PmcLinks> {
653 self.pubmed.get_pmc_links(pmids).await
654 }
655
656 /// Get citing articles for given PMIDs
657 ///
658 /// # Arguments
659 ///
660 /// * `pmids` - List of PubMed IDs to find citing articles for
661 ///
662 /// # Returns
663 ///
664 /// Returns a `Result<Citations>` containing citing article information
665 ///
666 /// # Example
667 ///
668 /// ```no_run
669 /// use pubmed_client::Client;
670 ///
671 /// #[tokio::main]
672 /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
673 /// let client = Client::new();
674 /// let citations = client.get_citations(&[31978945]).await?;
675 /// println!("Found {} citing articles", citations.citing_pmids.len());
676 /// Ok(())
677 /// }
678 /// ```
679 pub async fn get_citations(&self, pmids: &[u32]) -> Result<Citations> {
680 self.pubmed.get_citations(pmids).await
681 }
682
683 /// Match citations to PMIDs using the ECitMatch API
684 ///
685 /// # Arguments
686 ///
687 /// * `citations` - List of citation queries to match
688 ///
689 /// # Returns
690 ///
691 /// Returns a `Result<CitationMatches>` containing match results
692 ///
693 /// # Example
694 ///
695 /// ```no_run
696 /// use pubmed_client::{Client, CitationQuery};
697 ///
698 /// #[tokio::main]
699 /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
700 /// let client = Client::new();
701 /// let citations = vec![
702 /// CitationQuery::new("science", "1987", "235", "182", "palmenberg ac", "ref1"),
703 /// ];
704 /// let results = client.match_citations(&citations).await?;
705 /// println!("Found {} matches", results.found_count());
706 /// Ok(())
707 /// }
708 /// ```
709 pub async fn match_citations(&self, citations: &[CitationQuery]) -> Result<CitationMatches> {
710 self.pubmed.match_citations(citations).await
711 }
712
713 /// Query all NCBI databases for record counts
714 ///
715 /// # Arguments
716 ///
717 /// * `term` - Search query string
718 ///
719 /// # Returns
720 ///
721 /// Returns a `Result<GlobalQueryResults>` containing counts per database
722 ///
723 /// # Example
724 ///
725 /// ```no_run
726 /// use pubmed_client::Client;
727 ///
728 /// #[tokio::main]
729 /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
730 /// let client = Client::new();
731 /// let results = client.global_query("asthma").await?;
732 /// for db in results.non_zero() {
733 /// println!("{}: {} records", db.menu_name, db.count);
734 /// }
735 /// Ok(())
736 /// }
737 /// ```
738 pub async fn global_query(&self, term: &str) -> Result<GlobalQueryResults> {
739 self.pubmed.global_query(term).await
740 }
741
742 /// Upload a list of PMIDs to the NCBI History server using EPost
743 ///
744 /// # Arguments
745 ///
746 /// * `pmids` - Slice of PubMed IDs as strings
747 ///
748 /// # Returns
749 ///
750 /// Returns a `Result<EPostResult>` containing WebEnv and query_key
751 ///
752 /// # Example
753 ///
754 /// ```no_run
755 /// use pubmed_client::Client;
756 ///
757 /// #[tokio::main]
758 /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
759 /// let client = Client::new();
760 /// let result = client.epost(&["31978945", "33515491"]).await?;
761 /// let session = result.history_session();
762 /// let articles = client.pubmed.fetch_from_history(&session, 0, 100).await?;
763 /// println!("Fetched {} articles", articles.len());
764 /// Ok(())
765 /// }
766 /// ```
767 pub async fn epost(&self, pmids: &[&str]) -> Result<EPostResult> {
768 self.pubmed.epost(pmids).await
769 }
770
771 /// Fetch all articles for a list of PMIDs using EPost and the History server
772 ///
773 /// Uploads the PMID list via EPost (HTTP POST), then fetches articles in
774 /// paginated batches. Recommended for large PMID lists (hundreds or thousands).
775 ///
776 /// # Example
777 ///
778 /// ```no_run
779 /// use pubmed_client::Client;
780 ///
781 /// #[tokio::main]
782 /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
783 /// let client = Client::new();
784 /// let articles = client.fetch_all_by_pmids(&["31978945", "33515491"]).await?;
785 /// for a in &articles {
786 /// println!("{}: {}", a.pmid, a.title);
787 /// }
788 /// Ok(())
789 /// }
790 /// ```
791 pub async fn fetch_all_by_pmids(&self, pmids: &[&str]) -> Result<Vec<PubMedArticle>> {
792 self.pubmed.fetch_all_by_pmids(pmids).await
793 }
794
795 /// Check spelling of a search term using the ESpell API
796 ///
797 /// Provides spelling suggestions for terms within a single text query.
798 /// Uses the PubMed database by default. For other databases, use
799 /// `client.pubmed.spell_check_db(term, db)` directly.
800 ///
801 /// # Arguments
802 ///
803 /// * `term` - The search term to spell-check
804 ///
805 /// # Returns
806 ///
807 /// Returns a `Result<SpellCheckResult>` containing spelling suggestions
808 ///
809 /// # Example
810 ///
811 /// ```no_run
812 /// use pubmed_client::Client;
813 ///
814 /// #[tokio::main]
815 /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
816 /// let client = Client::new();
817 /// let result = client.spell_check("asthmaa").await?;
818 /// println!("Corrected: {}", result.corrected_query);
819 /// Ok(())
820 /// }
821 /// ```
822 pub async fn spell_check(&self, term: &str) -> Result<SpellCheckResult> {
823 self.pubmed.spell_check(term).await
824 }
825}
826
827impl Default for Client {
828 fn default() -> Self {
829 Self::new()
830 }
831}