substrait_validator/output/
path.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Module for handling tree paths.
4//!
5//! The [`PathElement`], [`Path`], and [`PathBuf`] types are used to uniquely
6//! refer to any node in a Substrait plan (or, more accurately, any
7//! combination of protobuf and YAML data). [`Path`], and [`PathBuf`] work
8//! roughly the same as [`std::path::Path`], and [`std::path::PathBuf`], but
9//! for protobuf/YAML tree paths rather than filesystem paths.
10
11use crate::util;
12
13/// Element of a path to some field of a protobuf message and/or YAML file.
14#[derive(Clone, Debug, PartialEq, Eq)]
15pub enum PathElement {
16    /// Refers to an optional protobuf field with the given name within the
17    /// message, or a YAML map entry with the given key.
18    Field(String),
19
20    /// Refers to one of the elements of a repeated field with the given
21    /// name within the message referred to by the parent path.
22    Repeated(String, usize),
23
24    /// Refers to the selected variant of a OneOf field with the given name
25    /// within the message referred to by the parent path. The first str is
26    /// the field name, the second is the variant name.
27    Variant(String, String),
28
29    /// Refers to an indexed element within a YAML array.
30    Index(usize),
31}
32
33impl std::fmt::Display for PathElement {
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        if !f.alternate() {
36            match self {
37                PathElement::Index(_) => {}
38                _ => write!(f, ".")?,
39            }
40        }
41        match self {
42            PathElement::Field(field) => write!(f, "{}", util::string::as_ident_or_string(field)),
43            PathElement::Repeated(field, index) => {
44                write!(f, "{}[{index}]", util::string::as_ident_or_string(field))
45            }
46            PathElement::Variant(field, variant) => write!(
47                f,
48                "{}<{}>",
49                util::string::as_ident_or_string(field),
50                util::string::as_ident_or_string(variant)
51            ),
52            PathElement::Index(index) => write!(f, "[{index}]"),
53        }
54    }
55}
56
57impl PathElement {
58    /// Same as to_string(), but doesn't include the dot prefix for the
59    /// variants that would normally have one.
60    pub fn to_string_without_dot(&self) -> String {
61        format!("{:#}", self)
62    }
63}
64
65/// Refers to a location within a protobuf message.
66#[derive(Clone, Debug, PartialEq, Eq)]
67pub struct PathBuf {
68    pub root: &'static str,
69    pub elements: Vec<PathElement>,
70}
71
72impl std::fmt::Display for PathBuf {
73    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74        write!(f, "{}", self.root)?;
75        for element in self.elements.iter() {
76            write!(f, "{element}")?;
77        }
78        Ok(())
79    }
80}
81
82/// Used to track a location within a protobuf message. The owned version
83/// is PathBuf.
84#[derive(Clone, Debug, PartialEq)]
85pub enum Path<'a> {
86    /// Refers to the root message.
87    Root(&'static str),
88
89    /// Refers to an optional field with the given name within the message
90    /// referred to by the given parent path.
91    Select(&'a Path<'a>, PathElement),
92}
93
94impl Default for Path<'_> {
95    fn default() -> Self {
96        Path::Root("")
97    }
98}
99
100impl Path<'_> {
101    /// Returns a new Path that references an optional field with the
102    /// given name within the protobuf message referred to by the current
103    /// path, or likewise for the key within a YAML map.
104    pub fn with(&self, element: PathElement) -> Path {
105        Path::Select(self, element)
106    }
107
108    /// Returns a new Path that references an optional field with the
109    /// given name within the protobuf message referred to by the current
110    /// path, or likewise for the key within a YAML map.
111    pub fn with_field<S: Into<String>>(&self, name: S) -> Path {
112        self.with(PathElement::Field(name.into()))
113    }
114
115    /// Returns a new Path that references an element of a repeated field
116    /// with the given name within the message referred to by the current
117    /// path.
118    pub fn with_repeated<S: Into<String>>(&self, name: S, index: usize) -> Path {
119        self.with(PathElement::Repeated(name.into(), index))
120    }
121
122    /// Returns a new Path that references a particular variant of a
123    /// OneOf field with the given name within the message referred to
124    /// by the current path.
125    pub fn with_variant<S: Into<String>, V: Into<String>>(&self, name: S, variant: V) -> Path {
126        self.with(PathElement::Variant(name.into(), variant.into()))
127    }
128
129    /// Returns a new Path that references a YAML array element.
130    pub fn with_index(&self, index: usize) -> Path {
131        self.with(PathElement::Index(index))
132    }
133}
134
135impl std::fmt::Display for Path<'_> {
136    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137        match self {
138            Path::Root(name) => write!(f, "{name}"),
139            Path::Select(parent, element) => write!(f, "{parent}{element}"),
140        }
141    }
142}
143
144impl Path<'_> {
145    pub fn end_to_string(&self) -> String {
146        match self {
147            Path::Root(name) => name.to_string(),
148            Path::Select(_, element) => element.to_string(),
149        }
150    }
151
152    /// Creates an owned version of this Path.
153    pub fn to_path_buf(&self) -> PathBuf {
154        match self {
155            Path::Root(name) => PathBuf {
156                root: name,
157                elements: vec![],
158            },
159            Path::Select(parent, element) => {
160                let mut parent = parent.to_path_buf();
161                parent.elements.push(element.clone());
162                parent
163            }
164        }
165    }
166}
167
168impl From<Path<'_>> for PathBuf {
169    fn from(path: Path<'_>) -> Self {
170        path.to_path_buf()
171    }
172}
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177
178    #[test]
179    fn paths() {
180        let a = Path::Root("a");
181        let b = a.with_field("b");
182        let c = b.with_repeated("c", 42);
183        let d = c.with_variant("d", "e");
184        let e = d.with_index(33);
185        let buf: PathBuf = e.to_path_buf();
186        assert_eq!(e.to_string(), "a.b.c[42].d<e>[33]");
187        assert_eq!(buf.to_string(), "a.b.c[42].d<e>[33]");
188    }
189
190    #[test]
191    fn non_ident_paths() {
192        let a = Path::Root("a");
193        let b = a.with_field("4");
194        let c = b.with_repeated("8", 15);
195        let d = c.with_variant("16", "23");
196        let e = d.with_index(42);
197        let buf: PathBuf = e.to_path_buf();
198        assert_eq!(e.to_string(), "a.\"4\".\"8\"[15].\"16\"<\"23\">[42]");
199        assert_eq!(buf.to_string(), "a.\"4\".\"8\"[15].\"16\"<\"23\">[42]");
200    }
201}