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());
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 and extract a PMC article as tar.gz from the OA API
120//!     let files = client.download_and_extract_tar("PMC7906746", output_dir).await?;
121//!
122//!     println!("Extracted {} 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 pmc;
269pub mod pubmed;
270pub mod rate_limit;
271pub mod retry;
272pub mod time;
273
274// Re-export common module from pubmed-parser
275pub use pubmed_parser::common;
276
277// Re-export main types for convenience
278pub use common::{Affiliation, Author, PmcId, PubMedId};
279pub use config::ClientConfig;
280pub use error::{ParseError, PubMedError, Result};
281pub use pmc::{
282    Abstract, ArticleMeta, Back, Body, ExtractedFigure, Figure, Front, FundingInfo, HeadingStyle,
283    JournalMeta, License, MarkdownConfig, OaSubsetInfo, Permissions, PmcArticle, PmcClient,
284    PmcMarkdownConverter, PmcTarClient, Reference, ReferenceStyle, Section, SupplementaryMaterial,
285    Table, TitleGroup, parse_pmc_xml,
286};
287pub use pubmed::{
288    AbstractSection, ArticleSummary, ArticleType, CitationMatch, CitationMatchStatus,
289    CitationMatches, CitationQuery, Citations, DatabaseCount, DatabaseInfo, EPostResult,
290    ExportFormat, FieldInfo, GlobalQueryResults, HistorySession, Language, LinkInfo, PmcLinks,
291    PubMedArticle, PubMedClient, RelatedArticles, SearchQuery, SearchResult, SortOrder,
292    SpellCheckResult, SpelledQuerySegment, export, parse_article_from_xml, validate_year,
293};
294pub use rate_limit::RateLimiter;
295pub use time::{Duration, Instant, sleep};
296
297/// Convenience client that combines both PubMed and PMC functionality
298#[derive(Clone)]
299pub struct Client {
300    /// PubMed client for metadata
301    pub pubmed: PubMedClient,
302    /// PMC client for full text
303    pub pmc: PmcClient,
304}
305
306impl Client {
307    /// Create a new combined client with default configuration
308    ///
309    /// Uses default NCBI rate limiting (3 requests/second) and no API key.
310    /// For production use, consider using `with_config()` to set an API key.
311    ///
312    /// # Example
313    ///
314    /// ```
315    /// use pubmed_client::Client;
316    ///
317    /// let client = Client::new();
318    /// ```
319    pub fn new() -> Self {
320        let config = ClientConfig::new();
321        Self::with_config(config)
322    }
323
324    /// Create a new combined client with custom configuration
325    ///
326    /// Both PubMed and PMC clients will use the same configuration
327    /// for consistent rate limiting and API key usage.
328    ///
329    /// # Arguments
330    ///
331    /// * `config` - Client configuration including rate limits, API key, etc.
332    ///
333    /// # Example
334    ///
335    /// ```
336    /// use pubmed_client::{Client, ClientConfig};
337    ///
338    /// let config = ClientConfig::new()
339    ///     .with_api_key("your_api_key_here")
340    ///     .with_email("researcher@university.edu");
341    ///
342    /// let client = Client::with_config(config);
343    /// ```
344    pub fn with_config(config: ClientConfig) -> Self {
345        Self {
346            pubmed: PubMedClient::with_config(config.clone()),
347            pmc: PmcClient::with_config(config),
348        }
349    }
350
351    /// Create a new combined client with custom HTTP client
352    ///
353    /// # Arguments
354    ///
355    /// * `http_client` - Custom reqwest client with specific configuration
356    ///
357    /// # Example
358    ///
359    /// ```
360    /// use pubmed_client::Client;
361    /// use reqwest::ClientBuilder;
362    /// use std::time::Duration;
363    ///
364    /// let http_client = ClientBuilder::new()
365    ///     .timeout(Duration::from_secs(30))
366    ///     .build()
367    ///     .unwrap();
368    ///
369    /// let client = Client::with_http_client(http_client);
370    /// ```
371    pub fn with_http_client(http_client: reqwest::Client) -> Self {
372        Self {
373            pubmed: PubMedClient::with_client(http_client.clone()),
374            pmc: PmcClient::with_client(http_client),
375        }
376    }
377
378    /// Search for articles and attempt to fetch full text for each
379    ///
380    /// # Arguments
381    ///
382    /// * `query` - Search query string
383    /// * `limit` - Maximum number of articles to process
384    ///
385    /// # Returns
386    ///
387    /// Returns a vector of tuples containing (`PubMedArticle`, `Option<PmcArticle>`)
388    ///
389    /// # Example
390    ///
391    /// ```no_run
392    /// use pubmed_client::Client;
393    ///
394    /// #[tokio::main]
395    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
396    ///     let client = Client::new();
397    ///     let results = client.search_with_full_text("covid-19", 5).await?;
398    ///
399    ///     for (article, full_text) in results {
400    ///         println!("Article: {}", article.title);
401    ///         if let Some(ft) = full_text {
402    ///             println!("  Full text available with {} sections", ft.sections().len());
403    ///         } else {
404    ///             println!("  Full text not available");
405    ///         }
406    ///     }
407    ///
408    ///     Ok(())
409    /// }
410    /// ```
411    pub async fn search_with_full_text(
412        &self,
413        query: &str,
414        limit: usize,
415    ) -> Result<Vec<(PubMedArticle, Option<PmcArticle>)>> {
416        let articles = self.pubmed.search_and_fetch(query, limit, None).await?;
417        let mut results = Vec::new();
418
419        for article in articles {
420            let full_text = match self.pmc.check_pmc_availability(&article.pmid).await? {
421                Some(pmcid) => self.pmc.fetch_full_text(&pmcid).await.ok(),
422                None => None,
423            };
424            results.push((article, full_text));
425        }
426
427        Ok(results)
428    }
429
430    /// Fetch multiple articles by PMIDs in a single batch request
431    ///
432    /// This is significantly more efficient than fetching articles one by one,
433    /// as it sends fewer HTTP requests to the NCBI API.
434    ///
435    /// # Arguments
436    ///
437    /// * `pmids` - Slice of PubMed IDs as strings
438    ///
439    /// # Returns
440    ///
441    /// Returns a `Result<Vec<PubMedArticle>>` containing articles with metadata
442    ///
443    /// # Example
444    ///
445    /// ```no_run
446    /// use pubmed_client::Client;
447    ///
448    /// #[tokio::main]
449    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
450    ///     let client = Client::new();
451    ///     let articles = client.fetch_articles(&["31978945", "33515491"]).await?;
452    ///     for article in &articles {
453    ///         println!("{}: {}", article.pmid, article.title);
454    ///     }
455    ///     Ok(())
456    /// }
457    /// ```
458    pub async fn fetch_articles(&self, pmids: &[&str]) -> Result<Vec<PubMedArticle>> {
459        self.pubmed.fetch_articles(pmids).await
460    }
461
462    /// Fetch lightweight article summaries by PMIDs using the ESummary API
463    ///
464    /// Returns basic metadata (title, authors, journal, dates, DOI) without
465    /// abstracts, MeSH terms, or chemical lists. Faster than `fetch_articles()`
466    /// when you only need bibliographic overview data.
467    ///
468    /// # Arguments
469    ///
470    /// * `pmids` - Slice of PubMed IDs as strings
471    ///
472    /// # Returns
473    ///
474    /// Returns a `Result<Vec<ArticleSummary>>` containing lightweight article metadata
475    ///
476    /// # Example
477    ///
478    /// ```no_run
479    /// use pubmed_client::Client;
480    ///
481    /// #[tokio::main]
482    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
483    ///     let client = Client::new();
484    ///     let summaries = client.fetch_summaries(&["31978945", "33515491"]).await?;
485    ///     for summary in &summaries {
486    ///         println!("{}: {}", summary.pmid, summary.title);
487    ///     }
488    ///     Ok(())
489    /// }
490    /// ```
491    pub async fn fetch_summaries(&self, pmids: &[&str]) -> Result<Vec<ArticleSummary>> {
492        self.pubmed.fetch_summaries(pmids).await
493    }
494
495    /// Search and fetch lightweight summaries in a single operation
496    ///
497    /// Combines search and ESummary fetch. Use this when you only need basic
498    /// metadata (title, authors, journal, dates) and want faster retrieval
499    /// than `search_and_fetch()` which uses EFetch.
500    ///
501    /// # Arguments
502    ///
503    /// * `query` - Search query string
504    /// * `limit` - Maximum number of articles
505    ///
506    /// # Returns
507    ///
508    /// Returns a `Result<Vec<ArticleSummary>>` containing lightweight article metadata
509    ///
510    /// # Example
511    ///
512    /// ```no_run
513    /// use pubmed_client::Client;
514    ///
515    /// #[tokio::main]
516    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
517    ///     let client = Client::new();
518    ///     let summaries = client.search_and_fetch_summaries("covid-19", 20).await?;
519    ///     for summary in &summaries {
520    ///         println!("{}: {}", summary.pmid, summary.title);
521    ///     }
522    ///     Ok(())
523    /// }
524    /// ```
525    pub async fn search_and_fetch_summaries(
526        &self,
527        query: &str,
528        limit: usize,
529    ) -> Result<Vec<ArticleSummary>> {
530        self.pubmed
531            .search_and_fetch_summaries(query, limit, None)
532            .await
533    }
534
535    /// Get list of all available NCBI databases
536    ///
537    /// # Returns
538    ///
539    /// Returns a `Result<Vec<String>>` containing names of all available databases
540    ///
541    /// # Example
542    ///
543    /// ```no_run
544    /// use pubmed_client::Client;
545    ///
546    /// #[tokio::main]
547    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
548    ///     let client = Client::new();
549    ///     let databases = client.get_database_list().await?;
550    ///     println!("Available databases: {:?}", databases);
551    ///     Ok(())
552    /// }
553    /// ```
554    pub async fn get_database_list(&self) -> Result<Vec<String>> {
555        self.pubmed.get_database_list().await
556    }
557
558    /// Get detailed information about a specific database
559    ///
560    /// # Arguments
561    ///
562    /// * `database` - Name of the database (e.g., "pubmed", "pmc", "books")
563    ///
564    /// # Returns
565    ///
566    /// Returns a `Result<DatabaseInfo>` containing detailed database information
567    ///
568    /// # Example
569    ///
570    /// ```no_run
571    /// use pubmed_client::Client;
572    ///
573    /// #[tokio::main]
574    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
575    ///     let client = Client::new();
576    ///     let db_info = client.get_database_info("pubmed").await?;
577    ///     println!("Database: {}", db_info.name);
578    ///     println!("Description: {}", db_info.description);
579    ///     println!("Fields: {}", db_info.fields.len());
580    ///     Ok(())
581    /// }
582    /// ```
583    pub async fn get_database_info(&self, database: &str) -> Result<DatabaseInfo> {
584        self.pubmed.get_database_info(database).await
585    }
586
587    /// Get related articles for given PMIDs
588    ///
589    /// # Arguments
590    ///
591    /// * `pmids` - List of PubMed IDs to find related articles for
592    ///
593    /// # Returns
594    ///
595    /// Returns a `Result<RelatedArticles>` containing related article information
596    ///
597    /// # Example
598    ///
599    /// ```no_run
600    /// use pubmed_client::Client;
601    ///
602    /// #[tokio::main]
603    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
604    ///     let client = Client::new();
605    ///     let related = client.get_related_articles(&[31978945]).await?;
606    ///     println!("Found {} related articles", related.related_pmids.len());
607    ///     Ok(())
608    /// }
609    /// ```
610    pub async fn get_related_articles(&self, pmids: &[u32]) -> Result<RelatedArticles> {
611        self.pubmed.get_related_articles(pmids).await
612    }
613
614    /// Get PMC links for given PMIDs (full-text availability)
615    ///
616    /// # Arguments
617    ///
618    /// * `pmids` - List of PubMed IDs to check for PMC availability
619    ///
620    /// # Returns
621    ///
622    /// Returns a `Result<PmcLinks>` containing PMC IDs with full text available
623    ///
624    /// # Example
625    ///
626    /// ```no_run
627    /// use pubmed_client::Client;
628    ///
629    /// #[tokio::main]
630    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
631    ///     let client = Client::new();
632    ///     let pmc_links = client.get_pmc_links(&[31978945]).await?;
633    ///     println!("Found {} PMC articles", pmc_links.pmc_ids.len());
634    ///     Ok(())
635    /// }
636    /// ```
637    pub async fn get_pmc_links(&self, pmids: &[u32]) -> Result<PmcLinks> {
638        self.pubmed.get_pmc_links(pmids).await
639    }
640
641    /// Get citing articles for given PMIDs
642    ///
643    /// # Arguments
644    ///
645    /// * `pmids` - List of PubMed IDs to find citing articles for
646    ///
647    /// # Returns
648    ///
649    /// Returns a `Result<Citations>` containing citing article information
650    ///
651    /// # Example
652    ///
653    /// ```no_run
654    /// use pubmed_client::Client;
655    ///
656    /// #[tokio::main]
657    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
658    ///     let client = Client::new();
659    ///     let citations = client.get_citations(&[31978945]).await?;
660    ///     println!("Found {} citing articles", citations.citing_pmids.len());
661    ///     Ok(())
662    /// }
663    /// ```
664    pub async fn get_citations(&self, pmids: &[u32]) -> Result<Citations> {
665        self.pubmed.get_citations(pmids).await
666    }
667
668    /// Match citations to PMIDs using the ECitMatch API
669    ///
670    /// # Arguments
671    ///
672    /// * `citations` - List of citation queries to match
673    ///
674    /// # Returns
675    ///
676    /// Returns a `Result<CitationMatches>` containing match results
677    ///
678    /// # Example
679    ///
680    /// ```no_run
681    /// use pubmed_client::{Client, CitationQuery};
682    ///
683    /// #[tokio::main]
684    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
685    ///     let client = Client::new();
686    ///     let citations = vec![
687    ///         CitationQuery::new("science", "1987", "235", "182", "palmenberg ac", "ref1"),
688    ///     ];
689    ///     let results = client.match_citations(&citations).await?;
690    ///     println!("Found {} matches", results.found_count());
691    ///     Ok(())
692    /// }
693    /// ```
694    pub async fn match_citations(&self, citations: &[CitationQuery]) -> Result<CitationMatches> {
695        self.pubmed.match_citations(citations).await
696    }
697
698    /// Query all NCBI databases for record counts
699    ///
700    /// # Arguments
701    ///
702    /// * `term` - Search query string
703    ///
704    /// # Returns
705    ///
706    /// Returns a `Result<GlobalQueryResults>` containing counts per database
707    ///
708    /// # Example
709    ///
710    /// ```no_run
711    /// use pubmed_client::Client;
712    ///
713    /// #[tokio::main]
714    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
715    ///     let client = Client::new();
716    ///     let results = client.global_query("asthma").await?;
717    ///     for db in results.non_zero() {
718    ///         println!("{}: {} records", db.menu_name, db.count);
719    ///     }
720    ///     Ok(())
721    /// }
722    /// ```
723    pub async fn global_query(&self, term: &str) -> Result<GlobalQueryResults> {
724        self.pubmed.global_query(term).await
725    }
726
727    /// Upload a list of PMIDs to the NCBI History server using EPost
728    ///
729    /// # Arguments
730    ///
731    /// * `pmids` - Slice of PubMed IDs as strings
732    ///
733    /// # Returns
734    ///
735    /// Returns a `Result<EPostResult>` containing WebEnv and query_key
736    ///
737    /// # Example
738    ///
739    /// ```no_run
740    /// use pubmed_client::Client;
741    ///
742    /// #[tokio::main]
743    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
744    ///     let client = Client::new();
745    ///     let result = client.epost(&["31978945", "33515491"]).await?;
746    ///     let session = result.history_session();
747    ///     let articles = client.pubmed.fetch_from_history(&session, 0, 100).await?;
748    ///     println!("Fetched {} articles", articles.len());
749    ///     Ok(())
750    /// }
751    /// ```
752    pub async fn epost(&self, pmids: &[&str]) -> Result<EPostResult> {
753        self.pubmed.epost(pmids).await
754    }
755
756    /// Fetch all articles for a list of PMIDs using EPost and the History server
757    ///
758    /// Uploads the PMID list via EPost (HTTP POST), then fetches articles in
759    /// paginated batches. Recommended for large PMID lists (hundreds or thousands).
760    ///
761    /// # Example
762    ///
763    /// ```no_run
764    /// use pubmed_client::Client;
765    ///
766    /// #[tokio::main]
767    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
768    ///     let client = Client::new();
769    ///     let articles = client.fetch_all_by_pmids(&["31978945", "33515491"]).await?;
770    ///     for a in &articles {
771    ///         println!("{}: {}", a.pmid, a.title);
772    ///     }
773    ///     Ok(())
774    /// }
775    /// ```
776    pub async fn fetch_all_by_pmids(&self, pmids: &[&str]) -> Result<Vec<PubMedArticle>> {
777        self.pubmed.fetch_all_by_pmids(pmids).await
778    }
779
780    /// Check spelling of a search term using the ESpell API
781    ///
782    /// Provides spelling suggestions for terms within a single text query.
783    /// Uses the PubMed database by default. For other databases, use
784    /// `client.pubmed.spell_check_db(term, db)` directly.
785    ///
786    /// # Arguments
787    ///
788    /// * `term` - The search term to spell-check
789    ///
790    /// # Returns
791    ///
792    /// Returns a `Result<SpellCheckResult>` containing spelling suggestions
793    ///
794    /// # Example
795    ///
796    /// ```no_run
797    /// use pubmed_client::Client;
798    ///
799    /// #[tokio::main]
800    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
801    ///     let client = Client::new();
802    ///     let result = client.spell_check("asthmaa").await?;
803    ///     println!("Corrected: {}", result.corrected_query);
804    ///     Ok(())
805    /// }
806    /// ```
807    pub async fn spell_check(&self, term: &str) -> Result<SpellCheckResult> {
808        self.pubmed.spell_check(term).await
809    }
810}
811
812impl Default for Client {
813    fn default() -> Self {
814        Self::new()
815    }
816}