1use std::fmt;
10
11#[derive(Clone, Debug, PartialEq, Eq)]
13pub enum PathSeg {
14 Key(String),
16 Index(usize),
18 Wildcard,
21}
22
23#[derive(Clone, Debug, PartialEq, Eq, Default)]
25pub struct JsonPath(Vec<PathSeg>);
26
27impl JsonPath {
28 #[must_use]
30 pub fn root() -> Self {
31 Self(Vec::new())
32 }
33
34 #[must_use]
36 pub fn segs(&self) -> &[PathSeg] {
37 &self.0
38 }
39
40 #[must_use]
42 pub fn key(mut self, k: impl Into<String>) -> Self {
43 self.0.push(PathSeg::Key(k.into()));
44 self
45 }
46
47 #[must_use]
49 pub fn index(mut self, i: usize) -> Self {
50 self.0.push(PathSeg::Index(i));
51 self
52 }
53
54 #[must_use]
56 pub fn wildcard(mut self) -> Self {
57 self.0.push(PathSeg::Wildcard);
58 self
59 }
60
61 #[must_use]
66 pub fn concat(&self, tail: &JsonPath) -> Self {
67 let mut segs = self.0.clone();
68 segs.extend(tail.0.iter().cloned());
69 Self(segs)
70 }
71
72 #[must_use]
82 pub fn get<'a>(&self, v: &'a serde_json::Value) -> Option<&'a serde_json::Value> {
83 let mut cur = v;
84 for seg in &self.0 {
85 cur = match seg {
86 PathSeg::Key(k) => cur.get(k)?,
87 PathSeg::Index(i) => cur.get(i)?,
88 PathSeg::Wildcard => return None,
89 };
90 }
91 Some(cur)
92 }
93
94 #[must_use]
102 pub fn parse(spec: &str) -> Self {
103 let mut p = Self::root();
104 for raw in spec.split('.') {
105 if raw.is_empty() {
106 continue;
107 }
108 if let Some(br_start) = raw.find('[') {
109 let name = &raw[..br_start];
110 if !name.is_empty() {
111 p.0.push(PathSeg::Key(name.to_string()));
112 }
113 for br in raw[br_start..].split(']') {
114 let inner = br.trim_start_matches('[');
115 if inner.is_empty() {
116 continue;
117 }
118 if inner == "*" {
119 p.0.push(PathSeg::Wildcard);
120 } else if let Ok(n) = inner.parse::<usize>() {
121 p.0.push(PathSeg::Index(n));
122 }
123 }
124 } else {
125 p.0.push(PathSeg::Key(raw.to_string()));
126 }
127 }
128 p
129 }
130}
131
132impl fmt::Display for JsonPath {
133 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134 if self.0.is_empty() {
135 return write!(f, ".");
136 }
137 for seg in &self.0 {
138 match seg {
139 PathSeg::Key(k) => write!(f, ".{k}")?,
140 PathSeg::Index(i) => write!(f, "[{i}]")?,
141 PathSeg::Wildcard => write!(f, "[*]")?,
142 }
143 }
144 Ok(())
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 #[test]
153 fn builder_and_display_round_trip() {
154 let p = JsonPath::root().key("metadata").key("name");
155 assert_eq!(p.to_string(), ".metadata.name");
156 assert_eq!(JsonPath::root().to_string(), ".");
157 let idx = JsonPath::root().key("items").index(0).key("uid");
158 assert_eq!(idx.to_string(), ".items[0].uid");
159 }
160
161 #[test]
162 fn parse_handles_wildcard_and_index() {
163 let p = JsonPath::parse("metadata.managedFields[*].time");
164 assert_eq!(
165 p.segs(),
166 &[
167 PathSeg::Key("metadata".into()),
168 PathSeg::Key("managedFields".into()),
169 PathSeg::Wildcard,
170 PathSeg::Key("time".into()),
171 ]
172 );
173 assert_eq!(p.to_string(), ".metadata.managedFields[*].time");
174
175 let idx = JsonPath::parse("spec.containers[0].image");
176 assert_eq!(idx.to_string(), ".spec.containers[0].image");
177 }
178}