1pub mod builder;
2pub(crate) mod tag_action_map;
3pub mod uid_root;
4
5use crate::actions::Action;
6use crate::hasher::{blake3_hash_fn, HashFn};
7use crate::Tag;
8use serde::{Deserialize, Serialize};
9use tag_action_map::TagActionMap;
10use thiserror::Error;
11use uid_root::{UidRoot, UidRootError};
12
13const DEIDENTIFIER: &str = "CARECODERS.IO";
14
15#[derive(Error, Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
16pub enum ConfigError {
17 #[error("invalid UID root: {0}")]
18 InvalidUidRoot(String),
19
20 #[error("invalid hash length: {0}")]
21 InvalidHashLength(String),
22}
23
24impl From<UidRootError> for ConfigError {
25 fn from(err: UidRootError) -> Self {
26 ConfigError::InvalidUidRoot(err.0)
27 }
28}
29
30pub fn default_hash_fn() -> HashFn {
31 blake3_hash_fn
32}
33
34#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
48pub struct Config {
49 #[serde(skip, default = "default_hash_fn")]
50 hash_fn: HashFn,
51
52 #[serde(default, skip_serializing_if = "Option::is_none")]
53 uid_root: Option<UidRoot>,
54 #[serde(default, skip_serializing_if = "Option::is_none")]
55 remove_private_tags: Option<bool>,
56 #[serde(default, skip_serializing_if = "Option::is_none")]
57 remove_curves: Option<bool>,
58 #[serde(default, skip_serializing_if = "Option::is_none")]
59 remove_overlays: Option<bool>,
60
61 #[serde(
62 default = "TagActionMap::default",
63 skip_serializing_if = "TagActionMap::is_empty"
64 )]
65 tag_actions: TagActionMap,
66}
67
68impl Config {
69 fn new(
70 hash_fn: HashFn,
71 uid_root: Option<UidRoot>,
72 remove_private_tags: Option<bool>,
73 remove_curves: Option<bool>,
74 remove_overlays: Option<bool>,
75 ) -> Self {
76 Self {
77 hash_fn,
78 uid_root,
79 remove_private_tags,
80 remove_curves,
81 remove_overlays,
82 tag_actions: TagActionMap::new(),
83 }
84 }
85}
86
87impl Default for Config {
88 fn default() -> Self {
89 Self::new(blake3_hash_fn, None, None, None, None)
90 }
91}
92
93pub(crate) fn is_private_tag(tag: &Tag) -> bool {
94 tag.group() % 2 != 0
96}
97
98pub(crate) fn is_curve_tag(tag: &Tag) -> bool {
99 (tag.group() & 0xFF00) == 0x5000
100}
101
102pub(crate) fn is_overlay_tag(tag: &Tag) -> bool {
103 (tag.group() & 0xFF00) == 0x6000
104}
105
106impl Config {
107 pub fn get_hash_fn(&self) -> HashFn {
108 self.hash_fn
109 }
110
111 pub fn get_uid_root(&self) -> &Option<UidRoot> {
112 &self.uid_root
113 }
114
115 pub fn get_action(&self, tag: &Tag) -> &Action {
136 match self.tag_actions.get(tag) {
137 Some(action) if action == &Action::None && self.should_be_removed(tag) => {
138 &Action::Remove
139 }
140 Some(action) => action,
141 None if self.should_be_removed(tag) => &Action::Remove,
142 None => &Action::Keep,
143 }
144 }
145
146 fn should_be_removed(&self, tag: &Tag) -> bool {
147 (self.remove_private_tags.unwrap_or(false) && is_private_tag(tag))
148 || (self.remove_curves.unwrap_or(false) && is_curve_tag(tag))
149 || (self.remove_overlays.unwrap_or(false) && is_overlay_tag(tag))
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 use crate::tags;
158
159 use builder::ConfigBuilder;
160 use uid_root::UidRoot;
161
162 #[test]
163 fn test_config_builder() {
164 let config = ConfigBuilder::new()
165 .tag_action(tags::PATIENT_NAME, Action::Empty)
166 .build();
167 let tag_action = config.get_action(&tags::PATIENT_NAME);
168 assert_eq!(tag_action, &Action::Empty);
169
170 let tag_action = config.get_action(&tags::PATIENT_ID);
172 assert_eq!(tag_action, &Action::Keep);
173 }
174
175 #[test]
176 fn test_is_private_tag() {
177 assert!(is_private_tag(&Tag::from([1, 0])));
179 assert!(is_private_tag(&Tag::from([13, 12])));
180 assert!(is_private_tag(&Tag::from([33, 33])));
181
182 assert!(!is_private_tag(&tags::ACCESSION_NUMBER));
184 assert!(!is_private_tag(&tags::PATIENT_ID));
185 assert!(!is_private_tag(&tags::PIXEL_DATA));
186 }
187
188 #[test]
189 fn test_keep_private_tag() {
190 let tag = Tag(0x0033, 0x0010);
191 let config = ConfigBuilder::new()
192 .remove_private_tags(true)
193 .tag_action(tag, Action::Keep)
194 .build();
195
196 let tag_action = config.get_action(&tag);
198 assert_eq!(tag_action, &Action::Keep);
199 assert_eq!(config.get_action(&Tag(0x0033, 0x1010)), &Action::Remove);
201 assert_eq!(config.get_action(&tags::PATIENT_ID), &Action::Keep);
203 }
204
205 #[test]
206 fn test_remove_private_tag() {
207 let tag = Tag(0x0033, 0x0010);
208 let config = ConfigBuilder::new()
209 .remove_private_tags(true)
210 .tag_action(tag, Action::None)
211 .build();
212 let tag_action = config.get_action(&tag);
213 assert_eq!(tag_action, &Action::Remove);
214 assert_eq!(config.get_action(&Tag(0x0033, 0x1010)), &Action::Remove);
215 assert_eq!(config.get_action(&tags::PATIENT_ID), &Action::Keep);
217 }
218
219 #[test]
220 fn test_is_curve_tag() {
221 assert!(is_curve_tag(&Tag::from([0x5000, 0])));
223 assert!(is_curve_tag(&Tag::from([0x5010, 0x0011])));
224 assert!(is_curve_tag(&Tag::from([0x50FF, 0x0100])));
225
226 assert!(!is_curve_tag(&Tag::from([0x5100, 0])));
228 assert!(!is_curve_tag(&Tag::from([0x6000, 0])));
229 }
230
231 #[test]
232 fn test_keep_curve_tag() {
233 let tag = Tag(0x5010, 0x0011);
234 let config = ConfigBuilder::new()
235 .remove_curves(true)
236 .tag_action(tag, Action::Keep)
237 .build();
238
239 let tag_action = config.get_action(&tag);
241 assert_eq!(tag_action, &Action::Keep);
242 assert_eq!(config.get_action(&Tag(0x50FF, 0x0100)), &Action::Remove);
244 assert_eq!(config.get_action(&tags::PATIENT_ID), &Action::Keep);
246 }
247
248 #[test]
249 fn test_remove_curve_tag() {
250 let tag = Tag(0x5010, 0x0011);
251 let config = ConfigBuilder::new()
252 .remove_curves(true)
253 .tag_action(tag, Action::None)
254 .build();
255 let tag_action = config.get_action(&tag);
256 assert_eq!(tag_action, &Action::Remove);
257 assert_eq!(config.get_action(&Tag(0x50FF, 0x0100)), &Action::Remove);
258 assert_eq!(config.get_action(&tags::PATIENT_ID), &Action::Keep);
260 }
261
262 #[test]
263 fn test_is_overlay_tag() {
264 assert!(is_overlay_tag(&Tag::from([0x6000, 0])));
266 assert!(is_overlay_tag(&Tag::from([0x6010, 0x0011])));
267 assert!(is_overlay_tag(&Tag::from([0x60FF, 0x0100])));
268
269 assert!(!is_overlay_tag(&Tag::from([0x6100, 0])));
271 assert!(!is_overlay_tag(&Tag::from([0x5000, 0])));
272 }
273
274 #[test]
275 fn test_keep_overlay_tag() {
276 let tag = Tag(0x6010, 0x0011);
277 let config = ConfigBuilder::new()
278 .remove_overlays(true)
279 .tag_action(tag, Action::Keep)
280 .build();
281
282 let tag_action = config.get_action(&tag);
284 assert_eq!(tag_action, &Action::Keep);
285 assert_eq!(config.get_action(&Tag(0x60FF, 0x0100)), &Action::Remove);
287 assert_eq!(config.get_action(&tags::PATIENT_ID), &Action::Keep);
289 }
290
291 #[test]
292 fn test_remove_overlay_tag() {
293 let tag = Tag(0x6010, 0x0011);
294 let config = ConfigBuilder::new()
295 .remove_overlays(true)
296 .tag_action(tag, Action::None)
297 .build();
298 let tag_action = config.get_action(&tag);
299 assert_eq!(tag_action, &Action::Remove);
300 assert_eq!(config.get_action(&Tag(0x60FF, 0x0100)), &Action::Remove);
301 assert_eq!(config.get_action(&tags::PATIENT_ID), &Action::Keep);
303 }
304
305 fn create_sample_tag_actions() -> TagActionMap {
306 let mut map = TagActionMap::new(); map.insert(Tag(0x0010, 0x0010), Action::Empty); map.insert(Tag(0x0010, 0x0020), Action::Remove); map.insert(Tag(0x0008, 0x0050), Action::Hash { length: None }); map
311 }
312
313 #[test]
314 fn test_config_serialization() {
315 let config = Config {
317 uid_root: Some(UidRoot("1.2.826.0.1.3680043.10.188".to_string())),
318 tag_actions: create_sample_tag_actions(),
319 remove_private_tags: Some(true),
320 remove_curves: Some(false),
321 remove_overlays: Some(true),
322 ..Default::default()
323 };
324
325 let json = serde_json::to_string_pretty(&config).unwrap();
327
328 assert!(json.contains(r#""uid_root": "1.2.826.0.1.3680043.10.188"#));
330 assert!(json.contains(r#""remove_private_tags": true"#));
331 assert!(json.contains(r#""remove_curves": false"#));
332 assert!(json.contains(r#""remove_overlays": true"#));
333
334 assert!(json.contains(r#""00100010""#)); assert!(json.contains(r#""action": "empty""#));
337 assert!(json.contains(r#""00100020""#)); assert!(json.contains(r#""action": "remove""#));
339 assert!(json.contains(r#""00080050""#)); assert!(json.contains(r#""action": "hash""#));
341 }
342
343 #[test]
344 fn test_config_deserialization() {
345 let json = r#"{
347 "uid_root": "1.2.826.0.1.3680043.10.188",
348 "remove_private_tags": true,
349 "remove_curves": false,
350 "remove_overlays": true,
351 "tag_actions": {
352 "(0010,0010)": {"action": "empty"},
353 "(0010,0020)": {"action": "remove"},
354 "(0008,0050)": {"action": "hash"}
355 }
356 }"#;
357
358 let config: Config = serde_json::from_str(json).unwrap();
360
361 assert_eq!(config.uid_root.unwrap().0, "1.2.826.0.1.3680043.10.188");
363 assert_eq!(config.remove_private_tags, Some(true));
364 assert_eq!(config.remove_curves, Some(false));
365 assert_eq!(config.remove_overlays, Some(true));
366
367 let patient_name = config.tag_actions.get(&Tag(0x0010, 0x0010)).unwrap();
369 match patient_name {
370 Action::Empty => { }
371 _ => panic!("Expected Empty action for Patient Name"),
372 }
373
374 let patient_id = config.tag_actions.get(&Tag(0x0010, 0x0020)).unwrap();
375 match patient_id {
376 Action::Remove => { }
377 _ => panic!("Expected Remove action for Patient ID"),
378 }
379
380 let accession = config.tag_actions.get(&Tag(0x0008, 0x0050)).unwrap();
381 match accession {
382 Action::Hash { length } => {
383 assert_eq!(*length, None);
384 }
385 _ => panic!("Expected Hash action for Accession Number"),
386 }
387 }
388
389 #[test]
390 fn test_config_roundtrip() {
391 let original_config = Config {
393 uid_root: Some(UidRoot("1.2.826.0.1.3680043.10.188".to_string())),
394 tag_actions: create_sample_tag_actions(),
395 remove_private_tags: Some(true),
396 remove_curves: Some(false),
397 remove_overlays: Some(true),
398 ..Default::default()
399 };
400
401 let json = serde_json::to_string(&original_config).unwrap();
403 let deserialized: Config = serde_json::from_str(&json).unwrap();
404
405 assert_eq!(
407 original_config.uid_root.unwrap().0,
408 deserialized.uid_root.unwrap().0
409 );
410
411 assert_eq!(
413 original_config.remove_private_tags,
414 deserialized.remove_private_tags
415 );
416 assert_eq!(original_config.remove_curves, deserialized.remove_curves);
417 assert_eq!(
418 original_config.remove_overlays,
419 deserialized.remove_overlays
420 );
421
422 let tags_to_check = [
424 Tag(0x0010, 0x0010), Tag(0x0010, 0x0020), Tag(0x0008, 0x0050), ];
428
429 for tag in &tags_to_check {
430 let original_action = original_config.tag_actions.get(tag);
431 let deserialized_action = deserialized.tag_actions.get(tag);
432
433 assert_eq!(
434 original_action, deserialized_action,
435 "Action for tag ({}) didn't roundtrip correctly",
436 tag,
437 );
438 }
439 }
440
441 #[test]
442 fn test_empty_tag_actions() {
443 let empty_map = TagActionMap::new();
445 let config = Config {
446 uid_root: Some(UidRoot("1.2.826.0.1.3680043.10.188".to_string())),
447 tag_actions: empty_map,
448 ..Default::default()
449 };
450
451 let json = serde_json::to_string(&config).unwrap();
453 let deserialized: Config = serde_json::from_str(&json).unwrap();
454
455 assert_eq!(
456 deserialized.uid_root.unwrap().0,
457 "1.2.826.0.1.3680043.10.188"
458 );
459 assert_eq!(deserialized.remove_private_tags, None);
460 assert_eq!(deserialized.remove_curves, None);
461 assert_eq!(deserialized.remove_overlays, None);
462 assert_eq!(deserialized.tag_actions.len(), 0);
463 }
464
465 #[test]
466 fn test_partial_config_deserialization() {
467 let json = r#"{
468 "uid_root": "1.2.826.0.1.3680043.10.188",
469 "tag_actions": {
470 "(0010,0010)": {"action": "empty"}
471 }
472 }"#;
473
474 let result: Result<Config, _> = serde_json::from_str(json);
475 let config = result.unwrap();
476
477 assert_eq!(config.uid_root.unwrap().0, "1.2.826.0.1.3680043.10.188");
478 assert_eq!(config.remove_private_tags, None);
479 assert_eq!(config.remove_curves, None);
480 assert_eq!(config.remove_overlays, None);
481 assert_eq!(config.tag_actions.len(), 1);
482 }
483
484 #[test]
485 fn test_empty_uid_root_and_tag_actions() {
486 let json = r#"{
487 "uid_root": "",
488 "remove_private_tags": true,
489 "remove_curves": false,
490 "remove_overlays": true,
491 "tag_actions": {}
492 }"#;
493
494 let result: Result<Config, _> = serde_json::from_str(json);
495 let config = result.unwrap();
496
497 assert_eq!(config.uid_root.unwrap().0, "");
498 assert_eq!(config.remove_private_tags, Some(true));
499 assert_eq!(config.remove_curves, Some(false));
500 assert_eq!(config.remove_overlays, Some(true));
501 assert_eq!(config.tag_actions.len(), 0);
502 }
503
504 #[test]
505 fn test_missing_uid_root() {
506 let json = r#"{
507 "remove_private_tags": true,
508 "remove_curves": false,
509 "remove_overlays": true,
510 "tag_actions": {}
511 }"#;
512
513 let result: Result<Config, _> = serde_json::from_str(json);
514 let config = result.unwrap();
515
516 assert_eq!(config.uid_root, None);
517 assert_eq!(config.remove_private_tags, Some(true));
518 assert_eq!(config.remove_curves, Some(false));
519 assert_eq!(config.remove_overlays, Some(true));
520 assert_eq!(config.tag_actions.len(), 0);
521 }
522
523 #[test]
524 fn test_default_remove_fields() {
525 let json = r#"{
526 "uid_root": "9999",
527 "tag_actions": {}
528 }"#;
529
530 let result: Result<Config, _> = serde_json::from_str(json);
531 let config = result.unwrap();
532
533 assert_eq!(config.uid_root, Some(UidRoot("9999".into())));
534 assert_eq!(config.remove_private_tags, None);
535 assert_eq!(config.remove_curves, None);
536 assert_eq!(config.remove_overlays, None);
537 assert_eq!(config.tag_actions.len(), 0);
538 }
539
540 #[test]
541 fn test_only_empty_tag_actions() {
542 let json = r#"{
543 "tag_actions": {}
544 }"#;
545
546 let result: Result<Config, _> = serde_json::from_str(json);
547 let config = result.unwrap();
548
549 assert_eq!(config.uid_root, None);
550 assert_eq!(config.remove_private_tags, None);
551 assert_eq!(config.remove_curves, None);
552 assert_eq!(config.remove_overlays, None);
553 assert_eq!(config.tag_actions.len(), 0);
554 }
555
556 #[test]
557 fn test_empty_json() {
558 let json = r#"{}"#;
559
560 let result: Result<Config, _> = serde_json::from_str(json);
561 let config = result.unwrap();
562
563 assert_eq!(config.uid_root, None);
564 assert_eq!(config.remove_private_tags, None);
565 assert_eq!(config.remove_curves, None);
566 assert_eq!(config.remove_overlays, None);
567 assert_eq!(config.tag_actions.len(), 0);
568 }
569
570 #[test]
571 fn test_malformed_config() {
572 let json = r#"{
574 "uid_root": "1.2.826.0.1.3680043.10.188",
575 "remove_private_tags": true,
576 "remove_curves": false,
577 "remove_overlays": true,
578 "tag_actions": {
579 "invalid_tag_format": {"action": "empty"}
580 }
581 }"#;
582
583 let result: Result<Config, _> = serde_json::from_str(json);
584 assert!(result.is_err());
585
586 let json = r#"{
588 "uid_root": "1.2.826.0.1.3680043.10.188",
589 "remove_private_tags": true,
590 "remove_curves": false,
591 "remove_overlays": true,
592 "tag_actions": {
593 "(0010,0010)": {"action": "invalid_action"}
594 },
595 }"#;
596
597 let result: Result<Config, _> = serde_json::from_str(json);
598 assert!(result.is_err());
599 }
600}