Skip to main content

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_rs::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//!         .open_access_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_rs::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_rs::{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_rs::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_rs::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_rs::{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_rs::{PmcClient, ClientConfig};
186//! use pubmed_client_rs::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//!     };
196//!
197//!     let config = ClientConfig::new()
198//!         .with_cache_config(cache_config);
199//!     let client = PmcClient::with_config(config);
200//!
201//!     // Use the client normally - caching happens automatically
202//!     let article = client.fetch_full_text("PMC7906746").await?;
203//!
204//!     Ok(())
205//! }
206//! ```
207//!
208//! ### Hybrid Cache with Disk Persistence
209//!
210//! ```no_run
211//! #[cfg(not(target_arch = "wasm32"))]
212//! {
213//! use pubmed_client_rs::{PmcClient, ClientConfig};
214//! use pubmed_client_rs::cache::CacheConfig;
215//! use std::time::Duration;
216//! use std::path::PathBuf;
217//!
218//! #[tokio::main]
219//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
220//!     // Memory cache configuration
221//!     let cache_config = CacheConfig {
222//!         max_capacity: 1000,
223//!         time_to_live: Duration::from_secs(24 * 60 * 60),
224//!     };
225//!
226//!     let config = ClientConfig::new()
227//!         .with_cache_config(cache_config);
228//!     let client = PmcClient::with_config(config);
229//!
230//!     // Articles are cached in memory
231//!     let article = client.fetch_full_text("PMC7906746").await?;
232//!
233//!     Ok(())
234//! }
235//! }
236//! ```
237//!
238//! ### Cache Management
239//!
240//! ```no_run
241//! use pubmed_client_rs::{PmcClient, ClientConfig};
242//!
243//! #[tokio::main]
244//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
245//!     let config = ClientConfig::new().with_cache();
246//!     let client = PmcClient::with_config(config);
247//!
248//!     // Fetch some articles
249//!     client.fetch_full_text("PMC7906746").await?;
250//!     client.fetch_full_text("PMC10618641").await?;
251//!
252//!     // Check cache statistics
253//!     let count = client.cache_entry_count();
254//!     println!("Cached items: {}", count);
255//!
256//!     // Clear the cache when needed
257//!     client.clear_cache().await;
258//!
259//!     Ok(())
260//! }
261//! ```
262
263pub mod cache;
264pub mod common;
265pub mod config;
266pub mod error;
267pub mod pmc;
268pub mod pubmed;
269pub mod rate_limit;
270pub mod retry;
271pub mod time;
272
273// Re-export main types for convenience
274pub use common::{Affiliation, Author, PmcId, PubMedId};
275pub use config::ClientConfig;
276pub use error::{PubMedError, Result};
277pub use pmc::{
278    models::ExtractedFigure, parse_pmc_xml, ArticleSection, Figure, FundingInfo, HeadingStyle,
279    JournalInfo, MarkdownConfig, OaSubsetInfo, PmcClient, PmcFullText, PmcMarkdownConverter,
280    PmcTarClient, Reference, ReferenceStyle, Table,
281};
282pub use pubmed::{
283    parse_article_from_xml, ArticleSummary, ArticleType, CitationMatch, CitationMatchStatus,
284    CitationMatches, CitationQuery, Citations, DatabaseCount, DatabaseInfo, FieldInfo,
285    GlobalQueryResults, HistorySession, Language, LinkInfo, PmcLinks, PubMedArticle, PubMedClient,
286    RelatedArticles, SearchQuery, SearchResult, SortOrder, SpellCheckResult, SpelledQuerySegment,
287};
288pub use rate_limit::RateLimiter;
289pub use time::{sleep, Duration, Instant};
290
291/// Convenience client that combines both PubMed and PMC functionality
292#[derive(Clone)]
293pub struct Client {
294    /// PubMed client for metadata
295    pub pubmed: PubMedClient,
296    /// PMC client for full text
297    pub pmc: PmcClient,
298}
299
300impl Client {
301    /// Create a new combined client with default configuration
302    ///
303    /// Uses default NCBI rate limiting (3 requests/second) and no API key.
304    /// For production use, consider using `with_config()` to set an API key.
305    ///
306    /// # Example
307    ///
308    /// ```
309    /// use pubmed_client_rs::Client;
310    ///
311    /// let client = Client::new();
312    /// ```
313    pub fn new() -> Self {
314        let config = ClientConfig::new();
315        Self::with_config(config)
316    }
317
318    /// Create a new combined client with custom configuration
319    ///
320    /// Both PubMed and PMC clients will use the same configuration
321    /// for consistent rate limiting and API key usage.
322    ///
323    /// # Arguments
324    ///
325    /// * `config` - Client configuration including rate limits, API key, etc.
326    ///
327    /// # Example
328    ///
329    /// ```
330    /// use pubmed_client_rs::{Client, ClientConfig};
331    ///
332    /// let config = ClientConfig::new()
333    ///     .with_api_key("your_api_key_here")
334    ///     .with_email("researcher@university.edu");
335    ///
336    /// let client = Client::with_config(config);
337    /// ```
338    pub fn with_config(config: ClientConfig) -> Self {
339        Self {
340            pubmed: PubMedClient::with_config(config.clone()),
341            pmc: PmcClient::with_config(config),
342        }
343    }
344
345    /// Create a new combined client with custom HTTP client
346    ///
347    /// # Arguments
348    ///
349    /// * `http_client` - Custom reqwest client with specific configuration
350    ///
351    /// # Example
352    ///
353    /// ```
354    /// use pubmed_client_rs::Client;
355    /// use reqwest::ClientBuilder;
356    /// use std::time::Duration;
357    ///
358    /// let http_client = ClientBuilder::new()
359    ///     .timeout(Duration::from_secs(30))
360    ///     .build()
361    ///     .unwrap();
362    ///
363    /// let client = Client::with_http_client(http_client);
364    /// ```
365    pub fn with_http_client(http_client: reqwest::Client) -> Self {
366        Self {
367            pubmed: PubMedClient::with_client(http_client.clone()),
368            pmc: PmcClient::with_client(http_client),
369        }
370    }
371
372    /// Search for articles and attempt to fetch full text for each
373    ///
374    /// # Arguments
375    ///
376    /// * `query` - Search query string
377    /// * `limit` - Maximum number of articles to process
378    ///
379    /// # Returns
380    ///
381    /// Returns a vector of tuples containing (`PubMedArticle`, `Option<PmcFullText>`)
382    ///
383    /// # Example
384    ///
385    /// ```no_run
386    /// use pubmed_client_rs::Client;
387    ///
388    /// #[tokio::main]
389    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
390    ///     let client = Client::new();
391    ///     let results = client.search_with_full_text("covid-19", 5).await?;
392    ///
393    ///     for (article, full_text) in results {
394    ///         println!("Article: {}", article.title);
395    ///         if let Some(ft) = full_text {
396    ///             println!("  Full text available with {} sections", ft.sections.len());
397    ///         } else {
398    ///             println!("  Full text not available");
399    ///         }
400    ///     }
401    ///
402    ///     Ok(())
403    /// }
404    /// ```
405    pub async fn search_with_full_text(
406        &self,
407        query: &str,
408        limit: usize,
409    ) -> Result<Vec<(PubMedArticle, Option<PmcFullText>)>> {
410        let articles = self.pubmed.search_and_fetch(query, limit, None).await?;
411        let mut results = Vec::new();
412
413        for article in articles {
414            let full_text = match self.pmc.check_pmc_availability(&article.pmid).await? {
415                Some(pmcid) => self.pmc.fetch_full_text(&pmcid).await.ok(),
416                None => None,
417            };
418            results.push((article, full_text));
419        }
420
421        Ok(results)
422    }
423
424    /// Fetch multiple articles by PMIDs in a single batch request
425    ///
426    /// This is significantly more efficient than fetching articles one by one,
427    /// as it sends fewer HTTP requests to the NCBI API.
428    ///
429    /// # Arguments
430    ///
431    /// * `pmids` - Slice of PubMed IDs as strings
432    ///
433    /// # Returns
434    ///
435    /// Returns a `Result<Vec<PubMedArticle>>` containing articles with metadata
436    ///
437    /// # Example
438    ///
439    /// ```no_run
440    /// use pubmed_client_rs::Client;
441    ///
442    /// #[tokio::main]
443    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
444    ///     let client = Client::new();
445    ///     let articles = client.fetch_articles(&["31978945", "33515491"]).await?;
446    ///     for article in &articles {
447    ///         println!("{}: {}", article.pmid, article.title);
448    ///     }
449    ///     Ok(())
450    /// }
451    /// ```
452    pub async fn fetch_articles(&self, pmids: &[&str]) -> Result<Vec<PubMedArticle>> {
453        self.pubmed.fetch_articles(pmids).await
454    }
455
456    /// Fetch lightweight article summaries by PMIDs using the ESummary API
457    ///
458    /// Returns basic metadata (title, authors, journal, dates, DOI) without
459    /// abstracts, MeSH terms, or chemical lists. Faster than `fetch_articles()`
460    /// when you only need bibliographic overview data.
461    ///
462    /// # Arguments
463    ///
464    /// * `pmids` - Slice of PubMed IDs as strings
465    ///
466    /// # Returns
467    ///
468    /// Returns a `Result<Vec<ArticleSummary>>` containing lightweight article metadata
469    ///
470    /// # Example
471    ///
472    /// ```no_run
473    /// use pubmed_client_rs::Client;
474    ///
475    /// #[tokio::main]
476    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
477    ///     let client = Client::new();
478    ///     let summaries = client.fetch_summaries(&["31978945", "33515491"]).await?;
479    ///     for summary in &summaries {
480    ///         println!("{}: {}", summary.pmid, summary.title);
481    ///     }
482    ///     Ok(())
483    /// }
484    /// ```
485    pub async fn fetch_summaries(&self, pmids: &[&str]) -> Result<Vec<ArticleSummary>> {
486        self.pubmed.fetch_summaries(pmids).await
487    }
488
489    /// Search and fetch lightweight summaries in a single operation
490    ///
491    /// Combines search and ESummary fetch. Use this when you only need basic
492    /// metadata (title, authors, journal, dates) and want faster retrieval
493    /// than `search_and_fetch()` which uses EFetch.
494    ///
495    /// # Arguments
496    ///
497    /// * `query` - Search query string
498    /// * `limit` - Maximum number of articles
499    ///
500    /// # Returns
501    ///
502    /// Returns a `Result<Vec<ArticleSummary>>` containing lightweight article metadata
503    ///
504    /// # Example
505    ///
506    /// ```no_run
507    /// use pubmed_client_rs::Client;
508    ///
509    /// #[tokio::main]
510    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
511    ///     let client = Client::new();
512    ///     let summaries = client.search_and_fetch_summaries("covid-19", 20).await?;
513    ///     for summary in &summaries {
514    ///         println!("{}: {}", summary.pmid, summary.title);
515    ///     }
516    ///     Ok(())
517    /// }
518    /// ```
519    pub async fn search_and_fetch_summaries(
520        &self,
521        query: &str,
522        limit: usize,
523    ) -> Result<Vec<ArticleSummary>> {
524        self.pubmed
525            .search_and_fetch_summaries(query, limit, None)
526            .await
527    }
528
529    /// Get list of all available NCBI databases
530    ///
531    /// # Returns
532    ///
533    /// Returns a `Result<Vec<String>>` containing names of all available databases
534    ///
535    /// # Example
536    ///
537    /// ```no_run
538    /// use pubmed_client_rs::Client;
539    ///
540    /// #[tokio::main]
541    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
542    ///     let client = Client::new();
543    ///     let databases = client.get_database_list().await?;
544    ///     println!("Available databases: {:?}", databases);
545    ///     Ok(())
546    /// }
547    /// ```
548    pub async fn get_database_list(&self) -> Result<Vec<String>> {
549        self.pubmed.get_database_list().await
550    }
551
552    /// Get detailed information about a specific database
553    ///
554    /// # Arguments
555    ///
556    /// * `database` - Name of the database (e.g., "pubmed", "pmc", "books")
557    ///
558    /// # Returns
559    ///
560    /// Returns a `Result<DatabaseInfo>` containing detailed database information
561    ///
562    /// # Example
563    ///
564    /// ```no_run
565    /// use pubmed_client_rs::Client;
566    ///
567    /// #[tokio::main]
568    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
569    ///     let client = Client::new();
570    ///     let db_info = client.get_database_info("pubmed").await?;
571    ///     println!("Database: {}", db_info.name);
572    ///     println!("Description: {}", db_info.description);
573    ///     println!("Fields: {}", db_info.fields.len());
574    ///     Ok(())
575    /// }
576    /// ```
577    pub async fn get_database_info(&self, database: &str) -> Result<DatabaseInfo> {
578        self.pubmed.get_database_info(database).await
579    }
580
581    /// Get related articles for given PMIDs
582    ///
583    /// # Arguments
584    ///
585    /// * `pmids` - List of PubMed IDs to find related articles for
586    ///
587    /// # Returns
588    ///
589    /// Returns a `Result<RelatedArticles>` containing related article information
590    ///
591    /// # Example
592    ///
593    /// ```no_run
594    /// use pubmed_client_rs::Client;
595    ///
596    /// #[tokio::main]
597    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
598    ///     let client = Client::new();
599    ///     let related = client.get_related_articles(&[31978945]).await?;
600    ///     println!("Found {} related articles", related.related_pmids.len());
601    ///     Ok(())
602    /// }
603    /// ```
604    pub async fn get_related_articles(&self, pmids: &[u32]) -> Result<RelatedArticles> {
605        self.pubmed.get_related_articles(pmids).await
606    }
607
608    /// Get PMC links for given PMIDs (full-text availability)
609    ///
610    /// # Arguments
611    ///
612    /// * `pmids` - List of PubMed IDs to check for PMC availability
613    ///
614    /// # Returns
615    ///
616    /// Returns a `Result<PmcLinks>` containing PMC IDs with full text available
617    ///
618    /// # Example
619    ///
620    /// ```no_run
621    /// use pubmed_client_rs::Client;
622    ///
623    /// #[tokio::main]
624    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
625    ///     let client = Client::new();
626    ///     let pmc_links = client.get_pmc_links(&[31978945]).await?;
627    ///     println!("Found {} PMC articles", pmc_links.pmc_ids.len());
628    ///     Ok(())
629    /// }
630    /// ```
631    pub async fn get_pmc_links(&self, pmids: &[u32]) -> Result<PmcLinks> {
632        self.pubmed.get_pmc_links(pmids).await
633    }
634
635    /// Get citing articles for given PMIDs
636    ///
637    /// # Arguments
638    ///
639    /// * `pmids` - List of PubMed IDs to find citing articles for
640    ///
641    /// # Returns
642    ///
643    /// Returns a `Result<Citations>` containing citing article information
644    ///
645    /// # Example
646    ///
647    /// ```no_run
648    /// use pubmed_client_rs::Client;
649    ///
650    /// #[tokio::main]
651    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
652    ///     let client = Client::new();
653    ///     let citations = client.get_citations(&[31978945]).await?;
654    ///     println!("Found {} citing articles", citations.citing_pmids.len());
655    ///     Ok(())
656    /// }
657    /// ```
658    pub async fn get_citations(&self, pmids: &[u32]) -> Result<Citations> {
659        self.pubmed.get_citations(pmids).await
660    }
661
662    /// Match citations to PMIDs using the ECitMatch API
663    ///
664    /// # Arguments
665    ///
666    /// * `citations` - List of citation queries to match
667    ///
668    /// # Returns
669    ///
670    /// Returns a `Result<CitationMatches>` containing match results
671    ///
672    /// # Example
673    ///
674    /// ```no_run
675    /// use pubmed_client_rs::{Client, CitationQuery};
676    ///
677    /// #[tokio::main]
678    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
679    ///     let client = Client::new();
680    ///     let citations = vec![
681    ///         CitationQuery::new("science", "1987", "235", "182", "palmenberg ac", "ref1"),
682    ///     ];
683    ///     let results = client.match_citations(&citations).await?;
684    ///     println!("Found {} matches", results.found_count());
685    ///     Ok(())
686    /// }
687    /// ```
688    pub async fn match_citations(&self, citations: &[CitationQuery]) -> Result<CitationMatches> {
689        self.pubmed.match_citations(citations).await
690    }
691
692    /// Query all NCBI databases for record counts
693    ///
694    /// # Arguments
695    ///
696    /// * `term` - Search query string
697    ///
698    /// # Returns
699    ///
700    /// Returns a `Result<GlobalQueryResults>` containing counts per database
701    ///
702    /// # Example
703    ///
704    /// ```no_run
705    /// use pubmed_client_rs::Client;
706    ///
707    /// #[tokio::main]
708    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
709    ///     let client = Client::new();
710    ///     let results = client.global_query("asthma").await?;
711    ///     for db in results.non_zero() {
712    ///         println!("{}: {} records", db.menu_name, db.count);
713    ///     }
714    ///     Ok(())
715    /// }
716    /// ```
717    pub async fn global_query(&self, term: &str) -> Result<GlobalQueryResults> {
718        self.pubmed.global_query(term).await
719    }
720
721    /// Check spelling of a search term using the ESpell API
722    ///
723    /// Provides spelling suggestions for terms within a single text query.
724    /// Uses the PubMed database by default. For other databases, use
725    /// `client.pubmed.spell_check_db(term, db)` directly.
726    ///
727    /// # Arguments
728    ///
729    /// * `term` - The search term to spell-check
730    ///
731    /// # Returns
732    ///
733    /// Returns a `Result<SpellCheckResult>` containing spelling suggestions
734    ///
735    /// # Example
736    ///
737    /// ```no_run
738    /// use pubmed_client_rs::Client;
739    ///
740    /// #[tokio::main]
741    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
742    ///     let client = Client::new();
743    ///     let result = client.spell_check("asthmaa").await?;
744    ///     println!("Corrected: {}", result.corrected_query);
745    ///     Ok(())
746    /// }
747    /// ```
748    pub async fn spell_check(&self, term: &str) -> Result<SpellCheckResult> {
749        self.pubmed.spell_check(term).await
750    }
751}
752
753impl Default for Client {
754    fn default() -> Self {
755        Self::new()
756    }
757}