1use std::fmt;
26
27use crate::{css, go, html, java, javascript, json, python, ruby, 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! {
50 display_html => html::write_html
52}
53
54display_fn! {
55 display_html_content => html::write_html_content
57}
58
59display_fn! {
60 display_html_attribute => html::write_html_attribute
62}
63
64display_fn! {
65 display_html_unquoted_attribute => html::write_html_unquoted_attribute
68}
69
70display_fn! {
73 display_xml => xml::write_xml
75}
76
77display_fn! {
78 display_xml_content => xml::write_xml_content
80}
81
82display_fn! {
83 display_xml_attribute => xml::write_xml_attribute
85}
86
87display_fn! {
88 display_xml_comment => xml::write_xml_comment
90}
91
92display_fn! {
93 display_cdata => xml::write_cdata
95}
96
97display_fn! {
98 display_xml11 => xml::write_xml11
100}
101
102display_fn! {
103 display_xml11_content => xml::write_xml11_content
105}
106
107display_fn! {
108 display_xml11_attribute => xml::write_xml11_attribute
110}
111
112display_fn! {
115 display_javascript => javascript::write_javascript
117}
118
119display_fn! {
120 display_javascript_attribute => javascript::write_javascript_attribute
123}
124
125display_fn! {
126 display_javascript_block => javascript::write_javascript_block
129}
130
131display_fn! {
132 display_javascript_source => javascript::write_javascript_source
135}
136
137display_fn! {
138 display_js_template => javascript::write_js_template
140}
141
142display_fn! {
145 display_css_string => css::write_css_string
147}
148
149display_fn! {
150 display_css_url => css::write_css_url
152}
153
154display_fn! {
157 display_uri_component => uri::write_uri_component
159}
160
161display_fn! {
164 display_json => json::write_json
166}
167
168display_fn! {
171 display_java => java::write_java
173}
174
175display_fn! {
178 display_go_string => go::write_go_string
180}
181
182display_fn! {
183 display_go_char => go::write_go_char
185}
186
187display_fn! {
188 display_go_byte_string => go::write_go_byte_string
190}
191
192display_fn! {
195 display_rust_string => rust::write_rust_string
197}
198
199display_fn! {
200 display_rust_char => rust::write_rust_char
202}
203
204display_fn! {
205 display_rust_byte_string => rust::write_rust_byte_string
208}
209
210display_fn! {
213 display_ruby_string => ruby::write_ruby_string
215}
216
217display_fn! {
220 display_python_string => python::write_python_string
222}
223
224display_fn! {
225 display_python_bytes => python::write_python_bytes
227}
228
229display_fn! {
230 display_python_raw_string => python::write_python_raw_string
233}
234
235display_fn! {
238 display_sql => sql::write_sql
240}
241
242display_fn! {
243 display_sql_backslash => sql::write_sql_backslash
245}
246
247#[cfg(test)]
248mod tests {
249 use super::*;
250
251 macro_rules! display_matches_for {
254 ($name:ident, $display_fn:ident, $for_fn:path) => {
255 #[test]
256 fn $name() {
257 for input in [
258 "",
259 "hello",
260 "<script>alert('xss')</script>",
261 "café",
262 "世界",
263 "😀",
264 "a&b<c>d\"e'f",
265 "\x00\x01\x1F\x7F",
266 "\t\n\r",
267 "\u{0080}\u{009F}",
268 "\u{2028}\u{2029}",
269 "a\\b/c",
270 "key=val&foo=bar",
271 "`${inject}`",
272 ] {
273 assert_eq!(
274 format!("{}", $display_fn(input)),
275 $for_fn(input),
276 "mismatch for {:?} on {}",
277 input,
278 stringify!($display_fn),
279 );
280 }
281 }
282 };
283 }
284
285 display_matches_for!(html, display_html, crate::for_html);
287 display_matches_for!(html_content, display_html_content, crate::for_html_content);
288 display_matches_for!(
289 html_attribute,
290 display_html_attribute,
291 crate::for_html_attribute
292 );
293 display_matches_for!(
294 html_unquoted_attribute,
295 display_html_unquoted_attribute,
296 crate::for_html_unquoted_attribute
297 );
298
299 display_matches_for!(xml, display_xml, crate::for_xml);
301 display_matches_for!(xml_content, display_xml_content, crate::for_xml_content);
302 display_matches_for!(
303 xml_attribute,
304 display_xml_attribute,
305 crate::for_xml_attribute
306 );
307 display_matches_for!(xml_comment, display_xml_comment, crate::for_xml_comment);
308 display_matches_for!(cdata, display_cdata, crate::for_cdata);
309 display_matches_for!(xml11, display_xml11, crate::for_xml11);
310 display_matches_for!(
311 xml11_content,
312 display_xml11_content,
313 crate::for_xml11_content
314 );
315 display_matches_for!(
316 xml11_attribute,
317 display_xml11_attribute,
318 crate::for_xml11_attribute
319 );
320
321 display_matches_for!(javascript, display_javascript, crate::for_javascript);
323 display_matches_for!(
324 javascript_attribute,
325 display_javascript_attribute,
326 crate::for_javascript_attribute
327 );
328 display_matches_for!(
329 javascript_block,
330 display_javascript_block,
331 crate::for_javascript_block
332 );
333 display_matches_for!(
334 javascript_source,
335 display_javascript_source,
336 crate::for_javascript_source
337 );
338 display_matches_for!(js_template, display_js_template, crate::for_js_template);
339
340 display_matches_for!(css_string, display_css_string, crate::for_css_string);
342 display_matches_for!(css_url, display_css_url, crate::for_css_url);
343
344 display_matches_for!(
346 uri_component,
347 display_uri_component,
348 crate::for_uri_component
349 );
350
351 display_matches_for!(json, display_json, crate::for_json);
353
354 display_matches_for!(java, display_java, crate::for_java);
356
357 display_matches_for!(go_string, display_go_string, crate::for_go_string);
359 display_matches_for!(go_char, display_go_char, crate::for_go_char);
360 display_matches_for!(
361 go_byte_string,
362 display_go_byte_string,
363 crate::for_go_byte_string
364 );
365
366 display_matches_for!(rust_string, display_rust_string, crate::for_rust_string);
368 display_matches_for!(rust_char, display_rust_char, crate::for_rust_char);
369 display_matches_for!(
370 rust_byte_string,
371 display_rust_byte_string,
372 crate::for_rust_byte_string
373 );
374
375 display_matches_for!(ruby_string, display_ruby_string, crate::for_ruby_string);
377
378 display_matches_for!(
380 python_string,
381 display_python_string,
382 crate::for_python_string
383 );
384 display_matches_for!(python_bytes, display_python_bytes, crate::for_python_bytes);
385 display_matches_for!(
386 python_raw_string,
387 display_python_raw_string,
388 crate::for_python_raw_string
389 );
390
391 display_matches_for!(sql, display_sql, crate::for_sql);
393 display_matches_for!(
394 sql_backslash,
395 display_sql_backslash,
396 crate::for_sql_backslash
397 );
398
399 #[test]
402 fn inline_format_html() {
403 let input = "<b>bold</b>";
404 let result = format!("<p>{}</p>", display_html(input));
405 assert_eq!(result, "<p><b>bold</b></p>");
406 }
407
408 #[test]
409 fn inline_format_nested_contexts() {
410 let query = "hello world & goodbye";
411 let href = format!("/search?q={}", display_uri_component(query));
412 let attr = format!(r#"<a href="{}">"#, display_html_attribute(&href));
413 assert!(attr.contains("/search?q=hello%20world%20%26%20goodbye"));
414 }
415
416 #[test]
417 fn write_macro_integration() {
418 use std::fmt::Write;
419 let mut buf = String::new();
420 write!(buf, "<p>{}</p>", display_html("a & b")).unwrap();
421 assert_eq!(buf, "<p>a & b</p>");
422 }
423
424 #[test]
425 fn display_wrapper_is_reusable() {
426 let wrapper = display_html("<b>");
427 let first = format!("{wrapper}");
428 let second = format!("{wrapper}");
429 assert_eq!(first, second);
430 assert_eq!(first, "<b>");
431 }
432}