use std::fmt;
use std::ops::{Add, AddAssign};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct SafeString(String);
impl SafeString {
#[must_use]
pub fn new(s: impl Into<String>) -> Self {
Self(s.into())
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
#[must_use]
pub fn into_inner(self) -> String {
self.0
}
}
impl fmt::Display for SafeString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl From<String> for SafeString {
fn from(value: String) -> Self {
Self(value)
}
}
impl From<&str> for SafeString {
fn from(value: &str) -> Self {
Self(value.to_owned())
}
}
#[must_use]
pub fn mark_safe(s: impl Into<String>) -> SafeString {
SafeString::new(s)
}
impl Add for SafeString {
type Output = SafeString;
fn add(self, rhs: Self) -> Self::Output {
SafeString(self.0 + &rhs.0)
}
}
impl AddAssign<&str> for SafeString {
fn add_assign(&mut self, rhs: &str) {
self.0 += rhs;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mark_safe_wraps_content() {
let safe = mark_safe("<strong>safe</strong>");
assert_eq!(safe.as_str(), "<strong>safe</strong>");
}
#[test]
fn display_uses_inner_string() {
let safe = SafeString::new("hello");
assert_eq!(safe.to_string(), "hello");
}
#[test]
fn add_concatenates_safe_strings() {
let left = mark_safe("<em>");
let right = mark_safe("value</em>");
assert_eq!((left + right).into_inner(), "<em>value</em>");
}
#[test]
fn add_assign_appends_plain_text() {
let mut safe = mark_safe("Hello");
safe += " world";
assert_eq!(safe.as_str(), "Hello world");
}
#[test]
fn from_string_preserves_content() {
let safe = SafeString::from(String::from("owned"));
assert_eq!(safe.as_str(), "owned");
}
#[test]
fn from_str_preserves_content() {
let safe = SafeString::from("borrowed");
assert_eq!(safe.into_inner(), "borrowed");
}
}