use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter, Result as FmtResult};
use std::str::Chars;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct Affiliation {
pub id: Option<String>,
pub institution: Option<String>,
pub department: Option<String>,
pub address: Option<String>,
pub country: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct Author {
pub surname: Option<String>,
pub given_names: Option<String>,
pub initials: Option<String>,
pub suffix: Option<String>,
pub full_name: String,
pub affiliations: Vec<Affiliation>,
pub orcid: Option<String>,
pub email: Option<String>,
pub is_corresponding: bool,
pub roles: Vec<String>,
}
impl Display for Author {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
write!(f, "{}", self.full_name)
}
}
impl Author {
pub fn new(surname: Option<String>, given_names: Option<String>) -> Self {
let full_name = format_author_name(&surname, &given_names, &None);
Author {
surname,
given_names,
initials: None,
suffix: None,
full_name,
affiliations: Vec::new(),
orcid: None,
email: None,
is_corresponding: false,
roles: Vec::new(),
}
}
pub fn from_full_name(full_name: String) -> Self {
Author {
surname: None,
given_names: None,
initials: None,
suffix: None,
full_name,
affiliations: Vec::new(),
orcid: None,
email: None,
is_corresponding: false,
roles: Vec::new(),
}
}
pub fn is_affiliated_with(&self, institution: &str) -> bool {
let institution_lower = institution.to_lowercase();
self.affiliations.iter().any(|affil| {
affil
.institution
.as_ref()
.is_some_and(|inst| inst.to_lowercase().contains(&institution_lower))
})
}
pub fn primary_affiliation(&self) -> Option<&Affiliation> {
self.affiliations.first()
}
pub fn has_orcid(&self) -> bool {
self.orcid.is_some()
}
pub fn is_empty(&self) -> bool {
self.full_name.trim().is_empty()
}
pub fn len(&self) -> usize {
self.full_name.len()
}
pub fn chars(&self) -> Chars<'_> {
self.full_name.chars()
}
}
impl Affiliation {
pub fn new(institution: Option<String>) -> Self {
Self {
id: None,
institution,
department: None,
address: None,
country: None,
}
}
}
pub fn format_author_name(
surname: &Option<String>,
given_names: &Option<String>,
initials: &Option<String>,
) -> String {
match (given_names, surname) {
(Some(given), Some(sur)) => format!("{given} {sur}"),
(None, Some(sur)) => {
if let Some(init) = initials {
format!("{init} {sur}")
} else {
sur.clone()
}
}
(Some(given), None) => given.clone(),
(None, None) => "Unknown Author".to_string(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_author_creation() {
let author = Author::new(Some("Smith".to_string()), Some("Jane".to_string()));
assert_eq!(author.surname, Some("Smith".to_string()));
assert_eq!(author.given_names, Some("Jane".to_string()));
assert_eq!(author.full_name, "Jane Smith");
assert!(!author.has_orcid());
assert!(!author.is_corresponding);
}
#[test]
fn test_author_affiliations() {
let mut author = Author::new(Some("Doe".to_string()), Some("John".to_string()));
author.affiliations.push(Affiliation {
id: None,
institution: Some("Harvard Medical School".to_string()),
department: Some("Department of Medicine".to_string()),
address: Some("Boston, MA".to_string()),
country: Some("USA".to_string()),
});
assert!(author.is_affiliated_with("Harvard"));
assert!(!author.is_affiliated_with("Stanford"));
let primary = author.primary_affiliation().unwrap();
assert_eq!(
primary.institution,
Some("Harvard Medical School".to_string())
);
}
#[test]
fn test_format_author_name() {
assert_eq!(
format_author_name(&Some("Smith".to_string()), &Some("John".to_string()), &None),
"John Smith"
);
assert_eq!(
format_author_name(&Some("Doe".to_string()), &None, &Some("J".to_string())),
"J Doe"
);
assert_eq!(
format_author_name(&Some("Johnson".to_string()), &None, &None),
"Johnson"
);
assert_eq!(
format_author_name(&None, &Some("Jane".to_string()), &None),
"Jane"
);
assert_eq!(format_author_name(&None, &None, &None), "Unknown Author");
}
#[test]
fn test_affiliation_creation() {
let affil = Affiliation::new(Some("MIT".to_string()));
assert_eq!(affil.institution, Some("MIT".to_string()));
assert_eq!(affil.id, None);
assert_eq!(affil.department, None);
}
}