use super::*;
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct FixedStrBuf<const N: usize> {
pub(super) buffer: [u8; N],
pub(super) len: usize,
}
impl<const N: usize> FixedStrBuf<N> {
pub const fn capacity(&self) -> usize {
N
}
pub fn remaining(&self) -> usize {
N - self.len
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub const fn new() -> Self {
panic_on_zero(N);
Self {
buffer: [0u8; N],
len: 0,
}
}
pub fn try_as_str(&self) -> Result<&str, FixedStrError> {
core::str::from_utf8(self.effective_bytes()).map_err(|_| FixedStrError::InvalidUtf8)
}
pub fn try_push_str(&mut self, s: &str) -> Result<(), FixedStrError> {
let bytes = s.effective_bytes();
if bytes.len() > self.remaining() {
return Err(FixedStrError::Overflow {
available: self.remaining(),
found: bytes.len(),
});
}
self.buffer[self.len..self.len + bytes.len()].copy_from_slice(bytes);
self.len += bytes.len();
Ok(())
}
pub fn try_push_char(&mut self, c: char) -> Result<(), FixedStrError> {
let mut buf = [0u8; 4];
let s = c.encode_utf8(&mut buf);
self.try_push_str(s)
}
pub fn push_str_lossy(&mut self, s: &str) -> bool {
let remaining = self.remaining();
let valid = if s.len() > remaining {
truncate_utf8_lossy(s.as_bytes(), remaining)
} else {
s
};
let bytes = valid.as_bytes();
if !bytes.is_empty() {
self.buffer[self.len..self.len + bytes.len()].copy_from_slice(bytes);
self.len += bytes.len();
}
bytes.len() == s.len()
}
pub fn finalize(mut self) -> FixedStr<N> {
self.buffer[self.len..N].fill(0);
FixedStr::from_bytes(self.buffer)
}
pub fn finalize_unsafe(mut self) -> FixedStr<N> {
self.buffer[self.len..N].fill(0);
FixedStr::from_bytes_unsafe(self.buffer)
}
pub fn clear(&mut self) {
self.buffer.fill(0);
self.len = 0;
}
pub fn truncate(&mut self, new_len: usize) {
if new_len < self.len {
for i in new_len..self.len {
self.buffer[i] = 0;
}
self.len = new_len;
}
}
#[cfg(feature = "std")]
pub fn to_string_lossy(&self) -> String {
String::from_utf8_lossy(self.effective_bytes()).into_owned()
}
}
impl<const N: usize> fmt::Display for FixedStrBuf<N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = core::str::from_utf8(&self.buffer[..self.len]).unwrap_or("<invalid UTF-8>");
write!(f, "{}", s)
}
}
impl<const N: usize> fmt::Debug for FixedStrBuf<N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match str::from_utf8(&self.buffer[..self.len]) {
Ok(s) => write!(f, "FixedStrBuf<{}>({:?})", N, s),
Err(_) => write!(
f,
"FixedStrBuf<{}>(<invalid UTF-8>) {:?}",
N,
fast_format_hex::<384>(&self.buffer, 16, Some(8))
),
}
}
}
impl<const N: usize> EffectiveBytes for FixedStrBuf<N> {
fn effective_bytes(&self) -> &[u8] {
self.buffer.effective_bytes()
}
}
impl<const N: usize> AsRef<[u8]> for FixedStrBuf<N> {
fn as_ref(&self) -> &[u8] {
&self.buffer
}
}
impl<const N: usize> Default for FixedStrBuf<N> {
fn default() -> Self {
Self {
buffer: [0; N],
len: 0,
}
}
}
impl<const N: usize> core::ops::Deref for FixedStrBuf<N> {
type Target = [u8];
fn deref(&self) -> &Self::Target {
&self.buffer
}
}
impl<const N: usize> From<FixedStr<N>> for FixedStrBuf<N> {
fn from(fixed: FixedStr<N>) -> Self {
Self {
buffer: fixed.data,
len: fixed.len(),
}
}
}
impl<const N: usize> core::convert::TryFrom<&[u8]> for FixedStrBuf<N> {
type Error = FixedStrError;
fn try_from(slice: &[u8]) -> Result<Self, Self::Error> {
let buf = copy_into_buffer(slice, BufferCopyMode::Exact)?;
let effective_len = find_first_null(&buf);
Ok(Self {
buffer: buf,
len: effective_len,
})
}
}
impl<const N: usize> Hash for FixedStrBuf<N> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.effective_bytes().hash(state);
}
}
impl<const N: usize> IntoIterator for FixedStrBuf<N> {
type Item = u8;
type IntoIter = core::array::IntoIter<u8, N>;
fn into_iter(self) -> Self::IntoIter {
core::array::IntoIter::into_iter(self.buffer.into_iter())
}
}
impl<const N: usize> Ord for FixedStrBuf<N> {
fn cmp(&self, other: &Self) -> Ordering {
self.effective_bytes().cmp(other.effective_bytes())
}
}
impl<const N: usize> PartialOrd for FixedStrBuf<N> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<const N: usize> PartialEq<[u8]> for FixedStrBuf<N> {
fn eq(&self, other: &[u8]) -> bool {
self.effective_bytes() == other.effective_bytes()
}
}
impl<const N: usize> PartialEq<FixedStrBuf<N>> for [u8] {
fn eq(&self, other: &FixedStrBuf<N>) -> bool {
self.effective_bytes() == other.effective_bytes()
}
}
impl<const N: usize> PartialEq<&[u8]> for FixedStrBuf<N> {
fn eq(&self, other: &&[u8]) -> bool {
self.effective_bytes() == other.effective_bytes()
}
}
impl<const N: usize> PartialEq<FixedStrBuf<N>> for &[u8] {
fn eq(&self, other: &FixedStrBuf<N>) -> bool {
self.effective_bytes() == other.effective_bytes()
}
}
impl<const N: usize> PartialEq<[u8; N]> for FixedStrBuf<N> {
fn eq(&self, other: &[u8; N]) -> bool {
self.effective_bytes() == other.effective_bytes()
}
}
impl<const N: usize> PartialEq<FixedStrBuf<N>> for [u8; N] {
fn eq(&self, other: &FixedStrBuf<N>) -> bool {
self.effective_bytes() == other.effective_bytes()
}
}
impl<const N: usize> PartialEq<FixedStr<N>> for FixedStrBuf<N> {
fn eq(&self, other: &FixedStr<N>) -> bool {
self.effective_bytes() == other.effective_bytes()
}
}
impl<const N: usize> PartialEq<FixedStrBuf<N>> for FixedStr<N> {
fn eq(&self, other: &FixedStrBuf<N>) -> bool {
self.effective_bytes() == other.effective_bytes()
}
}
#[cfg(feature = "std")]
impl<const N: usize> PartialEq<Vec<u8>> for FixedStrBuf<N> {
fn eq(&self, other: &Vec<u8>) -> bool {
self.effective_bytes() == other.effective_bytes()
}
}
#[cfg(feature = "std")]
impl<const N: usize> PartialEq<FixedStrBuf<N>> for Vec<u8> {
fn eq(&self, other: &FixedStrBuf<N>) -> bool {
self.effective_bytes() == other.effective_bytes()
}
}
#[cfg(test)]
mod buffer_tests {
use super::*;
#[test]
fn test_try_push_str_success() {
let mut buf = FixedStrBuf::<10>::new();
assert!(buf.try_push_str("Hello").is_ok());
assert_eq!(buf.len(), 5);
}
#[test]
fn test_try_push_str_fail() {
let mut buf = FixedStrBuf::<5>::new();
let result = buf.try_push_str("Hello, world!");
assert!(result.is_err());
assert_eq!(buf.len(), 0);
}
#[test]
fn test_try_push_char_success() {
let mut buf = FixedStrBuf::<5>::new();
assert!(buf.try_push_char('A').is_ok());
assert_eq!(buf.len(), 1);
}
#[test]
fn test_push_str_lossy() {
let mut buf = FixedStrBuf::<5>::new();
assert!(buf.push_str_lossy("Hello"));
let result = buf.push_str_lossy(", world!");
assert!(!result);
let fixed: FixedStr<5> = buf.finalize();
assert_eq!(fixed.as_str(), "Hello");
}
#[test]
fn test_finalize_trailing_zeros() {
let mut buf = FixedStrBuf::<10>::new();
buf.try_push_str("Hi").unwrap();
let fixed: FixedStr<10> = buf.finalize();
assert_eq!(fixed.len(), 2);
assert_eq!(fixed.as_str(), "Hi");
assert_eq!(fixed.as_bytes()[2], 0);
}
#[test]
fn test_fixed_str_buf_clear() {
let mut buf = FixedStrBuf::<10>::new();
buf.try_push_str("Hello").unwrap();
assert_eq!(buf.len(), 5);
buf.clear();
assert_eq!(buf.len(), 0);
assert_eq!(&buf, &[0u8; 10]);
buf.try_push_str("Rust").unwrap();
assert_eq!(buf.len(), 4);
assert_eq!(&buf[..4], b"Rust");
}
#[test]
fn test_fixed_str_buf_try_from_slice() {
let input = b"Hello!";
let result = FixedStrBuf::<5>::try_from(&input[..]);
assert!(result.is_err());
}
#[test]
fn test_fixed_str_buf_ordering() {
let mut buf1 = FixedStrBuf::<10>::new();
let mut buf2 = FixedStrBuf::<10>::new();
buf1.try_push_str("Apple").unwrap();
buf2.try_push_str("Banana").unwrap();
assert!(buf1 < buf2);
let mut buf3 = FixedStrBuf::<10>::new();
buf3.try_push_str("Apple").unwrap();
assert_eq!(buf1, buf3);
}
#[test]
fn test_truncate_reduces_length() {
let mut buf = FixedStrBuf::<10>::new();
buf.try_push_str("HelloWorld").unwrap();
assert_eq!(buf.len(), 10);
buf.truncate(5);
assert_eq!(buf.len(), 5);
let fixed = buf.finalize();
assert_eq!(fixed.as_str(), "Hello");
for &b in &buf.as_ref()[5..] {
assert_eq!(b, 0);
}
}
#[test]
fn test_truncate_no_effect_when_new_len_is_greater() {
let mut buf = FixedStrBuf::<10>::new();
buf.try_push_str("Hi").unwrap();
assert_eq!(buf.len(), 2);
buf.truncate(5);
assert_eq!(buf.len(), 2);
}
#[test]
fn test_from_fixedstr_effective_length() {
let fixed = FixedStr::<10>::new("Hello");
let buf: FixedStrBuf<10> = fixed.into();
assert_eq!(buf.len(), 5);
assert_eq!(buf.effective_bytes(), b"Hello");
}
#[test]
fn test_try_from_slice_effective_length() {
let bytes = b"Hello\0World";
let buf = FixedStrBuf::<11>::try_from(&bytes[..]).unwrap();
assert_eq!(buf.len(), 5);
assert_eq!(buf.effective_bytes(), b"Hello");
}
#[cfg(feature = "std")]
#[test]
fn test_fixed_str_buf_into_iter() {
let mut buf = FixedStrBuf::<5>::new();
buf.try_push_str("Hey").unwrap();
let bytes: Vec<u8> = buf.into_iter().collect();
assert_eq!(bytes[..3], *b"Hey");
assert_eq!(bytes[3..], [0u8; 2]);
}
}