dicom_anonymization/actions/
mod.rs1mod empty;
2pub(crate) mod errors;
3pub mod hash;
4mod hash_date;
5mod hash_uid;
6mod keep;
7mod no_action;
8mod remove;
9mod replace;
10mod utils;
11
12use crate::actions::errors::ActionError;
13use crate::actions::hash::HASH_LENGTH_MINIMUM;
14use crate::config::Config;
15use crate::Tag;
16use dicom_object::mem::InMemElement;
17use dicom_object::DefaultDicomObject;
18use empty::Empty;
19use garde::Validate;
20use hash::{Hash, HashLength};
21use hash_date::HashDate;
22use hash_uid::HashUID;
23use keep::Keep;
24use no_action::NoAction;
25use remove::Remove;
26use replace::Replace;
27use serde::{Deserialize, Deserializer, Serialize, Serializer};
28use std::borrow::Cow;
29
30pub(crate) trait DataElementAction {
31 fn process<'a>(
32 &'a self,
33 config: &Config,
34 obj: &DefaultDicomObject,
35 elem: &'a InMemElement,
36 ) -> Result<Option<Cow<'a, InMemElement>>, ActionError>;
37}
38
39#[derive(Debug, Clone)]
40pub struct TagString(pub Tag);
41
42impl Serialize for TagString {
43 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
44 where
45 S: Serializer,
46 {
47 let tag_str = format!("{}", self.0);
48 serializer.serialize_str(&tag_str)
49 }
50}
51
52impl<'de> Deserialize<'de> for TagString {
53 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
54 where
55 D: Deserializer<'de>,
56 {
57 let tag_str = String::deserialize(deserializer)?;
58
59 let tag: Tag = tag_str.parse().map_err(|_| {
60 serde::de::Error::custom(format!(
61 "Tag must be in format 'GGGGEEEE' where G and E are hex digits, got: {}",
62 tag_str
63 ))
64 })?;
65
66 Ok(TagString(tag))
67 }
68}
69
70#[derive(Validate, Serialize, Deserialize, Debug, Clone, PartialEq)]
72#[serde(tag = "action", rename_all = "lowercase")]
73pub enum Action {
74 Empty,
76
77 Hash {
79 #[serde(skip_serializing_if = "Option::is_none")]
80 #[garde(inner(range(min = HASH_LENGTH_MINIMUM)))]
81 length: Option<usize>,
82 },
83
84 HashDate,
86
87 HashUID,
89
90 Keep,
92
93 None,
95
96 Remove,
98
99 Replace {
101 #[garde(skip)]
102 value: String,
103 },
104}
105
106impl Action {
107 pub(crate) fn get_action_struct(&self) -> Box<dyn DataElementAction> {
108 match self {
109 Action::Empty => Box::new(Empty),
110 Action::Hash { length } => {
111 let hash_length = length.as_ref().map(|length| HashLength(*length));
112 Box::new(Hash::new(hash_length))
113 }
114 Action::HashDate => Box::new(HashDate::default()),
115 Action::HashUID => Box::new(HashUID),
116 Action::Keep => Box::new(Keep),
117 Action::None => Box::new(NoAction),
118 Action::Remove => Box::new(Remove),
119 Action::Replace { value } => Box::new(Replace::new(value.clone())),
120 }
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::Action;
127 use serde_json;
128
129 #[test]
130 fn test_serialize_empty() {
131 let action = Action::Empty;
132 let json = serde_json::to_string(&action).unwrap();
133 assert_eq!(json, r#"{"action":"empty"}"#);
134 }
135
136 #[test]
137 fn test_serialize_hash() {
138 let action = Action::Hash { length: Some(10) };
139 let json = serde_json::to_string(&action).unwrap();
140 assert_eq!(json, r#"{"action":"hash","length":10}"#);
141 }
142
143 #[test]
144 fn test_serialize_hash_date() {
145 let action = Action::HashDate;
146 let json = serde_json::to_string(&action).unwrap();
147 assert_eq!(json, r#"{"action":"hashdate"}"#);
148 }
149
150 #[test]
151 fn test_serialize_hash_uid() {
152 let action = Action::HashUID;
153 let json = serde_json::to_string(&action).unwrap();
154 assert_eq!(json, r#"{"action":"hashuid"}"#);
155 }
156
157 #[test]
158 fn test_serialize_keep() {
159 let action = Action::Keep;
160 let json = serde_json::to_string(&action).unwrap();
161 assert_eq!(json, r#"{"action":"keep"}"#);
162 }
163
164 #[test]
165 fn test_serialize_none() {
166 let action = Action::None;
167 let json = serde_json::to_string(&action).unwrap();
168 assert_eq!(json, r#"{"action":"none"}"#);
169 }
170
171 #[test]
172 fn test_serialize_remove() {
173 let action = Action::Remove;
174 let json = serde_json::to_string(&action).unwrap();
175 assert_eq!(json, r#"{"action":"remove"}"#);
176 }
177
178 #[test]
179 fn test_serialize_replace() {
180 let action = Action::Replace {
181 value: "ANONYMIZED".to_string(),
182 };
183 let json = serde_json::to_string(&action).unwrap();
184 assert_eq!(json, r#"{"action":"replace","value":"ANONYMIZED"}"#);
185 }
186
187 #[test]
188 fn test_deserialize_empty() {
189 let json = r#"{"action":"empty"}"#;
190 let action: Action = serde_json::from_str(json).unwrap();
191 assert_eq!(action, Action::Empty);
192 }
193
194 #[test]
195 fn test_deserialize_hash() {
196 let json = r#"{"action":"hash","length":null}"#;
197 let action: Action = serde_json::from_str(json).unwrap();
198 assert_eq!(action, Action::Hash { length: None });
199 }
200
201 #[test]
202 fn test_deserialize_hash_date() {
203 let json = r#"{"action":"hashdate"}"#;
204 let action: Action = serde_json::from_str(json).unwrap();
205 assert_eq!(action, Action::HashDate);
206 }
207
208 #[test]
209 fn test_deserialize_hash_uid() {
210 let json = r#"{"action":"hashuid"}"#;
211 let action: Action = serde_json::from_str(json).unwrap();
212 assert_eq!(action, Action::HashUID);
213 }
214
215 #[test]
216 fn test_deserialize_keep() {
217 let json = r#"{"action":"keep"}"#;
218 let action: Action = serde_json::from_str(json).unwrap();
219 assert_eq!(action, Action::Keep);
220 }
221
222 #[test]
223 fn test_deserialize_none() {
224 let json = r#"{"action":"none"}"#;
225 let action: Action = serde_json::from_str(json).unwrap();
226 assert_eq!(action, Action::None);
227 }
228
229 #[test]
230 fn test_deserialize_remove() {
231 let json = r#"{"action":"remove"}"#;
232 let action: Action = serde_json::from_str(json).unwrap();
233 assert_eq!(action, Action::Remove);
234 }
235
236 #[test]
237 fn test_deserialize_replace() {
238 let json = r#"{"action":"replace","value":"ANONYMIZED"}"#;
239 let action: Action = serde_json::from_str(json).unwrap();
240 assert_eq!(
241 action,
242 Action::Replace {
243 value: "ANONYMIZED".to_string()
244 }
245 );
246 }
247
248 #[test]
249 fn test_case_handling_on_deserialization() {
250 let json = r#"{"action":"empty"}"#;
252 let action: Action = serde_json::from_str(json).unwrap();
253 assert_eq!(action, Action::Empty);
254
255 let json = r#"{"action":"EMPTY"}"#;
257 let result: Result<Action, _> = serde_json::from_str(json);
258 assert!(result.is_err());
259
260 let json = r#"{"action":"Hash"}"#;
262 let result: Result<Action, _> = serde_json::from_str(json);
263 assert!(result.is_err());
264 }
265
266 #[test]
267 fn test_roundtrip_all_variants() {
268 let variants = vec![
270 Action::Empty,
271 Action::Hash { length: None },
272 Action::HashDate,
273 Action::HashUID,
274 Action::Keep,
275 Action::None,
276 Action::Remove,
277 Action::Replace {
278 value: "TEST".to_string(),
279 },
280 ];
281
282 for variant in variants {
283 let json = serde_json::to_string(&variant).unwrap();
284 let deserialized: Action = serde_json::from_str(&json).unwrap();
285 assert_eq!(
286 variant, deserialized,
287 "Roundtrip failed for variant: {:?}",
288 variant
289 );
290 }
291 }
292
293 #[test]
294 fn test_error_handling_missing_action() {
295 let json = r#"{"with":"ANONYMIZED"}"#;
296 let result: Result<Action, _> = serde_json::from_str(json);
297 assert!(result.is_err());
298 }
299
300 #[test]
301 fn test_error_handling_invalid_action() {
302 let json = r#"{"action":"invalidaction"}"#;
303 let result: Result<Action, _> = serde_json::from_str(json);
304 assert!(result.is_err());
305 }
306
307 #[test]
308 fn test_error_handling_missing_replace_with() {
309 let json = r#"{"action":"replace"}"#;
310 let result: Result<Action, _> = serde_json::from_str(json);
311 assert!(result.is_err());
312 }
313
314 #[test]
315 fn test_pretty_print() {
316 let action = Action::Replace {
317 value: "ANONYMIZED".to_string(),
318 };
319 let json = serde_json::to_string_pretty(&action).unwrap();
320 let expected = r#"{
321 "action": "replace",
322 "value": "ANONYMIZED"
323}"#;
324 assert_eq!(json, expected);
325 }
326}