cytryna/
string.rs

1use std::{borrow::Cow, fmt, str, string};
2
3use thiserror::Error;
4
5/// An error for SizedCString construction
6#[derive(Error, Debug)]
7pub enum SizedCStringError {
8    #[error("Input string too big to fit into storage")]
9    TooBig,
10}
11
12/// A wrapper over u8 array of a fixed size, allowing it to be used directly in a struct that is
13/// transmuted to and from raw bytes
14#[derive(Clone)]
15#[repr(transparent)]
16pub struct SizedCString<const SIZE: usize>([u8; SIZE]);
17
18impl<const SIZE: usize> SizedCString<SIZE> {
19    /// Returns a reference to string stored within, or str::Utf8Error if it's not valid UTF-8 data
20    /// <https://doc.rust-lang.org/std/str/fn.from_utf8.html>
21    #[must_use]
22    pub fn as_str(&self) -> Result<&str, str::Utf8Error> {
23        str::from_utf8(&self.0)
24    }
25    /// Converts to a string, replacing invalid UTF-8 sequences with replacement character
26    /// <https://doc.rust-lang.org/std/string/struct.String.html#method.from_utf8_lossy>
27    #[must_use]
28    pub fn to_string_lossy(&self) -> Cow<'_, str> {
29        String::from_utf8_lossy(&self.0)
30    }
31    /// Checks if string inside this struct is all zeroes
32    #[must_use]
33    pub fn is_zero(&self) -> bool {
34        self.0.iter().all(|v| *v == 0)
35    }
36    /// Returns a reference to data stored inside
37    #[must_use]
38    pub fn data(&self) -> &[u8] {
39        &self.0
40    }
41}
42
43impl<const SIZE: usize> From<[u8; SIZE]> for SizedCString<SIZE> {
44    fn from(other: [u8; SIZE]) -> SizedCString<SIZE> {
45        SizedCString(other)
46    }
47}
48
49/// A UTF-16 version of SizedCString
50#[derive(Clone)]
51#[repr(C)]
52pub struct SizedCStringUtf16<const SIZE: usize> {
53    data: [u16; SIZE],
54}
55
56impl<const SIZE: usize> SizedCStringUtf16<SIZE> {
57    /// Converts a SizedCStringUtf16 to a Rust String, returing an error on any invalid data
58    /// <https://doc.rust-lang.org/std/string/struct.String.html#method.from_utf16>
59    #[must_use]
60    pub fn to_string(&self) -> Result<String, string::FromUtf16Error> {
61        String::from_utf16(&self.data)
62    }
63    /// Converts a SizedCStringUtf16 to a Rust String, replacing invalid data with the replacement
64    /// character
65    /// <https://doc.rust-lang.org/std/string/struct.String.html#method.from_utf16_lossy>
66    #[must_use]
67    pub fn to_string_lossy(&self) -> String {
68        String::from_utf16_lossy(&self.data)
69    }
70    /// Checks if string inside this struct is all zeroes
71    #[must_use]
72    pub fn is_zero(&self) -> bool {
73        self.data.iter().all(|v| *v == 0)
74    }
75    /// Returns a reference to data stored inside
76    #[must_use]
77    pub fn data(&self) -> &[u16] {
78        &self.data
79    }
80}
81
82impl<const SIZE: usize> TryFrom<&str> for SizedCStringUtf16<SIZE> {
83    type Error = SizedCStringError;
84
85    fn try_from(value: &str) -> Result<Self, Self::Error> {
86        let mut data: Vec<u16> = value.encode_utf16().collect();
87        if data.len() > SIZE {
88            return Err(SizedCStringError::TooBig);
89        }
90        data.resize(SIZE, 0u16);
91        Ok(Self { data: data.try_into().unwrap() })
92    }
93}
94
95impl<const SIZE: usize> fmt::Debug for SizedCString<SIZE> {
96    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
97        fmt.write_fmt(format_args!("\"{}\"", self.to_string_lossy()))
98    }
99}
100
101impl<const SIZE: usize> fmt::Debug for SizedCStringUtf16<SIZE> {
102    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
103        fmt.write_fmt(format_args!("\"{}\"", self.to_string_lossy()))
104    }
105}