varing 0.14.0

Protobuf's varint encoding/decoding for LEB128 friendly types with full const context operations supports.
Documentation
use crate::{
  ConstDecodeError, ConstEncodeError, DecodeError, EncodeError, Varint,
  bnum::{decode_uint_d8, encode_uint_d8_to, encoded_uint_d8_len},
  packable::Packable,
  utils::{pack_i128, pack_u128, unpack_i128, unpack_u128},
};
use ::bnum_0_13::{BInt, BIntD8, BIntD16, BIntD32, BUint, BUintD8, BUintD16, BUintD32};
use num_rational_0_4::Ratio;

use core::num::NonZeroUsize;

type U256 = BUintD8<32>;

// `MIN_ENCODED_LEN` is the encoded length of the shortest representable `Ratio`,
// `0/1`, whose packed form is `pack(0, 1)` (numerator low, denominator high) —
// not the merged `U256`'s one-byte minimum.
impl_varint_for_ratio!(@inner
  u::128(U256) => crate::bnum::encoded_uint_d8_len(&crate::utils::pack_u128(0, 1)),
  i::128(U256) => crate::bnum::encoded_uint_d8_len(&crate::utils::pack_i128(0, 1)),
);

/// Returns the encoded length of the `Ratio<u128>` value.
#[inline]
pub const fn encoded_ratio_u128_len(val: &Ratio<u128>) -> NonZeroUsize {
  encoded_uint_d8_len(&pack_u128(*val.numer(), *val.denom()))
}

/// Encodes the `Ratio<u128>` value.
#[inline]
pub const fn encode_ratio_u128_to(
  val: &Ratio<u128>,
  buf: &mut [u8],
) -> Result<NonZeroUsize, ConstEncodeError> {
  encode_uint_d8_to(pack_u128(*val.numer(), *val.denom()), buf)
}

/// Decodes the `Ratio<u128>` from the given buffer
///
/// Returns the bytes read and the value.
#[inline]
pub const fn decode_ratio_u128(
  buf: &[u8],
) -> Result<(NonZeroUsize, Ratio<u128>), ConstDecodeError> {
  match decode_uint_d8::<32>(buf) {
    Ok((read, val)) => {
      let (numer, denom) = unpack_u128(val);
      if denom == 0 {
        return Err(ConstDecodeError::other("denominator cannot be zero"));
      }
      Ok((read, Ratio::new_raw(numer, denom)))
    }
    Err(e) => Err(e),
  }
}

/// Returns the encoded length of the `Ratio<i128>` value.
#[inline]
pub const fn encoded_ratio_i128_len(val: &Ratio<i128>) -> NonZeroUsize {
  encoded_uint_d8_len(&pack_i128(*val.numer(), *val.denom()))
}

/// Encodes the `Ratio<i128>` value.
#[inline]
pub const fn encode_ratio_i128_to(
  val: &Ratio<i128>,
  buf: &mut [u8],
) -> Result<NonZeroUsize, ConstEncodeError> {
  encode_uint_d8_to(pack_i128(*val.numer(), *val.denom()), buf)
}

/// Decodes the `Ratio<i128>` from the given buffer
///
/// Returns the bytes read and the value.
#[inline]
pub const fn decode_ratio_i128(
  buf: &[u8],
) -> Result<(NonZeroUsize, Ratio<i128>), ConstDecodeError> {
  match decode_uint_d8::<32>(buf) {
    Ok((read, val)) => {
      let (numer, denom) = unpack_i128(val);
      if denom == 0 {
        return Err(ConstDecodeError::other("denominator cannot be zero"));
      }
      Ok((read, Ratio::new_raw(numer, denom)))
    }
    Err(e) => Err(e),
  }
}

