Skip to content

Commit ecae1e4

Browse files
authored
fix: template exec yaml encode obj (#2031)
Signed-off-by: Peefy <xpf6677@163.com>
1 parent 60763ce commit ecae1e4

File tree

12 files changed

+213
-2
lines changed

12 files changed

+213
-2
lines changed

crates/runtime/src/template/mod.rs

Lines changed: 167 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,33 @@
11
use std::collections::HashMap;
22

33
use crate::*;
4-
use handlebars::{Handlebars, html_escape};
4+
use handlebars::{
5+
Context, Handlebars, Helper, HelperResult, Output, RenderContext, RenderErrorReason,
6+
html_escape,
7+
};
8+
9+
/// Custom helper that renders a value without HTML escaping.
10+
/// Usage: {{raw var}}
11+
fn raw_helper(
12+
h: &Helper,
13+
_: &Handlebars,
14+
_: &Context,
15+
_: &mut RenderContext,
16+
out: &mut dyn Output,
17+
) -> HelperResult {
18+
let param = h
19+
.param(0)
20+
.ok_or(RenderErrorReason::Other("Parameter not found".into()))?;
21+
let value = param.value();
22+
23+
// Output the raw JSON value without HTML escaping
24+
if let Some(s) = value.as_str() {
25+
out.write(s)?;
26+
} else {
27+
out.write(&value.to_string())?;
28+
}
29+
Ok(())
30+
}
531

632
/// Applies a parsed template to the specified data object and
733
/// returns the string output.
@@ -19,6 +45,10 @@ pub unsafe extern "C-unwind" fn kcl_template_execute(
1945

2046
if let Some(template) = get_call_arg_str(args, kwargs, 0, Some("template")) {
2147
let mut handlebars = Handlebars::new();
48+
49+
// Register helper for raw (unescaped) output
50+
handlebars.register_helper("raw", Box::new(raw_helper));
51+
2252
handlebars
2353
.register_template_string("template", template)
2454
.expect("register template failed");
@@ -53,5 +83,140 @@ pub unsafe extern "C-unwind" fn kcl_template_html_escape(
5383
if let Some(data) = get_call_arg_str(args, kwargs, 0, Some("data")) {
5484
return ValueRef::str(&html_escape(&data)).into_raw(ctx);
5585
}
56-
panic!("html_escape() takes exactly one argument (0 given)");
86+
panic!("html_escape() takes exactly one argument (0 given)")
87+
}
88+
89+
#[cfg(test)]
90+
mod tests {
91+
use super::*;
92+
93+
#[test]
94+
fn test_raw_helper_with_html() {
95+
let mut handlebars = Handlebars::new();
96+
handlebars.register_helper("raw", Box::new(raw_helper));
97+
98+
let template = r#"{{raw value}}"#.to_string();
99+
handlebars
100+
.register_template_string("test", template)
101+
.expect("register template failed");
102+
103+
let mut data = serde_json::map::Map::new();
104+
data.insert(
105+
"value".to_string(),
106+
serde_json::Value::String("<div>test</div>".to_string()),
107+
);
108+
109+
let result = handlebars.render("test", &data).expect("render failed");
110+
assert_eq!(result, "<div>test</div>");
111+
}
112+
113+
#[test]
114+
fn test_raw_helper_with_quotes() {
115+
let mut handlebars = Handlebars::new();
116+
handlebars.register_helper("raw", Box::new(raw_helper));
117+
118+
let template = r#"{{raw value}}"#.to_string();
119+
handlebars
120+
.register_template_string("test", template)
121+
.expect("register template failed");
122+
123+
let mut data = serde_json::map::Map::new();
124+
data.insert(
125+
"value".to_string(),
126+
serde_json::Value::String(r#"timeout: "5m""#.to_string()),
127+
);
128+
129+
let result = handlebars.render("test", &data).expect("render failed");
130+
assert_eq!(result, r#"timeout: "5m""#);
131+
}
132+
133+
#[test]
134+
fn test_raw_helper_with_yaml_like_content() {
135+
let mut handlebars = Handlebars::new();
136+
handlebars.register_helper("raw", Box::new(raw_helper));
137+
138+
let template = r#"{{raw value}}"#.to_string();
139+
handlebars
140+
.register_template_string("test", template)
141+
.expect("register template failed");
142+
143+
let yaml_content = r#"config:
144+
timeout: "5m""#;
145+
let mut data = serde_json::map::Map::new();
146+
data.insert(
147+
"value".to_string(),
148+
serde_json::Value::String(yaml_content.to_string()),
149+
);
150+
151+
let result = handlebars.render("test", &data).expect("render failed");
152+
assert_eq!(result, yaml_content);
153+
}
154+
155+
#[test]
156+
fn test_normal_template_still_escapes() {
157+
let mut handlebars = Handlebars::new();
158+
handlebars.register_helper("raw", Box::new(raw_helper));
159+
160+
// Test that normal {{var}} still escapes
161+
let template = r#"{{value}}"#.to_string();
162+
handlebars
163+
.register_template_string("test", template)
164+
.expect("register template failed");
165+
166+
let mut data = serde_json::map::Map::new();
167+
data.insert(
168+
"value".to_string(),
169+
serde_json::Value::String("<div>test</div>".to_string()),
170+
);
171+
172+
let result = handlebars.render("test", &data).expect("render failed");
173+
// Normal syntax should escape
174+
assert_eq!(result, "&lt;div&gt;test&lt;/div&gt;");
175+
}
176+
177+
#[test]
178+
fn test_triple_brace_syntax() {
179+
let mut handlebars = Handlebars::new();
180+
handlebars.register_helper("raw", Box::new(raw_helper));
181+
182+
// Test standard Handlebars triple brace syntax
183+
let template = r#"{{{value}}}"#.to_string();
184+
handlebars
185+
.register_template_string("test", template)
186+
.expect("register template failed");
187+
188+
let mut data = serde_json::map::Map::new();
189+
data.insert(
190+
"value".to_string(),
191+
serde_json::Value::String("<div>test</div>".to_string()),
192+
);
193+
194+
let result = handlebars.render("test", &data).expect("render failed");
195+
// Triple braces should not escape
196+
assert_eq!(result, "<div>test</div>");
197+
}
198+
199+
#[test]
200+
fn test_raw_vs_normal_syntax() {
201+
let mut handlebars = Handlebars::new();
202+
handlebars.register_helper("raw", Box::new(raw_helper));
203+
204+
let template = r#"normal: {{value}}, raw: {{raw value}}, triple: {{{value}}}"#.to_string();
205+
handlebars
206+
.register_template_string("test", template)
207+
.expect("register template failed");
208+
209+
let mut data = serde_json::map::Map::new();
210+
data.insert(
211+
"value".to_string(),
212+
serde_json::Value::String(r#""test""#.to_string()),
213+
);
214+
215+
let result = handlebars.render("test", &data).expect("render failed");
216+
// normal should escape quotes to &quot;, raw and triple should not
217+
assert_eq!(
218+
result,
219+
r#"normal: &quot;test&quot;, raw: "test", triple: "test""#
220+
);
221+
}
57222
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import template
2+
3+
# Verify that normal template still escapes HTML (backward compatibility)
4+
content = template.execute("""{{value}}""", {value = "<div>test</div>"})
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
content: '&lt;div&gt;test&lt;/div&gt;'
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import template
2+
3+
# Test comparison: normal {{var}} vs {{raw var}}
4+
escaped = template.execute("""{{value}}""", {value = "<div>test</div>"})
5+
unescaped = template.execute("""{{raw value}}""", {value = "<div>test</div>"})
6+
unescaped_triple = template.execute("""{{{value}}}""", {value = "<div>test</div>"})
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
escaped: '&lt;div&gt;test&lt;/div&gt;'
2+
unescaped: <div>test</div>
3+
unescaped_triple: <div>test</div>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
content: '<div>test</div>'
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import yaml
2+
import template
3+
4+
# Test raw helper with multiline YAML content
5+
content = template.execute("""{{raw value}}""", {value = yaml.encode(_val)})
6+
7+
_val = {
8+
name = "test"
9+
config = {
10+
timeout = "5m"
11+
retry = 3
12+
}
13+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
content: |
2+
name: test
3+
config:
4+
timeout: '5m'
5+
retry: 3
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import template
2+
3+
# Test that raw helper preserves quotes
4+
content = template.execute("""{{raw value}}""", {value = "value: \"test\""})
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
content: 'value: "test"'

0 commit comments

Comments
 (0)