1use crate::{Buf, BufsMut, Decode, Encode, EncodeSize, FixedSize, Read, Viewing, Write};
4use bytes::{Buf as _, Bytes};
5use core::hash::Hash;
6#[cfg(feature = "std")]
7use std::sync::OnceLock;
8
9#[derive(Clone)]
67pub struct Lazy<T: Read> {
68 pending: Option<Pending<T>>,
70 #[cfg(feature = "std")]
71 value: OnceLock<Option<T>>,
72 #[cfg(not(feature = "std"))]
73 value: Option<T>,
74}
75
76#[derive(Clone)]
77struct Pending<T: Read> {
78 bytes: Bytes,
79 #[cfg_attr(not(feature = "std"), allow(dead_code))]
80 cfg: T::Cfg,
81}
82
83impl<T: Read> Lazy<T> {
84 #[cfg(feature = "std")]
87 pub fn new(value: T) -> Self {
88 Self {
89 pending: None,
90 value: OnceLock::from(Some(value)),
91 }
92 }
93
94 #[cfg(not(feature = "std"))]
96 pub const fn new(value: T) -> Self {
97 Self {
98 pending: None,
99 value: Some(value),
100 }
101 }
102
103 pub fn deferred(buf: &mut impl Buf, cfg: T::Cfg) -> Self {
110 let bytes = buf.copy_to_bytes(buf.remaining());
111 cfg_if::cfg_if! {
112 if #[cfg(feature = "std")] {
113 Self {
114 pending: Some(Pending { bytes, cfg }),
115 value: Default::default(),
116 }
117 } else {
118 Self {
119 value: T::decode_cfg(Viewing::new(&bytes), &cfg).ok(),
120 pending: Some(Pending { bytes, cfg }),
121 }
122 }
123 }
124 }
125
126 #[cfg(feature = "std")]
133 pub fn get(&self) -> Option<&T> {
134 self.value
135 .get_or_init(|| {
136 let Pending { bytes, cfg } = self
137 .pending
138 .as_ref()
139 .expect("Lazy should have pending if value is not initialized");
140 T::decode_cfg(Viewing::new(bytes), cfg).ok()
141 })
142 .as_ref()
143 }
144
145 #[cfg(not(feature = "std"))]
147 pub const fn get(&self) -> Option<&T> {
148 self.value.as_ref()
149 }
150}
151
152impl<T: Read + Encode> From<T> for Lazy<T> {
153 fn from(value: T) -> Self {
154 Self::new(value)
155 }
156}
157
158impl<T: Read + EncodeSize> EncodeSize for Lazy<T> {
164 fn encode_size(&self) -> usize {
165 if let Some(pending) = &self.pending {
166 return pending.bytes.len();
167 }
168 self.get()
169 .expect("Lazy should have a value if pending is None")
170 .encode_size()
171 }
172
173 fn encode_inline_size(&self) -> usize {
174 if self.pending.is_some() {
175 return 0;
176 }
177 self.get()
178 .expect("Lazy should have a value if pending is None")
179 .encode_inline_size()
180 }
181}
182
183impl<T: Read + Write> Write for Lazy<T> {
184 fn write(&self, buf: &mut impl bytes::BufMut) {
185 if let Some(pending) = &self.pending {
186 buf.put_slice(&pending.bytes);
188 return;
189 }
190 self.get()
191 .expect("Lazy should have a value if pending is None")
192 .write(buf);
193 }
194
195 fn write_bufs(&self, buf: &mut impl BufsMut) {
196 if let Some(pending) = &self.pending {
197 buf.push(pending.bytes.clone());
199 return;
200 }
201 self.get()
202 .expect("Lazy should have a value if pending is None")
203 .write_bufs(buf);
204 }
205}
206
207impl<T: Read + FixedSize> Read for Lazy<T> {
208 type Cfg = T::Cfg;
209
210 fn read_cfg(buf: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, crate::Error> {
211 if buf.remaining() < T::SIZE {
214 return Err(crate::Error::EndOfBuffer);
215 }
216 Ok(Self::deferred(&mut buf.take(T::SIZE), cfg.clone()))
217 }
218}
219
220impl<T: Read + PartialEq> PartialEq for Lazy<T> {
226 fn eq(&self, other: &Self) -> bool {
227 self.get() == other.get()
228 }
229}
230
231impl<T: Read + Eq> Eq for Lazy<T> {}
232
233impl<T: Read + PartialOrd> PartialOrd for Lazy<T> {
234 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
235 self.get().partial_cmp(&other.get())
236 }
237}
238
239impl<T: Read + Ord> Ord for Lazy<T> {
240 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
241 self.get().cmp(&other.get())
242 }
243}
244
245impl<T: Read + Hash> Hash for Lazy<T> {
246 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
247 self.get().hash(state);
248 }
249}
250
251impl<T: Read + core::fmt::Debug> core::fmt::Debug for Lazy<T> {
252 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
253 self.get().fmt(f)
254 }
255}
256
257#[cfg(test)]
258mod test {
259 use super::Lazy;
260 use crate::{
261 Copying, Decode, DecodeExt, Encode, FixedSize, Read, Write, types::tests::TrackingWriteBuf,
262 };
263 use bytes::Bytes;
264 use proptest::prelude::*;
265
266 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
268 struct Small(u8);
269
270 impl FixedSize for Small {
271 const SIZE: usize = 1;
272 }
273
274 impl Write for Small {
275 fn write(&self, buf: &mut impl bytes::BufMut) {
276 self.0.write(buf);
277 }
278 }
279
280 impl Read for Small {
281 type Cfg = ();
282
283 fn read_cfg(buf: &mut impl crate::Buf, _cfg: &Self::Cfg) -> Result<Self, crate::Error> {
284 let byte = u8::read_cfg(buf, &())?;
285 if byte > 100 {
286 return Err(crate::Error::Invalid("Small", "value > 100"));
287 }
288 Ok(Self(byte))
289 }
290 }
291
292 impl Arbitrary for Small {
293 type Parameters = ();
294 type Strategy = BoxedStrategy<Self>;
295
296 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
297 (0..=100u8).prop_map(Small).boxed()
298 }
299 }
300
301 proptest! {
302 #[test]
303 fn test_lazy_new_eq_deferred(x: Small) {
304 let from_new = Lazy::new(x);
305 let from_deferred = Lazy::deferred(&mut x.encode(), ());
306 prop_assert_eq!(from_new, from_deferred);
307 }
308
309 #[test]
310 fn test_lazy_write_eq_direct(x: Small) {
311 let direct = x.encode();
312 let via_lazy = Lazy::new(x).encode();
313 prop_assert_eq!(direct, via_lazy);
314 }
315
316 #[test]
317 fn test_lazy_encode_consistent_across_construction(x: Small) {
318 let direct = x.encode();
319 let via_new = Lazy::new(x).encode();
320 let via_deferred = Lazy::<Small>::deferred(&mut x.encode(), ()).encode();
321 prop_assert_eq!(&direct, &via_new);
322 prop_assert_eq!(&direct, &via_deferred);
323 }
324
325 #[test]
326 fn test_lazy_read_eq_direct(byte: u8) {
327 let direct: Option<Small> = Small::decode(byte.encode()).ok();
328 let via_lazy: Option<Small> =
329 Lazy::<Small>::decode(byte.encode()).ok().and_then(|l| l.get().copied());
330 prop_assert_eq!(direct, via_lazy);
331 }
332
333 #[test]
334 fn test_lazy_cmp_eq_direct(a: Small, b: Small) {
335 let la = Lazy::new(a);
336 let lb = Lazy::new(b);
337 prop_assert_eq!(a == b, la == lb);
338 prop_assert_eq!(a < b, la < lb);
339 prop_assert_eq!(a >= b, la >= lb);
340 }
341 }
342
343 #[test]
344 fn test_lazy_view() {
345 let value: Vec<Lazy<Small>> = (0..64u8).map(|i| Lazy::new(Small(i))).collect();
346 let source = value.encode();
347 let cfg = ((..).into(), ());
348 let range = source.as_ptr_range();
349
350 let decoded = Vec::<Lazy<Small>>::decode_cfg(source.clone(), &cfg).unwrap();
352 assert_eq!(decoded, value);
353 let mut buf = TrackingWriteBuf::new();
354 decoded.write_bufs(&mut buf);
355 assert_eq!(buf.pushed.len(), value.len());
356 assert!(buf.pushed.iter().all(|b| range.contains(&b.as_ptr())));
357
358 let copied = Vec::<Lazy<Small>>::decode_cfg(Copying(&source), &cfg).unwrap();
360 assert_eq!(copied, value);
361 let mut buf = TrackingWriteBuf::new();
362 copied.write_bufs(&mut buf);
363 assert_eq!(buf.pushed.len(), value.len());
364 assert!(buf.pushed.iter().all(|b| !range.contains(&b.as_ptr())));
365 }
366
367 #[test]
368 fn test_lazy_get_shares_retained_bytes() {
369 let encoded = Bytes::from_static(b"hello").encode();
370 let range = encoded.as_ptr_range();
371 let lazy = Lazy::<Bytes>::deferred(&mut encoded.clone(), (..).into());
372 let value = lazy.get().unwrap();
373 assert_eq!(value, &b"hello"[..]);
374 assert!(range.contains(&value.as_ptr()));
375
376 let fields = (
378 Bytes::from_static(b"hello"),
379 7u32,
380 Bytes::from_static(b"world"),
381 );
382 let fields = fields.encode();
383 let cfg = ((..).into(), (), (..).into());
384 let lazy = Lazy::<(Bytes, u32, Bytes)>::deferred(&mut fields.clone(), cfg);
385 let (first, scalar, second) = lazy.get().unwrap();
386 assert_eq!(first, &b"hello"[..]);
387 assert_eq!(*scalar, 7);
388 assert_eq!(second, &b"world"[..]);
389 assert_eq!(first.as_ptr(), fields[1..].as_ptr());
390 assert_eq!(second.as_ptr(), fields[11..].as_ptr());
391
392 let mut extra = encoded.to_vec();
394 extra.push(0);
395 let lazy = Lazy::<Bytes>::deferred(&mut Bytes::from(extra), (..).into());
396 assert!(lazy.get().is_none());
397 }
398}