#![cfg(feature = "serde")]
use std::collections::HashMap;
use gotmpl::{Template, ToSerdeValue, to_value};
use serde::Serialize;
fn render(src: &str, data: &gotmpl::Value) -> String {
Template::new("t")
.parse(src)
.unwrap()
.execute_to_string(data)
.unwrap()
}
#[derive(Serialize)]
#[serde(rename_all = "PascalCase")]
struct Address {
city: String,
zip: String,
}
#[derive(Serialize)]
#[serde(rename_all = "PascalCase")]
struct User {
name: String,
age: u32,
active: bool,
roles: Vec<String>,
address: Address,
nickname: Option<String>,
}
fn sample_user() -> User {
User {
name: "Alice".into(),
age: 30,
active: true,
roles: vec!["admin".into(), "user".into()],
address: Address {
city: "Paris".into(),
zip: "75001".into(),
},
nickname: None,
}
}
#[test]
fn field_access_and_nested_struct() {
let data = to_value(&sample_user()).unwrap();
assert_eq!(
render(
"{{.Name}} lives in {{.Address.City}} ({{.Address.Zip}})",
&data
),
"Alice lives in Paris (75001)"
);
}
#[test]
fn method_form_matches_function_form() {
let user = sample_user();
let src = "{{.Name}}/{{.Age}}";
let via_fn = render(src, &to_value(&user).unwrap());
let via_method = render(src, &user.to_serde_value().unwrap());
assert_eq!(via_fn, "Alice/30");
assert_eq!(via_fn, via_method);
}
#[test]
fn if_truthiness_bool_and_option() {
let data = to_value(&sample_user()).unwrap();
assert_eq!(render("{{if .Active}}on{{else}}off{{end}}", &data), "on");
assert_eq!(
render("{{if .Nickname}}{{.Nickname}}{{else}}(none){{end}}", &data),
"(none)"
);
}
#[test]
fn range_over_serialized_vec() {
let data = to_value(&sample_user()).unwrap();
assert_eq!(
render("{{range .Roles}}[{{.}}]{{end}}", &data),
"[admin][user]"
);
}
#[test]
fn range_over_serialized_map_is_key_sorted() {
let mut m: HashMap<String, i32> = HashMap::new();
m.insert("beta".into(), 2);
m.insert("alpha".into(), 1);
m.insert("gamma".into(), 3);
let data = to_value(&m).unwrap();
assert_eq!(
render("{{range $k, $v := .}}{{$k}}={{$v}} {{end}}", &data),
"alpha=1 beta=2 gamma=3 "
);
}
#[test]
fn range_over_numeric_key_map_is_lexically_sorted() {
let mut m: HashMap<u32, i32> = HashMap::new();
m.insert(1, 1);
m.insert(2, 2);
m.insert(10, 10);
let data = to_value(&m).unwrap();
assert_eq!(
render("{{range $k, $v := .}}{{$k}} {{end}}", &data),
"1 10 2 "
);
}
#[test]
fn index_into_serialized_map_and_vec() {
let data = to_value(&sample_user()).unwrap();
assert_eq!(render(r#"{{index .Roles 1}}"#, &data), "user");
assert_eq!(render(r#"{{index .Address "City"}}"#, &data), "Paris");
}
#[test]
fn externally_tagged_enum_rendering() {
#[derive(Serialize)]
#[serde(rename_all = "PascalCase", rename_all_fields = "PascalCase")]
enum Shape {
Circle { radius: f64 },
Named(String),
}
let circle = to_value(&Shape::Circle { radius: 2.5 }).unwrap();
assert_eq!(render("{{.Circle.Radius}}", &circle), "2.5");
let named = to_value(&Shape::Named("sq".into())).unwrap();
assert_eq!(render("{{.Named}}", &named), "sq");
}
#[test]
fn top_level_scalar_as_dot() {
let data = to_value(&42u64).unwrap();
assert_eq!(render("value: {{.}}", &data), "value: 42");
}
#[test]
fn top_level_vec_of_structs() {
let users = vec![
User {
name: "A".into(),
age: 1,
active: true,
roles: vec![],
address: Address {
city: "X".into(),
zip: "1".into(),
},
nickname: None,
},
User {
name: "B".into(),
age: 2,
active: false,
roles: vec![],
address: Address {
city: "Y".into(),
zip: "2".into(),
},
nickname: Some("Bee".into()),
},
];
let data = to_value(&users).unwrap();
assert_eq!(
render("{{range .}}{{.Name}}:{{.Age}} {{end}}", &data),
"A:1 B:2 "
);
}
#[test]
fn missing_field_follows_map_semantics() {
let data = to_value(&sample_user()).unwrap();
assert_eq!(render("{{.DoesNotExist}}", &data), "<no value>");
}
#[test]
fn missing_field_errors_with_missing_key_error() {
use gotmpl::MissingKey;
let data = to_value(&sample_user()).unwrap();
let err = Template::new("t")
.missing_key(MissingKey::Error)
.parse("{{.DoesNotExist}}")
.unwrap()
.execute_to_string(&data)
.unwrap_err();
assert!(
err.to_string().contains("DoesNotExist"),
"unexpected error: {err}"
);
}
#[test]
fn serialization_error_surfaces_from_to_value() {
#[derive(Serialize, PartialEq, Eq, PartialOrd, Ord)]
struct Key {
a: i32,
}
let mut m: std::collections::BTreeMap<Key, i32> = std::collections::BTreeMap::new();
m.insert(Key { a: 1 }, 9);
assert!(to_value(&m).is_err());
}