From 7155b46602f741694debb4e1764bfa74f3d5c25b Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Thu, 17 Sep 2020 08:42:58 +0300 Subject: feat: add initial ChunkMeta struct --- src/chunkmeta.rs | 147 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + 2 files changed, 148 insertions(+) create mode 100644 src/chunkmeta.rs (limited to 'src') diff --git a/src/chunkmeta.rs b/src/chunkmeta.rs new file mode 100644 index 0000000..952dc8d --- /dev/null +++ b/src/chunkmeta.rs @@ -0,0 +1,147 @@ +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(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, + ended: Option, +} + +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 { + match self.generation { + Some(true) => true, + _ => false, + } + } + + /// When did this generation end? + pub fn ended(&self) -> Option<&str> { + self.ended.as_deref().map(|s| s) + } + + /// 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 metdata. + fn from_str(s: &str) -> Result { + 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); + } +} diff --git a/src/lib.rs b/src/lib.rs index dd83e5b..a0a24d4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1 +1,2 @@ pub mod chunkid; +pub mod chunkmeta; -- cgit v1.2.1