Skip to main content

entrenar/research/artifact/
author.rs

1//! Author with ORCID and contributor roles.
2
3use serde::{Deserialize, Serialize};
4
5use super::{validate_orcid, Affiliation, ContributorRole, ValidationError};
6
7/// Author with ORCID and contributor roles
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
9pub struct Author {
10    /// Full name
11    pub name: String,
12    /// ORCID identifier (optional)
13    pub orcid: Option<String>,
14    /// Institutional affiliations
15    pub affiliations: Vec<Affiliation>,
16    /// Contributor roles (CRediT taxonomy)
17    pub roles: Vec<ContributorRole>,
18}
19
20impl Author {
21    /// Create a new author
22    pub fn new(name: impl Into<String>) -> Self {
23        Self { name: name.into(), orcid: None, affiliations: Vec::new(), roles: Vec::new() }
24    }
25
26    /// Set the ORCID (validates format)
27    pub fn with_orcid(mut self, orcid: impl Into<String>) -> Result<Self, ValidationError> {
28        let id = orcid.into();
29        if !validate_orcid(&id) {
30            return Err(ValidationError::InvalidOrcid(id));
31        }
32        self.orcid = Some(id);
33        Ok(self)
34    }
35
36    /// Add an affiliation
37    pub fn with_affiliation(mut self, affiliation: Affiliation) -> Self {
38        self.affiliations.push(affiliation);
39        self
40    }
41
42    /// Add a contributor role
43    pub fn with_role(mut self, role: ContributorRole) -> Self {
44        if !self.roles.contains(&role) {
45            self.roles.push(role);
46        }
47        self
48    }
49
50    /// Add multiple contributor roles
51    pub fn with_roles(mut self, roles: impl IntoIterator<Item = ContributorRole>) -> Self {
52        for role in roles {
53            if !self.roles.contains(&role) {
54                self.roles.push(role);
55            }
56        }
57        self
58    }
59
60    /// Get the author's last name (for citation keys)
61    pub fn last_name(&self) -> &str {
62        self.name.split_whitespace().next_back().unwrap_or(&self.name)
63    }
64}