dicom_anonymization/
actions.rs

1use crate::config::ConfigError;
2use dicom_core::Tag;
3use thiserror::Error;
4
5const HASH_LENGTH_MINIMUM: usize = 8;
6
7#[derive(Error, Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
8#[error("{0}")]
9pub struct HashLengthError(String);
10
11/// A newtype wrapper for specifying the length of a hash value.
12/// The internal value represents the number of characters the hash should be.
13#[derive(Debug, Clone, Copy, PartialEq)]
14pub struct HashLength(pub(crate) usize);
15
16impl HashLength {
17    /// Creates a new [`HashLength`] instance.
18    ///
19    /// # Arguments
20    /// * `length` - The desired length of the hash in characters
21    ///
22    /// # Returns
23    /// * `Ok(HashLength)` if length is valid (>= `HASH_LENGTH_MINIMUM`, which is `8`)
24    /// * `Err(HashLengthError)` if length is too short
25    pub fn new(length: usize) -> Result<Self, HashLengthError> {
26        if length < HASH_LENGTH_MINIMUM {
27            return Err(HashLengthError(format!(
28                "hash length must be at least {}",
29                HASH_LENGTH_MINIMUM
30            )));
31        }
32        Ok(HashLength(length))
33    }
34}
35
36impl From<HashLengthError> for ConfigError {
37    fn from(err: HashLengthError) -> Self {
38        ConfigError::InvalidHashLength(err.0)
39    }
40}
41
42impl TryFrom<usize> for HashLength {
43    type Error = HashLengthError;
44
45    fn try_from(value: usize) -> Result<Self, HashLengthError> {
46        let hash_length = HashLength::new(value)?;
47        Ok(hash_length)
48    }
49}
50
51/// Specifies the action to perform on DICOM data elements during processing.
52#[derive(Debug, Clone, PartialEq)]
53pub enum Action {
54    /// Clear the value of the data element.
55    Empty,
56
57    /// Completely remove the data element from the DICOM dataset.
58    Remove,
59
60    /// Replace the data element value with the specified string.
61    Replace(String),
62
63    /// Hash the data element value using an optional custom hash length.
64    Hash(Option<HashLength>),
65
66    /// Change a date, using a hash of the given tag value to determine the offset.
67    HashDate(Tag),
68
69    /// Generate a new unique identifier (UID) by hashing the original UID.
70    HashUID,
71
72    /// Preserve the original data element value without modification.
73    Keep,
74
75    /// No action specified.
76    None,
77}
78
79#[cfg(test)]
80mod tests {
81    use crate::actions::HashLength;
82
83    #[test]
84    fn test_hash_length() {
85        assert_eq!(HashLength::new(9).unwrap().0, 9);
86    }
87
88    #[test]
89    fn test_hash_length_new() {
90        assert!(HashLength::new(9).is_ok());
91        assert!(HashLength::new(8).is_ok());
92        assert!(HashLength::new(7).is_err());
93    }
94
95    #[test]
96    fn test_hash_length_try_into() {
97        assert!(<usize as TryInto<HashLength>>::try_into(9).is_ok());
98        assert!(<usize as TryInto<HashLength>>::try_into(8).is_ok());
99        assert!(<usize as TryInto<HashLength>>::try_into(7).is_err());
100    }
101
102    #[test]
103    fn test_hash_length_error() {
104        let result = HashLength::new(7);
105        assert!(result.is_err());
106        let error = result.unwrap_err();
107        assert_eq!(error.to_string(), "hash length must be at least 8");
108    }
109}