hers 0.1.1

HTML Embedded Rust - Runtime types and utilities
Documentation
use std::fmt;

pub use hers_macro::hers;

/// A type-safe wrapper for HTML content
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HtmlString(String);

impl HtmlString {
    /// Create a new HtmlString from a raw string
    /// Note: This assumes the string is already properly escaped
    pub fn new(s: String) -> Self {
        Self(s)
    }

    /// Get the inner HTML string
    pub fn as_str(&self) -> &str {
        &self.0
    }

    /// Convert to owned String
    pub fn into_string(self) -> String {
        self.0
    }
}

impl fmt::Display for HtmlString {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl From<String> for HtmlString {
    fn from(s: String) -> Self {
        Self::new(s)
    }
}

impl From<&str> for HtmlString {
    fn from(s: &str) -> Self {
        Self::new(s.to_string())
    }
}

/// Trait for types that can be converted to HTML
pub trait ToHtml {
    fn to_html(&self) -> HtmlString;
}

impl ToHtml for String {
    fn to_html(&self) -> HtmlString {
        HtmlString::new(escape_html(self))
    }
}

impl ToHtml for &str {
    fn to_html(&self) -> HtmlString {
        HtmlString::new(escape_html(self))
    }
}

impl ToHtml for i32 {
    fn to_html(&self) -> HtmlString {
        HtmlString::new(self.to_string())
    }
}

impl ToHtml for f64 {
    fn to_html(&self) -> HtmlString {
        HtmlString::new(self.to_string())
    }
}

impl ToHtml for bool {
    fn to_html(&self) -> HtmlString {
        HtmlString::new(self.to_string())
    }
}

impl ToHtml for usize {
    fn to_html(&self) -> HtmlString {
        HtmlString::new(self.to_string())
    }
}

/// Escape HTML special characters to prevent XSS
pub fn escape_html(input: &str) -> String {
    input
        .replace('&', "&amp;")
        .replace('<', "&lt;")
        .replace('>', "&gt;")
        .replace('"', "&quot;")
        .replace('\'', "&#x27;")
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_html_string_creation() {
        let html = HtmlString::new("Hello".to_string());
        assert_eq!(html.as_str(), "Hello");
        assert_eq!(html.to_string(), "Hello");
    }

    #[test]
    fn test_html_string_from_str() {
        let html: HtmlString = "Hello".into();
        assert_eq!(html.as_str(), "Hello");
    }

    #[test]
    fn test_escape_html() {
        assert_eq!(escape_html("Hello"), "Hello");
        assert_eq!(escape_html("<script>"), "&lt;script&gt;");
        assert_eq!(escape_html("&"), "&amp;");
        assert_eq!(escape_html("\""), "&quot;");
        assert_eq!(escape_html("'"), "&#x27;");
        assert_eq!(
            escape_html("<script>alert('xss')</script>"),
            "&lt;script&gt;alert(&#x27;xss&#x27;)&lt;/script&gt;"
        );
    }

    #[test]
    fn test_to_html_string() {
        let s = "Hello <world>".to_string();
        let html = s.to_html();
        assert_eq!(html.as_str(), "Hello &lt;world&gt;");
    }

    #[test]
    fn test_to_html_str() {
        let html = "Hello <world>".to_html();
        assert_eq!(html.as_str(), "Hello &lt;world&gt;");
    }

    #[test]
    fn test_to_html_numbers() {
        assert_eq!(42.to_html().as_str(), "42");
        assert_eq!(7.99.to_html().as_str(), "7.99");
        assert_eq!(true.to_html().as_str(), "true");
        assert_eq!(false.to_html().as_str(), "false");
    }
}