1#![warn(clippy::all, missing_docs, nonstandard_style, future_incompatible)]
33
34use serde::Serialize;
35use serde_json::Value;
36use std::collections::HashMap;
37
38pub trait Format {
40 const PLACEHOLDERS: (&'static str, &'static str) = ("{{", "}}");
42
43 fn format(&self, template: impl Into<String>) -> String
45 where
46 Self: Serialize,
47 {
48 let mut result = template.into();
49 let data_map: HashMap<String, Value> =
50 serde_json::from_value(serde_json::to_value(self).unwrap_or_default())
51 .unwrap_or_default();
52 let (left, right) = Self::PLACEHOLDERS;
53 for (key, value) in data_map.iter() {
54 let placeholder = format!("{left}{key}{right}");
55 result = result.replace(
56 &placeholder,
57 &value
58 .as_str()
59 .map(ToOwned::to_owned)
60 .unwrap_or_else(|| value.to_string()),
61 );
62 }
63 result
64 }
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70
71 #[test]
72 fn default_placeholders() {
73 #[derive(Serialize)]
74 struct Foo {
75 s: String,
76 n: u32,
77 b: bool,
78 }
79
80 impl Format for Foo {}
81
82 let foo = Foo {
83 s: "hey".into(),
84 n: 1,
85 b: true,
86 };
87
88 assert_eq!(foo.format("s={{s}} n={{n}} b={{b}}"), "s=hey n=1 b=true");
89 }
90
91 #[test]
92 fn custom_placeholders() {
93 #[derive(Serialize)]
94 struct Foo {
95 s: String,
96 n: u32,
97 b: bool,
98 }
99
100 impl Format for Foo {
101 const PLACEHOLDERS: (&'static str, &'static str) = ("${", "}");
102 }
103
104 let foo = Foo {
105 s: "hey".into(),
106 n: 1,
107 b: true,
108 };
109
110 assert_eq!(foo.format("s=${s} n=${n} b=${b}"), "s=hey n=1 b=true");
111 }
112}