use crate::{
crypto::{BtHasher, HashKind},
Principal, Result,
};
use core::hash::Hash;
use serde::{
de::{self, Visitor},
Deserialize, Deserializer, Serialize, Serializer,
};
use std::{
fmt::{Display, Write},
hash::Hasher,
ops::{Deref, DerefMut},
result::Result as StdResult,
};
pub use private::{
BlockPath, BlockPathError, BlockPathGen, BlockPathRef, Components, RelBlockPath,
};
mod private {
use std::str::FromStr;
use super::*;
#[derive(Debug, Clone, Default)]
pub struct BlockPath(BlockPathGen<String>);
impl BlockPath {
pub const SEP: char = '/';
pub const BYTE_LIMIT: usize = 4096;
pub fn new(path: String) -> StdResult<BlockPath, BlockPathError> {
Ok(Self(BlockPathGen::new(path)?))
}
pub fn from_components<'a, I: Iterator<Item = &'a str>>(
root: Principal,
components: I,
) -> Self {
Self(BlockPathGen::from_components(root, components))
}
pub fn borrow(&self) -> BlockPathRef<'_> {
BlockPathRef(self.0.borrow())
}
}
impl Deref for BlockPath {
type Target = BlockPathGen<String>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for BlockPath {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl Display for BlockPath {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.deref().fmt(f)
}
}
impl TryFrom<String> for BlockPath {
type Error = BlockPathError;
fn try_from(value: String) -> StdResult<Self, Self::Error> {
Ok(Self(BlockPathGen::try_from(value)?))
}
}
impl<'a> TryFrom<&'a str> for BlockPath {
type Error = BlockPathError;
fn try_from(value: &'a str) -> StdResult<Self, Self::Error> {
Ok(Self(BlockPathGen::try_from(value.to_owned())?))
}
}
impl FromStr for BlockPath {
type Err = BlockPathError;
fn from_str(s: &str) -> StdResult<Self, Self::Err> {
Self::try_from(s)
}
}
impl Serialize for BlockPath {
fn serialize<S: Serializer>(&self, serializer: S) -> StdResult<S::Ok, S::Error> {
self.deref().serialize(serializer)
}
}
impl<'de> Deserialize<'de> for BlockPath {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> StdResult<Self, D::Error> {
Ok(Self(BlockPathGen::deserialize(deserializer)?))
}
}
impl PartialEq for BlockPath {
fn eq(&self, other: &Self) -> bool {
self.deref().eq(other.deref())
}
}
impl Eq for BlockPath {}
impl PartialOrd for BlockPath {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.deref().partial_cmp(other.deref())
}
}
impl Ord for BlockPath {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.deref().cmp(other.deref())
}
}
impl<T: AsRef<str>> PartialEq<T> for BlockPath {
fn eq(&self, other: &T) -> bool {
self.deref().eq(&other.as_ref())
}
}
impl Hash for BlockPath {
fn hash<H: Hasher>(&self, state: &mut H) {
self.deref().hash(state)
}
}
#[derive(Debug, Clone, Default)]
pub struct BlockPathRef<'a>(BlockPathGen<&'a str>);
impl<'a> BlockPathRef<'a> {
pub fn new(path: &'a str) -> StdResult<BlockPathRef, BlockPathError> {
Ok(Self(BlockPathGen::new(path)?))
}
}
impl<'a> Deref for BlockPathRef<'a> {
type Target = BlockPathGen<&'a str>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<'a> Display for BlockPathRef<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.deref().fmt(f)
}
}
impl<'a> TryFrom<&'a str> for BlockPathRef<'a> {
type Error = BlockPathError;
fn try_from(string: &'a str) -> StdResult<BlockPathRef, BlockPathError> {
Ok(Self(BlockPathGen::try_from(string)?))
}
}
impl<'a> Serialize for BlockPathRef<'a> {
fn serialize<S: Serializer>(&self, serializer: S) -> StdResult<S::Ok, S::Error> {
self.deref().serialize(serializer)
}
}
impl<'de> Deserialize<'de> for BlockPathRef<'de> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> StdResult<Self, D::Error> {
Ok(Self(BlockPathGen::deserialize(deserializer)?))
}
}
impl<'a> PartialEq for BlockPathRef<'a> {
fn eq(&self, other: &Self) -> bool {
self.deref().eq(other.deref())
}
}
impl<'a> Eq for BlockPathRef<'a> {}
impl<'a> PartialOrd for BlockPathRef<'a> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.deref().partial_cmp(other.deref())
}
}
impl<'a> Ord for BlockPathRef<'a> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.deref().cmp(other.deref())
}
}
impl<'a, T: AsRef<str>> PartialEq<T> for BlockPathRef<'a> {
fn eq(&self, other: &T) -> bool {
self.deref().eq(&other.as_ref())
}
}
impl<'a> Hash for BlockPathRef<'a> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.deref().hash(state)
}
}
#[derive(Debug, Clone, Default)]
pub struct BlockPathGen<T> {
root: Principal,
begin_rel_part: usize,
path: T,
}
impl<T: AsRef<str>> BlockPathGen<T> {
fn new(path: T) -> StdResult<Self, BlockPathError> {
let path_str = path.as_ref();
Self::assert_not_too_long(path_str)?;
let mut components = path_str.split(BlockPath::SEP);
if let Some("") = components.next() {
} else {
return Err(BlockPathError::NotAbsolute);
}
let root_str = components
.next()
.ok_or(BlockPathError::InvalidLeadingComponent)?;
let root = Principal::try_from(root_str)
.map_err(|_| BlockPathError::InvalidLeadingComponent)?;
let begin_rel_path = 2 * BlockPath::SEP.len_utf8() + root_str.len();
Ok(Self {
root,
begin_rel_part: begin_rel_path,
path,
})
}
fn assert_not_too_long(string: &str) -> StdResult<(), BlockPathError> {
let len = string.len();
if len <= BlockPath::BYTE_LIMIT {
Ok(())
} else {
Err(BlockPathError::PathTooLong(len))
}
}
pub fn contains<U: AsRef<str>>(&self, other: &BlockPathGen<U>) -> bool {
if self.root != other.root {
return false;
};
other.path.as_ref().starts_with(self.path.as_ref())
}
pub fn root(&self) -> &Principal {
&self.root
}
fn rel_part(&self) -> Option<&str> {
let path_str = self.path.as_ref();
if self.begin_rel_part < path_str.len() {
Some(&path_str[self.begin_rel_part..])
} else {
None
}
}
pub fn components(&self) -> Components<'_> {
if let Some(rel) = self.rel_part() {
Components::Split(rel.split(BlockPath::SEP))
} else if self.path.as_ref().ends_with(BlockPath::SEP) {
Components::Split("".split(BlockPath::SEP))
} else {
Components::Empty
}
}
pub fn relative_to<'a, U: AsRef<str>>(
&'a self,
rel_to: &BlockPathGen<U>,
) -> Result<RelBlockPath<'a>> {
let self_str = self.path.as_ref();
let other_str = rel_to.path.as_ref();
if self_str.starts_with(other_str) {
let start = self_str.len().min(other_str.len() + 1);
let rel_path = &self_str[start..];
Ok(RelBlockPath(rel_path))
} else {
Err(BlockPathError::NotContained.into())
}
}
pub fn port(&self) -> Result<u16> {
let mut hasher = BtHasher::new(HashKind::Sha2_256)?;
self.hash(&mut hasher);
let hash = hasher.finish();
const NUM_RES_PORTS: u16 = 49153;
const PORTS_AVAIL: u64 = (u16::MAX - NUM_RES_PORTS) as u64;
let port = NUM_RES_PORTS + (hash % PORTS_AVAIL) as u16;
Ok(port)
}
}
impl BlockPathGen<String> {
fn from_components<'a, I: Iterator<Item = &'a str>>(
root: Principal,
components: I,
) -> Self {
let mut path = String::new();
write!(path, "{}{}", BlockPath::SEP, root).unwrap();
let begin_rel_path = path.len() + BlockPath::SEP.len_utf8();
for component in components {
write!(path, "{}{}", BlockPath::SEP, component).unwrap();
}
Self {
root,
begin_rel_part: begin_rel_path,
path,
}
}
fn borrow(&self) -> BlockPathGen<&'_ str> {
BlockPathGen {
root: self.root.clone(),
begin_rel_part: self.begin_rel_part,
path: self.path.as_str(),
}
}
pub fn set_root(&mut self, new_root: Principal) {
self.root = new_root;
if let Some(rel) = self.rel_part() {
let rel = rel.to_owned();
self.path.truncate(0);
write!(
self.path,
"{}{}{}{}",
BlockPath::SEP,
self.root,
BlockPath::SEP,
rel
)
.unwrap();
} else {
self.path.truncate(0);
write!(self.path, "{}{}", BlockPath::SEP, self.root).unwrap();
}
}
pub fn push_component<T: Display>(&mut self, component: T) {
if !self.path.ends_with(BlockPath::SEP) {
self.path.push(BlockPath::SEP);
}
write!(self.path, "{component}").unwrap();
}
pub fn pop_component(&mut self) {
if let Some((remaining, _)) = self.path.rsplit_once(BlockPath::SEP) {
if self.begin_rel_part <= remaining.len() + BlockPath::SEP.len_utf8() {
self.path.truncate(remaining.len())
}
}
}
}
impl<T: AsRef<str>> Display for BlockPathGen<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.path.as_ref())
}
}
impl<'s> TryFrom<&'s str> for BlockPathGen<&'s str> {
type Error = BlockPathError;
fn try_from(value: &'s str) -> StdResult<Self, Self::Error> {
Self::new(value)
}
}
impl TryFrom<String> for BlockPathGen<String> {
type Error = BlockPathError;
fn try_from(value: String) -> StdResult<Self, Self::Error> {
Self::new(value)
}
}
impl<T: AsRef<str>> Serialize for BlockPathGen<T> {
fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
serializer.collect_str(self)
}
}
impl<'de> Deserialize<'de> for BlockPathGen<String> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> StdResult<Self, D::Error> {
struct GenBlockPathVisitor;
impl<'a> Visitor<'a> for GenBlockPathVisitor {
type Value = BlockPathGen<String>;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a string containing a block path")
}
fn visit_string<E: de::Error>(self, v: String) -> StdResult<Self::Value, E> {
BlockPathGen::new(v).map_err(de::Error::custom)
}
fn visit_str<E: de::Error>(self, v: &str) -> StdResult<Self::Value, E> {
self.visit_string(v.to_owned())
}
}
deserializer.deserialize_string(GenBlockPathVisitor)
}
}
impl<'de> Deserialize<'de> for BlockPathGen<&'de str> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> StdResult<Self, D::Error> {
struct GenBlockPathVisitor;
impl<'a> Visitor<'a> for GenBlockPathVisitor {
type Value = BlockPathGen<&'a str>;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a string containing a block path")
}
fn visit_borrowed_str<E: de::Error>(self, v: &'a str) -> StdResult<Self::Value, E> {
BlockPathGen::new(v).map_err(de::Error::custom)
}
}
deserializer.deserialize_str(GenBlockPathVisitor)
}
}
impl<T: AsRef<str>> PartialEq for BlockPathGen<T> {
fn eq(&self, other: &Self) -> bool {
self.path.as_ref() == other.path.as_ref()
}
}
impl<T: AsRef<str>> Eq for BlockPathGen<T> {}
impl<T: AsRef<str>> PartialOrd for BlockPathGen<T> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.path.as_ref().partial_cmp(other.path.as_ref())
}
}
impl<T: AsRef<str>> Ord for BlockPathGen<T> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.path.as_ref().cmp(other.path.as_ref())
}
}
impl<T: AsRef<str>, U: AsRef<str>> PartialEq<U> for BlockPathGen<T> {
fn eq(&self, other: &U) -> bool {
self.path.as_ref() == other.as_ref()
}
}
impl<T: AsRef<str>> Hash for BlockPathGen<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write(self.path.as_ref().as_bytes())
}
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Default, Hash)]
pub struct RelBlockPath<'a>(&'a str);
impl<'a> RelBlockPath<'a> {
pub fn new(rel_path: &'a str) -> StdResult<Self, BlockPathError> {
if rel_path.starts_with(BlockPath::SEP) {
return Err(BlockPathError::NotRelative);
}
Ok(Self(rel_path))
}
pub fn components(&self) -> impl DoubleEndedIterator<Item = &str> {
self.0.split(BlockPath::SEP)
}
pub fn empty() -> Self {
Self::default()
}
}
impl<'a> Display for RelBlockPath<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl<'a> TryFrom<&'a str> for RelBlockPath<'a> {
type Error = BlockPathError;
fn try_from(value: &'a str) -> std::result::Result<Self, Self::Error> {
Self::new(value)
}
}
impl<'a> TryFrom<&'a String> for RelBlockPath<'a> {
type Error = BlockPathError;
fn try_from(value: &'a String) -> StdResult<Self, Self::Error> {
Self::new(value.as_str())
}
}
impl<'a, T: AsRef<str>> PartialEq<T> for RelBlockPath<'a> {
fn eq(&self, other: &T) -> bool {
self.0 == other.as_ref()
}
}
pub enum Components<'a> {
Split(std::str::Split<'a, char>),
Empty,
}
impl<'a> Iterator for Components<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
match self {
Self::Split(split) => split.next(),
Self::Empty => None,
}
}
}
impl<'a> DoubleEndedIterator for Components<'a> {
fn next_back(&mut self) -> Option<Self::Item> {
match self {
Self::Split(split) => split.next_back(),
Self::Empty => None,
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum BlockPathError {
PathTooLong(usize),
Empty,
InvalidLeadingComponent,
NotContained,
NotRelative,
NotAbsolute,
}
impl Display for BlockPathError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BlockPathError::PathTooLong(length) => formatter.write_fmt(format_args!(
"path contained {} bytes, which is over the {} byte limit",
length,
BlockPath::BYTE_LIMIT
)),
BlockPathError::Empty => formatter.write_str("path was empty"),
BlockPathError::InvalidLeadingComponent => {
formatter.write_str("invalid leading path component")
}
BlockPathError::NotContained => {
formatter.write_str("one path was not contained in another")
}
BlockPathError::NotRelative => formatter.write_str("expected a relative path"),
BlockPathError::NotAbsolute => formatter.write_str("expected an absolute path"),
}
}
}
impl std::error::Error for BlockPathError {}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
test_helpers::{make_path, make_principal, PRINCIPAL2},
VarHash,
};
fn path_new_test_case(
expected: StdResult<BlockPath, BlockPathError>,
input: String,
) -> StdResult<(), BlockPathError> {
let result = BlockPath::new(input);
assert_eq!(expected, result);
Ok(())
}
#[test]
fn path_new_multiple_components_ok() -> StdResult<(), BlockPathError> {
let expected = make_path(["red", "green", "blue"].into_iter());
let input = format!("/{}/red/green/blue", expected.root());
path_new_test_case(Ok(expected), input)
}
#[test]
fn path_new_one_component_ok() -> StdResult<(), BlockPathError> {
let expected = make_path(std::iter::empty());
let input = format!("/{}", expected.root());
path_new_test_case(Ok(expected), input)
}
#[test]
fn path_new_trailing_slash_ok() -> StdResult<(), BlockPathError> {
let expected = make_path(["orange", "banana", "shotgun", ""].into_iter());
let input = format!("/{}/orange/banana/shotgun/", expected.root());
path_new_test_case(Ok(expected), input)
}
#[test]
fn path_new_path_too_long_fail() -> StdResult<(), BlockPathError> {
let principal = make_principal();
let input = format!("/{}/{}", principal, "*".repeat(4097));
let expected = Err(BlockPathError::PathTooLong(input.len()));
path_new_test_case(expected, input)
}
#[test]
fn path_new_no_leading_slash_fail() -> StdResult<(), BlockPathError> {
let expected = Err(BlockPathError::NotAbsolute);
let principal = make_principal();
let input = format!("{}", principal);
path_new_test_case(expected, input)
}
#[test]
fn path_round_trip() -> StdResult<(), BlockPathError> {
let expected = make_path(["interstitial", "inter-related", "intersections"].into_iter());
let string = expected.to_string();
let actual = BlockPath::new(string)?;
assert_eq!(expected, actual);
Ok(())
}
#[test]
fn path_from_components() {
let expected_root = make_principal();
let expected_components = ["first", "second"];
let actual =
BlockPath::from_components(expected_root.clone(), expected_components.into_iter());
assert_eq!(&expected_root, actual.root());
assert!(expected_components.into_iter().eq(actual.components()));
}
#[test]
fn path_contains_true() {
let larger = make_path(["apps"].into_iter());
let smaller = make_path(["apps", "bohdi"].into_iter());
assert!(larger.contains(&smaller));
}
#[test]
fn path_contains_true_only_owner() {
let larger = make_path(std::iter::empty());
let smaller = make_path(std::iter::empty());
assert!(larger.contains(&smaller));
}
#[test]
fn path_contains_false_self_is_longer() {
let first = make_path(["apps", "bohdi"].into_iter());
let second = make_path(["apps"].into_iter());
assert!(!first.contains(&second));
}
#[test]
fn path_contains_false_same_owners() {
let first = make_path(["apps"].into_iter());
let second = make_path(["nodes"].into_iter());
assert!(!first.contains(&second));
}
#[test]
fn path_contains_false_different_owners() {
let first = make_path(["apps"].into_iter());
let mut second = make_path(["apps"].into_iter());
second.set_root(Principal(VarHash::Sha2_256(PRINCIPAL2.into())));
assert!(!first.contains(&second));
}
#[test]
fn path_set_root_no_components() {
let expected = Principal::default();
let mut path = make_path(std::iter::empty());
assert_ne!(&expected, path.root());
path.set_root(expected.clone());
assert_eq!(&expected, path.root());
assert_eq!(path, format!("/{}", expected));
}
#[test]
fn path_set_root_with_components() {
let original = make_principal();
let new = Principal::default();
let expected = format!("/{}/first/second", new);
let mut path = BlockPath::new(format!("/{}/first/second", original)).unwrap();
assert_ne!(&new, path.root());
path.set_root(new.clone());
assert_eq!(&new, path.root());
assert_eq!(path, expected)
}
#[test]
fn components_multiple() {
let expected = ["first", "second"];
let path = make_path(expected.into_iter());
let actual = path.components();
assert!(expected.into_iter().eq(actual));
}
#[test]
fn components_multiple_with_trailing_slash() {
let expected = ["first", ""];
let path = BlockPath::new(format!("/{}/first/", make_principal())).unwrap();
let actual = path.components();
assert!(expected.into_iter().eq(actual));
}
#[test]
fn components_single_empty() {
let expected = [""];
let path = BlockPath::new(format!("/{}/", make_principal())).unwrap();
let actual = path.components();
assert!(expected.into_iter().eq(actual));
}
#[test]
fn components_none() {
let empty = std::iter::empty::<&str>();
let path = BlockPath::new(format!("/{}", make_principal())).unwrap();
let actual = path.components();
assert!(empty.eq(actual));
}
#[test]
fn push_component_when_component_already_present() {
let mut path = make_path(["first"].into_iter());
path.push_component("second");
assert_eq!(path, format!("/{}/first/second", path.root()));
}
#[test]
fn push_component_when_no_component_present() {
let mut path = make_path(std::iter::empty());
let root = path.root().clone();
path.push_component("first");
assert_eq!(path, format!("/{}/first", root));
}
#[test]
fn push_component_path_ends_with_sep() {
let mut path = make_path([""].into_iter());
let root = path.root().clone();
path.push_component("component");
assert_eq!(path, format!("/{}/component", root));
}
#[test]
fn path_pop_component_single_component() {
let mut path = make_path(["first"].into_iter());
let root = path.root().clone();
path.pop_component();
assert_eq!(path, format!("/{}", root));
}
#[test]
fn path_pop_component_two_components() {
let mut path = make_path(["first", "second"].into_iter());
let root = path.root().clone();
path.pop_component();
assert_eq!(path, format!("/{}/first", root));
}
#[test]
fn path_pop_component_no_components() {
let mut path = make_path(std::iter::empty());
let root = path.root().clone();
path.pop_component();
assert_eq!(path, format!("/{}", root));
}
#[test]
fn path_pop_component_empty_path() {
let mut path = make_path(std::iter::empty());
let root = path.root().clone();
path.pop_component();
path.pop_component();
assert_eq!(path, format!("/{}", root));
}
#[test]
fn relative_to_self_contained_in_rel_to() {
let sich = make_path(["sub", "alpha", "sub", "beta"].into_iter());
let rel_to = make_path(["sub", "alpha"].into_iter());
let relative = sich.relative_to(&rel_to).unwrap();
assert_eq!(relative, "sub/beta");
}
#[test]
fn relative_to_no_difference_is_ok() {
let sich = make_path(["sub", "alpha"].into_iter());
let rel_to = make_path(["sub", "alpha"].into_iter());
let relative = sich.relative_to(&rel_to).unwrap();
assert_eq!(relative, "");
}
#[test]
fn relative_to_roots_differ_is_err() {
let sich = make_path(["etc"].into_iter());
let mut rel_to = make_path(["etc"].into_iter());
let default = Principal::default();
assert_ne!(sich.root(), &default);
rel_to.set_root(default);
let result = sich.relative_to(&rel_to);
let err = result.err().unwrap().downcast::<BlockPathError>().unwrap();
let matched = if let BlockPathError::NotContained = err {
true
} else {
false
};
assert!(matched)
}
#[test]
fn relative_to_self_shorter_is_err() {
let sich = make_path(["etc"].into_iter());
let rel_to = make_path(["etc", "fstab"].into_iter());
let result = sich.relative_to(&rel_to);
let err = result.err().unwrap().downcast::<BlockPathError>().unwrap();
let matched = if let BlockPathError::NotContained = err {
true
} else {
false
};
assert!(matched)
}
#[test]
fn relative_to_not_contained_is_err() {
let sich = make_path(["etc"].into_iter());
let rel_to = make_path(["etsy"].into_iter());
let result = sich.relative_to(&rel_to);
let err = result.err().unwrap().downcast::<BlockPathError>().unwrap();
let matched = if let BlockPathError::NotContained = err {
true
} else {
false
};
assert!(matched)
}
}