dicom_anonymization/config/
uid_root.rs1use regex::Regex;
2use serde::{Deserialize, Serialize};
3use std::str::FromStr;
4use std::sync::OnceLock;
5use thiserror::Error;
6
7static UID_ROOT_REGEX: OnceLock<Regex> = OnceLock::new();
8const UID_ROOT_MAX_LENGTH: usize = 32;
9pub const UID_ROOT_DEFAULT_VALUE: &str = "9999";
10
11#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
33pub struct UidRoot(pub String);
34
35#[derive(Error, Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
36#[error("{0} is not a valid UID root")]
37pub struct UidRootError(pub String);
38
39impl UidRoot {
40 pub fn new(uid_root: &str) -> Result<Self, UidRootError> {
41 let regex = UID_ROOT_REGEX.get_or_init(|| {
42 Regex::new(&format!(
43 r"^([1-9][0-9.]{{0,{}}})?$",
44 UID_ROOT_MAX_LENGTH - 1
45 ))
46 .unwrap()
47 });
48
49 if !regex.is_match(uid_root) {
50 return Err(UidRootError(format!(
51 "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"
52 )));
53 }
54
55 Ok(Self(uid_root.into()))
56 }
57
58 pub fn as_prefix(&self) -> String {
67 if !self.0.is_empty() && !self.0.ends_with('.') {
68 format!("{}.", self.0.trim())
69 } else {
70 self.0.trim().into()
71 }
72 }
73}
74
75impl Default for UidRoot {
76 fn default() -> Self {
79 Self("".into())
80 }
81}
82
83impl FromStr for UidRoot {
84 type Err = UidRootError;
85
86 fn from_str(s: &str) -> Result<Self, Self::Err> {
87 UidRoot::new(s)
88 }
89}
90
91impl AsRef<str> for UidRoot {
92 fn as_ref(&self) -> &str {
93 &self.0
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 #[test]
102 fn test_uid_root_validation() {
103 assert!(UidRoot::new("").is_ok());
105 assert!(UidRoot::new("1").is_ok());
106 assert!(UidRoot::new("1.2.3").is_ok());
107 assert!(UidRoot::new("123.456.").is_ok());
108 assert!(UidRoot::new(&"1".repeat(32)).is_ok());
109
110 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()); }
116
117 #[test]
118 fn test_uid_root_from_str() {
119 let uid_root: Result<UidRoot, _> = "1.2.736.120".parse();
121 assert!(uid_root.is_ok());
122
123 let uid_root: Result<UidRoot, _> = "".parse();
124 assert!(uid_root.is_ok());
125
126 let uid_root: Result<UidRoot, _> = "0.1.2".parse();
128 assert!(uid_root.is_err());
129
130 let uid_root: Result<UidRoot, _> = "invalid".parse();
131 assert!(uid_root.is_err());
132 }
133
134 #[test]
135 fn test_uid_root_as_ref() {
136 let uid_root = UidRoot::new("").unwrap();
138 assert_eq!(uid_root.as_ref(), "");
139
140 let uid_root = UidRoot::new("1.2.3").unwrap();
142 assert_eq!(uid_root.as_ref(), "1.2.3");
143
144 let uid_root = UidRoot::new("1.2.3.").unwrap();
146 assert_eq!(uid_root.as_ref(), "1.2.3.");
147
148 fn takes_str(_s: &str) {}
150 let uid_root = UidRoot::new("1.2.3").unwrap();
151 takes_str(uid_root.as_ref());
152 }
153}