use base64::{
Engine,
engine::{
DecodePaddingMode,
general_purpose::{GeneralPurpose, GeneralPurposeConfig, STANDARD},
},
};
use cel::{Context, ExecutionError, ResolveResult, objects::Value};
use std::sync::Arc;
const STANDARD_INDIFFERENT: GeneralPurpose = GeneralPurpose::new(
&base64::alphabet::STANDARD,
GeneralPurposeConfig::new().with_decode_padding_mode(DecodePaddingMode::Indifferent),
);
pub fn register(ctx: &mut Context<'_>) {
ctx.add_function("base64.decode", base64_decode);
ctx.add_function("base64.encode", base64_encode);
}
fn base64_decode(s: Arc<String>) -> ResolveResult {
let bytes = STANDARD_INDIFFERENT
.decode(s.as_bytes())
.map_err(|e| ExecutionError::function_error("base64.decode", e.to_string()))?;
Ok(Value::Bytes(Arc::new(bytes)))
}
fn base64_encode(b: Arc<Vec<u8>>) -> ResolveResult {
Ok(Value::String(Arc::new(STANDARD.encode(b.as_ref()))))
}
#[cfg(test)]
mod tests {
use super::*;
use cel::Program;
fn eval(expr: &str) -> Value {
let mut ctx = Context::default();
register(&mut ctx);
Program::compile(expr).unwrap().execute(&ctx).unwrap()
}
fn eval_err(expr: &str) -> ExecutionError {
let mut ctx = Context::default();
register(&mut ctx);
Program::compile(expr).unwrap().execute(&ctx).unwrap_err()
}
#[test]
fn test_base64_decode() {
assert_eq!(
eval("base64.decode('aGVsbG8=')"),
Value::Bytes(Arc::new(b"hello".to_vec()))
);
}
#[test]
fn test_base64_encode() {
assert_eq!(
eval("base64.encode(b'hello')"),
Value::String(Arc::new("aGVsbG8=".into()))
);
}
#[test]
fn test_base64_roundtrip() {
assert_eq!(
eval("base64.decode('dGVzdA==')"),
Value::Bytes(Arc::new(b"test".to_vec()))
);
}
#[test]
fn test_base64_decode_invalid() {
eval_err("base64.decode('!!!')");
}
#[test]
fn test_base64_decode_empty() {
assert_eq!(eval("base64.decode('')"), Value::Bytes(Arc::new(vec![])));
}
#[test]
fn test_base64_decode_unpadded() {
assert_eq!(
eval("base64.decode('aGVsbG8')"),
Value::Bytes(Arc::new(b"hello".to_vec()))
);
}
}