research_master/sources/
mod.rs1#[cfg(feature = "source-acm")]
59mod acm;
60#[cfg(feature = "source-arxiv")]
61mod arxiv;
62#[cfg(feature = "source-base")]
63mod base;
64#[cfg(feature = "source-biorxiv")]
65mod biorxiv;
66#[cfg(feature = "source-connected_papers")]
67mod connected_papers;
68#[cfg(feature = "source-core-repo")]
69mod core;
70#[cfg(feature = "source-crossref")]
71mod crossref;
72#[cfg(feature = "source-dblp")]
73mod dblp;
74#[cfg(feature = "source-dimensions")]
75mod dimensions;
76#[cfg(feature = "source-doaj")]
77mod doaj;
78#[cfg(feature = "source-europe_pmc")]
79mod europe_pmc;
80#[cfg(feature = "source-google_scholar")]
81mod google_scholar;
82#[cfg(feature = "source-hal")]
83mod hal;
84#[cfg(feature = "source-iacr")]
85mod iacr;
86#[cfg(feature = "source-ieee_xplore")]
87mod ieee_xplore;
88#[cfg(feature = "source-jstor")]
89mod jstor;
90#[cfg(feature = "source-mdpi")]
91mod mdpi;
92#[cfg(feature = "source-openalex")]
93mod openalex;
94#[cfg(feature = "source-osf")]
95mod osf;
96#[cfg(feature = "source-pmc")]
97mod pmc;
98#[cfg(feature = "source-pubmed")]
99mod pubmed;
100mod registry;
101#[cfg(feature = "source-scispace")]
102mod scispace;
103#[cfg(feature = "source-semantic")]
104mod semantic;
105#[cfg(feature = "source-springer")]
106mod springer;
107#[cfg(feature = "source-ssrn")]
108mod ssrn;
109#[cfg(feature = "source-unpaywall")]
110mod unpaywall;
111#[cfg(feature = "source-worldwidescience")]
112mod worldwidescience;
113#[cfg(feature = "source-zenodo")]
114mod zenodo;
115
116pub mod mock;
117
118pub use mock::MockSource;
119
120pub use registry::{SourceCapabilities, SourceRegistry};
121
122use crate::models::{
123 CitationRequest, DownloadRequest, DownloadResult, Paper, ReadRequest, ReadResult, SearchQuery,
124 SearchResponse,
125};
126use async_trait::async_trait;
127
128#[async_trait]
139pub trait Source: Send + Sync + std::fmt::Debug {
140 fn id(&self) -> &str;
142
143 fn name(&self) -> &str;
145
146 fn capabilities(&self) -> SourceCapabilities {
148 SourceCapabilities::SEARCH
149 }
150
151 fn supports_search(&self) -> bool {
153 self.capabilities().contains(SourceCapabilities::SEARCH)
154 }
155
156 fn supports_download(&self) -> bool {
158 self.capabilities().contains(SourceCapabilities::DOWNLOAD)
159 }
160
161 fn supports_read(&self) -> bool {
163 self.capabilities().contains(SourceCapabilities::READ)
164 }
165
166 fn supports_citations(&self) -> bool {
168 self.capabilities().contains(SourceCapabilities::CITATIONS)
169 }
170
171 fn supports_doi_lookup(&self) -> bool {
173 self.capabilities().contains(SourceCapabilities::DOI_LOOKUP)
174 }
175
176 fn supports_author_search(&self) -> bool {
178 self.capabilities()
179 .contains(SourceCapabilities::AUTHOR_SEARCH)
180 }
181
182 async fn search(&self, _query: &SearchQuery) -> Result<SearchResponse, SourceError> {
186 Err(SourceError::NotImplemented)
187 }
188
189 async fn search_by_author(
191 &self,
192 _author: &str,
193 _max_results: usize,
194 _year: Option<&str>,
195 ) -> Result<SearchResponse, SourceError> {
196 Err(SourceError::NotImplemented)
197 }
198
199 async fn download(&self, _request: &DownloadRequest) -> Result<DownloadResult, SourceError> {
203 Err(SourceError::NotImplemented)
204 }
205
206 async fn read(&self, _request: &ReadRequest) -> Result<ReadResult, SourceError> {
210 Err(SourceError::NotImplemented)
211 }
212
213 async fn get_citations(
217 &self,
218 _request: &CitationRequest,
219 ) -> Result<SearchResponse, SourceError> {
220 Err(SourceError::NotImplemented)
221 }
222
223 async fn get_references(
225 &self,
226 _request: &CitationRequest,
227 ) -> Result<SearchResponse, SourceError> {
228 Err(SourceError::NotImplemented)
229 }
230
231 async fn get_related(&self, _request: &CitationRequest) -> Result<SearchResponse, SourceError> {
233 Err(SourceError::NotImplemented)
234 }
235
236 async fn get_by_doi(&self, _doi: &str) -> Result<Paper, SourceError> {
240 Err(SourceError::NotImplemented)
241 }
242
243 async fn get_by_id(&self, _id: &str) -> Result<Paper, SourceError> {
245 Err(SourceError::NotImplemented)
246 }
247
248 fn validate_id(&self, _id: &str) -> Result<(), SourceError> {
250 Ok(())
251 }
252}
253
254#[derive(Debug, thiserror::Error)]
256pub enum SourceError {
257 #[error("Operation not implemented for this source")]
259 NotImplemented,
260
261 #[error("Network error: {0}")]
263 Network(String),
264
265 #[error("Parse error: {0}")]
267 Parse(String),
268
269 #[error("Invalid request: {0}")]
271 InvalidRequest(String),
272
273 #[error("Rate limit exceeded")]
275 RateLimit,
276
277 #[error("Paper not found: {0}")]
279 NotFound(String),
280
281 #[error("API error: {0}")]
283 Api(String),
284
285 #[error("IO error: {0}")]
287 Io(#[from] std::io::Error),
288
289 #[error("Error: {0}")]
291 Other(String),
292}
293
294impl From<reqwest::Error> for SourceError {
295 fn from(err: reqwest::Error) -> Self {
296 SourceError::Network(err.to_string())
297 }
298}
299
300impl From<serde_json::Error> for SourceError {
301 fn from(err: serde_json::Error) -> Self {
302 SourceError::Parse(format!("JSON: {}", err))
303 }
304}
305
306impl From<quick_xml::DeError> for SourceError {
307 fn from(err: quick_xml::DeError) -> Self {
308 SourceError::Parse(format!("XML: {}", err))
309 }
310}
311
312#[cfg(test)]
313mod tests {
314 use super::*;
315
316 #[test]
317 fn test_source_capabilities() {
318 let caps = SourceCapabilities::SEARCH | SourceCapabilities::DOWNLOAD;
319
320 assert!(caps.contains(SourceCapabilities::SEARCH));
321 assert!(caps.contains(SourceCapabilities::DOWNLOAD));
322 assert!(!caps.contains(SourceCapabilities::CITATIONS));
323 }
324}