Skip to main content

acyclic_fs/kernel/
namespace_path.rs

1//! Bounded profile-independent namespace paths.
2
3use super::{LogicalName, NameEncoding, TreePageError};
4use crate::model::{FilesystemProfile, VolumeLimits};
5use crate::path::PortablePath;
6use thiserror::Error;
7
8/// Canonical absolute path represented as exact logical-name components.
9///
10/// An empty component vector denotes the volume root. Unlike
11/// [`PortablePath`], this type preserves raw POSIX bytes and Windows UTF-16LE
12/// names. A volume executor additionally checks that every component encoding
13/// is admitted by that volume's filesystem profile.
14#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
15pub struct NamespacePath {
16    components: Vec<LogicalName>,
17    encoded_bytes: u32,
18}
19
20impl NamespacePath {
21    /// Constructs one bounded absolute namespace path.
22    ///
23    /// # Errors
24    ///
25    /// Rejects excessive depth, component bytes, total bytes, or arithmetic
26    /// overflow before the path reaches a storage backend.
27    pub fn new(
28        components: Vec<LogicalName>,
29        limits: VolumeLimits,
30    ) -> Result<Self, NamespacePathError> {
31        if u16::try_from(components.len()).unwrap_or(u16::MAX) > limits.maximum_path_depth {
32            return Err(NamespacePathError::TooDeep);
33        }
34        let mut encoded_bytes = 1_u32;
35        for component in &components {
36            let length = u32::try_from(component.as_bytes().len())
37                .map_err(|_| NamespacePathError::ComponentTooLong)?;
38            if length > limits.maximum_component_bytes {
39                return Err(NamespacePathError::ComponentTooLong);
40            }
41            encoded_bytes = encoded_bytes
42                .checked_add(length)
43                .and_then(|value| value.checked_add(1))
44                .ok_or(NamespacePathError::PathTooLong)?;
45        }
46        if !components.is_empty() {
47            encoded_bytes = encoded_bytes.saturating_sub(1);
48        }
49        if encoded_bytes > limits.maximum_path_bytes {
50            return Err(NamespacePathError::PathTooLong);
51        }
52        Ok(Self {
53            components,
54            encoded_bytes,
55        })
56    }
57
58    /// Converts a strict UTF-8 portable path without losing information.
59    ///
60    /// # Errors
61    ///
62    /// Returns the same component and total bounds as [`Self::new`], plus a
63    /// canonical-name failure if the portable input violates the name format.
64    pub fn from_portable(
65        path: &PortablePath,
66        limits: VolumeLimits,
67    ) -> Result<Self, NamespacePathError> {
68        Self::from_portable_in_profile(path, FilesystemProfile::Portable, limits)
69    }
70
71    /// Converts a portable path to the canonical names of one volume profile.
72    ///
73    /// The string API can address names representable as Unicode; native
74    /// capture APIs retain raw POSIX bytes or UTF-16 units outside that subset.
75    ///
76    /// # Errors
77    ///
78    /// Returns bounded-name or path errors when the encoded host names exceed
79    /// the volume limits.
80    pub fn from_portable_in_profile(
81        path: &PortablePath,
82        profile: FilesystemProfile,
83        limits: VolumeLimits,
84    ) -> Result<Self, NamespacePathError> {
85        let components = path
86            .components()
87            .map(|component| {
88                let (encoding, bytes) = match profile {
89                    FilesystemProfile::Portable | FilesystemProfile::Browser => {
90                        (NameEncoding::Utf8, component.as_bytes().to_vec())
91                    }
92                    FilesystemProfile::Posix => {
93                        (NameEncoding::PosixBytes, component.as_bytes().to_vec())
94                    }
95                    FilesystemProfile::Windows => (
96                        NameEncoding::WindowsUtf16Le,
97                        component
98                            .encode_utf16()
99                            .flat_map(u16::to_le_bytes)
100                            .collect(),
101                    ),
102                };
103                LogicalName::new(encoding, bytes, limits.maximum_component_bytes)
104                    .map_err(NamespacePathError::Name)
105            })
106            .collect::<Result<Vec<_>, _>>()?;
107        Self::new(components, limits)
108    }
109
110    /// Returns whether this is the volume root.
111    #[must_use]
112    pub fn is_root(&self) -> bool {
113        self.components.is_empty()
114    }
115
116    /// Exact logical path depth.
117    #[must_use]
118    pub fn depth(&self) -> usize {
119        self.components.len()
120    }
121
122    /// Canonical encoded bytes including separators and the leading root mark.
123    #[must_use]
124    pub const fn encoded_bytes(&self) -> u32 {
125        self.encoded_bytes
126    }
127
128    /// Borrows exact logical-name components in root-to-leaf order.
129    #[must_use]
130    pub fn components(&self) -> &[LogicalName] {
131        &self.components
132    }
133
134    /// Returns the parent path and final name, or `None` for root.
135    #[must_use]
136    pub fn split_last(&self) -> Option<(&[LogicalName], &LogicalName)> {
137        self.components
138            .split_last()
139            .map(|(name, parent)| (parent, name))
140    }
141
142    /// Returns the exact parent path, or `None` for the namespace root.
143    ///
144    /// This preserves the already-validated component representation and does
145    /// not consult a filesystem or allocate beyond cloning the parent prefix.
146    #[must_use]
147    pub fn parent(&self) -> Option<Self> {
148        let (_, name) = self.split_last()?;
149        let encoded_bytes = if self.components.len() == 1 {
150            1
151        } else {
152            self.encoded_bytes
153                .saturating_sub(u32::try_from(name.as_bytes().len()).unwrap_or(u32::MAX))
154                .saturating_sub(1)
155        };
156        let mut components = self.components.clone();
157        components.pop();
158        Some(Self {
159            components,
160            encoded_bytes,
161        })
162    }
163
164    /// Returns whether this path is equal to or below `ancestor`.
165    #[must_use]
166    pub fn is_within(&self, ancestor: &Self) -> bool {
167        self.components.starts_with(&ancestor.components)
168    }
169}
170
171/// Profile-independent namespace path failures.
172#[derive(Clone, Debug, Eq, Error, PartialEq)]
173pub enum NamespacePathError {
174    /// Component count exceeds the volume bound.
175    #[error("namespace path exceeds its depth bound")]
176    TooDeep,
177    /// One exact encoded component exceeds the volume bound.
178    #[error("namespace path component exceeds its byte bound")]
179    ComponentTooLong,
180    /// Total canonical encoded path bytes exceed the volume bound.
181    #[error("namespace path exceeds its byte bound")]
182    PathTooLong,
183    /// One portable component could not become a canonical logical name.
184    #[error(transparent)]
185    Name(TreePageError),
186}
187
188#[cfg(test)]
189#[path = "tests/namespace_path.rs"]
190mod tests;