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}