Skip to main content

ss_tools/
lib.rs

1//! # Semantic Scholar Tools
2//! This library provides tools to interact with the Semantic Scholar API.
3//!
4//! ## Quick Start
5//!
6//! This is a simple example to get you started with `ss-tools`.  
7//! The following code snippet shows how to query a paper by its title:
8//!
9//! ```rust
10//! use anyhow::Result;
11//! use ss_tools::{SemanticScholar, QueryParams};
12//! # #[tokio::main]
13//! # async fn main() -> Result<()> {
14//!
15//! let query_text = "Attention Is All You Need";
16//!
17//! // Create a new instance of SemanticScholar
18//! let mut ss = SemanticScholar::new();
19//! let mut query_params = QueryParams::default();
20//! query_params.query_text(query_text);
21//!
22//! let max_retry_count = 5;
23//! let wait_time = 10;
24//! let paper = ss.query_a_paper_by_title(query_params, max_retry_count, wait_time).await.unwrap();
25//
26//! assert_eq!(
27//!    paper.title.clone().unwrap().to_lowercase(),
28//!   "attention is all you need".to_string()
29//! );
30//! # Ok(())
31//! # }
32//! ```
33//!
34//! ## Tutorials
35//! - Step 1 - [Hello `ss-tools`](tutorials::step_1)
36//! - Step 2 - [Paper & Author structs](tutorials::step_2)
37//! - Step 3 - [Build QueryParam](tutorials::step_3)
38//! - Step 4 - [Available Endpoints](tutorials::step_4)
39//!
40//! ## Implemented Endpoints
41//! | Endpoint | Implementation | Reference |
42//! | --- |:---:|:---:|
43//! | [Suggest paper query completions](https://api.semanticscholar.org/api-docs/#tag/Paper-Data/operation/get_graph_get_paper_autocomplete) | - | |
44//! | [Get details for multiple papers at once](https://api.semanticscholar.org/api-docs/#tag/Paper-Data/operation/post_graph_get_papers)| - | |
45//! | [Paper relevance search](https://api.semanticscholar.org/api-docs/#tag/Paper-Data/operation/get_graph_paper_relevance_search) | ✅ | [`SemanticScholar::query_papers_by_title`] |
46//! | [Paper bulk search](https://api.semanticscholar.org/api-docs/#tag/Paper-Data/operation/get_graph_paper_bulk_search) | ✅  | [`SemanticScholar::bulk_query_by_ids`] |
47//! | [Paper title search](https://api.semanticscholar.org/api-docs/#tag/Paper-Data/operation/get_graph_paper_title_search) | ✅ |[`SemanticScholar::query_a_paper_by_title`] |
48//! | [Details about a paper](https://api.semanticscholar.org/api-docs/#tag/Paper-Data/operation/get_graph_get_paper) | ✅ | [`SemanticScholar::query_paper_details`] |
49//! | [Details about a paper's authors](https://api.semanticscholar.org/api-docs/#tag/Paper-Data/operation/get_graph_get_paper_authors) | ✅ | [`SemanticScholar::query_paper_authors`] |
50//! | [Details about a paper's citations](https://api.semanticscholar.org/api-docs/#tag/Paper-Data/operation/get_graph_get_paper_citations) | ✅ | [`SemanticScholar::query_paper_citations`] |
51//! | [Details about a paper's references](https://api.semanticscholar.org/api-docs/#tag/Paper-Data/operation/get_graph_get_paper_references) | ✅ | [`SemanticScholar::query_paper_references`] |
52//! | [Get details for multiple authors at once](https://api.semanticscholar.org/api-docs/#tag/Author-Data/operation/post_graph_get_authors) | - | |
53//! | [Search for authors by name](https://api.semanticscholar.org/api-docs/#tag/Author-Data/operation/get_graph_get_author_search) | ✅ | [`SemanticScholar::search_authors`] |
54//! | [Details about an author](https://api.semanticscholar.org/api-docs/#tag/Author-Data/operation/get_graph_get_author) | ✅ | [`SemanticScholar::query_author_details`] |
55//! | [Details about an author's papers](https://api.semanticscholar.org/api-docs/#tag/Author-Data/operation/get_graph_get_author_papers) | ✅ | [`SemanticScholar::query_author_papers`] |
56
57pub mod structs;
58pub mod tutorials;
59
60use crate::structs::*;
61use anyhow::{Error, Result};
62use dotenvy::dotenv;
63use fxhash::FxHashMap;
64use indicatif::ProgressBar;
65use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC as NON_ALNUM};
66use reqwest::{self as request, header};
67
68#[cfg(test)]
69mod tests;
70
71fn encode(s: &str) -> String {
72    utf8_percent_encode(s, NON_ALNUM).to_string()
73}
74
75#[derive(Clone, Debug, Default)]
76pub struct QueryParams {
77    pub paper_id: String,
78    pub query_text: Option<String>,
79    pub fields: Option<Vec<PaperField>>,
80    pub author_fields: Option<Vec<AuthorField>>,
81    pub publication_types: Option<Vec<PublicationTypes>>,
82    pub open_access_pdf: Option<bool>,
83    pub min_citation_count: Option<u32>,
84    pub publication_date_or_year: Option<String>,
85    pub year: Option<String>,
86    pub venue: Option<Vec<String>>,
87    pub fields_of_study: Option<Vec<FieldsOfStudy>>,
88    pub offset: Option<u64>,
89    pub limit: Option<u64>,
90    pub token: Option<String>,
91    pub sort: Option<String>,
92}
93
94impl QueryParams {
95    pub fn paper_id(&mut self, paper_id: &str) -> &mut Self {
96        self.paper_id = paper_id.to_string();
97        self
98    }
99    pub fn query_text(&mut self, query_text: &str) -> &mut Self {
100        self.query_text = Some(query_text.to_string());
101        self
102    }
103    pub fn fields(&mut self, fields: Vec<PaperField>) -> &mut Self {
104        self.fields = Some(fields);
105        self
106    }
107    pub fn author_fields(&mut self, author_fields: Vec<AuthorField>) -> &mut Self {
108        self.author_fields = Some(author_fields);
109        self
110    }
111    pub fn publication_types(&mut self, publication_types: Vec<PublicationTypes>) -> &mut Self {
112        self.publication_types = Some(publication_types);
113        self
114    }
115    pub fn open_access_pdf(&mut self, open_access_pdf: bool) -> &mut Self {
116        self.open_access_pdf = Some(open_access_pdf);
117        self
118    }
119    pub fn min_citation_count(&mut self, min_citation_count: u32) -> &mut Self {
120        self.min_citation_count = Some(min_citation_count);
121        self
122    }
123    pub fn publication_date_or_year(&mut self, publication_date_or_year: &str) -> &mut Self {
124        self.publication_date_or_year = Some(publication_date_or_year.to_string());
125        self
126    }
127    pub fn year(&mut self, year: &str) -> &mut Self {
128        self.year = Some(year.to_string());
129        self
130    }
131    pub fn venue(&mut self, venue: Vec<&str>) -> &mut Self {
132        let venue: Vec<String> = venue.iter().map(|v| v.to_string()).collect();
133        self.venue = Some(venue);
134        self
135    }
136    pub fn fields_of_study(&mut self, fields_of_study: Vec<FieldsOfStudy>) -> &mut Self {
137        self.fields_of_study = Some(fields_of_study);
138        self
139    }
140    pub fn offset(&mut self, offset: u64) -> &mut Self {
141        self.offset = Some(offset);
142        self
143    }
144    pub fn limit(&mut self, limit: u64) -> &mut Self {
145        self.limit = Some(limit);
146        self
147    }
148    pub fn token(&mut self, token: &str) -> &mut Self {
149        self.token = Some(token.to_string());
150        self
151    }
152    pub fn sort(&mut self, sort: &str) -> &mut Self {
153        self.sort = Some(sort.to_string());
154        self
155    }
156
157    fn fields2string(&self, fields: Vec<PaperField>) -> String {
158        fields
159            .iter()
160            .map(|field| encode(&field.to_string()))
161            .collect::<Vec<String>>()
162            .join(",")
163    }
164
165    fn publication_types2string(&self, publication_types: Vec<PublicationTypes>) -> String {
166        publication_types
167            .iter()
168            .map(|publication_type| encode(&publication_type.to_string()))
169            .collect::<Vec<String>>()
170            .join(",")
171    }
172
173    fn fields_of_study2string(&self, fields_of_study: Vec<FieldsOfStudy>) -> String {
174        fields_of_study
175            .iter()
176            .map(|field| encode(&field.to_string()))
177            .collect::<Vec<String>>()
178            .join(",")
179    }
180
181    fn author_fields2string(&self, author_fields: Vec<AuthorField>) -> String {
182        author_fields
183            .iter()
184            .map(|field| encode(&field.to_string()))
185            .collect::<Vec<String>>()
186            .join(",")
187    }
188
189    pub fn build(&self) -> String {
190        let mut query_params = Vec::new();
191
192        if let Some(query_text) = &self.query_text {
193            query_params.push(format!("query={}", encode(query_text)));
194        }
195        if let Some(fields) = &self.fields {
196            let fields = self.fields2string(fields.clone());
197            query_params.push(format!("fields={}", fields));
198        }
199        if let Some(author_fields) = &self.author_fields {
200            let author_fields = self.author_fields2string(author_fields.clone());
201            query_params.push(format!("fields={}", author_fields));
202        }
203        if let Some(publication_types) = &self.publication_types {
204            let publication_types = self.publication_types2string(publication_types.clone());
205            query_params.push(format!("publicationTypes={}", publication_types));
206        }
207        if self.open_access_pdf.is_some() {
208            query_params.push("openAccessPdf".to_string());
209        }
210        if let Some(min_citation_count) = &self.min_citation_count {
211            query_params.push(format!("minCitationCount={}", min_citation_count));
212        }
213        if let Some(publication_date_or_year) = &self.publication_date_or_year {
214            query_params.push(format!(
215                "publicationDateOrYear={}",
216                publication_date_or_year
217            ));
218        }
219        if let Some(year) = &self.year {
220            query_params.push(format!("year={}", year));
221        }
222        if let Some(venue) = &self.venue {
223            let venue = venue
224                .iter()
225                .map(|v| encode(v))
226                .collect::<Vec<String>>()
227                .join(",");
228            query_params.push(format!("venue={}", venue));
229        }
230        if let Some(fields_of_study) = &self.fields_of_study {
231            let fields_of_study = self.fields_of_study2string(fields_of_study.clone());
232            query_params.push(format!("fieldsOfStudy={}", fields_of_study));
233        }
234        if let Some(offset) = &self.offset {
235            query_params.push(format!("offset={}", offset));
236        }
237        if let Some(limit) = &self.limit {
238            query_params.push(format!("limit={}", limit));
239        }
240        if let Some(token) = &self.token {
241            query_params.push(format!("token={}", token));
242        }
243        if let Some(sort) = &self.sort {
244            query_params.push(format!("sort={}", sort));
245        }
246
247        if query_params.is_empty() {
248            return "".to_string();
249        } else {
250            let query_params = query_params.join("&");
251            return format!("?{}", query_params);
252        }
253    }
254}
255
256#[derive(Clone, Debug, Default)]
257pub struct SemanticScholar {
258    pub api_key: String,
259}
260
261impl SemanticScholar {
262    pub fn new() -> Self {
263        dotenv().ok();
264        let vars = FxHashMap::from_iter(std::env::vars().into_iter().map(|(k, v)| (k, v)));
265        let api_key = vars
266            .get("SEMANTIC_SCHOLAR_API_KEY")
267            .unwrap_or(&"".to_string())
268            .to_string();
269        Self { api_key: api_key }
270    }
271
272    fn get_url(&self, endpoint: Endpoint, query_params: &mut QueryParams) -> String {
273        let paper_id = query_params.paper_id.clone();
274        let query_params = query_params.build();
275        match endpoint {
276            Endpoint::GetMultiplePpaerDetails => {
277                return format!(
278                    "https://api.semanticscholar.org/graph/v1/paper/batch{}",
279                    query_params
280                );
281            }
282            Endpoint::GetAPaperByTitle => {
283                let url = format!(
284                    "https://api.semanticscholar.org/graph/v1/paper/search/match{}",
285                    query_params
286                );
287                return url;
288            }
289            Endpoint::GetPapersByTitle => {
290                let url = format!(
291                    "https://api.semanticscholar.org/graph/v1/paper/search{}",
292                    query_params
293                );
294                return url;
295            }
296            Endpoint::GetPaperDetails => {
297                let url = format!(
298                    "https://api.semanticscholar.org/graph/v1/paper/{}{}",
299                    paper_id, query_params
300                );
301                return url;
302            }
303            Endpoint::GetAuthorDetails => {
304                let url = format!(
305                    "https://api.semanticscholar.org/graph/v1/author/{}{}",
306                    paper_id, query_params
307                );
308                return url;
309            }
310            Endpoint::GetReferencesOfAPaper => {
311                let url = format!(
312                    "https://api.semanticscholar.org/graph/v1/paper/{}/references{}",
313                    paper_id, query_params
314                );
315                return url;
316            }
317            Endpoint::GetCitationsOfAPaper => {
318                let url = format!(
319                    "https://api.semanticscholar.org/graph/v1/paper/{}/citations{}",
320                    paper_id, query_params
321                );
322                return url;
323            }
324            Endpoint::SearchAuthors => {
325                let url = format!(
326                    "https://api.semanticscholar.org/graph/v1/author/search{}",
327                    query_params
328                );
329                return url;
330            }
331            Endpoint::GetAuthorPapers => {
332                let url = format!(
333                    "https://api.semanticscholar.org/graph/v1/author/{}/papers{}",
334                    paper_id, query_params
335                );
336                return url;
337            }
338            Endpoint::GetPaperAuthors => {
339                let url = format!(
340                    "https://api.semanticscholar.org/graph/v1/paper/{}/authors{}",
341                    paper_id, query_params
342                );
343                return url;
344            }
345        }
346    }
347
348    fn sleep(&self, seconds: u64, message: &str) {
349        let pb = ProgressBar::new(seconds);
350        pb.set_style(
351            indicatif::ProgressStyle::default_bar()
352                .template(
353                    "{spinner:.green} [{elapsed_precise}] [{bar:40.green/cyan}] {pos}s/{len}s {msg}",
354                )
355                .unwrap()
356                .progress_chars("█▓▒░"),
357        );
358        if message.is_empty() {
359            pb.set_message("Waiting for the next request...");
360        } else {
361            pb.set_message(message.to_string());
362        }
363        for _ in 0..seconds {
364            pb.inc(1);
365            std::thread::sleep(std::time::Duration::from_secs(1));
366        }
367        pb.finish_and_clear();
368    }
369
370    /// # Description
371    /// Bulk retrieval of basic paper data without search relevance.  
372    /// Available fields for `fields: Vec<PaperField>`, see: [`PaperField`].  
373    /// See for more details: [Paper bulk search](https://api.semanticscholar.org/api-docs/#tag/Paper-Data/operation/get_graph_paper_bulk_search)  
374    ///
375    /// # Example
376    ///
377    /// ```rust
378    /// # use anyhow::Result;
379    /// # use ss_tools::SemanticScholar;
380    /// # use ss_tools::structs::PaperField;
381    /// # #[tokio::main]
382    /// # async fn main() -> Result<()> {
383    /// let paper_ids = vec![
384    ///     "5c5751d45e298cea054f32b392c12c61027d2fe7",
385    ///     "649def34f8be52c8b66281af98ae884c09aef38b",
386    ///     "ARXIV:2106.15928",
387    /// ];
388    /// let fields = vec![
389    ///     PaperField::Title,
390    ///     PaperField::CitationCount,
391    /// ];
392    /// let max_retry_count = 5;
393    /// let wait_time = 10;
394    /// let mut ss = SemanticScholar::new();
395    /// let papers = ss.bulk_query_by_ids(paper_ids, fields, max_retry_count, wait_time).await.unwrap();
396    ///
397    /// assert_eq!(papers.len(), 3);
398    /// let paper = &papers[0].clone();
399    /// assert_eq!(paper.title.clone().unwrap(), "S2ORC: The Semantic Scholar Open Research Corpus");
400    /// # Ok(())
401    /// # }
402    /// ```
403    pub async fn bulk_query_by_ids(
404        &mut self,
405        paper_ids: Vec<&str>,
406        fields: Vec<PaperField>,
407        max_retry_count: u64,
408        wait_time: u64,
409    ) -> Result<Vec<Paper>> {
410        let mut max_retry_count = max_retry_count.clone();
411
412        let mut headers = header::HeaderMap::new();
413        headers.insert("Content-Type", "application/json".parse().unwrap());
414        headers.insert("user-agent", "ss-tools/0.1".parse().unwrap());
415        if !self.api_key.is_empty() {
416            headers.insert("x-api-key", self.api_key.parse().unwrap());
417        }
418        let client = request::Client::builder()
419            .default_headers(headers)
420            .build()
421            .unwrap();
422
423        let mut query_params = QueryParams::default();
424        query_params.fields(fields.clone());
425        let url = self.get_url(Endpoint::GetMultiplePpaerDetails, &mut query_params);
426        let body = format!(
427            "{{\"ids\":[{}]}}",
428            paper_ids
429                .iter()
430                .map(|v| format!("\"{}\"", v))
431                .collect::<Vec<String>>()
432                .join(",")
433        );
434
435        loop {
436            if max_retry_count == 0 {
437                return Err(Error::msg("Failed to get papers"));
438            }
439            let body = client
440                .post(url.clone())
441                .body(body.clone())
442                .send()
443                .await?
444                .text()
445                .await?;
446
447            match serde_json::from_str::<Vec<Paper>>(&body) {
448                Ok(response) => {
449                    return Ok(response);
450                }
451                Err(e) => {
452                    max_retry_count -= 1;
453                    self.sleep(
454                        wait_time,
455                        format!("Error: {} Body: {}", &e.to_string(), &body).as_str(),
456                    );
457                    continue;
458                }
459            }
460        }
461    }
462
463    /// # Description
464    /// Search for papers related to the given title.  
465    /// Make sure to provide the `query_text` in the `query_params`.  
466    /// For details of 'query_params', see: [`QueryParams`].  
467    ///
468    /// # Example
469    /// ```rust
470    /// # use anyhow::Result;
471    /// # use ss_tools::{SemanticScholar, QueryParams};
472    /// # #[tokio::main]
473    /// # async fn main() -> Result<()> {
474    /// let mut ss = SemanticScholar::new();
475    /// let mut query_params = QueryParams::default();
476    /// query_params.query_text("attention is all you need");
477    /// let max_retry_count = 5;
478    /// let wait_time = 10;
479    ///
480    /// let papers = ss.query_papers_by_title(query_params, max_retry_count, wait_time).await.unwrap();
481    ///
482    /// assert!(papers.len() > 1);
483    /// let paper = papers.first().unwrap();
484    /// assert_eq!(paper.paper_id.clone().unwrap(), "204e3073870fae3d05bcbc2f6a8e263d9b72e776");
485    /// assert_eq!(
486    ///    paper.title.clone().unwrap().to_lowercase(),
487    ///   "attention is all you need".to_string()
488    /// );
489    /// # Ok(())
490    /// # }
491    /// ```
492    pub async fn query_papers_by_title(
493        &mut self,
494        query_params: QueryParams,
495        max_retry_count: u64,
496        wait_time: u64,
497    ) -> Result<Vec<Paper>> {
498        let mut query_params = query_params.clone();
499        let mut max_retry_count = max_retry_count.clone();
500
501        let mut headers = header::HeaderMap::new();
502        headers.insert("Content-Type", "application/json".parse().unwrap());
503        headers.insert("user-agent", "ss-tools/0.1".parse().unwrap());
504        if !self.api_key.is_empty() {
505            headers.insert("x-api-key", self.api_key.parse().unwrap());
506        }
507        let client = request::Client::builder()
508            .default_headers(headers)
509            .build()
510            .unwrap();
511
512        let url = self.get_url(Endpoint::GetPapersByTitle, &mut query_params);
513
514        loop {
515            if max_retry_count == 0 {
516                return Err(Error::msg(format!(
517                    "Failed to get paper id for: {}",
518                    query_params.query_text.unwrap().clone()
519                )));
520            }
521
522            let body = client.get(url.clone()).send().await?.text().await?;
523            match serde_json::from_str::<PaperIds>(&body) {
524                Ok(response) => {
525                    if response.data.is_empty() || response.total == 0 {
526                        max_retry_count -= 1;
527                        self.sleep(
528                            wait_time,
529                            format!("Error: Response is empty. Body: {}", &body).as_str(),
530                        );
531                        continue;
532                    }
533                    return Ok(response.data);
534                }
535                Err(e) => {
536                    max_retry_count -= 1;
537                    self.sleep(
538                        wait_time,
539                        format!("Error: {} Body: {}", &e.to_string(), &body).as_str(),
540                    );
541                    continue;
542                }
543            }
544        }
545    }
546
547    /// # Description
548    /// Retrieve a single paper based on closest match to the title.
549    /// For details of 'query_params', see: [`QueryParams`].
550    ///
551    /// # Example
552    ///
553    /// ```rust
554    /// # use anyhow::Result;
555    /// # use ss_tools::{SemanticScholar, QueryParams};
556    /// # #[tokio::main]
557    /// # async fn main() -> Result<()> {
558    /// let mut ss = SemanticScholar::new();
559    /// let mut query_params = QueryParams::default();
560    /// query_params.query_text("attention is all you need");
561    /// let max_retry_count = 5;
562    /// let wait_time = 10;
563    /// let paper = ss
564    ///     .query_a_paper_by_title(query_params, max_retry_count, wait_time)
565    ///     .await
566    ///     .unwrap();
567    /// assert_eq!(paper.paper_id.clone().unwrap(), "204e3073870fae3d05bcbc2f6a8e263d9b72e776");
568    /// assert_eq!(
569    ///     paper.title.clone().unwrap().to_lowercase(),
570    ///     "attention is all you need".to_string()
571    ///);
572    /// # Ok(())
573    /// # }
574    /// ```
575    pub async fn query_a_paper_by_title(
576        &mut self,
577        query_params: QueryParams,
578        max_retry_count: u64,
579        wait_time: u64,
580    ) -> Result<Paper> {
581        let mut query_params = query_params.clone();
582        let mut max_retry_count = max_retry_count.clone();
583
584        let mut headers = header::HeaderMap::new();
585        headers.insert("Content-Type", "application/json".parse().unwrap());
586        headers.insert("user-agent", "ss-tools/0.1".parse().unwrap());
587        if !self.api_key.is_empty() {
588            headers.insert("x-api-key", self.api_key.parse()?);
589        }
590        let client = request::Client::builder()
591            .default_headers(headers)
592            .build()?;
593
594        let url = self.get_url(Endpoint::GetAPaperByTitle, &mut query_params);
595        loop {
596            if max_retry_count == 0 {
597                return Err(Error::msg(format!(
598                    "Failed to get paper id for: {}",
599                    query_params.query_text.unwrap()
600                )));
601            }
602
603            let body = client.get(url.clone()).send().await?.text().await?;
604            match serde_json::from_str::<PaperIds>(&body) {
605                Ok(response) => {
606                    if response.data.len() < 1 {
607                        max_retry_count -= 1;
608                        self.sleep(
609                            wait_time,
610                            format!("Error: Response is empty. Body: {}", &body).as_str(),
611                        );
612                        continue;
613                    }
614                    let paper = response.data.first().unwrap().clone();
615                    return Ok(paper);
616                }
617                Err(e) => {
618                    max_retry_count -= 1;
619                    self.sleep(
620                        wait_time,
621                        format!("Error: {} Body: {}", &e.to_string(), &body).as_str(),
622                    );
623                    continue;
624                }
625            }
626        }
627    }
628
629    /// # Description
630    /// Retrieve details of a single paper based on the paper id.
631    /// Make sure to provide the `paper_id` in the `query_params`.
632    /// For details of 'query_params', see: [`QueryParams`].
633    ///
634    /// # Example
635    /// ```rust
636    /// # use anyhow::Result;
637    /// # use ss_tools::{SemanticScholar, QueryParams};
638    /// # use ss_tools::structs::PaperField;
639    /// # #[tokio::main]
640    /// # async fn main() -> Result<()> {
641    /// let mut ss = SemanticScholar::new();
642    /// let mut query_params = QueryParams::default();
643    /// query_params.paper_id("204e3073870fae3d05bcbc2f6a8e263d9b72e776");
644    /// query_params.fields(vec![
645    ///     PaperField::Title,
646    ///     PaperField::Abstract,
647    ///     PaperField::CitationCount,
648    ///     PaperField::ReferenceCount,
649    ///     PaperField::Year,
650    /// ]);
651    /// let paper_details = ss.query_paper_details(query_params, 5, 10).await.unwrap();
652    ///
653    /// let title = paper_details.title.clone().unwrap();
654    /// assert_eq!(title.to_lowercase(), "attention is all you need".to_string());
655    /// # Ok(())
656    /// # }
657    /// ```
658    pub async fn query_paper_details(
659        &mut self,
660        query_params: QueryParams,
661        max_retry_count: u64,
662        wait_time: u64,
663    ) -> Result<Paper> {
664        let mut query_params = query_params.clone();
665        let mut max_retry_count = max_retry_count.clone();
666
667        let mut fields = query_params.fields.clone().unwrap_or_default();
668        if !fields.contains(&PaperField::PaperId) {
669            fields.push(PaperField::PaperId);
670            query_params.fields = Some(fields);
671        }
672
673        let mut headers = header::HeaderMap::new();
674        headers.insert("Content-Type", "application/json".parse().unwrap());
675        headers.insert("user-agent", "ss-tools/0.1".parse().unwrap());
676        if !self.api_key.is_empty() {
677            headers.insert("x-api-key", self.api_key.parse().unwrap());
678        }
679        let client = request::Client::builder()
680            .default_headers(headers)
681            .build()
682            .unwrap();
683
684        let url = self.get_url(Endpoint::GetPaperDetails, &mut query_params);
685        loop {
686            if max_retry_count == 0 {
687                return Err(Error::msg(format!(
688                    "Failed to get paper details: {}",
689                    query_params.paper_id
690                )));
691            }
692            let body = client.get(url.clone()).send().await?.text().await?;
693            match serde_json::from_str::<Paper>(&body) {
694                Ok(response) => {
695                    return Ok(response);
696                }
697                Err(e) => {
698                    max_retry_count -= 1;
699                    self.sleep(
700                        wait_time,
701                        format!("Error: {} Body: {}", &e.to_string(), &body).as_str(),
702                    );
703                    continue;
704                }
705            }
706        }
707    }
708
709    pub async fn query_paper_citations(
710        &mut self,
711        query_params: QueryParams,
712        max_retry_count: u64,
713        wait_time: u64,
714    ) -> Result<ResponsePapers> {
715        let mut query_params = query_params.clone();
716        let mut max_retry_count = max_retry_count.clone();
717
718        let mut fields = query_params.fields.clone().unwrap_or_default();
719        if !fields.contains(&PaperField::PaperId) {
720            fields.push(PaperField::PaperId);
721            query_params.fields = Some(fields);
722        }
723
724        let mut headers = header::HeaderMap::new();
725        headers.insert("Content-Type", "application/json".parse().unwrap());
726        headers.insert("user-agent", "ss-tools/0.1".parse().unwrap());
727        if !self.api_key.is_empty() {
728            headers.insert("x-api-key", self.api_key.parse().unwrap());
729        }
730        let client = request::Client::builder()
731            .default_headers(headers)
732            .build()
733            .unwrap();
734
735        let url = self.get_url(Endpoint::GetCitationsOfAPaper, &mut query_params);
736
737        loop {
738            if max_retry_count == 0 {
739                return Err(Error::msg(format!(
740                    "Failed to get paper citations: {}",
741                    query_params.paper_id
742                )));
743            }
744            match client.get(url.clone()).send().await {
745                Ok(response) => {
746                    let body = response.text().await?;
747                    match serde_json::from_str::<ResponsePapers>(&body) {
748                        Ok(response) => {
749                            return Ok(response);
750                        }
751                        Err(e) => {
752                            max_retry_count -= 1;
753                            self.sleep(
754                                wait_time,
755                                format!("Error: {} Body: {}", &e.to_string(), &body).as_str(),
756                            );
757                            continue;
758                        }
759                    }
760                }
761                Err(e) => {
762                    max_retry_count -= 1;
763                    self.sleep(wait_time, &e.to_string());
764                    continue;
765                }
766            }
767        }
768    }
769
770    pub async fn query_paper_references(
771        &mut self,
772        query_params: QueryParams,
773        max_retry_count: u64,
774        wait_time: u64,
775    ) -> Result<ResponsePapers> {
776        let mut query_params = query_params.clone();
777        let mut max_retry_count = max_retry_count.clone();
778
779        let mut fields = query_params.fields.clone().unwrap_or_default();
780        if !fields.contains(&PaperField::PaperId) {
781            fields.push(PaperField::PaperId);
782            query_params.fields = Some(fields);
783        }
784
785        let mut headers = header::HeaderMap::new();
786        headers.insert("Content-Type", "application/json".parse().unwrap());
787        headers.insert("user-agent", "ss-tools/0.1".parse().unwrap());
788        if !self.api_key.is_empty() {
789            headers.insert("x-api-key", self.api_key.parse().unwrap());
790        }
791        let client = request::Client::builder()
792            .default_headers(headers)
793            .build()
794            .unwrap();
795
796        let url = self.get_url(Endpoint::GetReferencesOfAPaper, &mut query_params);
797        loop {
798            if max_retry_count == 0 {
799                return Err(Error::msg(format!(
800                    "Failed to get paper references: {}",
801                    query_params.paper_id
802                )));
803            }
804
805            match client.get(url.clone()).send().await {
806                Ok(response) => {
807                    let body = response.text().await?;
808                    match serde_json::from_str::<ResponsePapers>(&body) {
809                        Ok(response) => {
810                            return Ok(response);
811                        }
812                        Err(e) => {
813                            max_retry_count -= 1;
814                            self.sleep(
815                                wait_time,
816                                format!("Error: {} Body: {}", &e.to_string(), &body).as_str(),
817                            );
818                            continue;
819                        }
820                    }
821                }
822                Err(e) => {
823                    max_retry_count -= 1;
824                    self.sleep(wait_time, &e.to_string());
825                    continue;
826                }
827            }
828        }
829    }
830
831    /// # Description
832    /// Get details about an author by their Semantic Scholar author ID.
833    /// Available fields for `author_fields: Vec<AuthorField>`, see: [`AuthorField`].
834    /// See for more details: [Details about an author](https://api.semanticscholar.org/api-docs/#tag/Author-Data/operation/get_graph_get_author)
835    ///
836    /// # Example
837    ///
838    /// ```rust
839    /// # use anyhow::Result;
840    /// # use ss_tools::{SemanticScholar, QueryParams};
841    /// # use ss_tools::structs::AuthorField;
842    /// # #[tokio::main]
843    /// # async fn main() -> Result<()> {
844    /// let mut ss = SemanticScholar::new();
845    /// let mut query_params = QueryParams::default();
846    /// query_params.paper_id("1741101");  // author_id is passed via paper_id field
847    /// query_params.author_fields(vec![
848    ///     AuthorField::Name,
849    ///     AuthorField::PaperCount,
850    ///     AuthorField::CitationCount,
851    ///     AuthorField::HIndex,
852    /// ]);
853    /// let author = ss.query_author_details(query_params, 5, 10).await.unwrap();
854    /// assert!(author.name.is_some());
855    /// # Ok(())
856    /// # }
857    /// ```
858    pub async fn query_author_details(
859        &mut self,
860        query_params: QueryParams,
861        max_retry_count: u64,
862        wait_time: u64,
863    ) -> Result<Author> {
864        let mut query_params = query_params.clone();
865        let mut max_retry_count = max_retry_count.clone();
866
867        let mut headers = header::HeaderMap::new();
868        headers.insert("Content-Type", "application/json".parse().unwrap());
869        headers.insert("user-agent", "ss-tools/0.1".parse().unwrap());
870        if !self.api_key.is_empty() {
871            headers.insert("x-api-key", self.api_key.parse().unwrap());
872        }
873        let client = request::Client::builder()
874            .default_headers(headers)
875            .build()
876            .unwrap();
877
878        let url = self.get_url(Endpoint::GetAuthorDetails, &mut query_params);
879        loop {
880            if max_retry_count == 0 {
881                return Err(Error::msg(format!(
882                    "Failed to get author details: {}",
883                    query_params.paper_id
884                )));
885            }
886            let body = client.get(url.clone()).send().await?.text().await?;
887            match serde_json::from_str::<Author>(&body) {
888                Ok(response) => {
889                    return Ok(response);
890                }
891                Err(e) => {
892                    max_retry_count -= 1;
893                    self.sleep(
894                        wait_time,
895                        format!("Error: {} Body: {}", &e.to_string(), &body).as_str(),
896                    );
897                    continue;
898                }
899            }
900        }
901    }
902
903    /// # Description
904    /// Search for authors by name.
905    /// Available fields for `author_fields: Vec<AuthorField>`, see: [`AuthorField`].
906    /// See for more details: [Search for authors by name](https://api.semanticscholar.org/api-docs/#tag/Author-Data/operation/get_graph_get_author_search)
907    ///
908    /// # Example
909    ///
910    /// ```rust
911    /// # use anyhow::Result;
912    /// # use ss_tools::{SemanticScholar, QueryParams};
913    /// # use ss_tools::structs::AuthorField;
914    /// # #[tokio::main]
915    /// # async fn main() -> Result<()> {
916    /// let mut ss = SemanticScholar::new();
917    /// let mut query_params = QueryParams::default();
918    /// query_params.query_text("Yoshua Bengio");
919    /// query_params.author_fields(vec![
920    ///     AuthorField::Name,
921    ///     AuthorField::PaperCount,
922    ///     AuthorField::CitationCount,
923    /// ]);
924    /// let response = ss.search_authors(query_params, 5, 10).await.unwrap();
925    /// assert!(!response.data.is_empty());
926    /// # Ok(())
927    /// # }
928    /// ```
929    pub async fn search_authors(
930        &mut self,
931        query_params: QueryParams,
932        max_retry_count: u64,
933        wait_time: u64,
934    ) -> Result<AuthorSearchResponse> {
935        let mut query_params = query_params.clone();
936        let mut max_retry_count = max_retry_count.clone();
937
938        let mut headers = header::HeaderMap::new();
939        headers.insert("Content-Type", "application/json".parse().unwrap());
940        headers.insert("user-agent", "ss-tools/0.1".parse().unwrap());
941        if !self.api_key.is_empty() {
942            headers.insert("x-api-key", self.api_key.parse().unwrap());
943        }
944        let client = request::Client::builder()
945            .default_headers(headers)
946            .build()
947            .unwrap();
948
949        let url = self.get_url(Endpoint::SearchAuthors, &mut query_params);
950        loop {
951            if max_retry_count == 0 {
952                return Err(Error::msg("Failed to search authors"));
953            }
954            match client.get(url.clone()).send().await {
955                Ok(response) => {
956                    let body = response.text().await?;
957                    match serde_json::from_str::<AuthorSearchResponse>(&body) {
958                        Ok(response) => {
959                            return Ok(response);
960                        }
961                        Err(e) => {
962                            max_retry_count -= 1;
963                            self.sleep(
964                                wait_time,
965                                format!("Error: {} Body: {}", &e.to_string(), &body).as_str(),
966                            );
967                            continue;
968                        }
969                    }
970                }
971                Err(e) => {
972                    max_retry_count -= 1;
973                    self.sleep(wait_time, &e.to_string());
974                    continue;
975                }
976            }
977        }
978    }
979
980    /// # Description
981    /// Get papers by an author.
982    /// Available fields for `fields: Vec<PaperField>`, see: [`PaperField`].
983    /// See for more details: [Details about an author's papers](https://api.semanticscholar.org/api-docs/#tag/Author-Data/operation/get_graph_get_author_papers)
984    ///
985    /// # Example
986    ///
987    /// ```rust
988    /// # use anyhow::Result;
989    /// # use ss_tools::{SemanticScholar, QueryParams};
990    /// # use ss_tools::structs::PaperField;
991    /// # #[tokio::main]
992    /// # async fn main() -> Result<()> {
993    /// let mut ss = SemanticScholar::new();
994    /// let mut query_params = QueryParams::default();
995    /// query_params.paper_id("1741101");  // author_id is passed via paper_id field
996    /// query_params.fields(vec![
997    ///     PaperField::Title,
998    ///     PaperField::Year,
999    ///     PaperField::CitationCount,
1000    /// ]);
1001    /// query_params.limit(10);
1002    /// let response = ss.query_author_papers(query_params, 5, 10).await.unwrap();
1003    /// assert!(!response.data.is_empty());
1004    /// # Ok(())
1005    /// # }
1006    /// ```
1007    pub async fn query_author_papers(
1008        &mut self,
1009        query_params: QueryParams,
1010        max_retry_count: u64,
1011        wait_time: u64,
1012    ) -> Result<AuthorPapersResponse> {
1013        let mut query_params = query_params.clone();
1014        let mut max_retry_count = max_retry_count.clone();
1015
1016        let mut fields = query_params.fields.clone().unwrap_or_default();
1017        if !fields.contains(&PaperField::PaperId) {
1018            fields.push(PaperField::PaperId);
1019            query_params.fields = Some(fields);
1020        }
1021
1022        let mut headers = header::HeaderMap::new();
1023        headers.insert("Content-Type", "application/json".parse().unwrap());
1024        headers.insert("user-agent", "ss-tools/0.1".parse().unwrap());
1025        if !self.api_key.is_empty() {
1026            headers.insert("x-api-key", self.api_key.parse().unwrap());
1027        }
1028        let client = request::Client::builder()
1029            .default_headers(headers)
1030            .build()
1031            .unwrap();
1032
1033        let url = self.get_url(Endpoint::GetAuthorPapers, &mut query_params);
1034        loop {
1035            if max_retry_count == 0 {
1036                return Err(Error::msg(format!(
1037                    "Failed to get author papers: {}",
1038                    query_params.paper_id
1039                )));
1040            }
1041            match client.get(url.clone()).send().await {
1042                Ok(response) => {
1043                    let body = response.text().await?;
1044                    match serde_json::from_str::<AuthorPapersResponse>(&body) {
1045                        Ok(response) => {
1046                            return Ok(response);
1047                        }
1048                        Err(e) => {
1049                            max_retry_count -= 1;
1050                            self.sleep(
1051                                wait_time,
1052                                format!("Error: {} Body: {}", &e.to_string(), &body).as_str(),
1053                            );
1054                            continue;
1055                        }
1056                    }
1057                }
1058                Err(e) => {
1059                    max_retry_count -= 1;
1060                    self.sleep(wait_time, &e.to_string());
1061                    continue;
1062                }
1063            }
1064        }
1065    }
1066
1067    /// # Description
1068    /// Get authors of a paper.
1069    /// Available fields for `author_fields: Vec<AuthorField>`, see: [`AuthorField`].
1070    /// See for more details: [Details about a paper's authors](https://api.semanticscholar.org/api-docs/#tag/Paper-Data/operation/get_graph_get_paper_authors)
1071    ///
1072    /// # Example
1073    ///
1074    /// ```rust
1075    /// # use anyhow::Result;
1076    /// # use ss_tools::{SemanticScholar, QueryParams};
1077    /// # use ss_tools::structs::AuthorField;
1078    /// # #[tokio::main]
1079    /// # async fn main() -> Result<()> {
1080    /// let mut ss = SemanticScholar::new();
1081    /// let mut query_params = QueryParams::default();
1082    /// query_params.paper_id("204e3073870fae3d05bcbc2f6a8e263d9b72e776");
1083    /// query_params.author_fields(vec![
1084    ///     AuthorField::Name,
1085    ///     AuthorField::PaperCount,
1086    ///     AuthorField::CitationCount,
1087    /// ]);
1088    /// let response = ss.query_paper_authors(query_params, 5, 10).await.unwrap();
1089    /// assert!(!response.data.is_empty());
1090    /// # Ok(())
1091    /// # }
1092    /// ```
1093    pub async fn query_paper_authors(
1094        &mut self,
1095        query_params: QueryParams,
1096        max_retry_count: u64,
1097        wait_time: u64,
1098    ) -> Result<PaperAuthorsResponse> {
1099        let mut query_params = query_params.clone();
1100        let mut max_retry_count = max_retry_count.clone();
1101
1102        let mut headers = header::HeaderMap::new();
1103        headers.insert("Content-Type", "application/json".parse().unwrap());
1104        headers.insert("user-agent", "ss-tools/0.1".parse().unwrap());
1105        if !self.api_key.is_empty() {
1106            headers.insert("x-api-key", self.api_key.parse().unwrap());
1107        }
1108        let client = request::Client::builder()
1109            .default_headers(headers)
1110            .build()
1111            .unwrap();
1112
1113        let url = self.get_url(Endpoint::GetPaperAuthors, &mut query_params);
1114        loop {
1115            if max_retry_count == 0 {
1116                return Err(Error::msg(format!(
1117                    "Failed to get paper authors: {}",
1118                    query_params.paper_id
1119                )));
1120            }
1121            match client.get(url.clone()).send().await {
1122                Ok(response) => {
1123                    let body = response.text().await?;
1124                    match serde_json::from_str::<PaperAuthorsResponse>(&body) {
1125                        Ok(response) => {
1126                            return Ok(response);
1127                        }
1128                        Err(e) => {
1129                            max_retry_count -= 1;
1130                            self.sleep(
1131                                wait_time,
1132                                format!("Error: {} Body: {}", &e.to_string(), &body).as_str(),
1133                            );
1134                            continue;
1135                        }
1136                    }
1137                }
1138                Err(e) => {
1139                    max_retry_count -= 1;
1140                    self.sleep(wait_time, &e.to_string());
1141                    continue;
1142                }
1143            }
1144        }
1145    }
1146}