qubit_fs/path/path_component.rs
1// =============================================================================
2// Copyright (c) 2025 - 2026 Haixing Hu.
3//
4// SPDX-License-Identifier: Apache-2.0
5//
6// Licensed under the Apache License, Version 2.0.
7// =============================================================================
8
9//! One validated logical path component.
10
11use std::fmt::Display;
12use std::fmt::Formatter;
13use std::fmt::Result as FmtResult;
14
15use crate::error::FsError;
16use crate::error::FsOperation;
17use crate::error::FsResult;
18
19/// A non-empty logical component that cannot express hierarchy or traversal.
20///
21/// # Examples
22///
23/// ```rust
24/// use qubit_fs::path::PathComponent;
25///
26/// let component = PathComponent::parse("reports")?;
27/// assert_eq!("reports", component.as_str());
28/// # Ok::<(), qubit_fs::FsError>(())
29/// ```
30#[derive(Clone, Debug, Eq, Hash, PartialEq)]
31pub struct PathComponent(
32 /// Validated component text containing no hierarchy or traversal marker.
33 String,
34);
35
36impl PathComponent {
37 /// Parses one logical component.
38 ///
39 /// Returns an invalid-path error for empty input, separators, traversal
40 /// markers, or NUL. This method performs no native-path conversion.
41 ///
42 /// # Parameters
43 /// - `text`: Component text to validate.
44 ///
45 /// # Errors
46 /// Returns an invalid-path error when `text` is empty, contains a
47 /// separator, is a traversal marker, or contains NUL.
48 pub fn parse(text: &str) -> FsResult<Self> {
49 if text.is_empty() || matches!(text, "." | "..") || text.contains('/') || text.contains('\0') {
50 return Err(FsError::invalid_path(
51 FsOperation::ParsePath,
52 "path component must be a non-empty non-traversal component",
53 ));
54 }
55 Ok(Self(text.to_owned()))
56 }
57
58 /// Returns the validated logical component text.
59 #[inline]
60 #[must_use]
61 pub fn as_str(&self) -> &str {
62 &self.0
63 }
64}
65
66impl Display for PathComponent {
67 /// Formats the validated component without changing its lexical spelling.
68 #[inline]
69 fn fmt(&self, formatter: &mut Formatter<'_>) -> FmtResult {
70 formatter.write_str(self.as_str())
71 }
72}