macro_rules! impl_varint_for_ratio_bnum {
  (@unsigned $( ($base:ident($($bits:literal),+$(,)?)) ), +$(,)?) => {
    paste::paste! {
      $(
        $(
          impl Varint for Ratio<$base<{ $bits / 8 }>> {
            // Shortest representable `Ratio` is `0/1`, which packs to `1 << BITS`
            // (numerator low, denominator high); its encoded length is
            // `ceil((BITS + 1) / 7)`, not the merged integer's one-byte minimum.
            const MIN_ENCODED_LEN: NonZeroUsize =
              match NonZeroUsize::new(($base::<{ $bits / 8 }>::BITS as usize + 1).div_ceil(7)) {
                Some(v) => v,
                None => unreachable!(),
              };

            const MAX_ENCODED_LEN: NonZeroUsize = $base::<{($bits / 8) * 2}>::MAX_ENCODED_LEN;

            fn encoded_len(&self) -> NonZeroUsize {
              Packable::<$base::<{ $bits / 8 }>, $base::<{($bits / 8) * 2}>>::pack(self.numer(), self.denom()).encoded_len()
            }

            fn encode(&self, buf: &mut [u8]) -> Result<NonZeroUsize, EncodeError> {
              Packable::<$base::<{ $bits / 8 }>, $base::<{($bits / 8) * 2}>>::pack(self.numer(), self.denom()).encode(buf)
            }

            fn decode(buf: &[u8]) -> Result<(NonZeroUsize, Self), DecodeError>
            where
              Self: Sized,
            {
              let (bytes_read, merged) = $base::< {($bits / 8) * 2} >::decode(buf)?;
              let (numer, denom): ($base<{ $bits / 8 }>, $base<{ $bits / 8 }>) = Packable::<$base::<{ $bits / 8 }>, $base::<{($bits / 8) * 2}>>::unpack(merged);
              if denom.is_zero() {
                return Err(DecodeError::other("denominator cannot be zero"));
              }
              Ok((bytes_read, Ratio::new_raw(numer, denom)))
            }
          }
        )*
      )*
    }
  };
  (@signed $( ($base:ident <=> $unsigned:ident($($bits:literal),+$(,)?)) ), +$(,)?) => {
    paste::paste! {
      $(
        $(
          impl Varint for Ratio<$base<{ $bits / 8 }>> {
            // Shortest representable `Ratio` is `0/1`; the signed denominator `1`
            // zigzag-encodes to `2`, so it packs to `2 << BITS` and its encoded
            // length is `ceil((BITS + 2) / 7)`, not the merged integer's minimum.
            const MIN_ENCODED_LEN: NonZeroUsize =
              match NonZeroUsize::new(($base::<{ $bits / 8 }>::BITS as usize + 2).div_ceil(7)) {
                Some(v) => v,
                None => unreachable!(),
              };

            const MAX_ENCODED_LEN: NonZeroUsize = $unsigned::<{($bits / 8) * 2}>::MAX_ENCODED_LEN;

            fn encoded_len(&self) -> NonZeroUsize {
              Packable::<$base::<{ $bits / 8 }>, $unsigned::<{($bits / 8) * 2}>>::pack(self.numer(), self.denom()).encoded_len()
            }

            fn encode(&self, buf: &mut [u8]) -> Result<NonZeroUsize, EncodeError> {
              Packable::<$base::<{ $bits / 8 }>, $unsigned::<{($bits / 8) * 2}>>::pack(self.numer(), self.denom()).encode(buf)
            }

            fn decode(buf: &[u8]) -> Result<(NonZeroUsize, Self), DecodeError>
            where
              Self: Sized,
            {
              let (bytes_read, merged) = $unsigned::< {($bits / 8) * 2}>::decode(buf)?;
              let (numer, denom): ($base<{ $bits / 8 }>, $base<{ $bits / 8 }>) = Packable::<$base::<{ $bits / 8 }>, $unsigned::<{($bits / 8) * 2}>>::unpack(merged);
              if denom.is_zero() {
                return Err(DecodeError::other("denominator cannot be zero"));
              }
              Ok((bytes_read, Ratio::new_raw(numer, denom)))
            }
          }
        )*
      )*
    }
  };
}

// TODO: this can be implemented for all Uint<BITS, LIMBS> when feature(const_generics) is stable
impl_varint_for_ratio_bnum!(@unsigned
  (BUintD8(8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768)),
  (BUintD16(8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768)),
  (BUintD32(8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768)),
  (BUint(8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768)),
);

impl_varint_for_ratio_bnum!(@signed
  (BIntD8 <=> BUintD8(8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768)),
  (BIntD16 <=> BUintD16(8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768)),
  (BIntD32 <=> BUintD32(8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768)),
  (BInt <=> BUint(8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768)),
);

#[test]
fn t() {
  let c = Ratio::new_raw(BUintD8::<2>::ONE, BUintD8::<2>::TWO);
  let mut b = [0; Ratio::<BUintD8<2>>::MAX_ENCODED_LEN.get()];

  c.encode(&mut b).unwrap();
  let (_, decode) = Ratio::<BUintD8<2>>::decode(&b).unwrap();

  std::println!("{c:?}");
  std::println!("{decode:?}");

  assert!(c.denom().eq(decode.denom()));
  assert!(c.numer().eq(decode.numer()));
}