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.")
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}