diff --git a/src/string.rs b/src/string.rs index 3f40040..325362c 100644 --- a/src/string.rs +++ b/src/string.rs @@ -2,7 +2,7 @@ use std::borrow::{Borrow, Cow}; use std::hash::{Hash, Hasher}; use std::os::raw::c_void; use std::string::String as StdString; -use std::{slice, str}; +use std::{fmt, slice, str}; #[cfg(feature = "serialize")] use { @@ -17,7 +17,7 @@ use crate::types::LuaRef; /// Handle to an internal Lua string. /// /// Unlike Rust strings, Lua strings may not be valid UTF-8. -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct String<'lua>(pub(crate) LuaRef<'lua>); impl<'lua> String<'lua> { @@ -124,6 +124,35 @@ impl<'lua> String<'lua> { } } +impl<'lua> fmt::Debug for String<'lua> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let bytes = self.as_bytes(); + // Check if the string is valid utf8 + if let Ok(s) = str::from_utf8(bytes) { + return s.fmt(f); + } + + // Format as bytes + write!(f, "b\"")?; + for &b in bytes { + // https://doc.rust-lang.org/reference/tokens.html#byte-escapes + match b { + b'\n' => write!(f, "\\n")?, + b'\r' => write!(f, "\\r")?, + b'\t' => write!(f, "\\t")?, + b'\\' | b'"' => write!(f, "\\{}", b as char)?, + b'\0' => write!(f, "\\0")?, + // ASCII printable + 0x20..=0x7e => write!(f, "{}", b as char)?, + _ => write!(f, "\\x{b:02x}")?, + } + } + write!(f, "\"")?; + + Ok(()) + } +} + impl<'lua> AsRef<[u8]> for String<'lua> { fn as_ref(&self) -> &[u8] { self.as_bytes() diff --git a/tests/string.rs b/tests/string.rs index 35a9a71..7ca5fdd 100644 --- a/tests/string.rs +++ b/tests/string.rs @@ -83,3 +83,18 @@ fn test_string_hash() -> Result<()> { Ok(()) } + +#[test] +fn test_string_debug() -> Result<()> { + let lua = Lua::new(); + + // Valid utf8 + let s = lua.create_string("hello")?; + assert_eq!(format!("{s:?}"), r#""hello""#); + + // Invalid utf8 + let s = lua.create_string(b"hello\0world\r\n\t\xF0\x90\x80")?; + assert_eq!(format!("{s:?}"), r#"b"hello\0world\r\n\t\xf0\x90\x80""#); + + Ok(()) +}