use core::fmt::{self, Debug, Display, Write};
use arrayvec::ArrayString;
pub const DEFAULT_ERROR_ID_CAP: usize = 64;
const ELLIPSIS: &str = "…";
const fn truncate_at_char_boundary(s: &str, limit: usize) -> usize {
if limit >= s.len() {
return s.len();
}
let mut pos = limit;
while pos > 0 && !s.is_char_boundary(pos) {
pos -= 1;
}
pos
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct ErrorId<const N: usize = DEFAULT_ERROR_ID_CAP> {
inner: ArrayString<N>,
}
impl<const N: usize> ErrorId<N> {
#[must_use]
pub fn new() -> Self {
Self {
inner: ArrayString::new(),
}
}
#[must_use]
pub fn from_display(value: &impl Display) -> Self {
let mut filler = Filler::<N>::new();
let _ = write!(filler, "{value}");
filler.finish()
}
#[must_use]
pub fn as_str(&self) -> &str {
self.inner.as_str()
}
#[must_use]
pub const fn len(&self) -> usize {
self.inner.len()
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.inner.is_empty()
}
}
impl<const N: usize> Default for ErrorId<N> {
fn default() -> Self {
Self::new()
}
}
impl<const N: usize> Display for ErrorId<N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl<const N: usize> Debug for ErrorId<N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self.as_str())
}
}
struct Filler<const N: usize> {
buf: ArrayString<N>,
truncated: bool,
}
impl<const N: usize> Filler<N> {
fn new() -> Self {
Self {
buf: ArrayString::new(),
truncated: false,
}
}
fn finish(self) -> ErrorId<N> {
if self.truncated {
ErrorId {
inner: append_ellipsis(self.buf),
}
} else {
ErrorId { inner: self.buf }
}
}
}
impl<const N: usize> Write for Filler<N> {
fn write_str(&mut self, s: &str) -> fmt::Result {
if self.truncated {
return Ok(());
}
let remaining = N - self.buf.len();
if s.len() <= remaining {
self.buf.try_push_str(s).ok();
return Ok(());
}
let end = truncate_at_char_boundary(s, remaining);
self.buf.try_push_str(&s[..end]).ok();
self.truncated = true;
Ok(())
}
}
fn append_ellipsis<const N: usize>(mut buf: ArrayString<N>) -> ArrayString<N> {
if N < ELLIPSIS.len() {
return buf;
}
let target = N - ELLIPSIS.len();
let cut = truncate_at_char_boundary(buf.as_str(), target);
buf.truncate(cut);
buf.try_push_str(ELLIPSIS).ok();
buf
}