use crate::prelude::{format, String, ToString, Vec};
use crate::schema::namespaces::DEFAULT_ROR_SCHEMA_URI;
use crate::schema::pid::{mod_97_10_check_digit, PersistentIdentifier, PersistentIdentifierParse};
use crate::util::constants::{RE_ROR, RE_ROR_TEXT};
use crate::util::regex_capture_lookup;
use bon::Builder;
use core::fmt;
#[derive(Builder, Clone, Debug)]
#[builder(start_fn = init, on(String, into))]
pub struct ROR {
pub schema_uri: Option<String>,
pub identifier: Option<String>,
pub check_digit: Option<String>,
}
impl Default for ROR {
fn default() -> Self {
Self::new()
}
}
impl fmt::Display for ROR {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let schema_uri = self.schema_uri();
let result = self.identifier();
if result.is_empty() {
write!(f, "")
} else {
write!(f, "{schema_uri}{result}")
}
}
}
impl PersistentIdentifier for ROR {
fn new() -> Self {
ROR::init().build()
}
fn schema_uri(&self) -> String {
let processed = self
.schema_uri
.as_ref()
.cloned()
.unwrap_or_else(|| DEFAULT_ROR_SCHEMA_URI.to_string())
.trim_end_matches("/")
.replace(" ", "")
.to_string();
format!("{processed}/")
}
fn identifier(&self) -> String {
self.identifier.clone().unwrap_or_default()
}
fn suffix(&self) -> Option<String> {
self.identifier.clone()
}
fn check_digit(&self) -> Option<Vec<char>> {
self.identifier().get(1..).and_then(mod_97_10_check_digit)
}
}
impl PersistentIdentifierParse for ROR {
fn find_all(value: impl ToString) -> Vec<Self> {
let re = &RE_ROR;
re.find_iter(&value.to_string())
.filter_map(Result::ok)
.filter(|value| ROR::is_valid(value.as_str()))
.map(|m| ROR::from_string(m.as_str()))
.collect()
}
fn format(value: impl ToString) -> String {
ROR::from_string(value.to_string()).to_string()
}
fn from_string(value: impl ToString) -> Self {
let groups = ["schema_uri", "identifier", "check_digit"];
let pattern = format!("^{RE_ROR_TEXT}$");
let text = value.to_string();
let lookup = regex_capture_lookup(pattern.as_ref(), text.as_ref(), groups.to_vec());
ROR::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 = ROR::from_string(value.to_string());
let identifier = pid.identifier();
let last_two = identifier.chars().rev().take(2).collect::<String>().chars().rev().collect::<String>();
if identifier.is_empty() {
false
} else {
match mod_97_10_check_digit(&identifier[1..]) {
| Some(check_digit) => {
if identifier.len() == 9 {
let calculated_last_two = check_digit.iter().collect::<String>();
calculated_last_two == last_two
} else {
false
}
}
| _ => false,
}
}
}
}
#[cfg(test)]
mod tests;