Skip to content

Commit

Permalink
schnauzer: add toml_encode helper
Browse files Browse the repository at this point in the history
  • Loading branch information
piyush-jena committed Sep 4, 2024
1 parent a31fa58 commit 17abbba
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 1 deletion.
14 changes: 13 additions & 1 deletion sources/api/schnauzer/src/helpers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use url::Url;
pub mod stdlib;

pub use stdlib::{
any_enabled, base64_decode, default, goarch, join_array, join_map, negate_or_else,
any_enabled, base64_decode, default, goarch, join_array, join_map, negate_or_else, toml_encode,
IfNotNullHelper, IsArray, IsBool, IsNull, IsNumber, IsObject, IsString,
};

Expand Down Expand Up @@ -282,6 +282,18 @@ mod error {
rps: handlebars::JsonValue,
burst: handlebars::JsonValue,
},

#[snafu(display(
"Unable to encode input '{}' from template '{}' as toml: {}",
value,
source,
template
))]
TomlEncode {
value: serde_json::Value,
source: serde_json::Error,
template: String,
},
}

// Handlebars helpers are required to return a RenderError.
Expand Down
127 changes: 127 additions & 0 deletions sources/api/schnauzer/src/helpers/stdlib/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1130,3 +1130,130 @@ mod test_negate_or_else {
});
}
}

/// `toml_encode` accepts arbitrary input and encodes it as a toml string
///
/// # Example
///
/// Consider an array of values: `[ "a", "b", "c" ]` stored in a setting such as
/// `settings.somewhere.foo-list`. In our template we can write:
/// `{{ toml_encode settings.somewhere.foo-list }}`
///
/// This will render `["a", "b", "c"]`.
///
/// Similarly, for a string: `"string"`, the template {{ toml-encode "string" }}
/// will render `"string"`.
pub fn toml_encode(
helper: &Helper<'_, '_>,
_: &Handlebars,
_: &Context,
renderctx: &mut RenderContext<'_, '_>,
out: &mut dyn Output,
) -> Result<(), RenderError> {
trace!("Starting toml_encode helper");
let template_name = template_name(renderctx);
check_param_count(helper, template_name, 1)?;

// get the string
let encode_param = get_param(helper, 0)?;
let toml_value: toml::Value =
serde_json::from_value(encode_param.to_owned()).with_context(|_| {
error::TomlEncodeSnafu {
value: encode_param.to_owned(),
template: template_name,
}
})?;

let result = toml_value.to_string();

// write it to the template
out.write(&result)
.with_context(|_| error::TemplateWriteSnafu {
template: template_name.to_owned(),
})?;

Ok(())
}

#[cfg(test)]
mod test_toml_encode {
use crate::helpers::toml_encode;
use handlebars::{Handlebars, RenderError};
use serde::Serialize;
use serde_json::json;

// A thin wrapper around the handlebars render_template method that includes
// setup and registration of helpers
fn setup_and_render_template<T>(tmpl: &str, data: &T) -> Result<String, RenderError>
where
T: Serialize,
{
let mut registry = Handlebars::new();
registry.register_helper("toml_encode", Box::new(toml_encode));

registry.render_template(tmpl, data)
}

const TEMPLATE: &str = r#"{{ toml_encode settings.foo-string }}"#;

#[test]
fn toml_encode_map() {
let result = setup_and_render_template(
TEMPLATE,
&json!({"settings": {"foo-string": {"hello": "world"}}}),
)
.unwrap();
let expected = r#"{ hello = "world" }"#;
assert_eq!(result, expected);
}

#[test]
fn toml_encode_empty() {
let result =
setup_and_render_template(TEMPLATE, &json!({"settings": {"foo-string": []}})).unwrap();
let expected = r#"[]"#;
assert_eq!(result, expected);
}

#[test]
fn toml_encode_empty_string() {
let result =
setup_and_render_template(TEMPLATE, &json!({"settings": {"foo-string": [""]}}))
.unwrap();
let expected = r#"[""]"#;
assert_eq!(result, expected);
}

#[test]
fn toml_encode_toml_injection_1() {
let result = setup_and_render_template(
TEMPLATE,
&json!({"settings": {"foo-string": [ "apiclient set motd=hello', 'echo pwned\""]}}),
)
.unwrap();
let expected = "['''apiclient set motd=hello', 'echo pwned\"''']";
assert_eq!(result, expected);
}

#[test]
fn toml_encode_toml_injection_2() {
let result = setup_and_render_template(
TEMPLATE,
&json!({"settings": {"foo-string": [ "apiclient set motd=hello\", \"echo pwned\""]}}),
)
.unwrap();
let expected = "['apiclient set motd=hello\", \"echo pwned\"']";
assert_eq!(result, expected);
}

#[test]
fn toml_encode_toml_injection_3() {
let result = setup_and_render_template(
TEMPLATE,
&json!({"settings": {"foo-string": [ "apiclient set motd=hello\", \"echo pwned\", 'echo pwned2'"]}}),
)
.unwrap();
let expected = "[\"apiclient set motd=hello\\\", \\\"echo pwned\\\", 'echo pwned2'\"]";
assert_eq!(result, expected);
}
}
1 change: 1 addition & 0 deletions sources/api/schnauzer/src/v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ pub fn build_template_registry() -> Result<handlebars::Handlebars<'static>> {
template_registry.register_helper("host", Box::new(helpers::host));
template_registry.register_helper("goarch", Box::new(helpers::goarch));
template_registry.register_helper("join_array", Box::new(helpers::join_array));
template_registry.register_helper("toml_encode", Box::new(helpers::toml_encode));
template_registry.register_helper("kube_reserve_cpu", Box::new(helpers::kube_reserve_cpu));
template_registry.register_helper(
"kube_reserve_memory",
Expand Down
1 change: 1 addition & 0 deletions sources/api/schnauzer/src/v2/import/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ fn all_helpers() -> HashMap<ExtensionName, HashMap<HelperName, Box<dyn HelperDef
"any_enabled" => helper!(handlebars_helpers::any_enabled),
"base64_decode" => helper!(handlebars_helpers::base64_decode),
"default" => helper!(handlebars_helpers::default),
"toml_encode" => helper!(handlebars_helpers::toml_encode),
"join_array" => helper!(handlebars_helpers::join_array),
"join_map" => helper!(handlebars_helpers::join_map),
"if_not_null" => Box::new(handlebars_helpers::IfNotNullHelper),
Expand Down

0 comments on commit 17abbba

Please sign in to comment.