Skip to main content

iri_string/
format.rs

1//! Utilities for formatting (especially `Display` trait).
2//!
3//! This module contains utilities for [`Display`][`core::fmt::Display`]-able
4//! types.
5
6use core::fmt::{self, Write as _};
7
8#[cfg(feature = "alloc")]
9use alloc::collections::TryReserveError;
10#[cfg(all(feature = "alloc", not(feature = "std")))]
11use alloc::string::String;
12
13/// Output buffer capacity overflow error.
14#[derive(Debug, Clone, Copy)]
15pub struct CapacityOverflowError;
16
17impl fmt::Display for CapacityOverflowError {
18    #[inline]
19    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20        f.write_str("buffer capacity overflow")
21    }
22}
23
24#[cfg(feature = "std")]
25impl std::error::Error for CapacityOverflowError {}
26
27/// Writer to the bytes buffer.
28struct ByteBufWriter<'b> {
29    /// Destination buffer.
30    buffer: &'b mut [u8],
31    /// Total written number of bytes.
32    len_written: usize,
33}
34
35impl fmt::Write for ByteBufWriter<'_> {
36    fn write_str(&mut self, s: &str) -> fmt::Result {
37        let len = s.len();
38        // TODO: `<[_]>::split_at_mut_checked()` is stable since Rust 1.80.0.
39        if self.buffer.len() < len {
40            return Err(fmt::Error);
41        }
42        let (prefix, rest) = core::mem::take(&mut self.buffer).split_at_mut(len);
43        prefix.copy_from_slice(s.as_bytes());
44        self.buffer = rest;
45        self.len_written += len;
46        Ok(())
47    }
48}
49
50/// Writes to the bytes buffer.
51pub fn write_to_slice<'a, T: fmt::Display>(
52    buf: &'a mut [u8],
53    value: &T,
54) -> Result<&'a str, CapacityOverflowError> {
55    let mut writer = ByteBufWriter {
56        buffer: buf,
57        len_written: 0,
58    };
59    if write!(writer, "{}", value).is_err() {
60        return Err(CapacityOverflowError);
61    }
62    let len = writer.len_written;
63    let result =
64        core::str::from_utf8(&buf[..len]).expect("fmt::Display writes valid UTF-8 byte sequence");
65    Ok(result)
66}
67
68/// Writer that fails (not panics) on OOM.
69#[cfg(feature = "alloc")]
70struct StringWriter<'a> {
71    /// Destination buffer.
72    buffer: &'a mut String,
73    /// Memory allocation error.
74    error: Option<TryReserveError>,
75}
76
77#[cfg(feature = "alloc")]
78impl fmt::Write for StringWriter<'_> {
79    fn write_str(&mut self, s: &str) -> fmt::Result {
80        if self.error.is_some() {
81            return Err(fmt::Error);
82        }
83        if let Err(e) = self.buffer.try_reserve(s.len()) {
84            self.error = Some(e);
85            return Err(fmt::Error);
86        }
87        // This should never fail since `.try_reserve(s.len())` succeeded.
88        self.buffer.push_str(s);
89        Ok(())
90    }
91}
92
93/// Appends the data to the string.
94///
95/// When allocation failure happens, incompletely appended strings won't be
96/// stripped. Callers are responsible to clean up the destination if necessary.
97#[cfg(feature = "alloc")]
98pub fn try_append_to_string<T: fmt::Display>(
99    dest: &mut String,
100    value: &T,
101) -> Result<(), TryReserveError> {
102    let mut writer = StringWriter {
103        buffer: dest,
104        error: None,
105    };
106    if write!(writer, "{}", value).is_err() {
107        let e = writer
108            .error
109            .expect("allocation error should be set on formatting failure");
110        return Err(e);
111    }
112    Ok(())
113}
114
115/// Returns true if the two equals after they are converted to strings.
116pub(crate) fn eq_str_display<T>(s: &str, d: &T) -> bool
117where
118    T: ?Sized + fmt::Display,
119{
120    /// Dummy writer to compare the formatted object to the given string.
121    struct CmpWriter<'a>(&'a str);
122    impl fmt::Write for CmpWriter<'_> {
123        fn write_str(&mut self, s: &str) -> fmt::Result {
124            if let Some(rest) = self.0.strip_prefix(s) {
125                self.0 = rest;
126                Ok(())
127            } else {
128                Err(fmt::Error)
129            }
130        }
131    }
132
133    let mut writer = CmpWriter(s);
134    let succeeded = write!(writer, "{}", d).is_ok();
135    succeeded && writer.0.is_empty()
136}
137
138/// A debug-printable type to hide the sensitive information.
139#[derive(Clone, Copy)]
140pub(crate) struct Censored;
141
142impl core::fmt::Debug for Censored {
143    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> core::fmt::Result {
144        f.write_str("{censored}")
145    }
146}
147
148/// [`ToString`][`alloc::string::ToString`], but without panic.
149#[cfg(feature = "alloc")]
150pub trait ToStringFallible: alloc::string::ToString {
151    /// [`ToString::to_string`][`alloc::string::ToString::to_string`], but without panic on OOM.
152    fn try_to_string(&self) -> Result<String, TryReserveError>;
153}
154
155#[cfg(feature = "alloc")]
156impl<T: fmt::Display> ToStringFallible for T {
157    /// [`ToString::to_string`][`alloc::string::ToString::to_string`], but without panic on OOM.
158    #[inline]
159    fn try_to_string(&self) -> Result<String, TryReserveError> {
160        let mut buf = String::new();
161        try_append_to_string(&mut buf, self)?;
162        Ok(buf)
163    }
164}
165
166/// A trait for types that can be converted to a dedicated allocated string types.
167#[cfg(feature = "alloc")]
168pub trait ToDedicatedString {
169    /// Conversion target type.
170    type Target;
171
172    /// Converts the value to the allocated string.
173    fn try_to_dedicated_string(&self) -> Result<Self::Target, TryReserveError>;
174
175    /// Converts the value to the allocated string.
176    ///
177    /// # Panics
178    ///
179    /// Panics if memory allocation error occured.
180    #[inline]
181    #[must_use]
182    fn to_dedicated_string(&self) -> Self::Target {
183        self.try_to_dedicated_string()
184            .expect("failed to allocate enough memory")
185    }
186}
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191
192    #[test]
193    fn eq_str_display_1() {
194        assert!(eq_str_display("hello", "hello"));
195        assert!(eq_str_display("42", &42));
196
197        assert!(eq_str_display(
198            r#"\x00\t\r\n\xff\\"#,
199            &b"\x00\t\r\n\xff\\".escape_ascii()
200        ));
201
202        assert!(!eq_str_display("hello", "world"));
203        assert!(!eq_str_display("hello world", "hello"));
204        assert!(!eq_str_display("hello", "hello world"));
205        assert!(!eq_str_display("42", &4));
206        assert!(!eq_str_display("4", &42));
207    }
208}