pubmed_client/europe_pmc/
search.rs1use tracing::{debug, instrument};
4
5use pubmed_parser::europe_pmc::{EuropePmcResult, EuropePmcSearchResponse, parse_search_response};
6
7use crate::error::Result;
8
9use super::client::EuropePmcClient;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum ResultType {
14 IdList,
16 Lite,
18 Core,
20}
21
22impl ResultType {
23 fn as_str(&self) -> &'static str {
24 match self {
25 ResultType::IdList => "idlist",
26 ResultType::Lite => "lite",
27 ResultType::Core => "core",
28 }
29 }
30}
31
32#[derive(Debug, Clone)]
34pub struct EuropePmcSearchOptions {
35 pub result_type: ResultType,
37 pub page_size: u32,
39 pub cursor_mark: String,
41 pub sort: Option<String>,
43}
44
45impl Default for EuropePmcSearchOptions {
46 fn default() -> Self {
47 Self {
48 result_type: ResultType::Lite,
49 page_size: 25,
50 cursor_mark: "*".to_string(),
51 sort: None,
52 }
53 }
54}
55
56impl EuropePmcClient {
57 #[instrument(skip(self), fields(query = %query, limit))]
63 pub async fn search(&self, query: &str, limit: usize) -> Result<Vec<EuropePmcResult>> {
64 let opts = EuropePmcSearchOptions {
65 page_size: limit.clamp(1, 1000) as u32,
66 ..Default::default()
67 };
68 self.search_all(query, limit, &opts).await
69 }
70
71 #[instrument(skip(self, opts), fields(query = %query, cursor = %opts.cursor_mark))]
76 pub async fn search_page(
77 &self,
78 query: &str,
79 opts: &EuropePmcSearchOptions,
80 ) -> Result<EuropePmcSearchResponse> {
81 let page_size = opts.page_size.to_string();
82 let mut params: Vec<(&str, &str)> = vec![
83 ("query", query),
84 ("format", "json"),
85 ("resultType", opts.result_type.as_str()),
86 ("pageSize", page_size.as_str()),
87 ("cursorMark", opts.cursor_mark.as_str()),
88 ];
89 if let Some(sort) = &opts.sort {
90 params.push(("sort", sort.as_str()));
91 }
92
93 let response = self
94 .executor()
95 .get_endpoint(&self.base_url, "search", ¶ms)
96 .await?;
97 let text = response.text().await?;
98 Ok(parse_search_response(&text)?)
99 }
100
101 #[instrument(skip(self, opts), fields(query = %query, max_results))]
108 pub async fn search_all(
109 &self,
110 query: &str,
111 max_results: usize,
112 opts: &EuropePmcSearchOptions,
113 ) -> Result<Vec<EuropePmcResult>> {
114 let mut collected: Vec<EuropePmcResult> = Vec::new();
115 let mut cursor = opts.cursor_mark.clone();
116
117 while collected.len() < max_results {
118 let page_opts = EuropePmcSearchOptions {
119 cursor_mark: cursor.clone(),
120 ..opts.clone()
121 };
122 let page = self.search_page(query, &page_opts).await?;
123
124 if page.results.is_empty() {
125 break;
126 }
127 collected.extend(page.results);
128
129 match page.next_cursor_mark {
130 Some(next) if next != cursor => cursor = next,
132 _ => break,
133 }
134 }
135
136 collected.truncate(max_results);
137 debug!(returned = collected.len(), "Europe PMC search_all complete");
138 Ok(collected)
139 }
140}