#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct NonEmptyString(String);
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for NonEmptyString {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let string = String::deserialize(deserializer)?;
NonEmptyString::new(string).ok_or_else(|| {
serde::de::Error::custom("cannot deserialize empty string as NonEmptyString")
})
}
}
impl NonEmptyString {
#[must_use]
pub fn new(string: String) -> Option<Self> {
if string.is_empty() {
None
} else {
Some(Self(string))
}
}
#[must_use]
pub fn as_string(&self) -> &String {
&self.0
}
#[must_use]
pub fn into_string(self) -> String {
self.0
}
#[must_use]
pub fn as_str(&self) -> &str {
self.0.as_str()
}
pub fn push(&mut self, ch: char) {
self.0.push(ch);
}
pub fn push_str(&mut self, string: &str) {
self.0.push_str(string);
}
pub fn insert(&mut self, idx: usize, ch: char) {
self.0.insert(idx, ch);
}
pub fn insert_str(&mut self, idx: usize, string: &str) {
self.0.insert_str(idx, string);
}
pub fn remove(&mut self, idx: usize) -> Option<char> {
if self.0.len() == 1 {
None
} else {
Some(self.0.remove(idx))
}
}
pub fn pop(&mut self) -> Option<char> {
if self.0.len() == 1 {
None
} else {
self.0.pop()
}
}
#[expect(clippy::len_without_is_empty, reason = "NonEmptyString is never empty")] #[must_use]
pub fn len(&self) -> usize {
self.0.len()
}
pub fn clear_except_first(&mut self) {
let first = self.0.remove(0);
self.0.clear();
self.0.push(first);
}
pub fn chars(&self) -> std::str::Chars<'_> {
self.0.chars()
}
pub fn char_indices(&self) -> std::str::CharIndices<'_> {
self.0.char_indices()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_with_empty_string() {
let empty = String::new();
assert!(NonEmptyString::new(empty).is_none());
}
#[test]
fn new_with_non_empty_string() {
let non_empty = String::from("hello");
let non_empty_string = NonEmptyString::new(non_empty.clone()).unwrap();
assert_eq!(non_empty_string.as_string(), &non_empty);
}
#[test]
fn push_and_pop() {
let mut string = NonEmptyString::new(String::from("a")).unwrap();
string.push('b');
string.push('c');
assert_eq!(string.len(), 3);
assert_eq!(string.pop(), Some('c'));
assert_eq!(string.pop(), Some('b'));
assert_eq!(string.pop(), None);
}
#[test]
fn remove() {
let mut string = NonEmptyString::new(String::from("abc")).unwrap();
assert_eq!(string.remove(1), Some('b'));
assert_eq!(string.as_str(), "ac");
assert_eq!(string.remove(0), Some('a'));
assert_eq!(string.remove(0), None);
}
#[test]
fn clear_except_first() {
let mut string = NonEmptyString::new(String::from("abcd")).unwrap();
string.clear_except_first();
assert_eq!(string.as_str(), "a");
}
#[test]
fn push_str() {
let mut string = NonEmptyString::new(String::from("hello")).unwrap();
string.push_str(" world");
assert_eq!(string.as_str(), "hello world");
}
}