Skip to main content

vrl/path/
owned.rs

1#[cfg(any(test, feature = "proptest"))]
2use proptest::prelude::*;
3use regex::Regex;
4use serde::{Deserialize, Serialize};
5use std::fmt::{self, Debug, Display, Formatter, Write};
6use std::str::FromStr;
7
8use super::PathPrefix;
9use super::{BorrowedSegment, PathParseError, ValuePath, parse_target_path, parse_value_path};
10use crate::value::KeyString;
11use std::sync::LazyLock;
12
13/// A lookup path.
14#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
15#[serde(try_from = "String", into = "String")]
16pub struct OwnedValuePath {
17    pub segments: Vec<OwnedSegment>,
18}
19
20impl OwnedValuePath {
21    pub fn is_root(&self) -> bool {
22        self.segments.is_empty()
23    }
24
25    pub fn root() -> Self {
26        vec![].into()
27    }
28
29    pub fn push_field(&mut self, field: &str) {
30        self.segments.push(OwnedSegment::field(field));
31    }
32
33    pub fn push_segment(&mut self, segment: OwnedSegment) {
34        self.segments.push(segment);
35    }
36
37    pub fn push_front_field(&mut self, field: &str) {
38        self.segments.insert(0, OwnedSegment::field(field));
39    }
40
41    pub fn push_front_segment(&mut self, segment: OwnedSegment) {
42        self.segments.insert(0, segment);
43    }
44
45    pub fn with_field_appended(&self, field: &str) -> Self {
46        let mut new_path = self.clone();
47        new_path.push_field(field);
48        new_path
49    }
50
51    pub fn with_field_prefix(&self, field: &str) -> Self {
52        self.with_segment_prefix(OwnedSegment::field(field))
53    }
54
55    pub fn with_segment_prefix(&self, segment: OwnedSegment) -> Self {
56        let mut new_path = self.clone();
57        new_path.push_front_segment(segment);
58        new_path
59    }
60
61    pub fn push_index(&mut self, index: isize) {
62        self.segments.push(OwnedSegment::index(index));
63    }
64
65    pub fn with_index_appended(&self, index: isize) -> Self {
66        let mut new_path = self.clone();
67        new_path.push_index(index);
68        new_path
69    }
70
71    pub fn single_field(field: &str) -> Self {
72        vec![OwnedSegment::field(field)].into()
73    }
74
75    /// Create the possible fields that can be followed by this lookup.
76    ///
77    /// The limit specifies the limit of the path depth we are interested in.
78    /// Metrics is only interested in fields that are up to 3 levels deep (2 levels + 1 to check it
79    /// terminates).
80    ///
81    /// eg, .tags.nork.noog will never be an accepted path so we don't need to spend the time
82    /// collecting it.
83    pub fn to_alternative_components(&self, limit: usize) -> Option<Vec<&str>> {
84        let mut components = vec![];
85        for segment in self.segments.iter().take(limit) {
86            match segment {
87                OwnedSegment::Field(field) => {
88                    components.push(field.as_str());
89                }
90
91                OwnedSegment::Index(_) => {
92                    return None;
93                }
94            }
95        }
96
97        Some(components)
98    }
99
100    pub fn push(&mut self, segment: OwnedSegment) {
101        self.segments.push(segment);
102    }
103}
104
105// OwnedValuePath values must have at least one segment.
106#[cfg(any(test, feature = "proptest"))]
107impl Arbitrary for OwnedValuePath {
108    type Parameters = ();
109    type Strategy = BoxedStrategy<Self>;
110
111    fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
112        prop::collection::vec(any::<OwnedSegment>(), 1..10)
113            .prop_map(|segments| OwnedValuePath { segments })
114            .boxed()
115    }
116}
117
118/// An owned path that contains a target (pointing to either an Event or Metadata)
119#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
120#[cfg_attr(any(test, feature = "proptest"), derive(proptest_derive::Arbitrary))]
121#[serde(try_from = "String", into = "String")]
122pub struct OwnedTargetPath {
123    pub prefix: PathPrefix,
124    pub path: OwnedValuePath,
125}
126
127impl OwnedTargetPath {
128    pub fn event_root() -> Self {
129        Self::root(PathPrefix::Event)
130    }
131    pub fn metadata_root() -> Self {
132        Self::root(PathPrefix::Metadata)
133    }
134
135    pub fn root(prefix: PathPrefix) -> Self {
136        Self {
137            prefix,
138            path: OwnedValuePath::root(),
139        }
140    }
141
142    pub fn event(path: OwnedValuePath) -> Self {
143        Self {
144            prefix: PathPrefix::Event,
145            path,
146        }
147    }
148
149    pub fn metadata(path: OwnedValuePath) -> Self {
150        Self {
151            prefix: PathPrefix::Metadata,
152            path,
153        }
154    }
155
156    pub fn can_start_with(&self, prefix: &Self) -> bool {
157        if self.prefix != prefix.prefix {
158            return false;
159        }
160        (&self.path).can_start_with(&prefix.path)
161    }
162
163    pub fn with_field_appended(&self, field: &str) -> Self {
164        let mut new_path = self.path.clone();
165        new_path.push_field(field);
166        Self {
167            prefix: self.prefix,
168            path: new_path,
169        }
170    }
171
172    pub fn with_index_appended(&self, index: isize) -> Self {
173        let mut new_path = self.path.clone();
174        new_path.push_index(index);
175        Self {
176            prefix: self.prefix,
177            path: new_path,
178        }
179    }
180}
181
182impl Display for OwnedTargetPath {
183    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
184        match self.prefix {
185            PathPrefix::Event => write!(f, ".")?,
186            PathPrefix::Metadata => write!(f, "%")?,
187        }
188        Display::fmt(&self.path, f)
189    }
190}
191
192impl Debug for OwnedTargetPath {
193    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
194        Display::fmt(self, f)
195    }
196}
197
198impl From<OwnedTargetPath> for String {
199    fn from(target_path: OwnedTargetPath) -> Self {
200        Self::from(&target_path)
201    }
202}
203
204impl From<&OwnedTargetPath> for String {
205    fn from(target_path: &OwnedTargetPath) -> Self {
206        target_path.to_string()
207    }
208}
209
210impl Display for OwnedValuePath {
211    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
212        write!(f, "{}", String::from(self))
213    }
214}
215
216impl FromStr for OwnedValuePath {
217    type Err = PathParseError;
218
219    fn from_str(src: &str) -> Result<Self, Self::Err> {
220        parse_value_path(src).map_err(|_| PathParseError::InvalidPathSyntax {
221            path: src.to_owned(),
222        })
223    }
224}
225
226impl TryFrom<String> for OwnedValuePath {
227    type Error = PathParseError;
228
229    fn try_from(src: String) -> Result<Self, Self::Error> {
230        src.parse()
231    }
232}
233
234impl FromStr for OwnedTargetPath {
235    type Err = PathParseError;
236
237    fn from_str(src: &str) -> Result<Self, Self::Err> {
238        parse_target_path(src).map_err(|_| PathParseError::InvalidPathSyntax {
239            path: src.to_owned(),
240        })
241    }
242}
243
244impl TryFrom<String> for OwnedTargetPath {
245    type Error = PathParseError;
246
247    fn try_from(src: String) -> Result<Self, Self::Error> {
248        src.parse()
249    }
250}
251
252impl TryFrom<KeyString> for OwnedValuePath {
253    type Error = PathParseError;
254
255    fn try_from(src: KeyString) -> Result<Self, Self::Error> {
256        src.parse()
257    }
258}
259
260impl TryFrom<KeyString> for OwnedTargetPath {
261    type Error = PathParseError;
262
263    fn try_from(src: KeyString) -> Result<Self, Self::Error> {
264        src.parse()
265    }
266}
267
268impl From<OwnedValuePath> for String {
269    fn from(owned: OwnedValuePath) -> Self {
270        Self::from(&owned)
271    }
272}
273
274impl From<&OwnedValuePath> for String {
275    fn from(owned: &OwnedValuePath) -> Self {
276        let mut output = String::new();
277        for (i, segment) in owned.segments.iter().enumerate() {
278            match segment {
279                OwnedSegment::Field(field) => {
280                    serialize_field(&mut output, field.as_ref(), (i != 0).then_some("."))
281                }
282                OwnedSegment::Index(index) => {
283                    write!(output, "[{index}]").expect("Could not write to string")
284                }
285            }
286        }
287        output
288    }
289}
290
291fn serialize_field(string: &mut String, field: &str, separator: Option<&str>) {
292    // These characters should match the ones from the parser, implemented in `JitLookup`
293    let needs_quotes = field.is_empty()
294        || field
295            .chars()
296            .any(|c| !matches!(c, 'A'..='Z' | 'a'..='z' | '_' | '0'..='9' | '@'));
297
298    // Reserve enough to fit the field, a `.` and two `"` characters. This
299    // should suffice for the majority of cases when no escape sequence is used.
300    let separator_len = separator.map_or(0, |x| x.len());
301    string.reserve(field.len() + 2 + separator_len);
302    if let Some(separator) = separator {
303        string.push_str(separator);
304    }
305    if needs_quotes {
306        string.push('"');
307        for c in field.chars() {
308            if matches!(c, '"' | '\\') {
309                string.push('\\');
310            }
311            string.push(c);
312        }
313        string.push('"');
314    } else {
315        string.push_str(field);
316    }
317}
318
319impl From<Vec<OwnedSegment>> for OwnedValuePath {
320    fn from(segments: Vec<OwnedSegment>) -> Self {
321        Self { segments }
322    }
323}
324
325#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
326#[cfg_attr(any(test, feature = "proptest"), derive(proptest_derive::Arbitrary))]
327pub enum OwnedSegment {
328    Field(KeyString),
329    Index(isize),
330}
331
332impl OwnedSegment {
333    pub fn field(value: &str) -> OwnedSegment {
334        OwnedSegment::Field(value.into())
335    }
336    pub fn index(value: isize) -> OwnedSegment {
337        OwnedSegment::Index(value)
338    }
339
340    pub fn is_field(&self) -> bool {
341        matches!(self, OwnedSegment::Field(_))
342    }
343    pub fn is_index(&self) -> bool {
344        matches!(self, OwnedSegment::Index(_))
345    }
346
347    pub fn can_start_with(&self, prefix: &OwnedSegment) -> bool {
348        match (self, prefix) {
349            (OwnedSegment::Index(a), OwnedSegment::Index(b)) => a == b,
350            (OwnedSegment::Index(_), _) | (_, OwnedSegment::Index(_)) => false,
351            (OwnedSegment::Field(a), OwnedSegment::Field(b)) => a == b,
352        }
353    }
354}
355
356impl<'a> From<&'a str> for OwnedSegment {
357    fn from(field: &'a str) -> Self {
358        OwnedSegment::field(field)
359    }
360}
361
362impl<'a> From<&'a String> for OwnedSegment {
363    fn from(field: &'a String) -> Self {
364        OwnedSegment::field(field.as_str())
365    }
366}
367
368impl From<isize> for OwnedSegment {
369    fn from(index: isize) -> Self {
370        OwnedSegment::index(index)
371    }
372}
373
374impl<'a> ValuePath<'a> for &'a Vec<OwnedSegment> {
375    type Iter = OwnedSegmentSliceIter<'a>;
376
377    fn segment_iter(&self) -> Self::Iter {
378        OwnedSegmentSliceIter(self.iter())
379    }
380}
381
382impl<'a> ValuePath<'a> for &'a [OwnedSegment] {
383    type Iter = OwnedSegmentSliceIter<'a>;
384
385    fn segment_iter(&self) -> Self::Iter {
386        OwnedSegmentSliceIter(self.iter())
387    }
388}
389
390impl<'a> ValuePath<'a> for &'a OwnedValuePath {
391    type Iter = OwnedSegmentSliceIter<'a>;
392
393    fn segment_iter(&self) -> Self::Iter {
394        (&self.segments).segment_iter()
395    }
396}
397
398impl<'a> TryFrom<BorrowedSegment<'a>> for OwnedSegment {
399    type Error = ();
400
401    fn try_from(segment: BorrowedSegment<'a>) -> Result<Self, Self::Error> {
402        match segment {
403            BorrowedSegment::Invalid => Err(()),
404            BorrowedSegment::Index(i) => Ok(OwnedSegment::Index(i)),
405            BorrowedSegment::Field(field) => Ok(OwnedSegment::Field(field.into())),
406        }
407    }
408}
409
410static VALID_FIELD: LazyLock<Regex> =
411    LazyLock::new(|| Regex::new("^[0-9]*[a-zA-Z_@][0-9a-zA-Z_@]*$").unwrap());
412
413fn format_field(f: &mut Formatter<'_>, field: &str) -> fmt::Result {
414    // This can eventually just parse the field and see if it's valid, but the
415    // parser is currently lenient in what it accepts so it doesn't catch all cases that
416    // should be quoted
417    let needs_quotes = !VALID_FIELD.is_match(field);
418    if needs_quotes {
419        write!(f, "\"{field}\"")
420    } else {
421        write!(f, "{field}")
422    }
423}
424
425impl Display for OwnedSegment {
426    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
427        match self {
428            OwnedSegment::Index(i) => write!(f, "[{i}]"),
429            OwnedSegment::Field(field) => format_field(f, field),
430        }
431    }
432}
433
434#[derive(Clone)]
435pub struct OwnedSegmentSliceIter<'a>(std::slice::Iter<'a, OwnedSegment>);
436
437impl<'a> Iterator for OwnedSegmentSliceIter<'a> {
438    type Item = BorrowedSegment<'a>;
439
440    fn next(&mut self) -> Option<Self::Item> {
441        self.0.next().map(BorrowedSegment::from)
442    }
443}
444
445#[cfg(test)]
446mod test {
447    use super::*;
448    use crate::path::parse_value_path;
449
450    #[test]
451    fn to_alternative_components() -> Result<(), PathParseError> {
452        let limit = 3;
453
454        let path = parse_value_path(".")?;
455        assert_eq!(Some(vec![]), path.to_alternative_components(limit));
456
457        let path = parse_value_path(".first.second")?;
458        assert_eq!(
459            Some(vec!["first", "second"]),
460            path.to_alternative_components(limit)
461        );
462
463        let path = parse_value_path("a.b[1].c.d")?;
464        assert_eq!(None, path.to_alternative_components(limit));
465
466        let path = parse_value_path("a.b.c[1].d")?; // Has index after 3rd
467        assert_eq!(
468            Some(vec!["a", "b", "c"]),
469            path.to_alternative_components(limit)
470        );
471
472        let path = parse_value_path("a.b.c.d.e.f.g.h")?;
473        assert_eq!(
474            Some(vec!["a", "b", "c"]),
475            path.to_alternative_components(limit)
476        );
477
478        Ok(())
479    }
480
481    #[test]
482    fn owned_path_serialize() {
483        let test_cases = [
484            (".", Some("")),
485            ("", None),
486            ("]", None),
487            ("]foo", None),
488            ("..", None),
489            ("...", None),
490            ("f", Some("f")),
491            ("foo", Some("foo")),
492            (
493                r#"ec2.metadata."availability-zone""#,
494                Some(r#"ec2.metadata."availability-zone""#),
495            ),
496            ("@timestamp", Some("@timestamp")),
497            ("foo[", None),
498            ("foo$", None),
499            (r#""$peci@l chars""#, Some(r#""$peci@l chars""#)),
500            ("foo.foo bar", None),
501            (r#"foo."foo bar".bar"#, Some(r#"foo."foo bar".bar"#)),
502            ("[1]", Some("[1]")),
503            ("[42]", Some("[42]")),
504            ("foo.[42]", None),
505            ("[42].foo", Some("[42].foo")),
506            ("[-1]", Some("[-1]")),
507            ("[-42]", Some("[-42]")),
508            ("[-42].foo", Some("[-42].foo")),
509            ("[-42]foo", Some("[-42].foo")),
510            (r#""[42]. {}-_""#, Some(r#""[42]. {}-_""#)),
511            (r#""a\"a""#, Some(r#""a\"a""#)),
512            (r#"foo."a\"a"."b\\b".bar"#, Some(r#"foo."a\"a"."b\\b".bar"#)),
513            ("<invalid>", None),
514            (r#""🤖""#, Some(r#""🤖""#)),
515        ];
516
517        for (path, expected) in test_cases {
518            let path = parse_value_path(path).map(String::from).ok();
519
520            assert_eq!(path, expected.map(|x| x.to_owned()));
521        }
522    }
523
524    fn reparse_thing<T: std::fmt::Debug + std::fmt::Display + Eq + FromStr>(thing: T)
525    where
526        <T as FromStr>::Err: std::fmt::Debug,
527    {
528        let text = thing.to_string();
529        let thing2: T = text.parse().unwrap();
530        assert_eq!(thing, thing2);
531    }
532
533    proptest::proptest! {
534        #[test]
535        fn reparses_valid_value_path(path: OwnedValuePath) {
536            reparse_thing(path);
537        }
538
539        #[test]
540        fn reparses_valid_target_path(path: OwnedTargetPath) {
541            reparse_thing(path);
542        }
543    }
544}