|
@@ -137,7 +137,10 @@ struct Principal(Hash);
|
|
|
|
|
|
/// An identifier for a block in a tree.
|
|
|
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
|
|
|
-struct Path(Vec<String>, Principal);
|
|
|
+struct Path {
|
|
|
+ owner: Principal,
|
|
|
+ components: Vec<String>,
|
|
|
+}
|
|
|
|
|
|
impl Path {
|
|
|
/// The character that is used to separate path components.
|
|
@@ -176,21 +179,6 @@ impl Path {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- 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)
|
|
|
- }
|
|
|
-
|
|
|
/// Asserts that the number of bytes in the given string is no more than `Path::BYTE_LIMIT`.
|
|
|
fn assert_not_too_long(string: &str) -> Result<(), PathError> {
|
|
|
let len = string.len();
|
|
@@ -204,9 +192,8 @@ impl Path {
|
|
|
impl<'s> TryFrom<&'s str> for Path {
|
|
|
type Error = PathError;
|
|
|
|
|
|
- fn try_from(mut string: &'s str) -> Result<Path, PathError> {
|
|
|
+ fn try_from(string: &'s str) -> Result<Path, PathError> {
|
|
|
Path::assert_not_too_long(string)?;
|
|
|
- string = Path::trim_leading_sep(string)?;
|
|
|
let mut pairs = string.char_indices();
|
|
|
let mut components = Vec::new();
|
|
|
let mut last_end = 0;
|
|
@@ -223,18 +210,21 @@ impl<'s> TryFrom<&'s str> for Path {
|
|
|
let leading = components.get(0).ok_or(PathError::InvalidLeadingComponent)?;
|
|
|
let hash = Hash::try_from(leading.as_str())
|
|
|
.map_err(|_| PathError::InvalidLeadingComponent)?;
|
|
|
- Ok(Path(components, Principal(hash)))
|
|
|
+ Ok(Path {
|
|
|
+ owner: Principal(hash),
|
|
|
+ components,
|
|
|
+ })
|
|
|
}
|
|
|
}
|
|
|
|
|
|
impl Display for Path {
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
- if self.0.is_empty() {
|
|
|
- return write!(f, "{}", Path::SEP);
|
|
|
+ if self.components.is_empty() {
|
|
|
+ return write!(f, "");
|
|
|
};
|
|
|
- let mut iter = self.0.iter();
|
|
|
+ let mut iter = self.components.iter();
|
|
|
let first = iter.next().unwrap();
|
|
|
- let mut output = write!(f, "{}{}", Path::SEP, first);
|
|
|
+ let mut output = write!(f, "{}", first);
|
|
|
for component in iter {
|
|
|
output = write!(f, "{}{}", Path::SEP, component)
|
|
|
}
|
|
@@ -299,7 +289,7 @@ mod tests {
|
|
|
#[test]
|
|
|
fn path_from_str_multiple_components_ok() -> Result<(), PathError> {
|
|
|
let expected = make_path(vec!["red", "green", "blue"]);
|
|
|
- let input = format!("{}/red/green/blue", expected.1.0);
|
|
|
+ let input = format!("{}/red/green/blue", expected.owner.0);
|
|
|
path_from_str_test_case(Ok(expected), input.as_str())?;
|
|
|
Ok(())
|
|
|
}
|
|
@@ -307,26 +297,17 @@ mod tests {
|
|
|
#[test]
|
|
|
fn path_from_str_one_component_ok() -> Result<(), PathError> {
|
|
|
let expected = make_path(vec![]);
|
|
|
- let input = expected.1.0.to_string();
|
|
|
+ let input = expected.owner.0.to_string();
|
|
|
path_from_str_test_case(Ok(expected), input.as_str())?;
|
|
|
Ok(())
|
|
|
}
|
|
|
|
|
|
- #[test]
|
|
|
- fn path_from_str_leading_slash_ok() -> Result<(), PathError> {
|
|
|
- let expected = make_path(vec!["orange", "banana", "shotgun"]);
|
|
|
- let principal = expected.1.clone();
|
|
|
- path_from_str_test_case(
|
|
|
- Ok(expected), format!("/{}/orange/banana/shotgun", principal.0).as_str())?;
|
|
|
- Ok(())
|
|
|
- }
|
|
|
-
|
|
|
#[test]
|
|
|
fn path_from_str_trailing_slash_ok() -> Result<(), PathError> {
|
|
|
+ // Notice the empty component at the end of this path due to the trailing slash.
|
|
|
let expected = make_path(vec!["orange", "banana", "shotgun", ""]);
|
|
|
- let principal = expected.1.clone();
|
|
|
- path_from_str_test_case(
|
|
|
- Ok(expected), format!("{}/orange/banana/shotgun/", principal.0).as_str())?;
|
|
|
+ let input = format!("{}/orange/banana/shotgun/", expected.owner.0);
|
|
|
+ path_from_str_test_case(Ok(expected), input.as_str())?;
|
|
|
Ok(())
|
|
|
}
|
|
|
|
|
@@ -341,9 +322,25 @@ mod tests {
|
|
|
|
|
|
#[test]
|
|
|
fn path_from_str_multiple_slashes_fail() -> Result<(), PathError> {
|
|
|
- let principal = make_principal();
|
|
|
let expected = Err(PathError::EmptyComponent);
|
|
|
- path_from_str_test_case(expected, format!("{}//orange", principal.0).as_str())?;
|
|
|
+ let input = format!("{}//orange", make_principal().0);
|
|
|
+ path_from_str_test_case(expected, input.as_str())?;
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn path_from_str_leading_slash_fail() -> Result<(), PathError> {
|
|
|
+ let expected = Err(PathError::EmptyComponent);
|
|
|
+ let input = format!("/{}/orange/banana/shotgun", make_principal().0);
|
|
|
+ path_from_str_test_case(expected, input.as_str())?;
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn path_round_trip() -> Result<(), PathError> {
|
|
|
+ let expected = make_path(vec!["interstitial", "inter-related", "intersections"]);
|
|
|
+ let actual = Path::try_from(expected.to_string().as_str())?;
|
|
|
+ assert_eq!(expected, actual);
|
|
|
Ok(())
|
|
|
}
|
|
|
}
|