Ver Fonte

Wrote code for parsing a Path string into its components.

Matthew Carr há 3 anos atrás
pai
commit
f58789b37d
2 ficheiros alterados com 186 adições e 4 exclusões
  1. 168 0
      crates/node/src/main.rs
  2. 18 4
      crates/node/src/serde_tests.rs

+ 168 - 0
crates/node/src/main.rs

@@ -1,6 +1,8 @@
 use std::{
     collections::HashMap,
+    convert::TryFrom,
     hash::Hash as Hashable,
+    fmt::{self, Display, Formatter},
 };
 use serde::{Serialize, Deserialize};
 use serde_big_array::BigArray;
@@ -96,6 +98,18 @@ struct FragmentRecord {
     stored_by: Principal,
 }
 
+impl FragmentRecord {
+    /// Creates a new `FragmentRecord` whose `serial` and `stored_by` fields are set to
+    /// the given values.
+    #[allow(dead_code)]
+    fn new(serial: u32, stored_by: Hash) -> FragmentRecord {
+        FragmentRecord {
+            serial: FragmentSerial(serial),
+            stored_by: Principal(stored_by),
+        }
+    }
+}
+
 /// An identifier for a security principal, which is any entity that can be authenticated.
 #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Hashable)]
 struct Principal(Hash);
@@ -108,6 +122,109 @@ struct Ciphertext<T>(T);
 #[derive(Debug, PartialEq, Serialize, Deserialize)]
 struct Path(Vec<String>);
 
+impl Path {
+    /// The character that is used to separate path components.
+    const SEP: char = '/';
+    /// The limit, in bytes, of a `Path`'s length.
+    const BYTE_LIMIT: usize = 4096;
+}
+
+/// Errors which can occur when converting a string to a `Path`.
+#[derive(Debug, PartialEq)]
+enum PathError {
+    /// Occurs when the number of bytes in a string is greater than `Path::BYTE_LIMIT`.
+    PathTooLong(usize),
+    /// Indicates that a path string was empty.
+    Empty,
+    /// Occurs when a component in a path string was empty.
+    EmptyComponent,
+}
+
+impl Display for PathError {
+    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
+       match self {
+           PathError::PathTooLong(length) => formatter.write_fmt(format_args!(
+                "path contained {} bytes, which is over the {} byte limit",
+                length,
+                Path::BYTE_LIMIT)
+           ),
+           PathError::Empty => formatter.write_str("path was empty"),
+           PathError::EmptyComponent => formatter.write_str("component of path was empty"),
+       }
+    }
+}
+
+impl Path {
+    fn consume_component<I: Iterator<Item = (usize, char)>>(
+        start: usize, first: char, pairs: &mut I
+    ) -> Result<usize, PathError> {
+        if first == Path::SEP {
+            return Err(PathError::EmptyComponent);
+        }
+        let end;
+        let mut last = start;
+        loop {
+            match pairs.next() {
+                Some((index, Path::SEP)) => {
+                    end = index;
+                    break;
+                },
+                Some((index, _)) => last = index,
+                None => {
+                    end = last + 1;
+                    break
+                }
+            }
+        }
+        if end == start {
+            Err(PathError::EmptyComponent)
+        }
+        else {
+            Ok(end)
+        }
+    }
+
+    fn trim_leading_sep(mut string: &str) -> Result<&str, PathError> {
+        let mut pairs = string.char_indices();
+        string = match pairs.next() {
+            Some((_, Path::SEP)) => {
+                match pairs.next() {
+                    Some((index, _)) => &string[index..],
+                    None => return Err(PathError::Empty)
+                }
+            },
+            Some((_, _)) => string,
+            None => return Err(PathError::Empty)
+        };
+        Ok(string)
+    }
+}
+
+impl<'s> TryFrom<&'s str> for Path {
+    type Error = PathError;
+
+    fn try_from(mut string: &'s str) -> Result<Path, PathError> {
+        {
+            let len = string.len();
+            if len >= Path::BYTE_LIMIT {
+                return Err(PathError::PathTooLong(len))
+            }
+        }
+        string = Path::trim_leading_sep(string)?;
+        let mut pairs = string.char_indices();
+        let mut components = Vec::new();
+        loop {
+            let (start, end) = match pairs.next() {
+                Some((start, c)) => (start, Path::consume_component(start, c, &mut pairs)),
+                None => break
+            };
+            let slice = &string[start..end?];
+            components.push(slice.to_string());
+        }
+        Ok(Path(components))
+    }
+}
+
 /// An instant in time represented by the number of seconds since January 1st 1970, 00:00:00 UTC.
 #[derive(Debug, PartialEq, Serialize, Deserialize)]
 struct Epoch(u64);
