hotwire_turbo/
lib.rs

1use std::collections::BTreeMap;
2pub mod power;
3pub mod stream;
4const STATIC_TAGS: &str = "<turbo-stream><template></template></turbo-stream>";
5#[inline]
6pub(crate) fn turbo_stream_action<'a, S: AsRef<str>>(
7    action: &'a str,
8    attributes: &mut BTreeMap<&str, &'a str>,
9    content: S,
10) -> String {
11    attributes.insert("action", action);
12    turbo_stream_tag(attributes, content)
13}
14
15#[inline]
16pub(crate) fn turbo_stream_target<'a, S: AsRef<str>>(
17    action: &'a str,
18    target: &'a str,
19    attributes: &mut BTreeMap<&str, &'a str>,
20    content: S,
21) -> String {
22    attributes.insert("target", target);
23    turbo_stream_action(action, attributes, content)
24}
25
26#[inline]
27pub(crate) fn turbo_stream_target_all<'a, S: AsRef<str>>(
28    action: &'a str,
29    targets: &'a str,
30    attributes: &mut BTreeMap<&str, &'a str>,
31    content: S,
32) -> String {
33    attributes.insert("targets", targets);
34    turbo_stream_action(action, attributes, content)
35}
36
37#[inline]
38pub(crate) fn turbo_stream_tag<S: AsRef<str>>(
39    attributes: &mut BTreeMap<&str, &str>,
40    content: S,
41) -> String {
42    let content = content.as_ref();
43    let attributes: BTreeMap<&str, String> = attributes
44        .iter_mut()
45        .map(|(key, value)| {
46            let value = html_escape::encode_double_quoted_attribute(*value);
47            (*key, value.to_string())
48        })
49        .collect();
50    let attr_len: usize = attributes
51        .iter()
52        .map(|(key, value)| 4 + key.len() + value.len())
53        .sum();
54    let capacity = STATIC_TAGS.len() + attr_len + content.len();
55    let mut output = String::with_capacity(capacity);
56    output.push_str(r#"<turbo-stream"#);
57    if attributes.is_empty() {
58        output.push('>');
59    } else {
60        for (key, value) in attributes {
61            output.push(' ');
62            output.push_str(key);
63            output.push_str(r#"=""#);
64            output.push_str(value.as_str());
65            output.push('"');
66        }
67        output.push('>');
68    }
69    output.push_str("<template>");
70    output.push_str(content);
71    output.push_str("</template></turbo-stream>");
72    output
73}
74
75#[cfg(test)]
76mod tests {
77    use std::collections::BTreeMap;
78
79    #[test]
80    fn custom() {
81        let expected =
82            r#"<turbo-stream action="custom" target="target"><template></template></turbo-stream>"#;
83        assert_eq!(
84            expected,
85            super::turbo_stream_target("custom", "target", &mut Default::default(), "")
86        );
87    }
88
89    #[test]
90    fn additional_attributes() {
91        let expected = r#"<turbo-stream action="custom" foo="bar" hello="world" target="target"><template></template></turbo-stream>"#;
92        let mut attributes = BTreeMap::from([("hello", "world"), ("foo", "bar")]);
93        assert_eq!(
94            expected,
95            super::turbo_stream_target("custom", "target", &mut attributes, "")
96        );
97    }
98}