non_zero_byte_slice/
lib.rs

1use std::{
2    borrow::{Borrow, Cow, ToOwned},
3    convert::TryFrom,
4    error::Error,
5    ffi::{CStr, CString},
6    fmt,
7    num::NonZeroU8,
8    ops::Deref,
9};
10
11use serde::Serialize;
12
13#[derive(Debug, Eq, PartialEq, Hash, Serialize)]
14#[repr(transparent)]
15pub struct NonZeroByteSlice([u8]);
16
17impl NonZeroByteSlice {
18    pub const fn new(bytes: &[u8]) -> Option<&Self> {
19        let mut i = 0;
20        while i < bytes.len() {
21            if bytes[i] == 0 {
22                return None;
23            }
24            i += 1;
25        }
26
27        // safety: bytes does not contain 0
28        Some(unsafe { Self::new_unchecked(bytes) })
29    }
30
31    /// # Safety
32    ///
33    /// * `bytes` - Must not contain `0`.
34    pub const unsafe fn new_unchecked(bytes: &[u8]) -> &Self {
35        &*(bytes as *const [u8] as *const Self)
36    }
37
38    pub const fn into_inner(&self) -> &[u8] {
39        &self.0
40    }
41}
42
43/// The string contains null byte.
44#[derive(Debug)]
45pub struct NullByteError;
46
47impl fmt::Display for NullByteError {
48    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49        write!(f, "NullByteError")
50    }
51}
52
53impl Error for NullByteError {}
54
55impl<'a> TryFrom<&'a str> for &'a NonZeroByteSlice {
56    type Error = NullByteError;
57
58    fn try_from(s: &'a str) -> Result<Self, Self::Error> {
59        NonZeroByteSlice::new(s.as_bytes()).ok_or(NullByteError)
60    }
61}
62
63impl<'a> From<&'a CStr> for &'a NonZeroByteSlice {
64    fn from(s: &'a CStr) -> Self {
65        // safety: CStr cannot contain 0 byte
66        unsafe { NonZeroByteSlice::new_unchecked(s.to_bytes()) }
67    }
68}
69
70impl ToOwned for NonZeroByteSlice {
71    type Owned = NonZeroByteVec;
72
73    fn to_owned(&self) -> Self::Owned {
74        self.into()
75    }
76}
77
78#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize)]
79#[repr(transparent)]
80pub struct NonZeroByteVec(Vec<u8>);
81
82impl NonZeroByteVec {
83    pub fn new(bytes: Vec<u8>) -> Option<Self> {
84        for byte in bytes.iter() {
85            if *byte == 0 {
86                return None;
87            }
88        }
89
90        Some(Self(bytes))
91    }
92
93    pub fn from_bytes_remove_nul(mut bytes: Vec<u8>) -> Self {
94        bytes.retain(|byte| *byte != b'\0');
95        Self(bytes)
96    }
97
98    /// # Safety
99    ///
100    /// * `bytes` - Must not contain `0`.
101    pub const unsafe fn new_unchecked(bytes: Vec<u8>) -> Self {
102        Self(bytes)
103    }
104
105    pub fn from_slice(slice: &NonZeroByteSlice) -> Self {
106        Self(slice.into_inner().into())
107    }
108
109    pub fn push(&mut self, byte: NonZeroU8) {
110        self.0.push(byte.get())
111    }
112
113    pub fn from_bytes_slice_lossy(slice: &[u8]) -> Cow<'_, NonZeroByteSlice> {
114        NonZeroByteSlice::new(slice)
115            .map(Cow::Borrowed)
116            .unwrap_or_else(|| {
117                let bytes: Vec<u8> = slice
118                    .iter()
119                    .copied()
120                    .filter(|byte| *byte != b'\0')
121                    .collect();
122
123                // Safety: all null bytes from slice is filtered out.
124                Cow::Owned(unsafe { Self::new_unchecked(bytes) })
125            })
126    }
127}
128
129impl From<&NonZeroByteSlice> for NonZeroByteVec {
130    fn from(slice: &NonZeroByteSlice) -> Self {
131        Self::from_slice(slice)
132    }
133}
134
135impl TryFrom<String> for NonZeroByteVec {
136    type Error = NullByteError;
137
138    fn try_from(s: String) -> Result<Self, Self::Error> {
139        Self::new(s.into_bytes()).ok_or(NullByteError)
140    }
141}
142
143impl From<CString> for NonZeroByteVec {
144    fn from(s: CString) -> Self {
145        // safety: CString cannot contain 0 byte
146        unsafe { Self::new_unchecked(s.into_bytes()) }
147    }
148}
149
150impl Deref for NonZeroByteVec {
151    type Target = NonZeroByteSlice;
152
153    fn deref(&self) -> &Self::Target {
154        // safety: self.0 does not contain 0
155        unsafe { NonZeroByteSlice::new_unchecked(&self.0) }
156    }
157}
158
159impl Borrow<NonZeroByteSlice> for NonZeroByteVec {
160    fn borrow(&self) -> &NonZeroByteSlice {
161        self.deref()
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168
169    #[test]
170    fn test_byte_slice_with_zero() {
171        let mut vec: Vec<_> = (0..9).collect();
172        vec.push(0);
173
174        let option = NonZeroByteSlice::new(&vec);
175        debug_assert!(option.is_none(), "{:#?}", option);
176    }
177
178    #[test]
179    fn test_byte_slice_without_zero() {
180        let vec: Vec<_> = (1..102).collect();
181        NonZeroByteSlice::new(&vec).unwrap();
182    }
183
184    #[test]
185    fn test_byte_vec_without_zero() {
186        let vec: Vec<_> = (1..102).collect();
187        NonZeroByteVec::new(vec).unwrap();
188    }
189
190    #[test]
191    fn test_byte_vec_from_bytes_remove_nul_zero() {
192        let mut vec: Vec<_> = (0..3).collect();
193        vec.push(0);
194        assert_eq!(NonZeroByteVec::from_bytes_remove_nul(vec).0, vec![1, 2]);
195    }
196
197    #[test]
198    fn test_from_bytes_slice_lossy() {
199        let non_zero_bytes = b"1234x4r2ex";
200        assert_eq!(
201            NonZeroByteVec::from_bytes_slice_lossy(non_zero_bytes),
202            Cow::Borrowed(NonZeroByteSlice::new(non_zero_bytes).unwrap()),
203        );
204
205        let bytes_with_zero = b"\x00123x'1\x0023x\0";
206        assert_eq!(
207            NonZeroByteVec::from_bytes_slice_lossy(bytes_with_zero),
208            Cow::Owned(NonZeroByteVec::from_bytes_remove_nul(
209                bytes_with_zero.to_vec()
210            )),
211        );
212    }
213}