use core::ops::RangeBounds;
use super::error::{OutOfBounds, Utf8IndexError, normalize_range};
pub(crate) mod sealed {
pub trait Sealed {}
}
pub trait Utf8Buf: sealed::Sealed + AsRef<str> + Sized {
fn as_str(&self) -> &str {
self.as_ref()
}
fn len(&self) -> usize {
self.as_str().len()
}
fn is_empty(&self) -> bool {
self.len() == 0
}
fn split_to(&mut self, at: usize) -> Self {
self.try_split_to(at).expect("split_to failed")
}
fn try_split_to(&mut self, at: usize) -> Result<Self, Utf8IndexError>;
fn split_off(&mut self, at: usize) -> Self {
self.try_split_off(at).expect("split_off failed")
}
fn try_split_off(&mut self, at: usize) -> Result<Self, Utf8IndexError>;
fn slice(&self, range: impl RangeBounds<usize>) -> Self {
self.try_slice(range).expect("slice failed")
}
fn try_slice(&self, range: impl RangeBounds<usize>) -> Result<Self, Utf8IndexError>;
}
pub trait Utf8BufMut: Utf8Buf + AsMut<str> {
type CapacityError: core::error::Error;
fn as_mut_str(&mut self) -> &mut str {
self.as_mut()
}
fn try_truncate(&mut self, new_len: usize) -> Result<(), Utf8IndexError>;
fn try_push(&mut self, ch: char) -> Result<(), Self::CapacityError>;
fn try_push_str(&mut self, s: &str) -> Result<(), Self::CapacityError>;
fn push(&mut self, ch: char) {
if let Err(error) = self.try_push(ch) {
panic!("UTF-8 buffer capacity exceeded: {error}");
}
}
fn push_str(&mut self, s: &str) {
if let Err(error) = self.try_push_str(s) {
panic!("UTF-8 buffer capacity exceeded: {error}");
}
}
fn clear(&mut self);
}
pub(crate) fn validate_char_boundary(s: &str, at: usize) -> Result<(), Utf8IndexError> {
let len = s.len();
if at > len {
return Err(OutOfBounds::new(at, len).into());
}
if !s.is_char_boundary(at) {
return Err(Utf8IndexError::InvalidCharBoundary { index: at });
}
Ok(())
}
pub(crate) fn range_bounds_to_start_end(
s: &str,
range: impl RangeBounds<usize>,
) -> Result<(usize, usize), Utf8IndexError> {
let (start, end) = normalize_range(range, s.len())?;
if !s.is_char_boundary(start) {
return Err(Utf8IndexError::InvalidCharBoundary { index: start });
}
if !s.is_char_boundary(end) {
return Err(Utf8IndexError::InvalidCharBoundary { index: end });
}
Ok((start, end))
}
#[allow(dead_code)]
pub(crate) fn char_offset_to_byte(s: &str, chars: usize) -> Option<usize> {
if chars == s.chars().count() {
Some(s.len())
} else {
s.char_indices().nth(chars).map(|(index, _)| index)
}
}
#[cfg(test)]
mod char_offset_tests {
use super::char_offset_to_byte;
#[test]
fn maps_scalar_offset_to_byte_offset() {
assert_eq!(char_offset_to_byte("abc", 1), Some(1));
assert_eq!(char_offset_to_byte("é", 1), Some(2));
assert_eq!(char_offset_to_byte("aé", 1), Some(1));
assert_eq!(char_offset_to_byte("aé", 2), Some(3));
assert_eq!(char_offset_to_byte("", 0), Some(0));
assert_eq!(char_offset_to_byte("a", 2), None);
assert_eq!(char_offset_to_byte("🦀x", 1), Some(4));
}
}