-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
enhancement(remap): Add base64 encoding and decoding functions (#5768)
* Add encode_base64 function Signed-off-by: Luc Perkins <luc@timber.io> * Add decode_base64 function Signed-off-by: Luc Perkins <luc@timber.io> * Add padding parameter to encode function plus behvaior tests and docs Signed-off-by: Luc Perkins <luc@timber.io> * Fix TypeDef for encoding function Signed-off-by: Luc Perkins <luc@timber.io> * Fix test name Signed-off-by: Luc Perkins <luc@timber.io> * Update docs to include padding option Signed-off-by: Luc Perkins <luc@timber.io> * Fix misleading test name Signed-off-by: Luc Perkins <luc@timber.io> * Fix errant example in docs Signed-off-by: Luc Perkins <luc@timber.io> * Add test for decoding padded vs non padded Signed-off-by: Luc Perkins <luc@timber.io> * Incorporate @JeanMertz suggestions Signed-off-by: Luc Perkins <luc@timber.io> * Make decode_base64 always fallible Signed-off-by: Luc Perkins <luc@timber.io> * Refactor padding defaulting and parsing Signed-off-by: Jesse Szwedko <jesse@szwedko.me> * Fix CUE formatting Signed-off-by: Luc Perkins <luc@timber.io> Co-authored-by: Jesse Szwedko <jesse@szwedko.me>
- Loading branch information
1 parent
02c327a
commit 1b9880c
Showing
10 changed files
with
367 additions
and
1 deletion.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package metadata | ||
|
||
remap: functions: decode_base64: { | ||
arguments: [ | ||
{ | ||
name: "value" | ||
description: "The [Base64](\(urls.base64)) data to decode." | ||
required: true | ||
type: ["string"] | ||
}, | ||
] | ||
return: ["string"] | ||
category: "Decode" | ||
description: "Decodes the provided [Base64](\(urls.base64)) data to a string." | ||
examples: [ | ||
{ | ||
title: "Decode Base64 data" | ||
input: { | ||
message: "eW91IGhhdmUgc3VjY2Vzc2Z1bGx5IGRlY29kZWQgbWU=" | ||
} | ||
source: ".decoded = decode_base64(.message)" | ||
output: { | ||
message: "eW91IGhhdmUgc3VjY2Vzc2Z1bGx5IGRlY29kZWQgbWU=" | ||
decoded: "you have successfully decoded me" | ||
} | ||
}, | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package metadata | ||
|
||
remap: functions: encode_base64: { | ||
arguments: [ | ||
{ | ||
name: "value" | ||
description: "The string to encode." | ||
required: true | ||
type: ["string"] | ||
}, | ||
{ | ||
name: "padding" | ||
description: "Whether the Base64 output is [padded](\(urls.base64_padding))." | ||
required: false | ||
type: ["boolean"] | ||
default: true | ||
}, | ||
] | ||
return: ["string"] | ||
category: "Encode" | ||
description: "Encodes the provided string to [Base64](\(urls.base64))." | ||
examples: [ | ||
{ | ||
title: "Encode string" | ||
input: { | ||
message: "please encode me" | ||
} | ||
source: ".encoded = encode_base64(.message)" | ||
output: { | ||
message: "please encode me" | ||
encoded: "cGxlYXNlIGVuY29kZSBtZQ==" | ||
} | ||
}, | ||
{ | ||
title: "Encode string without padding" | ||
input: { | ||
message: "please encode me, no padding though" | ||
} | ||
source: ".encoded = encode_base64(.message, padding = false)" | ||
output: { | ||
message: "please encode me, no padding though" | ||
encoded: "cGxlYXNlIGVuY29kZSBtZSwgbm8gcGFkZGluZyB0aG91Z2g" | ||
} | ||
}, | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
use remap::prelude::*; | ||
|
||
#[derive(Clone, Copy, Debug)] | ||
pub struct DecodeBase64; | ||
|
||
impl Function for DecodeBase64 { | ||
fn identifier(&self) -> &'static str { | ||
"decode_base64" | ||
} | ||
|
||
fn parameters(&self) -> &'static [Parameter] { | ||
&[Parameter { | ||
keyword: "value", | ||
accepts: |v| matches!(v, Value::Bytes(_)), | ||
required: true, | ||
}] | ||
} | ||
|
||
fn compile(&self, mut arguments: ArgumentList) -> Result<Box<dyn Expression>> { | ||
let value = arguments.required("value")?.boxed(); | ||
|
||
Ok(Box::new(DecodeBase64Fn { value })) | ||
} | ||
} | ||
|
||
#[derive(Clone, Debug)] | ||
struct DecodeBase64Fn { | ||
value: Box<dyn Expression>, | ||
} | ||
|
||
impl Expression for DecodeBase64Fn { | ||
fn execute(&self, state: &mut state::Program, object: &mut dyn Object) -> Result<Value> { | ||
let value = self.value.execute(state, object)?.try_bytes()?; | ||
|
||
base64::decode(value) | ||
.map(Into::into) | ||
.map_err(|_| "unable to decode value to base64".into()) | ||
} | ||
|
||
fn type_def(&self, state: &state::Compiler) -> TypeDef { | ||
// Always fallible due to the possibility of decoding errors that VRL can't detect in | ||
// advance: https://docs.rs/base64/0.13.0/base64/enum.DecodeError.html | ||
self.value | ||
.type_def(state) | ||
.into_fallible(true) | ||
.with_constraint(value::Kind::Bytes) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use super::*; | ||
use value::Kind; | ||
|
||
test_type_def![ | ||
value_string_fallible { | ||
expr: |_| DecodeBase64Fn { | ||
value: Literal::from("foo").boxed(), | ||
}, | ||
def: TypeDef { fallible: true, kind: Kind::Bytes, ..Default::default() }, | ||
} | ||
|
||
value_non_string_fallible { | ||
expr: |_| DecodeBase64Fn { | ||
value: Literal::from(127).boxed(), | ||
}, | ||
def: TypeDef { fallible: true, kind: Kind::Bytes, ..Default::default() }, | ||
} | ||
]; | ||
|
||
test_function![ | ||
decode_base64 => DecodeBase64; | ||
|
||
string_value_with_padding { | ||
args: func_args![value: value!("c29tZSBzdHJpbmcgdmFsdWU=")], | ||
want: Ok(value!("some string value")), | ||
} | ||
|
||
string_value_no_padding { | ||
args: func_args![value: value!("c29tZSBzdHJpbmcgdmFsdWU")], | ||
want: Ok(value!("some string value")), | ||
} | ||
|
||
empty_string_value { | ||
args: func_args![value: value!("")], | ||
want: Ok(value!("")), | ||
} | ||
]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
use remap::prelude::*; | ||
|
||
#[derive(Clone, Copy, Debug)] | ||
pub struct EncodeBase64; | ||
|
||
impl Function for EncodeBase64 { | ||
fn identifier(&self) -> &'static str { | ||
"encode_base64" | ||
} | ||
|
||
fn parameters(&self) -> &'static [Parameter] { | ||
&[ | ||
Parameter { | ||
keyword: "value", | ||
accepts: |v| matches!(v, Value::Bytes(_)), | ||
required: true, | ||
}, | ||
Parameter { | ||
keyword: "padding", | ||
accepts: |v| matches!(v, Value::Boolean(_)), | ||
required: false, | ||
}, | ||
] | ||
} | ||
|
||
fn compile(&self, mut arguments: ArgumentList) -> Result<Box<dyn Expression>> { | ||
let value = arguments.required("value")?.boxed(); | ||
let padding = arguments.optional("padding").map(Expr::boxed); | ||
|
||
Ok(Box::new(EncodeBase64Fn { value, padding })) | ||
} | ||
} | ||
|
||
#[derive(Clone, Debug)] | ||
struct EncodeBase64Fn { | ||
value: Box<dyn Expression>, | ||
padding: Option<Box<dyn Expression>>, | ||
} | ||
|
||
impl Expression for EncodeBase64Fn { | ||
fn execute(&self, state: &mut state::Program, object: &mut dyn Object) -> Result<Value> { | ||
let value = self.value.execute(state, object)?.try_bytes()?; | ||
|
||
let padding = self | ||
.padding | ||
.as_ref() | ||
.map(|p| { | ||
p.execute(state, object) | ||
.and_then(|v| Value::try_boolean(v).map_err(Into::into)) | ||
}) | ||
.transpose()? | ||
.unwrap_or(true); | ||
|
||
let config = if padding { | ||
base64::STANDARD | ||
} else { | ||
base64::STANDARD_NO_PAD | ||
}; | ||
|
||
Ok(base64::encode_config(value, config).into()) | ||
} | ||
|
||
fn type_def(&self, state: &state::Compiler) -> TypeDef { | ||
use value::Kind; | ||
|
||
let padding_def = self | ||
.padding | ||
.as_ref() | ||
.map(|padding| padding.type_def(state).fallible_unless(Kind::Boolean)); | ||
|
||
self.value | ||
.type_def(state) | ||
.fallible_unless(Kind::Bytes) | ||
.merge_optional(padding_def) | ||
.with_constraint(Kind::Bytes) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use super::*; | ||
use value::Kind; | ||
|
||
test_type_def![ | ||
value_string_padding_unspecified_infallible { | ||
expr: |_| EncodeBase64Fn { | ||
value: Literal::from("foo").boxed(), | ||
padding: None, | ||
}, | ||
def: TypeDef { kind: Kind::Bytes, ..Default::default() }, | ||
} | ||
|
||
value_string_padding_boolean_infallible { | ||
expr: |_| EncodeBase64Fn { | ||
value: Literal::from("foo").boxed(), | ||
padding: Some(Literal::from(false).boxed()), | ||
}, | ||
def: TypeDef { kind: Kind::Bytes, ..Default::default() }, | ||
} | ||
|
||
value_string_padding_non_boolean_fallible { | ||
expr: |_| EncodeBase64Fn { | ||
value: Literal::from("foo").boxed(), | ||
padding: Some(Literal::from("foo").boxed()), | ||
}, | ||
def: TypeDef { fallible: true, kind: Kind::Bytes, ..Default::default() }, | ||
} | ||
|
||
value_non_string_fallible { | ||
expr: |_| EncodeBase64Fn { | ||
value: Literal::from(127).boxed(), | ||
padding: Some(Literal::from(true).boxed()), | ||
}, | ||
def: TypeDef { fallible: true, kind: Kind::Bytes, ..Default::default() }, | ||
} | ||
|
||
both_types_wrong_fallible { | ||
expr: |_| EncodeBase64Fn { | ||
value: Literal::from(127).boxed(), | ||
padding: Some(Literal::from("foo").boxed()), | ||
}, | ||
def: TypeDef { fallible: true, kind: Kind::Bytes, ..Default::default() }, | ||
} | ||
]; | ||
|
||
test_function![ | ||
encode_base64 => EncodeBase64; | ||
|
||
string_value_with_padding { | ||
args: func_args![value: value!("some string value"), padding: value!(true)], | ||
want: Ok(value!("c29tZSBzdHJpbmcgdmFsdWU=")), | ||
} | ||
|
||
string_value_with_default_padding { | ||
args: func_args![value: value!("some string value")], | ||
want: Ok(value!("c29tZSBzdHJpbmcgdmFsdWU=")), | ||
} | ||
|
||
string_value_no_padding { | ||
args: func_args![value: value!("some string value"), padding: value!(false)], | ||
want: Ok(value!("c29tZSBzdHJpbmcgdmFsdWU")), | ||
} | ||
|
||
empty_string_value { | ||
args: func_args![value: value!("")], | ||
want: Ok(value!("")), | ||
} | ||
]; | ||
} |
Oops, something went wrong.