1use shadow_nft_common::array_from_fn;
6
7#[derive(Debug, PartialEq, Clone, Copy)]
8pub struct ZeroCopyStr<'a> {
9 inner: &'a str,
10}
11
12type LenType = u8;
13const LEN_TYPE_SIZE: usize = ::core::mem::size_of::<LenType>();
14
15impl<'a> ZeroCopyStr<'a> {
16 pub fn write_to(str: &str, bytes: &'a mut [u8]) -> (ZeroCopyStr<'a>, &'a mut [u8]) {
23 let str_byte_len = str.as_bytes().len();
25 let required_size = str.as_bytes().len() + LEN_TYPE_SIZE;
26 if str_byte_len > LenType::MAX as usize {
27 panic!("the string provided is too large");
28 }
29 if bytes.len() < required_size {
30 panic!("buffer provided for initialization is not large enough");
31 }
32
33 bytes[..LEN_TYPE_SIZE].copy_from_slice(&(str.as_bytes().len() as LenType).to_le_bytes());
35
36 bytes[LEN_TYPE_SIZE..LEN_TYPE_SIZE + str_byte_len].copy_from_slice(str.as_bytes());
38
39 let (len_inner, rest) = bytes.split_at_mut(required_size);
41
42 let (_len, inner_bytes) = len_inner.split_at(LEN_TYPE_SIZE);
46 let inner = ::core::str::from_utf8(inner_bytes)
47 .expect("the user should have passed in a valid &str");
48
49 (ZeroCopyStr { inner }, rest)
51 }
52
53 pub fn write_to_update<'o>(str: &str, bytes: &'o mut &'a mut [u8]) -> ZeroCopyStr<'a> {
60 let str_byte_len = str.as_bytes().len();
62 let required_size = str.as_bytes().len() + LEN_TYPE_SIZE;
63 if str_byte_len > LenType::MAX as usize {
64 panic!("the string provided is too large");
65 }
66 if bytes.len() < required_size {
67 panic!("buffer provided for initialization is not large enough");
68 }
69
70 bytes[..LEN_TYPE_SIZE].copy_from_slice(&(str.as_bytes().len() as LenType).to_le_bytes());
72
73 bytes[LEN_TYPE_SIZE..LEN_TYPE_SIZE + str_byte_len].copy_from_slice(str.as_bytes());
75
76 let (len_inner, rest) = unsafe {
78 ::core::mem::transmute::<(&'o mut [u8], &'o mut [u8]), (&'a mut [u8], &'a mut [u8])>(
79 bytes.split_at_mut(required_size),
80 )
81 };
82 *bytes = rest;
83
84 let (_len, inner_bytes) = len_inner.split_at(LEN_TYPE_SIZE);
88 let inner = ::core::str::from_utf8(inner_bytes)
89 .expect("the user should have passed in a valid &str");
90
91 ZeroCopyStr { inner }
93 }
94
95 pub fn read_from(bytes: &mut &'a [u8]) -> ZeroCopyStr<'a> {
101 if bytes.len() < LEN_TYPE_SIZE {
103 panic!("invalid buffer")
104 }
105
106 let str_byte_len = LenType::from_le_bytes(array_from_fn::from_fn(|i| bytes[i]));
108
109 let inner =
113 ::core::str::from_utf8(&bytes[LEN_TYPE_SIZE..LEN_TYPE_SIZE + str_byte_len as usize])
114 .expect("invalid utf8");
115
116 *bytes = &bytes[LEN_TYPE_SIZE + str_byte_len as usize..];
118
119 ZeroCopyStr { inner }
121 }
122
123 pub fn serialized_size(&self) -> usize {
125 self.inner.as_bytes().len() + LEN_TYPE_SIZE
126 }
127
128 pub fn as_bytes(&self) -> &[u8] {
132 self.inner.as_bytes()
133 }
134
135 pub fn as_str(&self) -> &str {
137 self.inner
138 }
139
140 pub fn to_vec(&self) -> Vec<u8> {
142 let mut buf = vec![0; self.serialized_size()];
143 let mut buf_cursor = unsafe { ::core::mem::transmute(buf.as_mut_slice()) };
150 Self::write_to_update(self.as_str(), &mut buf_cursor);
151 buf
152 }
153}
154
155impl<'a> From<&'a str> for ZeroCopyStr<'a> {
156 fn from(value: &'a str) -> Self {
157 Self { inner: value }
158 }
159}
160
161impl<'a> PartialEq<&str> for ZeroCopyStr<'a> {
162 fn eq(&self, other: &&str) -> bool {
163 self.inner.eq(*other)
164 }
165}
166
167#[cfg(test)]
168mod tests {
169 use crate::{LenType, ZeroCopyStr, LEN_TYPE_SIZE};
170
171 const SHADOWY_STR: &str =
174 "https://shdw-drive.genesysgo.net/2EC2FnYfstrscZDzQcCEgN3hSn1A5wc1pQNKp5DPfCVo/momma.html";
175
176 const WEAVY_STR: &str =
179 "https://4w5qopogxy735ydfrvlfjycvuyho5a2am3g5wtektb3kqmnspl7a.arweave.net/5bsHPca-P77gZY1WVOBVpg7ug0BmzdtMiph2qDGyev4/7500.png";
180
181 const A: u8 = 0x41;
183 const EMPTY_STR: &str = "";
184 const FULL_STR: &str =
185 unsafe { core::str::from_utf8_unchecked(&[A; LenType::MAX as usize]) };
186
187 #[test_case::test_case(SHADOWY_STR; "shadowy file")]
188 #[test_case::test_case(WEAVY_STR; "the king aquaman")]
189 #[test_case::test_case(EMPTY_STR; "empty str")]
190 #[test_case::test_case(FULL_STR; "full str")]
191 fn test_round_trip(str: &str) {
192 const BUFFER_LEN: usize = 1024;
193 let mut buffer = vec![0; BUFFER_LEN];
194
195 let (zcs, buffer) = ZeroCopyStr::write_to(str, &mut buffer);
199
200 assert_eq!(zcs, str);
202
203 let expected_bytes_written = str.as_bytes().len() + LEN_TYPE_SIZE;
206 assert_eq!(BUFFER_LEN, buffer.len() + expected_bytes_written);
207 }
208
209 #[test_case::test_case(SHADOWY_STR; "shadowy file")]
210 #[test_case::test_case(WEAVY_STR; "the king aquaman")]
211 #[test_case::test_case(EMPTY_STR; "empty str")]
212 #[test_case::test_case(FULL_STR; "full str")]
213 fn test_round_trip_update(str: &str) {
214 const BUFFER_LEN: usize = 1024;
215 let mut buffer = vec![0; BUFFER_LEN];
216
217 let mut buf = buffer.as_mut_slice();
221 let zcs = ZeroCopyStr::write_to_update(str, &mut buf);
222
223 assert_eq!(zcs, str);
225
226 let expected_bytes_written = str.as_bytes().len() + LEN_TYPE_SIZE;
229 assert_eq!(BUFFER_LEN, buf.len() + expected_bytes_written);
230 }
231}