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! {
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! {
162 display_uri_path => uri::write_uri_path
164}
165
166display_fn! {
169 display_json => json::write_json
171}
172
173display_fn! {
176 display_rust_string => rust::write_rust_string
178}
179
180display_fn! {
181 display_rust_char => rust::write_rust_char
183}
184
185display_fn! {
186 display_rust_byte_string => rust::write_rust_byte_string
189}
190
191display_fn! {
194 display_sql => sql::write_sql
196}
197
198display_fn! {
199 display_sql_backslash => sql::write_sql_backslash
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206
207 macro_rules! display_matches_for {
210 ($name:ident, $display_fn:ident, $for_fn:path) => {
211 #[test]
212 fn $name() {
213 for input in [
214 "",
215 "hello",
216 "<script>alert('xss')</script>",
217 "café",
218 "世界",
219 "😀",
220 "a&b<c>d\"e'f",
221 "\x00\x01\x1F\x7F",
222 "\t\n\r",
223 "\u{0080}\u{009F}",
224 "\u{2028}\u{2029}",
225 "a\\b/c",
226 "key=val&foo=bar",
227 "`${inject}`",
228 ] {
229 assert_eq!(
230 format!("{}", $display_fn(input)),
231 $for_fn(input),
232 "mismatch for {:?} on {}",
233 input,
234 stringify!($display_fn),
235 );
236 }
237 }
238 };
239 }
240
241 display_matches_for!(html, display_html, crate::for_html);
243 display_matches_for!(html_content, display_html_content, crate::for_html_content);
244 display_matches_for!(
245 html_attribute,
246 display_html_attribute,
247 crate::for_html_attribute
248 );
249 display_matches_for!(
250 html_unquoted_attribute,
251 display_html_unquoted_attribute,
252 crate::for_html_unquoted_attribute
253 );
254
255 display_matches_for!(xml, display_xml, crate::for_xml);
257 display_matches_for!(xml_content, display_xml_content, crate::for_xml_content);
258 display_matches_for!(
259 xml_attribute,
260 display_xml_attribute,
261 crate::for_xml_attribute
262 );
263 display_matches_for!(xml_comment, display_xml_comment, crate::for_xml_comment);
264 display_matches_for!(cdata, display_cdata, crate::for_cdata);
265 display_matches_for!(xml11, display_xml11, crate::for_xml11);
266 display_matches_for!(
267 xml11_content,
268 display_xml11_content,
269 crate::for_xml11_content
270 );
271 display_matches_for!(
272 xml11_attribute,
273 display_xml11_attribute,
274 crate::for_xml11_attribute
275 );
276
277 display_matches_for!(javascript, display_javascript, crate::for_javascript);
279 display_matches_for!(
280 javascript_attribute,
281 display_javascript_attribute,
282 crate::for_javascript_attribute
283 );
284 display_matches_for!(
285 javascript_block,
286 display_javascript_block,
287 crate::for_javascript_block
288 );
289 display_matches_for!(
290 javascript_source,
291 display_javascript_source,
292 crate::for_javascript_source
293 );
294 display_matches_for!(js_template, display_js_template, crate::for_js_template);
295
296 display_matches_for!(css_string, display_css_string, crate::for_css_string);
298 display_matches_for!(css_url, display_css_url, crate::for_css_url);
299
300 display_matches_for!(
302 uri_component,
303 display_uri_component,
304 crate::for_uri_component
305 );
306 display_matches_for!(uri_path, display_uri_path, crate::for_uri_path);
307
308 display_matches_for!(json, display_json, crate::for_json);
310
311 display_matches_for!(rust_string, display_rust_string, crate::for_rust_string);
313 display_matches_for!(rust_char, display_rust_char, crate::for_rust_char);
314 display_matches_for!(
315 rust_byte_string,
316 display_rust_byte_string,
317 crate::for_rust_byte_string
318 );
319
320 display_matches_for!(sql, display_sql, crate::for_sql);
322 display_matches_for!(
323 sql_backslash,
324 display_sql_backslash,
325 crate::for_sql_backslash
326 );
327
328 #[test]
331 fn inline_format_html() {
332 let input = "<b>bold</b>";
333 let result = format!("<p>{}</p>", display_html(input));
334 assert_eq!(result, "<p><b>bold</b></p>");
335 }
336
337 #[test]
338 fn inline_format_nested_contexts() {
339 let query = "hello world & goodbye";
340 let href = format!("/search?q={}", display_uri_component(query));
341 let attr = format!(r#"<a href="{}">"#, display_html_attribute(&href));
342 assert!(attr.contains("/search?q=hello%20world%20%26%20goodbye"));
343 }
344
345 #[test]
346 fn write_macro_integration() {
347 use std::fmt::Write;
348 let mut buf = String::new();
349 write!(buf, "<p>{}</p>", display_html("a & b")).unwrap();
350 assert_eq!(buf, "<p>a & b</p>");
351 }
352
353 #[test]
354 fn display_wrapper_is_reusable() {
355 let wrapper = display_html("<b>");
356 let first = format!("{wrapper}");
357 let second = format!("{wrapper}");
358 assert_eq!(first, second);
359 assert_eq!(first, "<b>");
360 }
361}