use crate::{BufsMut, Decode, Encode, EncodeSize, FixedSize, Read, Write};
use bytes::{Buf, Bytes};
use core::hash::Hash;
#[cfg(feature = "std")]
use std::sync::OnceLock;
#[derive(Clone)]
pub struct Lazy<T: Read> {
pending: Option<Pending<T>>,
#[cfg(feature = "std")]
value: OnceLock<Option<T>>,
#[cfg(not(feature = "std"))]
value: Option<T>,
}
#[derive(Clone)]
struct Pending<T: Read> {
bytes: Bytes,
#[cfg_attr(not(feature = "std"), allow(dead_code))]
cfg: T::Cfg,
}
impl<T: Read> Lazy<T> {
pub fn new(value: T) -> Self {
Self {
pending: None,
value: Some(value).into(),
}
}
pub fn deferred(buf: &mut impl Buf, cfg: T::Cfg) -> Self {
let bytes = buf.copy_to_bytes(buf.remaining());
cfg_if::cfg_if! {
if #[cfg(feature = "std")] {
Self {
pending: Some(Pending { bytes, cfg }),
value: Default::default(),
}
} else {
Self {
value: T::decode_cfg(bytes.clone(), &cfg).ok(),
pending: Some(Pending { bytes, cfg }),
}
}
}
}
}
impl<T: Read> Lazy<T> {
pub fn get(&self) -> Option<&T> {
cfg_if::cfg_if! {
if #[cfg(feature = "std")] {
self.value
.get_or_init(|| {
let Pending { bytes, cfg } = self
.pending
.as_ref()
.expect("Lazy should have pending if value is not initialized");
T::decode_cfg(bytes.clone(), cfg).ok()
})
.as_ref()
} else {
self.value.as_ref()
}
}
}
}
impl<T: Read + Encode> From<T> for Lazy<T> {
fn from(value: T) -> Self {
Self::new(value)
}
}
impl<T: Read + EncodeSize> EncodeSize for Lazy<T> {
fn encode_size(&self) -> usize {
if let Some(pending) = &self.pending {
return pending.bytes.len();
}
self.get()
.expect("Lazy should have a value if pending is None")
.encode_size()
}
fn encode_inline_size(&self) -> usize {
if self.pending.is_some() {
return 0;
}
self.get()
.expect("Lazy should have a value if pending is None")
.encode_inline_size()
}
}
impl<T: Read + Write> Write for Lazy<T> {
fn write(&self, buf: &mut impl bytes::BufMut) {
if let Some(pending) = &self.pending {
buf.put_slice(&pending.bytes);
return;
}
self.get()
.expect("Lazy should have a value if pending is None")
.write(buf);
}
fn write_bufs(&self, buf: &mut impl BufsMut) {
if let Some(pending) = &self.pending {
buf.push(pending.bytes.clone());
return;
}
self.get()
.expect("Lazy should have a value if pending is None")
.write_bufs(buf);
}
}
impl<T: Read + FixedSize> Read for Lazy<T> {
type Cfg = T::Cfg;
fn read_cfg(buf: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, crate::Error> {
if buf.remaining() < T::SIZE {
return Err(crate::Error::EndOfBuffer);
}
Ok(Self::deferred(&mut buf.take(T::SIZE), cfg.clone()))
}
}
impl<T: Read + PartialEq> PartialEq for Lazy<T> {
fn eq(&self, other: &Self) -> bool {
self.get() == other.get()
}
}
impl<T: Read + Eq> Eq for Lazy<T> {}
impl<T: Read + PartialOrd> PartialOrd for Lazy<T> {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
self.get().partial_cmp(&other.get())
}
}
impl<T: Read + Ord> Ord for Lazy<T> {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.get().cmp(&other.get())
}
}
impl<T: Read + Hash> Hash for Lazy<T> {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.get().hash(state);
}
}
impl<T: Read + core::fmt::Debug> core::fmt::Debug for Lazy<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.get().fmt(f)
}
}
#[cfg(test)]
mod test {
use super::Lazy;
use crate::{DecodeExt, Encode, FixedSize, Read, Write};
use proptest::prelude::*;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
struct Small(u8);
impl FixedSize for Small {
const SIZE: usize = 1;
}
impl Write for Small {
fn write(&self, buf: &mut impl bytes::BufMut) {
self.0.write(buf);
}
}
impl Read for Small {
type Cfg = ();
fn read_cfg(buf: &mut impl bytes::Buf, _cfg: &Self::Cfg) -> Result<Self, crate::Error> {
let byte = u8::read_cfg(buf, &())?;
if byte > 100 {
return Err(crate::Error::Invalid("Small", "value > 100"));
}
Ok(Self(byte))
}
}
impl Arbitrary for Small {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
(0..=100u8).prop_map(Small).boxed()
}
}
proptest! {
#[test]
fn test_lazy_new_eq_deferred(x: Small) {
let from_new = Lazy::new(x);
let from_deferred = Lazy::deferred(&mut x.encode(), ());
prop_assert_eq!(from_new, from_deferred);
}
#[test]
fn test_lazy_write_eq_direct(x: Small) {
let direct = x.encode();
let via_lazy = Lazy::new(x).encode();
prop_assert_eq!(direct, via_lazy);
}
#[test]
fn test_lazy_encode_consistent_across_construction(x: Small) {
let direct = x.encode();
let via_new = Lazy::new(x).encode();
let via_deferred = Lazy::<Small>::deferred(&mut x.encode(), ()).encode();
prop_assert_eq!(&direct, &via_new);
prop_assert_eq!(&direct, &via_deferred);
}
#[test]
fn test_lazy_read_eq_direct(byte: u8) {
let direct: Option<Small> = Small::decode(byte.encode()).ok();
let via_lazy: Option<Small> =
Lazy::<Small>::decode(byte.encode()).ok().and_then(|l| l.get().copied());
prop_assert_eq!(direct, via_lazy);
}
#[test]
fn test_lazy_cmp_eq_direct(a: Small, b: Small) {
let la = Lazy::new(a);
let lb = Lazy::new(b);
prop_assert_eq!(a == b, la == lb);
prop_assert_eq!(a < b, la < lb);
prop_assert_eq!(a >= b, la >= lb);
}
}
}