vrl/stdlib/
encode_base64.rs

1use crate::compiler::function::EnumVariant;
2use crate::compiler::prelude::*;
3use crate::stdlib::util::Base64Charset;
4use std::sync::LazyLock;
5
6static DEFAULT_PADDING: LazyLock<Value> = LazyLock::new(|| Value::Boolean(true));
7static DEFAULT_CHARSET: LazyLock<Value> = LazyLock::new(|| Value::Bytes(Bytes::from("standard")));
8
9static PARAMETERS: LazyLock<Vec<Parameter>> = LazyLock::new(|| {
10    vec![
11        Parameter::required("value", kind::BYTES, "The string to encode."),
12        Parameter::optional("padding", kind::BOOLEAN, "Whether the Base64 output is [padded](https://en.wikipedia.org/wiki/Base64#Output_padding).")
13            .default(&DEFAULT_PADDING),
14        Parameter::optional("charset", kind::BYTES, "The character set to use when encoding the data.")
15            .default(&DEFAULT_CHARSET)
16            .enum_variants(&[
17                EnumVariant {
18                    value: "standard",
19                    description: "[Standard](https://tools.ietf.org/html/rfc4648#section-4) Base64 format.",
20                },
21                EnumVariant {
22                    value: "url_safe",
23                    description: "Modified Base64 for [URL variants](https://en.wikipedia.org/wiki/Base64#URL_applications).",
24                },
25            ]),
26    ]
27});
28
29fn encode_base64(value: Value, padding: Value, charset: Value) -> Resolved {
30    let value = value.try_bytes()?;
31    let padding = padding.try_boolean()?;
32    let charset = Base64Charset::from_slice(&charset.try_bytes()?)?;
33
34    let encoder = match (padding, charset) {
35        (true, Base64Charset::Standard) => base64_simd::STANDARD,
36        (false, Base64Charset::Standard) => base64_simd::STANDARD_NO_PAD,
37        (true, Base64Charset::UrlSafe) => base64_simd::URL_SAFE,
38        (false, Base64Charset::UrlSafe) => base64_simd::URL_SAFE_NO_PAD,
39    };
40
41    Ok(encoder.encode_to_string(value).into())
42}
43
44#[derive(Clone, Copy, Debug)]
45pub struct EncodeBase64;
46
47impl Function for EncodeBase64 {
48    fn identifier(&self) -> &'static str {
49        "encode_base64"
50    }
51
52    fn usage(&self) -> &'static str {
53        "Encodes the `value` to [Base64](https://en.wikipedia.org/wiki/Base64)."
54    }
55
56    fn category(&self) -> &'static str {
57        Category::Codec.as_ref()
58    }
59
60    fn return_kind(&self) -> u16 {
61        kind::BYTES
62    }
63
64    fn parameters(&self) -> &'static [Parameter] {
65        PARAMETERS.as_slice()
66    }
67
68    fn compile(
69        &self,
70        _state: &state::TypeState,
71        _ctx: &mut FunctionCompileContext,
72        arguments: ArgumentList,
73    ) -> Compiled {
74        let value = arguments.required("value");
75        let padding = arguments.optional("padding");
76        let charset = arguments.optional("charset");
77
78        Ok(EncodeBase64Fn {
79            value,
80            padding,
81            charset,
82        }
83        .as_expr())
84    }
85
86    fn examples(&self) -> &'static [Example] {
87        &[
88            example! {
89                title: "Encode to Base64 (default)",
90                source: r#"encode_base64("please encode me")"#,
91                result: Ok("cGxlYXNlIGVuY29kZSBtZQ=="),
92            },
93            example! {
94                title: "Encode to Base64 (without padding)",
95                source: r#"encode_base64("please encode me, no padding though", padding: false)"#,
96                result: Ok("cGxlYXNlIGVuY29kZSBtZSwgbm8gcGFkZGluZyB0aG91Z2g"),
97            },
98            example! {
99                title: "Encode to Base64 (URL safe)",
100                source: r#"encode_base64("please encode me, but safe for URLs", charset: "url_safe")"#,
101                result: Ok("cGxlYXNlIGVuY29kZSBtZSwgYnV0IHNhZmUgZm9yIFVSTHM="),
102            },
103            example! {
104                title: "Encode to Base64 (without padding and URL safe)",
105                source: r#"encode_base64("some string value", padding: false, charset: "url_safe")"#,
106                result: Ok("c29tZSBzdHJpbmcgdmFsdWU"),
107            },
108        ]
109    }
110}
111
112#[derive(Clone, Debug)]
113struct EncodeBase64Fn {
114    value: Box<dyn Expression>,
115    padding: Option<Box<dyn Expression>>,
116    charset: Option<Box<dyn Expression>>,
117}
118
119impl FunctionExpression for EncodeBase64Fn {
120    fn resolve(&self, ctx: &mut Context) -> Resolved {
121        let value = self.value.resolve(ctx)?;
122        let padding = self
123            .padding
124            .map_resolve_with_default(ctx, || DEFAULT_PADDING.clone())?;
125        let charset = self
126            .charset
127            .map_resolve_with_default(ctx, || DEFAULT_CHARSET.clone())?;
128
129        encode_base64(value, padding, charset)
130    }
131
132    fn type_def(&self, _: &state::TypeState) -> TypeDef {
133        TypeDef::bytes().infallible()
134    }
135}
136
137#[cfg(test)]
138mod test {
139    use super::*;
140    use crate::value;
141
142    test_function![
143        encode_base64 => EncodeBase64;
144
145        with_defaults {
146            args: func_args![value: value!("some+=string/value")],
147            want: Ok(value!("c29tZSs9c3RyaW5nL3ZhbHVl")),
148            tdef: TypeDef::bytes().infallible(),
149        }
150
151        with_padding_standard_charset {
152            args: func_args![value: value!("some+=string/value"), padding: value!(true), charset: value!("standard")],
153            want: Ok(value!("c29tZSs9c3RyaW5nL3ZhbHVl")),
154            tdef: TypeDef::bytes().infallible(),
155        }
156
157        no_padding_standard_charset {
158            args: func_args![value: value!("some+=string/value"), padding: value!(false), charset: value!("standard")],
159            want: Ok(value!("c29tZSs9c3RyaW5nL3ZhbHVl")),
160            tdef: TypeDef::bytes().infallible(),
161        }
162
163        with_padding_urlsafe_charset {
164            args: func_args![value: value!("some+=string/value"), padding: value!(true), charset: value!("url_safe")],
165            want: Ok(value!("c29tZSs9c3RyaW5nL3ZhbHVl")),
166            tdef: TypeDef::bytes().infallible(),
167        }
168
169        no_padding_urlsafe_charset {
170            args: func_args![value: value!("some+=string/value"), padding: value!(false), charset: value!("url_safe")],
171            want: Ok(value!("c29tZSs9c3RyaW5nL3ZhbHVl")),
172            tdef: TypeDef::bytes().infallible(),
173        }
174
175        with_padding_standard_charset_unicode {
176            args: func_args![value: value!("some=string/řčža"), padding: value!(true), charset: value!("standard")],
177            want: Ok(value!("c29tZT1zdHJpbmcvxZnEjcW+YQ==")),
178            tdef: TypeDef::bytes().infallible(),
179        }
180
181        no_padding_standard_charset_unicode {
182            args: func_args![value: value!("some=string/řčža"), padding: value!(false), charset: value!("standard")],
183            want: Ok(value!("c29tZT1zdHJpbmcvxZnEjcW+YQ")),
184            tdef: TypeDef::bytes().infallible(),
185        }
186
187        with_padding_urlsafe_charset_unicode {
188            args: func_args![value: value!("some=string/řčža"), padding: value!(true), charset: value!("url_safe")],
189            want: Ok(value!("c29tZT1zdHJpbmcvxZnEjcW-YQ==")),
190            tdef: TypeDef::bytes().infallible(),
191        }
192
193        no_padding_urlsafe_charset_unicode {
194            args: func_args![value: value!("some=string/řčža"), padding: value!(false), charset: value!("url_safe")],
195            want: Ok(value!("c29tZT1zdHJpbmcvxZnEjcW-YQ")),
196            tdef: TypeDef::bytes().infallible(),
197        }
198
199        empty_string_standard_charset {
200            args: func_args![value: value!(""), charset: value!("standard")],
201            want: Ok(value!("")),
202            tdef: TypeDef::bytes().infallible(),
203        }
204
205        empty_string_urlsafe_charset {
206            args: func_args![value: value!(""), charset: value!("url_safe")],
207            want: Ok(value!("")),
208            tdef: TypeDef::bytes().infallible(),
209        }
210
211        invalid_charset_error {
212            args: func_args![value: value!("some string value"), padding: value!(false), charset: value!("foo")],
213            want: Err("unknown charset"),
214            tdef: TypeDef::bytes().infallible(),
215        }
216    ];
217}