use bytes::Bytes;
use std::fmt;
use std::ops::Deref;
use std::str::Utf8Error;
#[derive(Clone, Default, PartialEq, Eq)]
pub struct ByteStr(Bytes);
impl ByteStr {
#[inline]
pub fn from_utf8(bytes: Bytes) -> Result<Self, Utf8Error> {
std::str::from_utf8(&bytes)?;
Ok(Self(bytes))
}
#[inline]
pub fn from_static(s: &'static str) -> Self {
Self(Bytes::from_static(s.as_bytes()))
}
#[inline]
pub fn as_str(&self) -> &str {
std::str::from_utf8(&self.0).unwrap_or("")
}
#[inline]
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
#[inline]
pub fn into_bytes(self) -> Bytes {
self.0
}
#[inline]
pub fn len(&self) -> usize {
self.0.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
#[inline]
pub fn into_owned(&self) -> String {
self.as_str().to_owned()
}
}
impl From<&str> for ByteStr {
#[inline]
fn from(s: &str) -> Self {
Self(Bytes::copy_from_slice(s.as_bytes()))
}
}
impl From<String> for ByteStr {
#[inline]
fn from(s: String) -> Self {
Self(Bytes::from(s.into_bytes()))
}
}
impl From<Bytes> for ByteStr {
#[inline]
fn from(bytes: Bytes) -> Self {
Self::from_utf8(bytes).unwrap_or_default()
}
}
impl Deref for ByteStr {
type Target = str;
#[inline]
fn deref(&self) -> &str {
self.as_str()
}
}
impl fmt::Debug for ByteStr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(self.as_str(), f)
}
}
impl fmt::Display for ByteStr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self.as_str(), f)
}
}
impl PartialEq<str> for ByteStr {
#[inline]
fn eq(&self, other: &str) -> bool {
self.as_bytes() == other.as_bytes()
}
}
impl PartialEq<&str> for ByteStr {
#[inline]
fn eq(&self, other: &&str) -> bool {
self.as_bytes() == other.as_bytes()
}
}
impl PartialEq<String> for ByteStr {
#[inline]
fn eq(&self, other: &String) -> bool {
self.as_bytes() == other.as_bytes()
}
}
impl std::hash::Hash for ByteStr {
#[inline]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.as_str().hash(state);
}
}
impl PartialOrd for ByteStr {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for ByteStr {
#[inline]
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.as_str().cmp(other.as_str())
}
}
impl AsRef<str> for ByteStr {
#[inline]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl std::borrow::Borrow<str> for ByteStr {
#[inline]
fn borrow(&self) -> &str {
self.as_str()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_utf8_accepts_valid() {
let s = ByteStr::from_utf8(Bytes::from_static(b"/index.html")).unwrap();
assert_eq!(s.as_str(), "/index.html");
assert_eq!(&s, "/index.html");
assert_eq!(s.len(), 11);
}
#[test]
fn from_utf8_rejects_invalid() {
assert!(ByteStr::from_utf8(Bytes::from_static(&[0xff, 0xfe])).is_err());
}
#[test]
fn from_static_is_cheap_and_correct() {
let s = ByteStr::from_static("GET");
assert_eq!(s.as_str(), "GET");
}
#[test]
fn shares_the_parent_allocation() {
let parent = Bytes::from_static(b"GET /a/b HTTP/1.1");
let target = ByteStr::from_utf8(parent.slice(4..8)).unwrap();
assert_eq!(target.as_str(), "/a/b");
assert_eq!(parent.len(), 17, "parent must be untouched");
}
#[test]
fn deref_gives_str_methods() {
let s = ByteStr::from_static("/a/b?x=1");
assert!(s.starts_with("/a"));
assert_eq!(s.split('?').next(), Some("/a/b"));
}
#[test]
fn hashes_like_the_str_it_borrows_as() {
use std::collections::HashMap;
let mut map: HashMap<ByteStr, u32> = HashMap::new();
map.insert(ByteStr::from_static("content-type"), 7);
assert_eq!(map.get("content-type"), Some(&7));
}
#[test]
fn orders_and_compares_by_content() {
let mut v = [ByteStr::from_static("b"), ByteStr::from_static("a")];
v.sort();
assert_eq!(v[0], "a");
assert_eq!(ByteStr::from_static("x"), "x".to_string());
}
#[test]
fn empty_is_valid() {
let s = ByteStr::from_utf8(Bytes::new()).unwrap();
assert!(s.is_empty());
}
#[test]
fn from_string_takes_the_allocation_and_from_str_copies() {
let owned = String::from("/a/b");
let s = ByteStr::from(owned);
assert_eq!(s.as_str(), "/a/b");
let borrowed = ByteStr::from("/c");
assert_eq!(borrowed.as_str(), "/c");
assert_eq!(borrowed.into_owned(), "/c".to_string());
}
#[test]
fn from_non_utf8_bytes_is_empty_rather_than_panicking() {
let s = ByteStr::from(Bytes::from_static(&[0xff, 0xfe]));
assert!(s.is_empty());
assert!(ByteStr::from_utf8(Bytes::from_static(&[0xff, 0xfe])).is_err());
}
}