use regex::Regex;
use serde::{Deserialize, Serialize};
use std::str::FromStr;
use std::sync::OnceLock;
use thiserror::Error;
static UID_ROOT_REGEX: OnceLock<Regex> = OnceLock::new();
const UID_ROOT_MAX_LENGTH: usize = 32;
pub const UID_ROOT_DEFAULT_VALUE: &str = "9999";
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub struct UidRoot(pub String);
#[derive(Error, Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
#[error("{0} is not a valid UID root")]
pub struct UidRootError(pub String);
impl UidRoot {
pub fn new(uid_root: &str) -> Result<Self, UidRootError> {
let regex = UID_ROOT_REGEX.get_or_init(|| {
Regex::new(&format!(
r"^([1-9][0-9.]{{0,{}}})?$",
UID_ROOT_MAX_LENGTH - 1
))
.unwrap()
});
if !regex.is_match(uid_root) {
return Err(UidRootError(format!(
"UID root must be empty or start with 1-9, contain only numbers and dots, and be no longer than {UID_ROOT_MAX_LENGTH} characters"
)));
}
Ok(Self(uid_root.into()))
}
pub fn as_prefix(&self) -> String {
if !self.0.is_empty() && !self.0.ends_with('.') {
format!("{}.", self.0.trim())
} else {
self.0.trim().into()
}
}
}
impl Default for UidRoot {
fn default() -> Self {
Self("".into())
}
}
impl FromStr for UidRoot {
type Err = UidRootError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
UidRoot::new(s)
}
}
impl AsRef<str> for UidRoot {
fn as_ref(&self) -> &str {
&self.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_uid_root_validation() {
assert!(UidRoot::new("").is_ok());
assert!(UidRoot::new("1").is_ok());
assert!(UidRoot::new("1.2.3").is_ok());
assert!(UidRoot::new("123.456.").is_ok());
assert!(UidRoot::new(&"1".repeat(32)).is_ok());
assert!(UidRoot::new("0123").is_err()); assert!(UidRoot::new("a.1.2").is_err()); assert!(UidRoot::new("1.2.3-4").is_err()); assert!(UidRoot::new(&"1".repeat(33)).is_err()); }
#[test]
fn test_uid_root_from_str() {
let uid_root: Result<UidRoot, _> = "1.2.736.120".parse();
assert!(uid_root.is_ok());
let uid_root: Result<UidRoot, _> = "".parse();
assert!(uid_root.is_ok());
let uid_root: Result<UidRoot, _> = "0.1.2".parse();
assert!(uid_root.is_err());
let uid_root: Result<UidRoot, _> = "invalid".parse();
assert!(uid_root.is_err());
}
#[test]
fn test_uid_root_as_ref() {
let uid_root = UidRoot::new("").unwrap();
assert_eq!(uid_root.as_ref(), "");
let uid_root = UidRoot::new("1.2.3").unwrap();
assert_eq!(uid_root.as_ref(), "1.2.3");
let uid_root = UidRoot::new("1.2.3.").unwrap();
assert_eq!(uid_root.as_ref(), "1.2.3.");
fn takes_str(_s: &str) {}
let uid_root = UidRoot::new("1.2.3").unwrap();
takes_str(uid_root.as_ref());
}
}