vrl/stdlib/
split_path.rs

1use crate::compiler::prelude::*;
2use std::path::Path;
3
4fn split_path(path_str: &str) -> Value {
5    let path = Path::new(path_str);
6
7    let split_path: Vec<_> = path
8        .components()
9        .map(|comp| comp.as_os_str().to_string_lossy().into_owned())
10        .collect();
11    split_path.into()
12}
13
14#[derive(Clone, Copy, Debug)]
15pub struct SplitPath;
16
17impl Function for SplitPath {
18    fn identifier(&self) -> &'static str {
19        "split_path"
20    }
21
22    fn usage(&self) -> &'static str {
23        "Splits the given `path` into its constituent components, returning an array of strings. Each component represents a part of the file system path hierarchy."
24    }
25
26    fn category(&self) -> &'static str {
27        Category::String.as_ref()
28    }
29
30    fn internal_failure_reasons(&self) -> &'static [&'static str] {
31        &["`value` is not a valid string."]
32    }
33
34    fn return_kind(&self) -> u16 {
35        kind::ARRAY
36    }
37
38    fn parameters(&self) -> &'static [Parameter] {
39        const PARAMETERS: &[Parameter] = &[Parameter::required(
40            "value",
41            kind::BYTES,
42            "The path to split into components.",
43        )];
44        PARAMETERS
45    }
46
47    fn compile(
48        &self,
49        _state: &state::TypeState,
50        _ctx: &mut FunctionCompileContext,
51        arguments: ArgumentList,
52    ) -> Compiled {
53        let value = arguments.required("value");
54
55        Ok(SplitPathFn { value }.as_expr())
56    }
57
58    fn examples(&self) -> &'static [Example] {
59        &[
60            example! {
61                title: "Split path with trailing slash",
62                source: r#"split_path("/home/user/")"#,
63                result: Ok(r#"["/", "home", "user"]"#),
64            },
65            example! {
66                title: "Split path from file path",
67                source: r#"split_path("/home/user")"#,
68                result: Ok(r#"["/", "home", "user"]"#),
69            },
70            example! {
71                title: "Split path from root",
72                source: r#"split_path("/")"#,
73                result: Ok(r#"["/"]"#),
74            },
75            example! {
76                title: "Empty path returns empty array",
77                source: r#"split_path("")"#,
78                result: Ok("[]"),
79            },
80        ]
81    }
82}
83
84#[derive(Debug, Clone)]
85struct SplitPathFn {
86    value: Box<dyn Expression>,
87}
88
89impl FunctionExpression for SplitPathFn {
90    fn resolve(&self, ctx: &mut Context) -> Resolved {
91        let value = self.value.resolve(ctx)?;
92        let path_str = value.try_bytes_utf8_lossy()?;
93        Ok(split_path(&path_str))
94    }
95
96    fn type_def(&self, _: &state::TypeState) -> TypeDef {
97        TypeDef::array(Collection::any())
98    }
99}
100#[cfg(test)]
101mod tests {
102    use super::*;
103    use crate::value;
104
105    fn tdef() -> TypeDef {
106        TypeDef::array(Collection::any())
107    }
108
109    test_function![
110        split_path => SplitPath;
111
112        home_user_trailing_slash {
113            args: func_args![value: "/home/user/"],
114            want: Ok(value!(["/", "home", "user"])),
115            tdef: tdef(),
116        }
117
118        home_user_no_trailing_slash {
119            args: func_args![value: "/home/user"],
120            want: Ok(value!(["/", "home", "user"])),
121            tdef: tdef(),
122        }
123
124        root {
125            args: func_args![value: "/"],
126            want: Ok(value!(["/"])),
127            tdef: tdef(),
128        }
129
130        empty {
131            args: func_args![value: ""],
132            want: Ok(value!([])),
133            tdef: tdef(),
134        }
135
136    ];
137}