Skip to main content

qubit_argument/argument/
argument_path.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//! Structured paths that identify arguments in validation errors.
9
10use std::fmt;
11use std::fmt::Display;
12use std::fmt::Formatter;
13
14/// An owned path identifying an argument or one of its nested components.
15///
16/// The path is stored exactly as supplied; this type does not parse or
17/// normalize separators.
18///
19/// # Examples
20///
21/// ```
22/// use qubit_argument::ArgumentPath;
23///
24/// let path = ArgumentPath::new("value").with_prefix("user");
25/// assert_eq!(path.as_str(), "user.value");
26/// ```
27///
28/// ```compile_fail
29/// #![deny(unused_must_use)]
30/// use qubit_argument::ArgumentPath;
31///
32/// ArgumentPath::new("value");
33/// ```
34#[must_use]
35#[derive(Debug, Clone, PartialEq, Eq, Hash)]
36pub struct ArgumentPath(String);
37
38impl ArgumentPath {
39    /// Creates an argument path by copying `path` without normalization.
40    ///
41    /// # Parameters
42    ///
43    /// - `path`: The path text to store.
44    ///
45    /// # Returns
46    ///
47    /// A new owned argument path.
48    #[inline]
49    pub fn new(path: &str) -> Self {
50        Self(path.to_owned())
51    }
52
53    /// Returns the stored path text.
54    ///
55    /// The returned string slice remains valid for the lifetime of this path.
56    #[must_use]
57    #[inline]
58    pub fn as_str(&self) -> &str {
59        self.0.as_str()
60    }
61
62    /// Prepends a parent path component to this path.
63    ///
64    /// # Parameters
65    ///
66    /// - `prefix`: The parent path to place before the stored path.
67    ///
68    /// # Returns
69    ///
70    /// The composed path. Non-empty components are separated by one dot. If
71    /// either component is empty, no separator is added.
72    #[inline]
73    pub fn with_prefix(self, prefix: &str) -> Self {
74        if prefix.is_empty() {
75            return self;
76        }
77        if self.0.is_empty() {
78            return Self(prefix.to_owned());
79        }
80        let mut path = String::with_capacity(prefix.len() + 1 + self.0.len());
81        path.push_str(prefix);
82        path.push('.');
83        path.push_str(&self.0);
84        Self(path)
85    }
86}
87
88impl AsRef<str> for ArgumentPath {
89    /// Borrows the stored path as a string slice.
90    ///
91    /// # Returns
92    ///
93    /// The stored path text, borrowed from `self`.
94    #[inline]
95    fn as_ref(&self) -> &str {
96        self.as_str()
97    }
98}
99
100impl Display for ArgumentPath {
101    /// Writes the stored path text without additional decoration.
102    ///
103    /// # Parameters
104    ///
105    /// - `formatter`: The destination that receives the stored path text.
106    ///
107    /// # Returns
108    ///
109    /// `Ok(())` after writing the text.
110    ///
111    /// # Errors
112    ///
113    /// Returns the formatting error produced by the destination if writing
114    /// the text fails.
115    #[inline]
116    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
117        formatter.write_str(self.as_str())
118    }
119}