use thiserror::Error;
#[derive(Debug, Clone, PartialEq, Eq, Error)]
#[error("string too long: {len} bytes exceeds bound {bound}")]
pub struct BoundedStringTooLong {
pub len: usize,
pub bound: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct BoundedString<const N: usize> {
inner: String,
}
impl<const N: usize> BoundedString<N> {
pub fn new(s: impl Into<String>) -> Result<Self, BoundedStringTooLong> {
let inner = s.into();
if inner.len() > N {
return Err(BoundedStringTooLong {
len: inner.len(),
bound: N,
});
}
Ok(BoundedString { inner })
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.inner
}
#[must_use]
pub fn into_inner(self) -> String {
self.inner
}
#[must_use]
pub fn len(&self) -> usize {
self.inner.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bounded_string_accepts_at_bound_rejects_past_bound() {
const N: usize = 16;
assert!(BoundedString::<N>::new("").is_ok());
let exact = "a".repeat(N);
let ok = BoundedString::<N>::new(exact.clone()).unwrap();
assert_eq!(ok.len(), N);
assert_eq!(ok.as_str(), exact);
let over = "a".repeat(N + 1);
let err = BoundedString::<N>::new(over).unwrap_err();
assert_eq!(err.len, N + 1);
assert_eq!(err.bound, N);
}
#[test]
fn bounded_string_validates_bytes_not_chars() {
const N: usize = 3;
assert!(BoundedString::<N>::new("é").is_ok());
assert!(BoundedString::<N>::new("éé").is_err());
}
}