toe_beans/v4/message/
file.rs

1use crate::v4::error::Result;
2use std::cmp::Ordering;
3use std::ffi::{CStr, CString};
4
5/// A null-terminated string that always maps to the 128 bytes assigned to it in [Message](crate::v4::Message).
6///
7/// You can get the string value back out with `to_string()`:
8/// ```
9/// # use toe_beans::v4::File;
10/// # use std::ffi::CString;
11/// # let file = File::new(c"/example").unwrap();
12/// let path: CString = file.to_string();
13/// ```
14#[derive(Debug, PartialEq)]
15pub struct File([u8; 128]);
16
17impl File {
18    /// Construct a valid file field.
19    /// `path` must not be more than 128 bytes.
20    /// The last byte of which must be `0x00` (null-terminated).
21    pub fn new(path: &CStr) -> Result<Self> {
22        let bytes = path.to_bytes_with_nul();
23        match bytes.len().cmp(&128) {
24            Ordering::Less => {
25                let mut padding = [0u8; 128];
26                bytes
27                    .iter()
28                    .enumerate()
29                    .for_each(|(i, byte)| padding[i] = *byte);
30                Ok(Self(padding))
31            }
32            Ordering::Equal => Ok(Self(bytes.try_into().unwrap())),
33            Ordering::Greater => Err("File path does not fit in a 128 byte array"),
34        }
35    }
36
37    /// Converts an array slice of exactly 128 byte length
38    /// into a File without performing any checks (such as length or null-termination).
39    ///
40    /// Panics if slice is not 128 bytes.
41    ///
42    /// Useful if you are handling the return of `parse_file`.
43    pub fn from_slice_unchecked(path: &[u8]) -> Self {
44        Self(path.try_into().unwrap())
45    }
46
47    /// Create a File by passing the inner type directly.
48    pub fn from_array(name: [u8; 128]) -> Self {
49        Self(name)
50    }
51
52    /// A File that contains all zero bytes.
53    ///
54    /// This is more performant than `File::new(c"")`
55    pub const EMPTY: Self = Self([0; 128]);
56
57    /// Convert the internal array to a null-terminated
58    /// string representation with padding removed.
59    pub fn to_string(&self) -> CString {
60        CStr::from_bytes_until_nul(&self.0).unwrap().to_owned()
61    }
62
63    /// Get the length of File.
64    ///
65    /// Note that this returns the length of the inner array
66    /// which is always 128. To get the length of its string
67    /// representation call `file.to_string().len()`.
68    pub fn len(&self) -> usize {
69        128
70    }
71}
72
73impl From<&File> for [u8; 128] {
74    fn from(file: &File) -> Self {
75        file.0
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    #[test]
84    fn test_new_file_below_length() {
85        let file = File::new(c"12345");
86        assert!(file.is_ok());
87    }
88
89    #[test]
90    fn test_new_file_at_length() {
91        // 127 bytes plus the NUL byte
92        let file = File::new(c"1234512345123451234512345123451234512345123451234512345123451234512345123451234512345123451234512345123451234512345123451234512");
93        assert!(file.is_ok());
94    }
95
96    #[test]
97    fn test_new_file_above_length() {
98        let file = File::new(c"1234512345123451234512345123451234512345123451234512345123451234512345123451234512345123451234512345123451234512345123451234512345");
99        assert!(file.is_err());
100    }
101
102    #[test]
103    fn test_empty() {
104        let file = File::EMPTY;
105        assert_eq!(file.0, [0; 128]);
106    }
107}