use crate::prelude::{format, vec, String, ToString, Vec};
use crate::schema::namespaces::DEFAULT_ORCID_SCHEMA_URI;
use crate::schema::pid::{mod_11_2_check_digit, PersistentIdentifier, PersistentIdentifierParse};
use crate::util::constants::{RE_ORCID, RE_ORCID_TEXT};
use crate::util::{regex_capture_lookup, ToStringChunks};
use bon::Builder;
use core::fmt;
#[derive(Builder, Clone, Debug)]
#[builder(start_fn = init, on(String, into))]
pub struct ORCID {
pub schema_uri: Option<String>,
pub identifier: Option<String>,
pub check_digit: Option<String>,
}
impl Default for ORCID {
fn default() -> Self {
Self::new()
}
}
impl fmt::Display for ORCID {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let schema_uri = self.schema_uri();
let identifier = self.identifier();
let uri = if schema_uri.is_empty() { DEFAULT_ORCID_SCHEMA_URI } else { &schema_uri };
let values = match &self.identifier {
| Some(_) => [uri, &identifier].to_vec(),
| None => vec![],
};
let result = values
.into_iter()
.filter(|x| !x.is_empty())
.map(String::from)
.collect::<Vec<String>>()
.join("/");
write!(f, "{result}")
}
}
impl PersistentIdentifier for ORCID {
fn new() -> Self {
ORCID::init().build()
}
fn schema_uri(&self) -> String {
self.schema_uri.as_ref().cloned().unwrap_or_default().trim_end_matches("/").to_string()
}
fn identifier(&self) -> String {
let stripped = self.identifier.as_ref().cloned().unwrap_or_default().replace("-", "");
stripped.chunk(4).join("-")
}
fn suffix(&self) -> Option<String> {
Some(self.identifier())
}
fn check_digit(&self) -> Option<Vec<char>> {
mod_11_2_check_digit(self.identifier())
}
}
impl PersistentIdentifierParse for ORCID {
fn find_all(value: impl ToString) -> Vec<Self> {
let re = &RE_ORCID;
re.find_iter(&value.to_string())
.filter_map(Result::ok)
.map(|m| ORCID::from_string(m.as_str()))
.collect()
}
fn format(value: impl ToString) -> String {
ORCID::from_string(value).to_string()
}
fn from_string(value: impl ToString) -> Self {
let groups = ["schema_uri", "identifier", "check_digit"];
let pattern = format!("^{RE_ORCID_TEXT}$");
let text = value.to_string();
let lookup = regex_capture_lookup(pattern.as_ref(), text.as_ref(), groups.to_vec());
ORCID::init()
.maybe_schema_uri(lookup.get("schema_uri").cloned())
.maybe_identifier(lookup.get("identifier").cloned())
.maybe_check_digit(lookup.get("check_digit").cloned())
.build()
}
fn is_valid(value: impl ToString) -> bool {
let pid = ORCID::from_string(value.to_string());
let identifier = pid.identifier();
let last = identifier.chars().last().unwrap_or_default();
match mod_11_2_check_digit(identifier.as_str()) {
| Some(check_digit) => {
if check_digit.contains(&last) {
identifier.len() == 19
} else {
false
}
}
| _ => false,
}
}
}
#[cfg(test)]
mod tests;