commonware_codec/types/
lazy.rs1use crate::{BufsMut, Decode, Encode, EncodeSize, FixedSize, Read, Write};
4use bytes::{Buf, 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(bytes.as_ref(), &cfg).ok(),
120 pending: Some(Pending { bytes, cfg }),
121 }
122 }
123 }
124 }
125}
126
127impl<T: Read> Lazy<T> {
128 #[cfg(feature = "std")]
135 pub fn get(&self) -> Option<&T> {
136 self.value
137 .get_or_init(|| {
138 let Pending { bytes, cfg } = self
139 .pending
140 .as_ref()
141 .expect("Lazy should have pending if value is not initialized");
142 T::decode_cfg(bytes.as_ref(), cfg).ok()
143 })
144 .as_ref()
145 }
146
147 #[cfg(not(feature = "std"))]
149 pub const fn get(&self) -> Option<&T> {
150 self.value.as_ref()
151 }
152}
153
154impl<T: Read + Encode> From<T> for Lazy<T> {
155 fn from(value: T) -> Self {
156 Self::new(value)
157 }
158}
159
160impl<T: Read + EncodeSize> EncodeSize for Lazy<T> {
166 fn encode_size(&self) -> usize {
167 if let Some(pending) = &self.pending {
168 return pending.bytes.len();
169 }
170 self.get()
171 .expect("Lazy should have a value if pending is None")
172 .encode_size()
173 }
174
175 fn encode_inline_size(&self) -> usize {
176 if self.pending.is_some() {
177 return 0;
178 }
179 self.get()
180 .expect("Lazy should have a value if pending is None")
181 .encode_inline_size()
182 }
183}
184
185impl<T: Read + Write> Write for Lazy<T> {
186 fn write(&self, buf: &mut impl bytes::BufMut) {
187 if let Some(pending) = &self.pending {
188 buf.put_slice(&pending.bytes);
190 return;
191 }
192 self.get()
193 .expect("Lazy should have a value if pending is None")
194 .write(buf);
195 }
196
197 fn write_bufs(&self, buf: &mut impl BufsMut) {
198 if let Some(pending) = &self.pending {
199 buf.push(pending.bytes.clone());
201 return;
202 }
203 self.get()
204 .expect("Lazy should have a value if pending is None")
205 .write_bufs(buf);
206 }
207}
208
209impl<T: Read + FixedSize> Read for Lazy<T> {
210 type Cfg = T::Cfg;
211
212 fn read_cfg(buf: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, crate::Error> {
213 if buf.remaining() < T::SIZE {
216 return Err(crate::Error::EndOfBuffer);
217 }
218 Ok(Self::deferred(&mut buf.take(T::SIZE), cfg.clone()))
219 }
220}
221
222impl<T: Read + PartialEq> PartialEq for Lazy<T> {
228 fn eq(&self, other: &Self) -> bool {
229 self.get() == other.get()
230 }
231}
232
233impl<T: Read + Eq> Eq for Lazy<T> {}
234
235impl<T: Read + PartialOrd> PartialOrd for Lazy<T> {
236 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
237 self.get().partial_cmp(&other.get())
238 }
239}
240
241impl<T: Read + Ord> Ord for Lazy<T> {
242 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
243 self.get().cmp(&other.get())
244 }
245}
246
247impl<T: Read + Hash> Hash for Lazy<T> {
248 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
249 self.get().hash(state);
250 }
251}
252
253impl<T: Read + core::fmt::Debug> core::fmt::Debug for Lazy<T> {
254 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
255 self.get().fmt(f)
256 }
257}
258
259#[cfg(test)]
260mod test {
261 use super::Lazy;
262 use crate::{DecodeExt, Encode, FixedSize, Read, Write};
263 use proptest::prelude::*;
264
265 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
267 struct Small(u8);
268
269 impl FixedSize for Small {
270 const SIZE: usize = 1;
271 }
272
273 impl Write for Small {
274 fn write(&self, buf: &mut impl bytes::BufMut) {
275 self.0.write(buf);
276 }
277 }
278
279 impl Read for Small {
280 type Cfg = ();
281
282 fn read_cfg(buf: &mut impl bytes::Buf, _cfg: &Self::Cfg) -> Result<Self, crate::Error> {
283 let byte = u8::read_cfg(buf, &())?;
284 if byte > 100 {
285 return Err(crate::Error::Invalid("Small", "value > 100"));
286 }
287 Ok(Self(byte))
288 }
289 }
290
291 impl Arbitrary for Small {
292 type Parameters = ();
293 type Strategy = BoxedStrategy<Self>;
294
295 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
296 (0..=100u8).prop_map(Small).boxed()
297 }
298 }
299
300 proptest! {
301 #[test]
302 fn test_lazy_new_eq_deferred(x: Small) {
303 let from_new = Lazy::new(x);
304 let from_deferred = Lazy::deferred(&mut x.encode(), ());
305 prop_assert_eq!(from_new, from_deferred);
306 }
307
308 #[test]
309 fn test_lazy_write_eq_direct(x: Small) {
310 let direct = x.encode();
311 let via_lazy = Lazy::new(x).encode();
312 prop_assert_eq!(direct, via_lazy);
313 }
314
315 #[test]
316 fn test_lazy_encode_consistent_across_construction(x: Small) {
317 let direct = x.encode();
318 let via_new = Lazy::new(x).encode();
319 let via_deferred = Lazy::<Small>::deferred(&mut x.encode(), ()).encode();
320 prop_assert_eq!(&direct, &via_new);
321 prop_assert_eq!(&direct, &via_deferred);
322 }
323
324 #[test]
325 fn test_lazy_read_eq_direct(byte: u8) {
326 let direct: Option<Small> = Small::decode(byte.encode()).ok();
327 let via_lazy: Option<Small> =
328 Lazy::<Small>::decode(byte.encode()).ok().and_then(|l| l.get().copied());
329 prop_assert_eq!(direct, via_lazy);
330 }
331
332 #[test]
333 fn test_lazy_cmp_eq_direct(a: Small, b: Small) {
334 let la = Lazy::new(a);
335 let lb = Lazy::new(b);
336 prop_assert_eq!(a == b, la == lb);
337 prop_assert_eq!(a < b, la < lb);
338 prop_assert_eq!(a >= b, la >= lb);
339 }
340 }
341}