diskplan_filesystem/
root.rs1use anyhow::{bail, Result};
2use camino::{Utf8Path, Utf8PathBuf};
3
4#[derive(Debug, Clone, PartialEq, Eq, Hash)]
6pub struct Root(Utf8PathBuf);
7
8impl Root {
9 pub fn new(path: impl AsRef<Utf8Path>) -> Result<Self> {
11 path.as_ref().to_owned().try_into()
12 }
13 pub fn path(&self) -> &Utf8Path {
15 &self.0
16 }
17}
18
19impl AsRef<Utf8Path> for Root {
20 fn as_ref(&self) -> &Utf8Path {
21 &self.0
22 }
23}
24
25impl TryFrom<Utf8PathBuf> for Root {
26 type Error = anyhow::Error;
27
28 fn try_from(value: Utf8PathBuf) -> Result<Self, Self::Error> {
29 if !is_normalized(value.as_str()) {
30 bail!("Root must be a normalized path: {}", value);
31 }
32 if !value.is_absolute() {
33 bail!("Invalid root; path must be absolute: {}", value);
34 }
35 Ok(Root(value))
36 }
37}
38
39impl TryFrom<&Utf8Path> for Root {
40 type Error = anyhow::Error;
41
42 fn try_from(value: &Utf8Path) -> Result<Self, Self::Error> {
43 value.to_owned().try_into()
44 }
45}
46
47impl TryFrom<&str> for Root {
48 type Error = anyhow::Error;
49
50 fn try_from(value: &str) -> Result<Self, Self::Error> {
51 Utf8PathBuf::from(value).try_into()
52 }
53}
54
55fn is_normalized(path: impl AsRef<Utf8Path>) -> bool {
56 let path = path.as_ref().as_str();
57 !((path.ends_with('/') && path != "/") || path.contains("//") || path.contains("/./"))
58}