use std::borrow::Cow;
use std::fmt;
#[derive(Clone, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct PvString(Vec<u8>);
impl PvString {
pub fn new() -> Self {
PvString(Vec::new())
}
pub fn from_bytes(bytes: impl Into<Vec<u8>>) -> Self {
PvString(bytes.into())
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
pub fn into_bytes(self) -> Vec<u8> {
self.0
}
pub fn as_str_lossy(&self) -> Cow<'_, str> {
String::from_utf8_lossy(&self.0)
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl From<String> for PvString {
fn from(s: String) -> Self {
PvString(s.into_bytes())
}
}
impl From<&str> for PvString {
fn from(s: &str) -> Self {
PvString(s.as_bytes().to_vec())
}
}
impl From<&String> for PvString {
fn from(s: &String) -> Self {
PvString(s.as_bytes().to_vec())
}
}
impl From<Cow<'_, str>> for PvString {
fn from(s: Cow<'_, str>) -> Self {
PvString(s.into_owned().into_bytes())
}
}
impl From<Vec<u8>> for PvString {
fn from(b: Vec<u8>) -> Self {
PvString(b)
}
}
impl From<&[u8]> for PvString {
fn from(b: &[u8]) -> Self {
PvString(b.to_vec())
}
}
impl From<PvString> for Vec<u8> {
fn from(s: PvString) -> Self {
s.0
}
}
impl AsRef<[u8]> for PvString {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl PartialEq<str> for PvString {
fn eq(&self, other: &str) -> bool {
self.0 == other.as_bytes()
}
}
impl PartialEq<&str> for PvString {
fn eq(&self, other: &&str) -> bool {
self.0 == other.as_bytes()
}
}
impl PartialEq<String> for PvString {
fn eq(&self, other: &String) -> bool {
self.0 == other.as_bytes()
}
}
impl PartialEq<PvString> for str {
fn eq(&self, other: &PvString) -> bool {
self.as_bytes() == other.0
}
}
impl PartialEq<PvString> for &str {
fn eq(&self, other: &PvString) -> bool {
self.as_bytes() == other.0
}
}
impl PartialEq<PvString> for String {
fn eq(&self, other: &PvString) -> bool {
self.as_bytes() == other.0
}
}
impl fmt::Display for PvString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.as_str_lossy(), f)
}
}
impl fmt::Debug for PvString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.as_str_lossy(), f)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trips_non_utf8_bytes() {
let raw = vec![0xff, 0x00, 0x80, b'a'];
let s = PvString::from_bytes(raw.clone());
assert_eq!(s.as_bytes(), raw.as_slice());
assert_eq!(s.into_bytes(), raw);
}
#[test]
fn from_str_and_string_preserve_bytes() {
assert_eq!(PvString::from("abc").as_bytes(), b"abc");
assert_eq!(PvString::from("abc".to_string()).as_bytes(), b"abc");
let utf8 = "한글";
assert_eq!(PvString::from(utf8).as_bytes(), utf8.as_bytes());
}
#[test]
fn compares_against_str_and_string() {
let s = PvString::from("PV:NAME");
assert_eq!(s, "PV:NAME");
assert_eq!(s, "PV:NAME".to_string());
assert!(s != "OTHER");
assert_eq!("PV:NAME", s);
assert_eq!("PV:NAME".to_string(), s);
}
#[test]
fn lossy_view_substitutes_invalid_bytes() {
let s = PvString::from_bytes(vec![0xff, 0xfe]);
assert!(s.as_str_lossy().contains('\u{FFFD}'));
assert!(format!("{s:?}").starts_with('"'));
}
#[test]
fn default_is_empty() {
assert!(PvString::default().is_empty());
assert_eq!(PvString::default().len(), 0);
}
}