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