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}