1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
use std::cmp::Ordering;
use std::fmt;

use serde_derive::{Deserialize, Serialize};

use crate::algorithm::Algorithm;
use crate::errors::Error;

/**
Represents a single algorithm/digest pair.

This is mostly internal, although users might interact with it directly on
occasion.
*/
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Hash {
    pub algorithm: Algorithm,
    pub digest: String,
}

impl PartialOrd for Hash {
    fn partial_cmp(&self, other: &Hash) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for Hash {
    fn cmp(&self, other: &Hash) -> Ordering {
        self.algorithm.cmp(&other.algorithm)
    }
}
impl fmt::Display for Hash {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}-{}", self.algorithm, self.digest)
    }
}

impl std::str::FromStr for Hash {
    type Err = Error;

    fn from_str(s: &str) -> Result<Hash, Self::Err> {
        let mut parsed = s.trim().split(|c| c == '-');
        let algorithm = parsed
            .next()
            .ok_or(Error::ParseIntegrityError(s.into()))?
            .parse()?;
        let digest = String::from(parsed.next().ok_or(Error::ParseIntegrityError(s.into()))?);
        Ok(Hash { algorithm, digest })
    }
}

#[cfg(test)]
mod tests {
    use super::Algorithm;
    use super::Hash;

    #[test]
    fn hash_stringify() {
        assert_eq!(
            format!(
                "{}",
                Hash {
                    algorithm: Algorithm::Sha256,
                    digest: String::from("deadbeef==")
                }
            ),
            "sha256-deadbeef=="
        )
    }

    #[test]
    fn parsing() {
        assert_eq!(
            " sha256-deadbeef== \n".parse::<Hash>().unwrap(),
            Hash {
                algorithm: Algorithm::Sha256,
                digest: String::from("deadbeef==")
            }
        )
    }

    #[test]
    #[should_panic]
    fn bad_algorithm() {
        // TODO - test the actual error returned when it's more valuable
        "sha7-deadbeef==".parse::<Hash>().unwrap();
    }

    #[test]
    fn ordering() {
        let mut arr = [
            Hash {
                algorithm: Algorithm::Sha1,
                digest: String::from("foo=="),
            },
            Hash {
                algorithm: Algorithm::Sha256,
                digest: String::from("foo=="),
            },
            Hash {
                algorithm: Algorithm::Sha384,
                digest: String::from("foo=="),
            },
            Hash {
                algorithm: Algorithm::Sha512,
                digest: String::from("foo=="),
            },
        ];
        arr.sort_unstable();
        assert_eq!(
            arr,
            [
                Hash {
                    algorithm: Algorithm::Sha512,
                    digest: String::from("foo==")
                },
                Hash {
                    algorithm: Algorithm::Sha384,
                    digest: String::from("foo==")
                },
                Hash {
                    algorithm: Algorithm::Sha256,
                    digest: String::from("foo==")
                },
                Hash {
                    algorithm: Algorithm::Sha1,
                    digest: String::from("foo==")
                }
            ]
        )
    }
}