1use bytes::{Bytes, BytesMut};
12
13#[derive(Default, Clone)]
18pub struct Payload {
19 chunks: Vec<Bytes>,
20 len: usize,
21}
22
23impl Payload {
24 pub fn new() -> Self {
26 Self::default()
27 }
28
29 pub fn from_bytes(b: Bytes) -> Self {
31 let mut p = Self::new();
32 p.push(b);
33 p
34 }
35
36 pub fn from_vec(v: Vec<u8>) -> Self {
38 Self::from_bytes(Bytes::from(v))
39 }
40
41 pub fn len(&self) -> usize {
43 self.len
44 }
45
46 pub fn is_empty(&self) -> bool {
48 self.len == 0
49 }
50
51 pub fn chunk_count(&self) -> usize {
53 self.chunks.len()
54 }
55
56 pub fn chunks(&self) -> &[Bytes] {
58 &self.chunks
59 }
60
61 pub fn iter(&self) -> std::slice::Iter<'_, Bytes> {
63 self.chunks.iter()
64 }
65
66 pub fn push(&mut self, b: Bytes) {
68 if b.is_empty() {
69 return;
70 }
71 self.len += b.len();
72 self.chunks.push(b);
73 }
74
75 pub fn starts_with(&self, prefix: &[u8]) -> bool {
78 if prefix.len() > self.len {
79 return false;
80 }
81 let mut remaining = prefix;
82 for chunk in &self.chunks {
83 if remaining.is_empty() {
84 return true;
85 }
86 let take = remaining.len().min(chunk.len());
87 if chunk[..take] != remaining[..take] {
88 return false;
89 }
90 remaining = &remaining[take..];
91 }
92 remaining.is_empty()
93 }
94
95 pub fn into_bytes(mut self) -> Bytes {
101 match self.chunks.len() {
102 0 => Bytes::new(),
103 1 => self.chunks.pop().unwrap(),
104 _ => {
105 let mut out = BytesMut::with_capacity(self.len);
106 for chunk in self.chunks {
107 out.extend_from_slice(&chunk);
108 }
109 out.freeze()
110 }
111 }
112 }
113
114 pub fn into_vec(self) -> Vec<u8> {
116 let mut out = Vec::with_capacity(self.len);
117 for chunk in &self.chunks {
118 out.extend_from_slice(chunk);
119 }
120 out
121 }
122}
123
124impl From<Bytes> for Payload {
125 fn from(b: Bytes) -> Self {
126 Self::from_bytes(b)
127 }
128}
129
130impl From<Vec<u8>> for Payload {
131 fn from(v: Vec<u8>) -> Self {
132 Self::from_vec(v)
133 }
134}
135
136impl From<&'static [u8]> for Payload {
137 fn from(s: &'static [u8]) -> Self {
138 Self::from_bytes(Bytes::from_static(s))
139 }
140}
141
142impl<const N: usize> From<&'static [u8; N]> for Payload {
143 fn from(s: &'static [u8; N]) -> Self {
144 Self::from_bytes(Bytes::from_static(s))
145 }
146}
147
148impl std::fmt::Debug for Payload {
149 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150 f.debug_struct("Payload")
151 .field("len", &self.len)
152 .field("chunks", &self.chunks.len())
153 .finish()
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160 use assert2::check;
161
162 #[test]
163 fn empty_payload_round_trips() {
164 let p = Payload::new();
165 check!(p.is_empty());
166 check!(p.len() == 0);
167 check!(p.chunk_count() == 0);
168 check!(p.into_vec().is_empty());
169 }
170
171 #[test]
172 fn from_vec_is_single_chunk() {
173 let p = Payload::from_vec(b"hello".to_vec());
174 check!(p.len() == 5);
175 check!(p.chunk_count() == 1);
176 }
177
178 #[test]
179 fn push_drops_empty_chunks() {
180 let mut p = Payload::new();
181 p.push(Bytes::new());
182 p.push(Bytes::from_static(b"abc"));
183 p.push(Bytes::new());
184 check!(p.chunk_count() == 1);
185 check!(p.len() == 3);
186 }
187
188 #[test]
189 fn into_bytes_single_chunk_is_zero_copy() {
190 let original = Bytes::from(vec![1u8, 2, 3, 4, 5]);
191 let original_ptr = original.as_ptr();
192 let p = Payload::from_bytes(original);
193 let out = p.into_bytes();
194 check!(out.as_ptr() == original_ptr);
195 }
196
197 #[test]
198 fn into_bytes_multi_chunk_concatenates() {
199 let mut p = Payload::new();
200 p.push(Bytes::from_static(b"ab"));
201 p.push(Bytes::from_static(b"cde"));
202 p.push(Bytes::from_static(b"f"));
203 check!(p.chunk_count() == 3);
204 let out = p.into_bytes();
205 check!(&out[..] == b"abcdef");
206 }
207
208 #[test]
209 fn starts_with_walks_chunks() {
210 let mut p = Payload::new();
211 p.push(Bytes::from_static(&[0x1f]));
212 p.push(Bytes::from_static(&[0x8b, 0x08]));
213 check!(p.starts_with(&[0x1f, 0x8b]));
214 check!(p.starts_with(&[0x1f, 0x8b, 0x08]));
215 check!(!p.starts_with(&[0x1f, 0x8b, 0x08, 0x00]));
216 check!(!p.starts_with(&[0x1f, 0x00]));
217 }
218
219 #[test]
220 fn starts_with_empty_prefix_is_true() {
221 let p = Payload::from_vec(b"x".to_vec());
222 check!(p.starts_with(&[]));
223 let empty = Payload::new();
224 check!(empty.starts_with(&[]));
225 check!(!empty.starts_with(&[0]));
226 }
227}