summaryrefslogtreecommitdiff
path: root/src/chunkid.rs
blob: 353462759230042c61d0c118d37de15f8c10f86c (plain)
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
133
134
//! The identifier for a chunk.
//!
//! Chunk identifiers are chosen by the server. Each chunk has a
//! unique identifier, which isn't based on the contents of the chunk.

use crate::checksummer::Checksum;
use rusqlite::types::ToSqlOutput;
use rusqlite::ToSql;
use serde::{Deserialize, Serialize};
use std::ffi::OsStr;
use std::fmt;
use std::hash::Hash;
use std::str::FromStr;
use uuid::Uuid;

/// An identifier for a chunk.
///
/// An identifier is chosen randomly in such a way that even in
/// extremely large numbers of identifiers the likelihood of duplicate
/// identifiers is so small it can be ignored. The current
/// implementation uses UUID4 and provides a 122-bit random number.
/// For a discussion on collision likelihood, see
/// <https://en.wikipedia.org/wiki/Universally_unique_identifier#Collisions>.
///
/// We also need to be able to re-create identifiers from stored
/// values. When an identifier is formatted as a string and parsed
/// back, the result is the same value.
///
/// Because every identifier is meant to be different, there is no
/// default value, since default values should be identical.
#[derive(Debug, Eq, PartialEq, Clone, Hash, Serialize, Deserialize)]
pub struct ChunkId {
    id: String,
}

#[allow(clippy::new_without_default)]
impl ChunkId {
    /// Construct a new, random identifier.
    pub fn new() -> Self {
        ChunkId {
            id: Uuid::new_v4().to_string(),
        }
    }

    /// Re-construct an identifier from a previous value.
    pub fn recreate(s: &str) -> Self {
        ChunkId { id: s.to_string() }
    }

    /// Return the identifier as a slice of bytes.
    pub fn as_bytes(&self) -> &[u8] {
        self.id.as_bytes()
    }

    /// Return the SHA256 checksum of the identifier.
    pub fn sha256(&self) -> Checksum {
        Checksum::sha256(self.id.as_bytes())
    }
}

impl ToSql for ChunkId {
    /// Format identifier for SQL.
    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput> {
        Ok(ToSqlOutput::Owned(rusqlite::types::Value::Text(
            self.id.clone(),
        )))
    }
}

impl fmt::Display for ChunkId {
    /// Format an identifier for display.
    ///
    /// The output can be parsed to re-created an identical identifier.
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.id)
    }
}

impl From<&String> for ChunkId {
    /// Create a chunk identifier from a string.
    fn from(s: &String) -> Self {
        ChunkId { id: s.to_string() }
    }
}

impl From<&OsStr> for ChunkId {
    /// Create a chunk identifier from an operating system string.
    fn from(s: &OsStr) -> Self {
        ChunkId {
            id: s.to_string_lossy().to_string(),
        }
    }
}

impl FromStr for ChunkId {
    type Err = ();

    /// Create a chunk from a string.
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(ChunkId::recreate(s))
    }
}

#[cfg(test)]
mod test {
    use super::ChunkId;

    #[test]
    fn to_string() {
        let id = ChunkId::new();
        assert_ne!(id.to_string(), "")
    }

    #[test]
    fn never_the_same() {
        let id1 = ChunkId::new();
        let id2 = ChunkId::new();
        assert_ne!(id1, id2);
    }

    #[test]
    fn recreatable() {
        let id_str = "xyzzy"; // it doesn't matter what the string representation is
        let id: ChunkId = id_str.parse().unwrap();
        assert_eq!(id.to_string(), id_str);
    }

    #[test]
    fn survives_round_trip() {
        let id = ChunkId::new();
        let id_str = id.to_string();
        assert_eq!(id, ChunkId::recreate(&id_str))
    }
}