commonware_codec/types/
lazy.rs1use crate::{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 pub fn new(value: T) -> Self {
87 Self {
88 pending: None,
89 value: Some(value).into(),
90 }
91 }
92
93 pub fn deferred(buf: &mut impl Buf, cfg: T::Cfg) -> Self {
100 let bytes = buf.copy_to_bytes(buf.remaining());
101 cfg_if::cfg_if! {
102 if #[cfg(feature = "std")] {
103 Self {
104 pending: Some(Pending { bytes, cfg }),
105 value: Default::default(),
106 }
107 } else {
108 Self {
109 value: T::decode_cfg(bytes.clone(), &cfg).ok(),
110 pending: Some(Pending { bytes, cfg }),
111 }
112 }
113 }
114 }
115}
116
117impl<T: Read> Lazy<T> {
118 pub fn get(&self) -> Option<&T> {
125 cfg_if::cfg_if! {
126 if #[cfg(feature = "std")] {
127 self.value
128 .get_or_init(|| {
129 let Pending { bytes, cfg } = self
130 .pending
131 .as_ref()
132 .expect("Lazy should have pending if value is not initialized");
133 T::decode_cfg(bytes.clone(), cfg).ok()
134 })
135 .as_ref()
136 } else {
137 self.value.as_ref()
138 }
139 }
140 }
141}
142
143impl<T: Read + Encode> From<T> for Lazy<T> {
144 fn from(value: T) -> Self {
145 Self::new(value)
146 }
147}
148
149impl<T: Read + EncodeSize> EncodeSize for Lazy<T> {
155 fn encode_size(&self) -> usize {
156 if let Some(pending) = &self.pending {
157 return pending.bytes.len();
158 }
159 self.get()
160 .expect("Lazy should have a value if pending is None")
161 .encode_size()
162 }
163}
164
165impl<T: Read + Write> Write for Lazy<T> {
166 fn write(&self, buf: &mut impl bytes::BufMut) {
167 if let Some(pending) = &self.pending {
168 buf.put_slice(&pending.bytes);
170 return;
171 }
172 self.get()
173 .expect("Lazy should have a value if pending is None")
174 .write(buf);
175 }
176}
177
178impl<T: Read + FixedSize> Read for Lazy<T> {
179 type Cfg = T::Cfg;
180
181 fn read_cfg(buf: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, crate::Error> {
182 if buf.remaining() < T::SIZE {
185 return Err(crate::Error::EndOfBuffer);
186 }
187 Ok(Self::deferred(&mut buf.take(T::SIZE), cfg.clone()))
188 }
189}
190
191impl<T: Read + PartialEq> PartialEq for Lazy<T> {
197 fn eq(&self, other: &Self) -> bool {
198 self.get() == other.get()
199 }
200}
201
202impl<T: Read + Eq> Eq for Lazy<T> {}
203
204impl<T: Read + PartialOrd> PartialOrd for Lazy<T> {
205 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
206 self.get().partial_cmp(&other.get())
207 }
208}
209
210impl<T: Read + Ord> Ord for Lazy<T> {
211 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
212 self.get().cmp(&other.get())
213 }
214}
215
216impl<T: Read + Hash> Hash for Lazy<T> {
217 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
218 self.get().hash(state);
219 }
220}
221
222impl<T: Read + core::fmt::Debug> core::fmt::Debug for Lazy<T> {
223 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
224 self.get().fmt(f)
225 }
226}
227
228#[cfg(test)]
229mod test {
230 use super::Lazy;
231 use crate::{DecodeExt, Encode, FixedSize, Read, Write};
232 use proptest::prelude::*;
233
234 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
236 struct Small(u8);
237
238 impl FixedSize for Small {
239 const SIZE: usize = 1;
240 }
241
242 impl Write for Small {
243 fn write(&self, buf: &mut impl bytes::BufMut) {
244 self.0.write(buf);
245 }
246 }
247
248 impl Read for Small {
249 type Cfg = ();
250
251 fn read_cfg(buf: &mut impl bytes::Buf, _cfg: &Self::Cfg) -> Result<Self, crate::Error> {
252 let byte = u8::read_cfg(buf, &())?;
253 if byte > 100 {
254 return Err(crate::Error::Invalid("Small", "value > 100"));
255 }
256 Ok(Self(byte))
257 }
258 }
259
260 impl Arbitrary for Small {
261 type Parameters = ();
262 type Strategy = BoxedStrategy<Self>;
263
264 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
265 (0..=100u8).prop_map(Small).boxed()
266 }
267 }
268
269 proptest! {
270 #[test]
271 fn test_lazy_new_eq_deferred(x: Small) {
272 let from_new = Lazy::new(x);
273 let from_deferred = Lazy::deferred(&mut x.encode(), ());
274 prop_assert_eq!(from_new, from_deferred);
275 }
276
277 #[test]
278 fn test_lazy_write_eq_direct(x: Small) {
279 let direct = x.encode();
280 let via_lazy = Lazy::new(x).encode();
281 prop_assert_eq!(direct, via_lazy);
282 }
283
284 #[test]
285 fn test_lazy_encode_consistent_across_construction(x: Small) {
286 let direct = x.encode();
287 let via_new = Lazy::new(x).encode();
288 let via_deferred = Lazy::<Small>::deferred(&mut x.encode(), ()).encode();
289 prop_assert_eq!(&direct, &via_new);
290 prop_assert_eq!(&direct, &via_deferred);
291 }
292
293 #[test]
294 fn test_lazy_read_eq_direct(byte: u8) {
295 let direct: Option<Small> = Small::decode(byte.encode()).ok();
296 let via_lazy: Option<Small> =
297 Lazy::<Small>::decode(byte.encode()).ok().and_then(|l| l.get().copied());
298 prop_assert_eq!(direct, via_lazy);
299 }
300
301 #[test]
302 fn test_lazy_cmp_eq_direct(a: Small, b: Small) {
303 let la = Lazy::new(a);
304 let lb = Lazy::new(b);
305 prop_assert_eq!(a == b, la == lb);
306 prop_assert_eq!(a < b, la < lb);
307 prop_assert_eq!(a >= b, la >= lb);
308 }
309 }
310}