use core::fmt::{self, Write as _};
#[cfg(feature = "alloc")]
use alloc::collections::TryReserveError;
#[cfg(all(feature = "alloc", not(feature = "std")))]
use alloc::string::String;
#[derive(Debug, Clone, Copy)]
pub struct CapacityOverflowError;
impl fmt::Display for CapacityOverflowError {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("buffer capacity overflow")
}
}
#[cfg(feature = "std")]
impl std::error::Error for CapacityOverflowError {}
struct ByteBufWriter<'b> {
buffer: &'b mut [u8],
cursor: usize,
}
impl fmt::Write for ByteBufWriter<'_> {
fn write_str(&mut self, s: &str) -> fmt::Result {
let dest = &mut self.buffer[self.cursor..];
if dest.len() < s.len() {
return Err(fmt::Error);
}
dest[..s.len()].copy_from_slice(s.as_bytes());
self.cursor += s.len();
Ok(())
}
}
pub fn write_to_slice<'a, T: fmt::Display>(
buf: &'a mut [u8],
value: &T,
) -> Result<&'a str, CapacityOverflowError> {
let mut writer = ByteBufWriter {
buffer: buf,
cursor: 0,
};
if write!(writer, "{}", value).is_err() {
return Err(CapacityOverflowError);
}
let len = writer.cursor;
let result = core::str::from_utf8(&buf[..len])
.expect("[validity] fmt::Display writes valid UTF-8 byte sequence");
Ok(result)
}
#[cfg(feature = "alloc")]
struct StringWriter<'a> {
buffer: &'a mut String,
error: Option<TryReserveError>,
}
#[cfg(feature = "alloc")]
impl fmt::Write for StringWriter<'_> {
fn write_str(&mut self, s: &str) -> fmt::Result {
if self.error.is_some() {
return Err(fmt::Error);
}
if let Err(e) = self.buffer.try_reserve(s.len()) {
self.error = Some(e);
return Err(fmt::Error);
}
self.buffer.push_str(s);
Ok(())
}
}
#[cfg(feature = "alloc")]
pub fn try_append_to_string<T: fmt::Display>(
dest: &mut String,
value: &T,
) -> Result<(), TryReserveError> {
let mut writer = StringWriter {
buffer: dest,
error: None,
};
if write!(writer, "{}", value).is_err() {
let e = writer
.error
.expect("[consistency] allocation error should be set on formatting failure");
return Err(e);
}
Ok(())
}
pub(crate) fn eq_str_display<T>(s: &str, d: &T) -> bool
where
T: ?Sized + fmt::Display,
{
struct CmpWriter<'a>(&'a str);
impl fmt::Write for CmpWriter<'_> {
fn write_str(&mut self, s: &str) -> fmt::Result {
if self.0.len() < s.len() {
return Err(fmt::Error);
}
let (prefix, rest) = self.0.split_at(s.len());
self.0 = rest;
if prefix == s {
Ok(())
} else {
Err(fmt::Error)
}
}
}
let mut writer = CmpWriter(s);
let succeeded = write!(writer, "{}", d).is_ok();
succeeded && writer.0.is_empty()
}
#[derive(Clone, Copy)]
pub(crate) struct Censored;
impl core::fmt::Debug for Censored {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("{censored}")
}
}
#[cfg(feature = "alloc")]
pub trait ToStringFallible: alloc::string::ToString {
fn try_to_string(&self) -> Result<String, TryReserveError>;
}
#[cfg(feature = "alloc")]
impl<T: fmt::Display> ToStringFallible for T {
#[inline]
fn try_to_string(&self) -> Result<String, TryReserveError> {
let mut buf = String::new();
try_append_to_string(&mut buf, self)?;
Ok(buf)
}
}
#[cfg(feature = "alloc")]
pub trait ToDedicatedString {
type Target;
fn try_to_dedicated_string(&self) -> Result<Self::Target, TryReserveError>;
#[inline]
#[must_use]
fn to_dedicated_string(&self) -> Self::Target {
self.try_to_dedicated_string()
.expect("failed to allocate enough memory")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn eq_str_display_1() {
assert!(eq_str_display("hello", "hello"));
assert!(eq_str_display("42", &42));
assert!(eq_str_display(
r#"\x00\t\r\n\xff\\"#,
&b"\x00\t\r\n\xff\\".escape_ascii()
));
assert!(!eq_str_display("hello", "world"));
assert!(!eq_str_display("hello world", "hello"));
assert!(!eq_str_display("hello", "hello world"));
assert!(!eq_str_display("42", &4));
assert!(!eq_str_display("4", &42));
}
}