@@ -143,3 +260,54 @@ enum Key {
 fn main() {
     println!("Hello, world!");
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    fn path_from_str_test_case(
+        expected: Result<Path, PathError>, input: &str
+    ) -> Result<(), PathError> {
+        let result = Path::try_from(input);
+        assert_eq!(expected, result);
+        Ok(())
+    }
+
+    #[test]
+    fn path_from_str_multiple_components_ok() -> Result<(), PathError> {
+        let expected = Path(vec!["red".to_string(), "green".to_string(), "blue".to_string()]);
+        path_from_str_test_case(Ok(expected), "red/green/blue")?;
+        Ok(())
+    }
+
+    #[test]
+    fn path_from_str_one_component_ok() -> Result<(), PathError> {
+        let expected = Path(vec!["red".to_string()]);
+        path_from_str_test_case(Ok(expected), "red")?;
+        Ok(())
+    }
+
+    #[test]
+    fn path_from_str_leading_slash_ok() -> Result<(), PathError> {
+        let expected = Path(vec![
+            "orange".to_string(), "banana".to_string(), "shotgun".to_string()
+        ]);
+        path_from_str_test_case(Ok(expected), "/orange/banana/shotgun")?;
+        Ok(())
+    }
+
+    #[test]
+    fn path_from_str_path_too_long_fail() -> Result<(), PathError> {
+        let expected = Err(PathError::PathTooLong(4097));
+        let input = "*".repeat(4097);
+        path_from_str_test_case(expected, input.as_str())?;
+        Ok(())
+    }
+
+    #[test]
+    fn path_from_str_multiple_slashes_fail() -> Result<(), PathError> {
+        let expected = Err(PathError::EmptyComponent);
+        path_from_str_test_case(expected, "//orange")?;
+        Ok(())
+    }
+}

+ 18 - 4
crates/node/src/serde_tests.rs

@@ -5,14 +5,28 @@ use serde_block_tree::{Result, from_vec, to_vec};
 
 static RANDOM_32ARRAY: [u8; 32] = [
     0x75, 0x28, 0xA9, 0xE0, 0x9D, 0x24, 0xBA, 0xB3, 0x79, 0x56, 0x15, 0x68, 0xFD, 0xA4, 0xE2, 0xA4,
-    0xCF, 0xB2, 0xC, 0xE3, 0x96, 0xAE, 0xA2, 0x6E, 0x45, 0x15, 0x50, 0xED, 0xA6, 0xBE, 0x6D, 0xEC
+    0xCF, 0xB2, 0xC0, 0xE3, 0x96, 0xAE, 0xA2, 0x6E, 0x45, 0x15, 0x50, 0xED, 0xA6, 0xBE, 0x6D, 0xEC,
 ];
 
 #[test]
 fn roundtrip_fragment_record() -> Result<()> {
-    let expected = FragmentRecord {
-        serial: FragmentSerial(229),
-        stored_by: Principal(Hash::Sha2_256(RANDOM_32ARRAY))
+    let expected = FragmentRecord::new(229, Hash::Sha2_256(RANDOM_32ARRAY));
+    let ser_result = to_vec(&expected);
+    let de_result = from_vec(&ser_result?);
+    assert_eq!(expected, de_result?);
+    Ok(())
+}
+
+#[test]
+fn roundtrip_directory() -> Result<()> {
+    let principal = Principal(Hash::Sha2_256(RANDOM_32ARRAY));
+    let mut child = HashMap::new();
+    child.insert(FragmentSerial(0), FragmentRecord::new(0, principal.0));
+    let mut children = HashMap::new();
+    children.insert(".metadata".to_string(), child);
+    let expected = Directory {
+        nodes: vec![Principal(Hash::Sha2_256(RANDOM_32ARRAY))],
+        children
     };
     let ser_result = to_vec(&expected);
     let de_result = from_vec(&ser_result?);