summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2020-09-17 08:42:58 +0300
committerLars Wirzenius <liw@liw.fi>2020-09-17 09:21:49 +0300
commit7155b46602f741694debb4e1764bfa74f3d5c25b (patch)
treea313997b54c5d8545726d8299d589faf42d19e78 /src
parentde64a2fe2482d4c6c31ce7c632edf422f32e68b1 (diff)
downloadobnam2-7155b46602f741694debb4e1764bfa74f3d5c25b.tar.gz
feat: add initial ChunkMeta struct
Diffstat (limited to 'src')
-rw-r--r--src/chunkmeta.rs147
-rw-r--r--src/lib.rs1
2 files changed, 148 insertions, 0 deletions
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<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 {
+ 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<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);
+ }
+}
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;