use std::borrow::Cow;
use serde::{Deserialize, Serialize};
use crate::utils::contains_null;
#[derive(Debug, Display, Error)]
pub enum TempestStrError {
InputContainsNullByte,
}
#[derive(
Debug, Display, Clone, Deref, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
)]
pub struct TempestStr<'a>(Cow<'a, str>);
impl<'a> TempestStr<'a> {
pub fn from_borrowed(s: &'a str) -> Result<TempestStr<'a>, TempestStrError> {
if contains_null(s) {
return Err(TempestStrError::InputContainsNullByte);
}
Ok(TempestStr(s.into()))
}
pub const unsafe fn from_borrowed_unchecked(s: &'a str) -> TempestStr<'a> {
Self(Cow::Borrowed(s))
}
pub fn from_owned(s: String) -> Result<TempestStr<'static>, TempestStrError> {
if contains_null(&s) {
return Err(TempestStrError::InputContainsNullByte);
}
Ok(TempestStr(s.into()))
}
pub fn into_owned(self) -> TempestStr<'static> {
TempestStr(self.0.into_owned().into())
}
}
impl TryFrom<String> for TempestStr<'static> {
type Error = TempestStrError;
fn try_from(s: String) -> Result<Self, Self::Error> {
TempestStr::from_owned(s)
}
}
impl From<&'static str> for TempestStr<'static> {
fn from(s: &'static str) -> Self {
TempestStr::from_borrowed(s).expect("static str must not contain null bytes")
}
}
impl From<u32> for TempestStr<'static> {
fn from(value: u32) -> Self {
TempestStr::from_owned(value.to_string())
.expect("stringified number does not contain null bytes")
}
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_valid(s: &str) {
let ts = match TempestStr::from_borrowed(s) {
Ok(ts) => ts,
Err(e) => panic!("{} should be valid, but got error: {}", s, e),
};
assert_eq!(s, *ts);
}
fn assert_invalid(s: &str) {
assert!(
TempestStr::from_borrowed(s).is_err(),
"{} should not be valid",
s
);
assert!(
TempestStr::from_owned(s.to_owned()).is_err(),
"{} should not be valid, when converting into owned",
s
);
}
#[test]
fn test_tempest_str_valid() {
[
"sup",
"world",
"", "tempest",
"juice",
"eventual consistency",
]
.into_iter()
.for_each(assert_valid);
}
#[test]
fn test_tempest_str_invalid() {
[
"\x00",
"\x00hello",
"hel\x00lo",
"hello\x00",
"\x00\x00\x00",
]
.into_iter()
.for_each(assert_invalid);
}
#[test]
fn test_into_owned_produces_static_lifetime() {
let s = String::from("hello");
let ts: TempestStr<'static> = TempestStr::from_owned(s).unwrap().into_owned();
assert_eq!(*ts, "hello");
}
}