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