vrl/stdlib/casing/
kebabcase.rs

1use crate::compiler::prelude::*;
2
3use crate::stdlib::casing::{ORIGINAL_CASE, into_case};
4use convert_case::Case;
5
6#[derive(Clone, Copy, Debug)]
7pub struct Kebabcase;
8
9impl Function for Kebabcase {
10    fn identifier(&self) -> &'static str {
11        "kebabcase"
12    }
13
14    fn usage(&self) -> &'static str {
15        "Takes the `value` string, and turns it into kebab-case. Optionally, you can pass in the existing case of the function, or else we will try to figure out the case automatically."
16    }
17
18    fn category(&self) -> &'static str {
19        Category::String.as_ref()
20    }
21
22    fn return_kind(&self) -> u16 {
23        kind::BYTES
24    }
25
26    fn parameters(&self) -> &'static [Parameter] {
27        const PARAMETERS: &[Parameter] = &[
28            Parameter::required("value", kind::BYTES, "The string to convert to kebab-case."),
29            ORIGINAL_CASE,
30        ];
31        PARAMETERS
32    }
33
34    fn compile(
35        &self,
36        state: &state::TypeState,
37        _ctx: &mut FunctionCompileContext,
38        arguments: ArgumentList,
39    ) -> Compiled {
40        let value = arguments.required("value");
41        let original_case = arguments
42            .optional_enum("original_case", &super::variants(), state)?
43            .map(|b| {
44                into_case(
45                    b.try_bytes_utf8_lossy()
46                        .expect("cant convert to string")
47                        .as_ref(),
48                )
49            })
50            .transpose()?;
51
52        Ok(KebabcaseFn {
53            value,
54            original_case,
55        }
56        .as_expr())
57    }
58
59    fn examples(&self) -> &'static [Example] {
60        &[
61            example! {
62                title: "kebab-case a string without specifying original case",
63                source: r#"kebabcase("InputString")"#,
64                result: Ok("input-string"),
65            },
66            example! {
67                title: "kebab-case a snake_case string",
68                source: r#"kebabcase("foo_bar_baz", "snake_case")"#,
69                result: Ok("foo-bar-baz"),
70            },
71            example! {
72                title: "kebab-case specifying the wrong original case (noop)",
73                source: r#"kebabcase("foo_bar_baz", "PascalCase")"#,
74                result: Ok("foo_bar_baz"),
75            },
76        ]
77    }
78}
79
80#[derive(Debug, Clone)]
81struct KebabcaseFn {
82    value: Box<dyn Expression>,
83    original_case: Option<Case>,
84}
85
86impl FunctionExpression for KebabcaseFn {
87    fn resolve(&self, ctx: &mut Context) -> Resolved {
88        let value = self.value.resolve(ctx)?;
89        super::convert_case(&value, Case::Kebab, self.original_case)
90    }
91
92    fn type_def(&self, _: &state::TypeState) -> TypeDef {
93        TypeDef::bytes().infallible()
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100    use crate::value;
101
102    test_function![
103        kebabcase => Kebabcase;
104
105        simple {
106            args: func_args![value: value!("input_string"), original_case: "snake_case"],
107            want: Ok(value!("input-string")),
108            tdef: TypeDef::bytes(),
109        }
110
111        no_case {
112            args: func_args![value: value!("input_string")],
113            want: Ok(value!("input-string")),
114            tdef: TypeDef::bytes(),
115        }
116    ];
117}