1use std::fmt;
2
3pub use hers_macro::hers;
4
5#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct HtmlString(String);
8
9impl HtmlString {
10 pub fn new(s: String) -> Self {
13 Self(s)
14 }
15
16 pub fn as_str(&self) -> &str {
18 &self.0
19 }
20
21 pub fn into_string(self) -> String {
23 self.0
24 }
25}
26
27impl fmt::Display for HtmlString {
28 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29 write!(f, "{}", self.0)
30 }
31}
32
33impl From<String> for HtmlString {
34 fn from(s: String) -> Self {
35 Self::new(s)
36 }
37}
38
39impl From<&str> for HtmlString {
40 fn from(s: &str) -> Self {
41 Self::new(s.to_string())
42 }
43}
44
45pub trait ToHtml {
47 fn to_html(&self) -> HtmlString;
48}
49
50impl ToHtml for String {
51 fn to_html(&self) -> HtmlString {
52 HtmlString::new(escape_html(self))
53 }
54}
55
56impl ToHtml for &str {
57 fn to_html(&self) -> HtmlString {
58 HtmlString::new(escape_html(self))
59 }
60}
61
62impl ToHtml for i32 {
63 fn to_html(&self) -> HtmlString {
64 HtmlString::new(self.to_string())
65 }
66}
67
68impl ToHtml for f64 {
69 fn to_html(&self) -> HtmlString {
70 HtmlString::new(self.to_string())
71 }
72}
73
74impl ToHtml for bool {
75 fn to_html(&self) -> HtmlString {
76 HtmlString::new(self.to_string())
77 }
78}
79
80impl ToHtml for usize {
81 fn to_html(&self) -> HtmlString {
82 HtmlString::new(self.to_string())
83 }
84}
85
86pub fn escape_html(input: &str) -> String {
88 input
89 .replace('&', "&")
90 .replace('<', "<")
91 .replace('>', ">")
92 .replace('"', """)
93 .replace('\'', "'")
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99
100 #[test]
101 fn test_html_string_creation() {
102 let html = HtmlString::new("Hello".to_string());
103 assert_eq!(html.as_str(), "Hello");
104 assert_eq!(html.to_string(), "Hello");
105 }
106
107 #[test]
108 fn test_html_string_from_str() {
109 let html: HtmlString = "Hello".into();
110 assert_eq!(html.as_str(), "Hello");
111 }
112
113 #[test]
114 fn test_escape_html() {
115 assert_eq!(escape_html("Hello"), "Hello");
116 assert_eq!(escape_html("<script>"), "<script>");
117 assert_eq!(escape_html("&"), "&");
118 assert_eq!(escape_html("\""), """);
119 assert_eq!(escape_html("'"), "'");
120 assert_eq!(
121 escape_html("<script>alert('xss')</script>"),
122 "<script>alert('xss')</script>"
123 );
124 }
125
126 #[test]
127 fn test_to_html_string() {
128 let s = "Hello <world>".to_string();
129 let html = s.to_html();
130 assert_eq!(html.as_str(), "Hello <world>");
131 }
132
133 #[test]
134 fn test_to_html_str() {
135 let html = "Hello <world>".to_html();
136 assert_eq!(html.as_str(), "Hello <world>");
137 }
138
139 #[test]
140 fn test_to_html_numbers() {
141 assert_eq!(42.to_html().as_str(), "42");
142 assert_eq!(7.99.to_html().as_str(), "7.99");
143 assert_eq!(true.to_html().as_str(), "true");
144 assert_eq!(false.to_html().as_str(), "false");
145 }
146}