1mod client;
2pub mod error;
3pub mod models;
4
5pub use error::{Error, Result};
6pub use models::*;
7
8use client::HttpClient;
9
10pub mod fields {
12 pub const PAPER_SEARCH: &str = "paperId,title,year,citationCount,authors";
14
15 pub const PAPER_DETAIL: &str = "paperId,title,abstract,year,citationCount,\
17 referenceCount,influentialCitationCount,isOpenAccess,openAccessPdf,\
18 fieldsOfStudy,s2FieldsOfStudy,publicationTypes,publicationDate,\
19 journal,authors,tldr,venue";
20
21 pub const CITATION: &str = "paperId,title,year,citationCount,authors";
23
24 pub const AUTHOR_SEARCH: &str = "authorId,name,paperCount,citationCount,hIndex";
26
27 pub const AUTHOR_DETAIL: &str =
29 "authorId,name,affiliations,homepage,paperCount,citationCount,hIndex";
30}
31
32#[derive(Clone, Debug)]
54pub struct SemanticScholar {
55 client: HttpClient,
56}
57
58impl SemanticScholar {
59 pub fn new() -> Result<Self> {
64 Self::build(None)
65 }
66
67 pub fn with_api_key(api_key: &str) -> Result<Self> {
69 Self::build(Some(api_key))
70 }
71
72 pub fn set_base_url(&mut self, url: impl Into<String>) {
74 self.client.set_base_url(url);
75 }
76
77 fn build(api_key: Option<&str>) -> Result<Self> {
78 Ok(Self {
79 client: HttpClient::new(api_key)?,
80 })
81 }
82
83 pub fn search_papers(&self, query: &str) -> SearchPapersRequest<'_> {
87 SearchPapersRequest::new(&self.client, query)
88 }
89
90 pub fn get_paper(&self, paper_id: &str) -> GetPaperRequest<'_> {
94 GetPaperRequest::new(&self.client, paper_id)
95 }
96
97 pub fn get_paper_by_doi(&self, doi: &str) -> GetPaperRequest<'_> {
99 self.get_paper(&format!("DOI:{doi}"))
100 }
101
102 pub fn get_paper_by_arxiv(&self, arxiv_id: &str) -> GetPaperRequest<'_> {
104 self.get_paper(&format!("ARXIV:{arxiv_id}"))
105 }
106
107 pub fn get_citations(&self, paper_id: &str) -> GetCitationsRequest<'_> {
109 GetCitationsRequest::new(&self.client, paper_id)
110 }
111
112 pub fn get_references(&self, paper_id: &str) -> GetReferencesRequest<'_> {
114 GetReferencesRequest::new(&self.client, paper_id)
115 }
116
117 pub fn search_authors(&self, query: &str) -> SearchAuthorsRequest<'_> {
121 SearchAuthorsRequest::new(&self.client, query)
122 }
123
124 pub fn get_author(&self, author_id: &str) -> GetAuthorRequest<'_> {
126 GetAuthorRequest::new(&self.client, author_id)
127 }
128
129 pub fn get_recommendations(&self, paper_id: &str) -> GetRecommendationsRequest<'_> {
133 GetRecommendationsRequest::new(&self.client, paper_id)
134 }
135}
136
137pub struct SearchPapersRequest<'a> {
143 client: &'a HttpClient,
144 query: String,
145 limit: Option<u32>,
146 offset: Option<u32>,
147 year: Option<String>,
148 fields_of_study: Option<String>,
149 publication_types: Option<String>,
150 open_access_pdf: Option<bool>,
151 min_citation_count: Option<u32>,
152 fields: String,
153}
154
155impl<'a> SearchPapersRequest<'a> {
156 fn new(client: &'a HttpClient, query: &str) -> Self {
157 Self {
158 client,
159 query: query.to_string(),
160 limit: None,
161 offset: None,
162 year: None,
163 fields_of_study: None,
164 publication_types: None,
165 open_access_pdf: None,
166 min_citation_count: None,
167 fields: fields::PAPER_SEARCH.to_string(),
168 }
169 }
170
171 pub fn limit(mut self, limit: u32) -> Self {
172 self.limit = Some(limit);
173 self
174 }
175 pub fn offset(mut self, offset: u32) -> Self {
176 self.offset = Some(offset);
177 self
178 }
179 pub fn year(mut self, year: &str) -> Self {
181 self.year = Some(year.to_string());
182 self
183 }
184 pub fn fields_of_study(mut self, fos: &str) -> Self {
186 self.fields_of_study = Some(fos.to_string());
187 self
188 }
189 pub fn publication_types(mut self, types: &str) -> Self {
190 self.publication_types = Some(types.to_string());
191 self
192 }
193 pub fn open_access_pdf(mut self) -> Self {
194 self.open_access_pdf = Some(true);
195 self
196 }
197 pub fn min_citation_count(mut self, count: u32) -> Self {
198 self.min_citation_count = Some(count);
199 self
200 }
201 pub fn fields(mut self, fields: &str) -> Self {
203 self.fields = fields.to_string();
204 self
205 }
206
207 pub async fn send(self) -> Result<PaperSearchResponse> {
208 let limit_str = self.limit.map(|l| l.to_string());
209 let offset_str = self.offset.map(|o| o.to_string());
210 let min_cc_str = self.min_citation_count.map(|c| c.to_string());
211
212 let mut params: Vec<(&str, &str)> = vec![("query", &self.query), ("fields", &self.fields)];
213 if let Some(ref l) = limit_str {
214 params.push(("limit", l));
215 }
216 if let Some(ref o) = offset_str {
217 params.push(("offset", o));
218 }
219 if let Some(ref y) = self.year {
220 params.push(("year", y));
221 }
222 if let Some(ref fos) = self.fields_of_study {
223 params.push(("fieldsOfStudy", fos));
224 }
225 if let Some(ref pt) = self.publication_types {
226 params.push(("publicationTypes", pt));
227 }
228 if self.open_access_pdf == Some(true) {
229 params.push(("openAccessPdf", ""));
230 }
231 if let Some(ref cc) = min_cc_str {
232 params.push(("minCitationCount", cc));
233 }
234
235 self.client.get("/graph/v1/paper/search", ¶ms).await
236 }
237}
238
239pub struct GetPaperRequest<'a> {
241 client: &'a HttpClient,
242 paper_id: String,
243 fields: String,
244}
245
246impl<'a> GetPaperRequest<'a> {
247 fn new(client: &'a HttpClient, paper_id: &str) -> Self {
248 Self {
249 client,
250 paper_id: paper_id.to_string(),
251 fields: fields::PAPER_DETAIL.to_string(),
252 }
253 }
254
255 pub fn fields(mut self, fields: &str) -> Self {
256 self.fields = fields.to_string();
257 self
258 }
259
260 pub async fn send(self) -> Result<Paper> {
261 let path = format!("/graph/v1/paper/{}", self.paper_id);
262 let params = [("fields", self.fields.as_str())];
263 self.client.get(&path, ¶ms).await
264 }
265}
266
267pub struct GetCitationsRequest<'a> {
269 client: &'a HttpClient,
270 paper_id: String,
271 limit: Option<u32>,
272 offset: Option<u32>,
273 fields: String,
274}
275
276impl<'a> GetCitationsRequest<'a> {
277 fn new(client: &'a HttpClient, paper_id: &str) -> Self {
278 Self {
279 client,
280 paper_id: paper_id.to_string(),
281 limit: None,
282 offset: None,
283 fields: fields::CITATION.to_string(),
284 }
285 }
286
287 pub fn limit(mut self, limit: u32) -> Self {
288 self.limit = Some(limit);
289 self
290 }
291 pub fn offset(mut self, offset: u32) -> Self {
292 self.offset = Some(offset);
293 self
294 }
295 pub fn fields(mut self, fields: &str) -> Self {
296 self.fields = fields.to_string();
297 self
298 }
299
300 pub async fn send(self) -> Result<CitationResponse> {
301 let path = format!("/graph/v1/paper/{}/citations", self.paper_id);
302 let limit_str = self.limit.map(|l| l.to_string());
303 let offset_str = self.offset.map(|o| o.to_string());
304
305 let mut params: Vec<(&str, &str)> = vec![("fields", &self.fields)];
306 if let Some(ref l) = limit_str {
307 params.push(("limit", l));
308 }
309 if let Some(ref o) = offset_str {
310 params.push(("offset", o));
311 }
312
313 self.client.get(&path, ¶ms).await
314 }
315}
316
317pub struct GetReferencesRequest<'a> {
319 client: &'a HttpClient,
320 paper_id: String,
321 limit: Option<u32>,
322 offset: Option<u32>,
323 fields: String,
324}
325
326impl<'a> GetReferencesRequest<'a> {
327 fn new(client: &'a HttpClient, paper_id: &str) -> Self {
328 Self {
329 client,
330 paper_id: paper_id.to_string(),
331 limit: None,
332 offset: None,
333 fields: fields::CITATION.to_string(),
334 }
335 }
336
337 pub fn limit(mut self, limit: u32) -> Self {
338 self.limit = Some(limit);
339 self
340 }
341 pub fn offset(mut self, offset: u32) -> Self {
342 self.offset = Some(offset);
343 self
344 }
345 pub fn fields(mut self, fields: &str) -> Self {
346 self.fields = fields.to_string();
347 self
348 }
349
350 pub async fn send(self) -> Result<ReferenceResponse> {
351 let path = format!("/graph/v1/paper/{}/references", self.paper_id);
352 let limit_str = self.limit.map(|l| l.to_string());
353 let offset_str = self.offset.map(|o| o.to_string());
354
355 let mut params: Vec<(&str, &str)> = vec![("fields", &self.fields)];
356 if let Some(ref l) = limit_str {
357 params.push(("limit", l));
358 }
359 if let Some(ref o) = offset_str {
360 params.push(("offset", o));
361 }
362
363 self.client.get(&path, ¶ms).await
364 }
365}
366
367pub struct SearchAuthorsRequest<'a> {
369 client: &'a HttpClient,
370 query: String,
371 limit: Option<u32>,
372 offset: Option<u32>,
373 fields: String,
374}
375
376impl<'a> SearchAuthorsRequest<'a> {
377 fn new(client: &'a HttpClient, query: &str) -> Self {
378 Self {
379 client,
380 query: query.to_string(),
381 limit: None,
382 offset: None,
383 fields: fields::AUTHOR_SEARCH.to_string(),
384 }
385 }
386
387 pub fn limit(mut self, limit: u32) -> Self {
388 self.limit = Some(limit);
389 self
390 }
391 pub fn offset(mut self, offset: u32) -> Self {
392 self.offset = Some(offset);
393 self
394 }
395 pub fn fields(mut self, fields: &str) -> Self {
396 self.fields = fields.to_string();
397 self
398 }
399
400 pub async fn send(self) -> Result<AuthorSearchResponse> {
401 let limit_str = self.limit.map(|l| l.to_string());
402 let offset_str = self.offset.map(|o| o.to_string());
403
404 let mut params: Vec<(&str, &str)> = vec![("query", &self.query), ("fields", &self.fields)];
405 if let Some(ref l) = limit_str {
406 params.push(("limit", l));
407 }
408 if let Some(ref o) = offset_str {
409 params.push(("offset", o));
410 }
411
412 self.client.get("/graph/v1/author/search", ¶ms).await
413 }
414}
415
416pub struct GetAuthorRequest<'a> {
418 client: &'a HttpClient,
419 author_id: String,
420 fields: String,
421}
422
423impl<'a> GetAuthorRequest<'a> {
424 fn new(client: &'a HttpClient, author_id: &str) -> Self {
425 Self {
426 client,
427 author_id: author_id.to_string(),
428 fields: fields::AUTHOR_DETAIL.to_string(),
429 }
430 }
431
432 pub fn fields(mut self, fields: &str) -> Self {
433 self.fields = fields.to_string();
434 self
435 }
436
437 pub async fn send(self) -> Result<Author> {
438 let path = format!("/graph/v1/author/{}", self.author_id);
439 let params = [("fields", self.fields.as_str())];
440 self.client.get(&path, ¶ms).await
441 }
442}
443
444pub struct GetRecommendationsRequest<'a> {
446 client: &'a HttpClient,
447 paper_id: String,
448 limit: Option<u32>,
449 fields: String,
450}
451
452impl<'a> GetRecommendationsRequest<'a> {
453 fn new(client: &'a HttpClient, paper_id: &str) -> Self {
454 Self {
455 client,
456 paper_id: paper_id.to_string(),
457 limit: None,
458 fields: fields::PAPER_SEARCH.to_string(),
459 }
460 }
461
462 pub fn limit(mut self, limit: u32) -> Self {
463 self.limit = Some(limit);
464 self
465 }
466 pub fn fields(mut self, fields: &str) -> Self {
467 self.fields = fields.to_string();
468 self
469 }
470
471 pub async fn send(self) -> Result<RecommendationResponse> {
472 let path = format!("/recommendations/v1/papers/forpaper/{}", self.paper_id);
473 let limit_str = self.limit.map(|l| l.to_string());
474
475 let mut params: Vec<(&str, &str)> = vec![("fields", &self.fields)];
476 if let Some(ref l) = limit_str {
477 params.push(("limit", l));
478 }
479
480 self.client.get(&path, ¶ms).await
481 }
482}