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}