helm_schema_core/
value_path.rs1use std::cmp::Ordering;
2use std::hash::{Hash, Hasher};
3
4use serde::{Deserialize, Deserializer, Serialize, Serializer};
5
6#[derive(Clone, Debug, Default, Eq)]
11pub struct ValuesPath {
12 segments: Vec<Segment>,
13}
14
15#[derive(Clone, Debug, Eq, Hash, PartialEq)]
17pub enum Segment {
18 Literal(String),
20 EachMember,
22}
23
24impl PartialOrd for Segment {
25 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
26 Some(self.cmp(other))
27 }
28}
29
30impl Ord for Segment {
31 fn cmp(&self, other: &Self) -> Ordering {
32 self.encode_component().cmp(&other.encode_component())
33 }
34}
35
36impl Segment {
37 #[must_use]
39 pub fn literal(&self) -> Option<&str> {
40 match self {
41 Self::Literal(value) => Some(value),
42 Self::EachMember => None,
43 }
44 }
45
46 #[must_use]
48 pub const fn is_each_member(&self) -> bool {
49 matches!(self, Self::EachMember)
50 }
51
52 #[must_use]
54 pub fn encode_component(&self) -> String {
55 match self {
56 Self::Literal(value) if value == "*" => r"\*".to_string(),
57 Self::Literal(value) => value.replace('\\', r"\\"),
58 Self::EachMember => "*".to_string(),
59 }
60 }
61
62 #[must_use]
64 pub fn from_encoded_component(encoded: &str) -> Self {
65 if encoded == "*" {
66 return Self::EachMember;
67 }
68 let mut literal = String::new();
69 let mut characters = encoded.chars().peekable();
70 while let Some(character) = characters.next() {
71 if character == '\\'
72 && characters
73 .peek()
74 .is_some_and(|next| matches!(next, '\\' | '*'))
75 {
76 if let Some(escaped) = characters.next() {
77 literal.push(escaped);
78 }
79 } else {
80 literal.push(character);
81 }
82 }
83 Self::Literal(literal)
84 }
85}
86
87impl From<String> for Segment {
88 fn from(value: String) -> Self {
89 Self::Literal(value)
90 }
91}
92
93impl From<&str> for Segment {
94 fn from(value: &str) -> Self {
95 Self::Literal(value.to_string())
96 }
97}
98
99impl From<&Segment> for Segment {
100 fn from(value: &Segment) -> Self {
101 value.clone()
102 }
103}
104
105impl ValuesPath {
106 #[must_use]
108 pub fn parse(path: &str) -> Self {
109 let mut segments = Vec::new();
110 let mut segment = String::new();
111 let mut escaped_star = false;
112 let mut characters = path.chars().peekable();
113 while let Some(character) = characters.next() {
114 match character {
115 '.' => {
116 push_parsed_segment(&mut segments, &mut segment, &mut escaped_star);
117 }
118 '\\' if characters
119 .peek()
120 .is_some_and(|next| matches!(next, '.' | '\\' | '*')) =>
121 {
122 if let Some(escaped) = characters.next() {
123 escaped_star |= escaped == '*';
124 segment.push(escaped);
125 }
126 }
127 _ => segment.push(character),
128 }
129 }
130 push_parsed_segment(&mut segments, &mut segment, &mut escaped_star);
131 Self { segments }
132 }
133
134 #[must_use]
136 pub fn from_segments<I, S>(segments: I) -> Self
137 where
138 I: IntoIterator<Item = S>,
139 S: Into<Segment>,
140 {
141 Self {
142 segments: segments
143 .into_iter()
144 .map(Into::into)
145 .filter(|segment| !matches!(segment, Segment::Literal(value) if value.is_empty()))
146 .collect(),
147 }
148 }
149
150 #[must_use]
152 pub fn segments(&self) -> impl DoubleEndedIterator<Item = &Segment> + ExactSizeIterator {
153 self.segments.iter()
154 }
155
156 #[must_use]
158 pub fn encode(&self) -> String {
159 let mut encoded = String::new();
160 for (index, segment) in self.segments.iter().enumerate() {
161 if index != 0 {
162 encoded.push('.');
163 }
164 match segment {
165 Segment::EachMember => encoded.push('*'),
166 Segment::Literal(value) if value == "*" => encoded.push_str(r"\*"),
167 Segment::Literal(value) => {
168 for character in value.chars() {
169 if matches!(character, '.' | '\\') {
170 encoded.push('\\');
171 }
172 encoded.push(character);
173 }
174 }
175 }
176 }
177 encoded
178 }
179
180 #[must_use]
182 pub fn parent(&self) -> Option<Self> {
183 let (_, parent) = self.segments.split_last()?;
184 Some(Self {
185 segments: parent.to_vec(),
186 })
187 }
188
189 pub fn push(&mut self, segment: impl Into<String>) {
191 let segment = segment.into();
192 if !segment.is_empty() {
193 self.segments.push(Segment::Literal(segment));
194 }
195 }
196
197 pub fn push_each_member(&mut self) {
199 self.segments.push(Segment::EachMember);
200 }
201
202 #[must_use]
204 pub fn is_descendant_of(&self, ancestor: &Self) -> bool {
205 self.segments.len() > ancestor.segments.len()
206 && self.segments.starts_with(&ancestor.segments)
207 }
208
209 #[must_use]
211 pub fn item_parent(&self) -> Option<Self> {
212 (self.segments.last() == Some(&Segment::EachMember))
213 .then(|| self.parent())
214 .flatten()
215 }
216}
217
218fn push_parsed_segment(segments: &mut Vec<Segment>, segment: &mut String, escaped_star: &mut bool) {
219 if segment.is_empty() {
220 *escaped_star = false;
221 return;
222 }
223 let segment = std::mem::take(segment);
224 if segment == "*" && !*escaped_star {
225 segments.push(Segment::EachMember);
226 } else {
227 segments.push(Segment::Literal(segment));
228 }
229 *escaped_star = false;
230}
231
232impl PartialEq for ValuesPath {
233 fn eq(&self, other: &Self) -> bool {
234 self.segments == other.segments
235 }
236}
237
238impl PartialOrd for ValuesPath {
239 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
240 Some(self.cmp(other))
241 }
242}
243
244impl Ord for ValuesPath {
245 fn cmp(&self, other: &Self) -> Ordering {
246 self.encode().cmp(&other.encode())
247 }
248}
249
250impl Hash for ValuesPath {
251 fn hash<H: Hasher>(&self, state: &mut H) {
252 self.segments.hash(state);
253 }
254}
255
256impl Serialize for ValuesPath {
257 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
258 where
259 S: Serializer,
260 {
261 serializer.serialize_str(&self.encode())
262 }
263}
264
265impl<'de> Deserialize<'de> for ValuesPath {
266 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
267 where
268 D: Deserializer<'de>,
269 {
270 String::deserialize(deserializer).map(|path| Self::parse(&path))
271 }
272}
273
274#[must_use]
279pub fn join_value_path<I, S>(segments: I) -> String
280where
281 I: IntoIterator<Item = S>,
282 S: AsRef<str>,
283{
284 ValuesPath::from_segments(
285 segments
286 .into_iter()
287 .map(|segment| segment.as_ref().to_string()),
288 )
289 .encode()
290}
291
292#[must_use]
294pub fn join_encoded_value_path<I, S>(segments: I) -> String
295where
296 I: IntoIterator<Item = S>,
297 S: AsRef<str>,
298{
299 ValuesPath::from_segments(
300 segments
301 .into_iter()
302 .map(|segment| Segment::from_encoded_component(segment.as_ref())),
303 )
304 .encode()
305}
306
307#[must_use]
309pub fn split_value_path(path: &str) -> Vec<String> {
310 ValuesPath::parse(path)
311 .segments
312 .into_iter()
313 .map(|segment| match segment {
314 Segment::Literal(value) => value,
315 Segment::EachMember => "*".to_string(),
316 })
317 .collect()
318}
319
320#[must_use]
322pub fn append_value_path(path: &str, segment: &str) -> String {
323 let mut path = ValuesPath::parse(path);
324 path.push(segment);
325 path.encode()
326}
327
328#[must_use]
330pub fn append_each_member_value_path(path: &str) -> String {
331 let mut path = ValuesPath::parse(path);
332 path.push_each_member();
333 path.encode()
334}