1use alloc::{borrow::ToOwned, boxed::Box, string::String, vec::Vec};
4use core::{
5    convert::Infallible,
6    fmt,
7    str::{self, FromStr},
8};
9use encoding::{Decode, Encode, Error, Reader, Writer};
10
11#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
26pub struct Comment(Box<[u8]>);
27
28impl AsRef<[u8]> for Comment {
29    fn as_ref(&self) -> &[u8] {
30        self.as_bytes()
31    }
32}
33
34impl AsRef<str> for Comment {
35    fn as_ref(&self) -> &str {
36        self.as_str_lossy()
37    }
38}
39
40impl Decode for Comment {
41    type Error = Error;
42
43    fn decode(reader: &mut impl Reader) -> encoding::Result<Self> {
44        Vec::<u8>::decode(reader).map(Into::into)
45    }
46}
47
48impl Encode for Comment {
49    fn encoded_len(&self) -> Result<usize, Error> {
50        self.0.encoded_len()
51    }
52
53    fn encode(&self, writer: &mut impl Writer) -> Result<(), Error> {
54        self.0.encode(writer)
55    }
56}
57
58impl FromStr for Comment {
59    type Err = Infallible;
60
61    fn from_str(s: &str) -> Result<Comment, Infallible> {
62        Ok(s.into())
63    }
64}
65
66impl From<&str> for Comment {
67    fn from(s: &str) -> Comment {
68        s.to_owned().into()
69    }
70}
71
72impl From<String> for Comment {
73    fn from(s: String) -> Self {
74        s.into_bytes().into()
75    }
76}
77
78impl From<&[u8]> for Comment {
79    fn from(bytes: &[u8]) -> Comment {
80        bytes.to_owned().into()
81    }
82}
83
84impl From<Vec<u8>> for Comment {
85    fn from(vec: Vec<u8>) -> Self {
86        Self(vec.into_boxed_slice())
87    }
88}
89
90impl From<Comment> for Vec<u8> {
91    fn from(comment: Comment) -> Vec<u8> {
92        comment.0.into()
93    }
94}
95
96impl TryFrom<Comment> for String {
97    type Error = Error;
98
99    fn try_from(comment: Comment) -> Result<String, Error> {
100        comment.as_str().map(ToOwned::to_owned)
101    }
102}
103
104impl fmt::Display for Comment {
105    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106        f.write_str(self.as_str_lossy())
107    }
108}
109
110impl Comment {
111    pub fn as_bytes(&self) -> &[u8] {
113        &self.0
114    }
115
116    pub fn as_str(&self) -> Result<&str, Error> {
118        Ok(str::from_utf8(&self.0)?)
119    }
120
121    #[cfg(feature = "alloc")]
126    pub fn as_str_lossy(&self) -> &str {
127        for i in (1..=self.len()).rev() {
128            if let Ok(s) = str::from_utf8(&self.0[..i]) {
129                return s;
130            }
131        }
132
133        ""
134    }
135
136    pub fn is_empty(&self) -> bool {
138        self.0.is_empty()
139    }
140
141    pub fn len(&self) -> usize {
143        self.0.len()
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use super::Comment;
150
151    #[test]
152    fn as_str_lossy_ignores_non_utf8_data() {
153        const EXAMPLE: &[u8] = b"hello world\xc3\x28";
154
155        let comment = Comment::from(EXAMPLE);
156        assert_eq!(comment.as_str_lossy(), "hello world");
157    }
158}