acorn_lib/util/citeas.rs
1//! Module for communicating with CiteAs API
2//! > CiteAs is a way to get the correct citation for diverse research products including, software, datasets, preprints, and traditional articles. By making it easier to cite software and other "alternative" scholarly products, we aim to help the creators of such products get full credit for their work.
3//!
4//! See <https://citeas.org/api> for more information
5use crate::util::{reqwest_request, Label};
6use reqwest::blocking::Client;
7use serde::{Deserialize, Serialize};
8use serde_with::skip_serializing_none;
9use tracing::{debug, error};
10
11/// Author object
12#[derive(Clone, Debug, Deserialize, Serialize)]
13pub struct Author {
14    /// First name
15    pub given: String,
16    /// Last name
17    pub family: String,
18}
19/// Describes status of the CiteAs API
20#[derive(Clone, Debug, Deserialize, Serialize)]
21pub struct Status {
22    /// Where you can find documentation for this version
23    /// ### Example
24    /// > "<https://citeas.org/api>"
25    pub documentation_url: String,
26    /// Relevant messages
27    /// ### Example
28    /// > "Don't panic"
29    pub msg: String,
30    /// API version
31    /// ### Example
32    /// > "0.1"
33    pub version: String,
34}
35/// Main response object for the CiteAs API, returning citations for a given input
36#[derive(Clone, Debug, Deserialize, Serialize, Default)]
37pub struct Citations {
38    /// List of citation objects
39    pub citations: Vec<Citation>,
40    /// List of export objects
41    pub exports: Vec<Export>,
42    /// Metadata for listing all metadata found for a given resource.
43    /// <div class="warning">Varies by source</div>
44    pub metadata: Metadata,
45    /// Name of referenced resource
46    pub name: String,
47    /// List of provenance objects describing sources utilized to find and build citation data
48    pub provenance: Vec<Provenance>,
49    /// URL for the given resource
50    /// <div class="warning">If input is a keyword, the URL is the first Google search result for the given keyword</div>
51    pub url: String,
52}
53/// Citation API response object
54#[derive(Clone, Debug, Deserialize, Serialize)]
55pub struct Citation {
56    /// Citation entry
57    #[serde(alias = "citation")]
58    pub text: String,
59    /// Full name of the citation style
60    /// ### Example
61    /// > "American Psychological Association 6th edition"
62    pub style_fullname: String,
63    /// Short name of the citation style
64    /// ### Example
65    /// > "APA"
66    pub style_shortname: String,
67}
68/// Exported citation data
69#[derive(Clone, Debug, Deserialize, Serialize)]
70pub struct Export {
71    /// Citation export
72    pub export: String,
73    /// Export format
74    /// ### Note
75    /// > May include CSV, enw, [RIS], and [BibTeX].
76    ///
77    /// [RIS]: https://en.wikipedia.org/wiki/RIS_(file_format)
78    /// [BibTeX]: https://www.bibtex.org/
79    pub export_name: String,
80}
81/// Metadata for source
82/// <div class="warning">Varies by source</div>
83#[derive(Clone, Debug, Deserialize, Serialize, Default)]
84pub struct Metadata {
85    /// List of authors
86    pub author: Vec<Author>,
87    /// List of categories that resource applies to
88    pub categories: Vec<String>,
89    /// List of contributors
90    pub contributor: Vec<Author>,
91    /// Valid DOI
92    #[serde(alias = "DOI")]
93    pub doi: String,
94    /// ID for the resource
95    /// <div class="warning">Always "ITEM-1"</div>
96    pub id: String,
97    /// Publisher of resource
98    pub publisher: String,
99    /// Type of resource
100    #[serde(rename = "type")]
101    pub resource_type: String,
102    /// Title of the resource
103    /// ### Example
104    /// > "Oak Ridge National Laboratory (ORNL), Oak Ridge, TN (United States)"
105    pub title: String,
106    /// Resource URL
107    #[serde(alias = "URL")]
108    pub url: String,
109    /// Year of publication
110    pub year: u16,
111}
112/// Citation provenance object
113///
114/// Describes steps taken to try and find citation data, and whether citation data was found
115#[skip_serializing_none]
116#[derive(Clone, Debug, Deserialize, Serialize)]
117pub struct Provenance {
118    /// Additional URL utilized to discover citation data
119    pub additional_content_url: Option<String>,
120    /// URL utilized to discover citation data
121    pub content_url: Option<String>,
122    /// Original URL of the resource
123    pub original_url: Option<String>,
124    /// Returns "doi" or "arXiv ID" if found via DOI or arXiv, else "null"
125    pub found_via_proxy_type: Option<String>,
126    /// Returns true if content was found at the URL
127    pub has_content: bool,
128    /// Host of the resource, such as crossref, github or pypi
129    pub host: Option<String>,
130    /// Name of the step taken to find citation data
131    pub name: String,
132    /// Name of the parent step
133    pub parent_step_name: String,
134    /// Name of the parent subject
135    pub parent_subject: Option<String>,
136    /// Subject of the current step
137    /// ### Example
138    /// > "GitHub repository main page"
139    pub subject: String,
140    /// Resource keyword
141    pub key_word: Option<String>,
142}
143/// Get status of CiteAs API
144pub fn status() -> Option<Status> {
145    let client = Client::new();
146    match client.get("https://api.citeas.org?email=research@ornl.gov").send() {
147        | Ok(response) => {
148            let content: serde_json::Result<Status> = serde_json::from_str(&response.text().unwrap_or_default());
149            match content {
150                | Ok(status) => {
151                    debug!("=> {} Status", Label::using());
152                    Some(status)
153                }
154                | Err(why) => {
155                    error!("=> {} Parse API status response - {why}", Label::fail());
156                    None
157                }
158            }
159        }
160        | Err(why) => {
161            error!("{} Get API status - {why}", Label::fail());
162            None
163        }
164    }
165}
166impl Citations {
167    /// Use CiteAs API to get citation data from DOI value
168    pub fn from_doi(value: &str) -> Citations {
169        let url = format!("https://api.citeas.org/product/{value}?email=research@ornl.gov");
170        match reqwest_request(url).send() {
171            | Ok(response) => {
172                let content: serde_json::Result<Citations> = serde_json::from_str(&response.text().unwrap());
173                match content {
174                    | Ok(results) => {
175                        debug!("=> {} Response", Label::using());
176                        results
177                    }
178                    | Err(why) => {
179                        error!("=> {} Parse API status response - {why}", Label::fail());
180                        Citations::default()
181                    }
182                }
183            }
184            | Err(why) => {
185                error!("{} Get Citations data - {why}", Label::fail());
186                Citations::default()
187            }
188        }
189    }
190    /// Get citation data with given citation style (ex. "APA")
191    ///
192    /// If citation with desired style is not found, will return first citation
193    pub fn match_style(self, value: &str) -> Option<Citation> {
194        let citations = self.citations;
195        let result = citations
196            .iter()
197            .find(|&citation| citation.style_shortname.to_lowercase() == value.to_lowercase());
198        match result {
199            | Some(citation) => Some(citation.clone()),
200            | None => {
201                if citations.is_empty() {
202                    None
203                } else {
204                    match citations.first() {
205                        | Some(citation) => Some(citation.clone()),
206                        | None => None,
207                    }
208                }
209            }
210        }
211    }
212}