summaryrefslogtreecommitdiff
path: root/src/chunkmeta.rs
blob: 37e2ed56cfd7ae546aa66dbf1dd81954c5334386 (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
135
136
137
138
139
140
141
142
143
144
use serde::{Deserialize, Serialize};
use std::default::Default;
use std::str::FromStr;

/// Metadata about chunks.
///
/// We manage three bits of metadata about chunks, in addition to its
/// identifier:
///
/// * for all chunks, a [SHA256][] checksum of the chunk content
///
/// * for generation chunks, an indication that it is a generation
///   chunk, and a timestamp for when making the generation snapshot
///   ended
///
/// There is no syntax or semantics imposed on the timestamp, but a
/// client should probably use [ISO 8601][] representation.
///
/// For HTTP, the metadata will be serialised as a JSON object, like this:
///
/// ~~~json
/// {
///     "sha256": "09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b",
///     "generation": true,
///     "ended": "2020-09-17T08:17:13+03:00"
/// }
/// ~~~
///
/// This module provides functions for serializing to and from JSON.
/// The JSON doesn't have to include the fields for generations if
/// they're not needed, although when serialized, they will always be
/// there.
///
/// After chunk metadata is created, it is immutable.
///
/// [ISO 8601]: https://en.wikipedia.org/wiki/ISO_8601
/// [SHA256]: https://en.wikipedia.org/wiki/SHA-2
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct ChunkMeta {
    sha256: String,
    // The remaining fields are Options so that JSON parsing doesn't
    // insist on them being there in the textual representation.
    generation: Option<bool>,
    ended: Option<String>,
}

impl ChunkMeta {
    /// Create a new data chunk.
    ///
    /// Data chunks are not for generations.
    pub fn new(sha256: &str) -> Self {
        ChunkMeta {
            sha256: sha256.to_string(),
            generation: None,
            ended: None,
        }
    }

    /// Create a new generation chunk.
    pub fn new_generation(sha256: &str, ended: &str) -> Self {
        ChunkMeta {
            sha256: sha256.to_string(),
            generation: Some(true),
            ended: Some(ended.to_string()),
        }
    }

    /// Is this a generation chunk?
    pub fn is_generation(&self) -> bool {
        matches!(self.generation, Some(true))
    }

    /// When did this generation end?
    pub fn ended(&self) -> Option<&str> {
        self.ended.as_deref()
    }

    /// SHA256 checksum of the content of the chunk.
    pub fn sha256(&self) -> &str {
        &self.sha256
    }

    /// Serialize as JSON.
    pub fn to_json(&self) -> String {
        serde_json::to_string(self).unwrap()
    }
}

impl FromStr for ChunkMeta {
    type Err = serde_json::error::Error;

    /// Parse a JSON representation metadata.
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        serde_json::from_str(s)
    }
}

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

    #[test]
    fn new_creates_data_chunk() {
        let meta = ChunkMeta::new("abcdef");
        assert!(!meta.is_generation());
        assert_eq!(meta.ended(), None);
        assert_eq!(meta.sha256(), "abcdef");
    }

    #[test]
    fn new_generation_creates_generation_chunk() {
        let meta = ChunkMeta::new_generation("abcdef", "2020-09-17T08:17:13+03:00");
        assert!(meta.is_generation());
        assert_eq!(meta.ended(), Some("2020-09-17T08:17:13+03:00"));
        assert_eq!(meta.sha256(), "abcdef");
    }

    #[test]
    fn data_chunk_from_json() {
        let meta: ChunkMeta = r#"{"sha256": "abcdef"}"#.parse().unwrap();
        assert!(!meta.is_generation());
        assert_eq!(meta.ended(), None);
        assert_eq!(meta.sha256(), "abcdef");
    }

    #[test]
    fn generation_chunk_from_json() {
        let meta: ChunkMeta =
            r#"{"sha256": "abcdef", "generation": true, "ended": "2020-09-17T08:17:13+03:00"}"#
                .parse()
                .unwrap();
        assert!(meta.is_generation());
        assert_eq!(meta.ended(), Some("2020-09-17T08:17:13+03:00"));
        assert_eq!(meta.sha256(), "abcdef");
    }

    #[test]
    fn json_roundtrip() {
        let meta = ChunkMeta::new_generation("abcdef", "2020-09-17T08:17:13+03:00");
        let json = serde_json::to_string(&meta).unwrap();
        let meta2 = serde_json::from_str(&json).unwrap();
        assert_eq!(meta, meta2);
    }
}