1use std::fmt;
26
27use crate::{css, html, javascript, json, rust, sql, uri, xml};
28
29macro_rules! display_fn {
30 (
31 $(#[$meta:meta])*
32 $name:ident => $module:ident :: $write_fn:ident
33 ) => {
34 $(#[$meta])*
35 pub fn $name(input: &str) -> impl fmt::Display + '_ {
36 struct W<'a>(&'a str);
37 impl fmt::Display for W<'_> {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 $module::$write_fn(f, self.0)
40 }
41 }
42 W(input)
43 }
44 };
45}
46
47display_fn! {
48 display_html => html::write_html
50}
51
52display_fn! {
53 display_html_content => html::write_html_content
55}
56
57display_fn! {
58 display_html_attribute => html::write_html_attribute
60}
61
62display_fn! {
63 display_html_unquoted_attribute => html::write_html_unquoted_attribute
66}
67
68display_fn! {
69 display_xml => xml::write_xml
71}
72
73display_fn! {
74 display_xml_content => xml::write_xml_content
76}
77
78display_fn! {
79 display_xml_attribute => xml::write_xml_attribute
81}
82
83display_fn! {
84 display_xml_comment => xml::write_xml_comment
86}
87
88display_fn! {
89 display_cdata => xml::write_cdata
91}
92
93display_fn! {
94 display_xml11 => xml::write_xml11
96}
97
98display_fn! {
99 display_xml11_content => xml::write_xml11_content
101}
102
103display_fn! {
104 display_xml11_attribute => xml::write_xml11_attribute
106}
107
108display_fn! {
109 display_javascript => javascript::write_javascript
111}
112
113display_fn! {
114 display_javascript_attribute => javascript::write_javascript_attribute
117}
118
119display_fn! {
120 display_javascript_block => javascript::write_javascript_block
123}
124
125display_fn! {
126 display_javascript_source => javascript::write_javascript_source
129}
130
131display_fn! {
132 display_js_template => javascript::write_js_template
134}
135
136display_fn! {
137 display_css_string => css::write_css_string
139}
140
141display_fn! {
142 display_css_url => css::write_css_url
144}
145
146display_fn! {
147 display_uri_component => uri::write_uri_component
149}
150
151display_fn! {
152 display_uri_path => uri::write_uri_path
154}
155
156display_fn! {
157 display_form_urlencoded => uri::write_form_urlencoded
160}
161
162display_fn! {
163 display_json => json::write_json
165}
166
167display_fn! {
168 display_rust_string => rust::write_rust_string
170}
171
172display_fn! {
173 display_rust_char => rust::write_rust_char
175}
176
177display_fn! {
178 display_rust_byte_string => rust::write_rust_byte_string
181}
182
183display_fn! {
184 display_sql => sql::write_sql
186}
187
188display_fn! {
189 display_sql_backslash => sql::write_sql_backslash
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196
197 macro_rules! display_matches_for {
200 ($name:ident, $display_fn:ident, $for_fn:path) => {
201 #[test]
202 fn $name() {
203 for input in [
204 "",
205 "hello",
206 "<script>alert('xss')</script>",
207 "café",
208 "世界",
209 "😀",
210 "a&b<c>d\"e'f",
211 "\x00\x01\x1F\x7F",
212 "\t\n\r",
213 "\u{0080}\u{009F}",
214 "\u{2028}\u{2029}",
215 "a\\b/c",
216 "key=val&foo=bar",
217 "`${inject}`",
218 ] {
219 assert_eq!(
220 format!("{}", $display_fn(input)),
221 $for_fn(input),
222 "mismatch for {:?} on {}",
223 input,
224 stringify!($display_fn),
225 );
226 }
227 }
228 };
229 }
230
231 display_matches_for!(html, display_html, crate::for_html);
233 display_matches_for!(html_content, display_html_content, crate::for_html_content);
234 display_matches_for!(
235 html_attribute,
236 display_html_attribute,
237 crate::for_html_attribute
238 );
239 display_matches_for!(
240 html_unquoted_attribute,
241 display_html_unquoted_attribute,
242 crate::for_html_unquoted_attribute
243 );
244
245 display_matches_for!(xml, display_xml, crate::for_xml);
247 display_matches_for!(xml_content, display_xml_content, crate::for_xml_content);
248 display_matches_for!(
249 xml_attribute,
250 display_xml_attribute,
251 crate::for_xml_attribute
252 );
253 display_matches_for!(xml_comment, display_xml_comment, crate::for_xml_comment);
254 display_matches_for!(cdata, display_cdata, crate::for_cdata);
255 display_matches_for!(xml11, display_xml11, crate::for_xml11);
256 display_matches_for!(
257 xml11_content,
258 display_xml11_content,
259 crate::for_xml11_content
260 );
261 display_matches_for!(
262 xml11_attribute,
263 display_xml11_attribute,
264 crate::for_xml11_attribute
265 );
266
267 display_matches_for!(javascript, display_javascript, crate::for_javascript);
269 display_matches_for!(
270 javascript_attribute,
271 display_javascript_attribute,
272 crate::for_javascript_attribute
273 );
274 display_matches_for!(
275 javascript_block,
276 display_javascript_block,
277 crate::for_javascript_block
278 );
279 display_matches_for!(
280 javascript_source,
281 display_javascript_source,
282 crate::for_javascript_source
283 );
284 display_matches_for!(js_template, display_js_template, crate::for_js_template);
285
286 display_matches_for!(css_string, display_css_string, crate::for_css_string);
288 display_matches_for!(css_url, display_css_url, crate::for_css_url);
289
290 display_matches_for!(
292 uri_component,
293 display_uri_component,
294 crate::for_uri_component
295 );
296 display_matches_for!(uri_path, display_uri_path, crate::for_uri_path);
297 display_matches_for!(
298 form_urlencoded,
299 display_form_urlencoded,
300 crate::for_form_urlencoded
301 );
302
303 display_matches_for!(json, display_json, crate::for_json);
305
306 display_matches_for!(rust_string, display_rust_string, crate::for_rust_string);
308 display_matches_for!(rust_char, display_rust_char, crate::for_rust_char);
309 display_matches_for!(
310 rust_byte_string,
311 display_rust_byte_string,
312 crate::for_rust_byte_string
313 );
314
315 display_matches_for!(sql, display_sql, crate::for_sql);
317 display_matches_for!(
318 sql_backslash,
319 display_sql_backslash,
320 crate::for_sql_backslash
321 );
322
323 #[test]
326 fn inline_format_html() {
327 let input = "<b>bold</b>";
328 let result = format!("<p>{}</p>", display_html(input));
329 assert_eq!(result, "<p><b>bold</b></p>");
330 }
331
332 #[test]
333 fn inline_format_nested_contexts() {
334 let query = "hello world & goodbye";
335 let href = format!("/search?q={}", display_uri_component(query));
336 let attr = format!(r#"<a href="{}">"#, display_html_attribute(&href));
337 assert!(attr.contains("/search?q=hello%20world%20%26%20goodbye"));
338 }
339
340 #[test]
341 fn write_macro_integration() {
342 use std::fmt::Write;
343 let mut buf = String::new();
344 write!(buf, "<p>{}</p>", display_html("a & b")).unwrap();
345 assert_eq!(buf, "<p>a & b</p>");
346 }
347
348 #[test]
349 fn display_wrapper_is_reusable() {
350 let wrapper = display_html("<b>");
351 let first = format!("{wrapper}");
352 let second = format!("{wrapper}");
353 assert_eq!(first, second);
354 assert_eq!(first, "<b>");
355 }
356}