use shadow_nft_common::array_from_fn;
#[derive(Debug, PartialEq, Clone, Copy)]
pub struct ZeroCopyStr<'a> {
inner: &'a str,
}
type LenType = u8;
const LEN_TYPE_SIZE: usize = ::core::mem::size_of::<LenType>();
impl<'a> ZeroCopyStr<'a> {
pub fn write_to(str: &str, bytes: &'a mut [u8]) -> (ZeroCopyStr<'a>, &'a mut [u8]) {
let str_byte_len = str.as_bytes().len();
let required_size = str.as_bytes().len() + LEN_TYPE_SIZE;
if str_byte_len > LenType::MAX as usize {
panic!("the string provided is too large");
}
if bytes.len() < required_size {
panic!("buffer provided for initialization is not large enough");
}
bytes[..LEN_TYPE_SIZE].copy_from_slice(&(str.as_bytes().len() as LenType).to_le_bytes());
bytes[LEN_TYPE_SIZE..LEN_TYPE_SIZE + str_byte_len].copy_from_slice(str.as_bytes());
let (len_inner, rest) = bytes.split_at_mut(required_size);
let (_len, inner_bytes) = len_inner.split_at(LEN_TYPE_SIZE);
let inner = ::core::str::from_utf8(inner_bytes)
.expect("the user should have passed in a valid &str");
(ZeroCopyStr { inner }, rest)
}
pub fn write_to_update<'o>(str: &str, bytes: &'o mut &'a mut [u8]) -> ZeroCopyStr<'a> {
let str_byte_len = str.as_bytes().len();
let required_size = str.as_bytes().len() + LEN_TYPE_SIZE;
if str_byte_len > LenType::MAX as usize {
panic!("the string provided is too large");
}
if bytes.len() < required_size {
panic!("buffer provided for initialization is not large enough");
}
bytes[..LEN_TYPE_SIZE].copy_from_slice(&(str.as_bytes().len() as LenType).to_le_bytes());
bytes[LEN_TYPE_SIZE..LEN_TYPE_SIZE + str_byte_len].copy_from_slice(str.as_bytes());
let (len_inner, rest) = unsafe {
::core::mem::transmute::<(&'o mut [u8], &'o mut [u8]), (&'a mut [u8], &'a mut [u8])>(
bytes.split_at_mut(required_size),
)
};
*bytes = rest;
let (_len, inner_bytes) = len_inner.split_at(LEN_TYPE_SIZE);
let inner = ::core::str::from_utf8(inner_bytes)
.expect("the user should have passed in a valid &str");
ZeroCopyStr { inner }
}
pub fn read_from(bytes: &mut &'a [u8]) -> ZeroCopyStr<'a> {
if bytes.len() < LEN_TYPE_SIZE {
panic!("invalid buffer")
}
let str_byte_len = LenType::from_le_bytes(array_from_fn::from_fn(|i| bytes[i]));
let inner =
::core::str::from_utf8(&bytes[LEN_TYPE_SIZE..LEN_TYPE_SIZE + str_byte_len as usize])
.expect("invalid utf8");
*bytes = &bytes[LEN_TYPE_SIZE + str_byte_len as usize..];
ZeroCopyStr { inner }
}
pub fn serialized_size(&self) -> usize {
self.inner.as_bytes().len() + LEN_TYPE_SIZE
}
pub fn as_bytes(&self) -> &[u8] {
self.inner.as_bytes()
}
pub fn as_str(&self) -> &str {
self.inner
}
pub fn to_vec(&self) -> Vec<u8> {
let mut buf = vec![0; self.serialized_size()];
let mut buf_cursor = unsafe { ::core::mem::transmute(buf.as_mut_slice()) };
Self::write_to_update(self.as_str(), &mut buf_cursor);
buf
}
}
impl<'a> From<&'a str> for ZeroCopyStr<'a> {
fn from(value: &'a str) -> Self {
Self { inner: value }
}
}
impl<'a> PartialEq<&str> for ZeroCopyStr<'a> {
fn eq(&self, other: &&str) -> bool {
self.inner.eq(*other)
}
}
#[cfg(test)]
mod tests {
use crate::{LenType, ZeroCopyStr, LEN_TYPE_SIZE};
const SHADOWY_STR: &str =
"https://shdw-drive.genesysgo.net/2EC2FnYfstrscZDzQcCEgN3hSn1A5wc1pQNKp5DPfCVo/momma.html";
const WEAVY_STR: &str =
"https://4w5qopogxy735ydfrvlfjycvuyho5a2am3g5wtektb3kqmnspl7a.arweave.net/5bsHPca-P77gZY1WVOBVpg7ug0BmzdtMiph2qDGyev4/7500.png";
const A: u8 = 0x41;
const EMPTY_STR: &str = "";
const FULL_STR: &str =
unsafe { core::str::from_utf8_unchecked(&[A; LenType::MAX as usize]) };
#[test_case::test_case(SHADOWY_STR; "shadowy file")]
#[test_case::test_case(WEAVY_STR; "the king aquaman")]
#[test_case::test_case(EMPTY_STR; "empty str")]
#[test_case::test_case(FULL_STR; "full str")]
fn test_round_trip(str: &str) {
const BUFFER_LEN: usize = 1024;
let mut buffer = vec![0; BUFFER_LEN];
let (zcs, buffer) = ZeroCopyStr::write_to(str, &mut buffer);
assert_eq!(zcs, str);
let expected_bytes_written = str.as_bytes().len() + LEN_TYPE_SIZE;
assert_eq!(BUFFER_LEN, buffer.len() + expected_bytes_written);
}
#[test_case::test_case(SHADOWY_STR; "shadowy file")]
#[test_case::test_case(WEAVY_STR; "the king aquaman")]
#[test_case::test_case(EMPTY_STR; "empty str")]
#[test_case::test_case(FULL_STR; "full str")]
fn test_round_trip_update(str: &str) {
const BUFFER_LEN: usize = 1024;
let mut buffer = vec![0; BUFFER_LEN];
let mut buf = buffer.as_mut_slice();
let zcs = ZeroCopyStr::write_to_update(str, &mut buf);
assert_eq!(zcs, str);
let expected_bytes_written = str.as_bytes().len() + LEN_TYPE_SIZE;
assert_eq!(BUFFER_LEN, buf.len() + expected_bytes_written);
}
}