use super::ExperimentError;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Orcid(String);
impl Orcid {
pub fn new(orcid: impl Into<String>) -> Result<Self, ExperimentError> {
let orcid = orcid.into();
if Self::is_valid_orcid(&orcid) {
Ok(Self(orcid))
} else {
Err(ExperimentError::InvalidOrcid(orcid))
}
}
fn is_valid_orcid(orcid: &str) -> bool {
let parts: Vec<&str> = orcid.split('-').collect();
if parts.len() != 4 {
return false;
}
for part in parts.iter().take(3) {
if part.len() != 4 || !part.chars().all(|c| c.is_ascii_digit()) {
return false;
}
}
let last = parts[3];
if last.len() != 4 {
return false;
}
let chars: Vec<char> = last.chars().collect();
chars.iter().take(3).all(|c| c.is_ascii_digit())
&& (chars[3].is_ascii_digit() || chars[3] == 'X')
}
pub fn as_str(&self) -> &str {
&self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum CreditRole {
Conceptualization,
DataCuration,
FormalAnalysis,
FundingAcquisition,
Investigation,
Methodology,
ProjectAdministration,
Resources,
Software,
Supervision,
Validation,
Visualization,
WritingOriginalDraft,
WritingReviewEditing,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResearchContributor {
pub name: String,
pub orcid: Option<Orcid>,
pub affiliation: String,
pub roles: Vec<CreditRole>,
pub email: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResearchArtifact {
pub title: String,
pub abstract_text: String,
pub contributors: Vec<ResearchContributor>,
pub keywords: Vec<String>,
pub doi: Option<String>,
pub arxiv_id: Option<String>,
pub license: String,
pub created_at: String,
pub datasets: Vec<String>,
pub code_repositories: Vec<String>,
pub pre_registration: Option<PreRegistration>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PreRegistration {
pub timestamp: String,
pub signature: String,
pub public_key: String,
pub hypotheses_hash: String,
pub registry: String,
pub registration_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CitationMetadata {
pub citation_type: CitationType,
pub title: String,
pub authors: Vec<String>,
pub year: u16,
pub month: Option<u8>,
pub doi: Option<String>,
pub url: Option<String>,
pub venue: Option<String>,
pub volume: Option<String>,
pub pages: Option<String>,
pub publisher: Option<String>,
pub version: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum CitationType {
Article,
InProceedings,
Book,
Software,
Dataset,
Misc,
}
impl CitationMetadata {
pub fn to_bibtex(&self, key: &str) -> String {
let type_str = match self.citation_type {
CitationType::Article => "article",
CitationType::InProceedings => "inproceedings",
CitationType::Book => "book",
CitationType::Software => "software",
CitationType::Dataset => "dataset",
CitationType::Misc => "misc",
};
let mut bibtex = format!("@{}{{{},\n", type_str, key);
bibtex.push_str(&format!(" title = {{{}}},\n", self.title));
bibtex.push_str(&format!(" author = {{{}}},\n", self.authors.join(" and ")));
bibtex.push_str(&format!(" year = {{{}}},\n", self.year));
if let Some(month) = self.month {
bibtex.push_str(&format!(" month = {{{}}},\n", month));
}
if let Some(ref doi) = self.doi {
bibtex.push_str(&format!(" doi = {{{}}},\n", doi));
}
if let Some(ref url) = self.url {
bibtex.push_str(&format!(" url = {{{}}},\n", url));
}
if let Some(ref venue) = self.venue {
let field = match self.citation_type {
CitationType::Article => "journal",
CitationType::InProceedings => "booktitle",
_ => "howpublished",
};
bibtex.push_str(&format!(" {} = {{{}}},\n", field, venue));
}
if let Some(ref volume) = self.volume {
bibtex.push_str(&format!(" volume = {{{}}},\n", volume));
}
if let Some(ref pages) = self.pages {
bibtex.push_str(&format!(" pages = {{{}}},\n", pages));
}
if let Some(ref publisher) = self.publisher {
bibtex.push_str(&format!(" publisher = {{{}}},\n", publisher));
}
if let Some(ref version) = self.version {
bibtex.push_str(&format!(" version = {{{}}},\n", version));
}
bibtex.push('}');
bibtex
}
pub fn to_cff(&self) -> String {
let mut cff = String::from("cff-version: 1.2.0\n");
cff.push_str(&format!("title: \"{}\"\n", self.title));
cff.push_str("authors:\n");
for author in &self.authors {
cff.push_str(&format!(" - name: \"{}\"\n", author));
}
cff.push_str(&format!(
"date-released: \"{}-{:02}-01\"\n",
self.year,
self.month.unwrap_or(1)
));
if let Some(ref version) = self.version {
cff.push_str(&format!("version: \"{}\"\n", version));
}
if let Some(ref doi) = self.doi {
cff.push_str(&format!("doi: \"{}\"\n", doi));
}
if let Some(ref url) = self.url {
cff.push_str(&format!("url: \"{}\"\n", url));
}
cff
}
}