use crate::{BytesCursor, RecordedInput, hash_raw};
use bytes::Bytes;
use castaway::cast;
use codec::{Decode, Encode, Input, MaxEncodedLen};
use jam_types::{Hash, Wrap};
use std::{borrow::Borrow, ops::Deref};
pub trait HashOrNoHash {
fn hash(data: &[u8]) -> Self;
}
#[derive(Clone, Copy, Debug)]
pub struct NoHash;
impl HashOrNoHash for NoHash {
fn hash(_data: &[u8]) -> Self {
Self
}
}
impl<T: From<Hash>> HashOrNoHash for T {
fn hash(data: &[u8]) -> Self {
hash_raw(data).into()
}
}
#[derive(Clone, Debug)]
pub struct Immutable<T, H = NoHash> {
value: T,
encoded: Bytes,
hash: H,
}
impl<T, H: HashOrNoHash> Immutable<T, H> {
pub fn from_parts(value: T, hash: H, encoded: Bytes) -> Self {
Self { value, encoded, hash }
}
pub fn from_value_and_encoded(value: T, encoded: Bytes) -> Self {
let hash = H::hash(&encoded);
Self { value, encoded, hash }
}
pub fn encoded(&self) -> &Bytes {
&self.encoded
}
pub fn encoded_len(&self) -> usize {
self.encoded.len()
}
pub fn into_value(self) -> T {
self.value
}
#[allow(dead_code)]
pub fn into_encoded(self) -> Bytes {
self.encoded
}
pub fn into_value_and_encoded(self) -> (T, Bytes) {
(self.value, self.encoded)
}
pub fn into_parts(self) -> (T, Bytes, H) {
(self.value, self.encoded, self.hash)
}
}
impl<T, H: Into<Hash> + Copy> Immutable<T, H> {
pub fn hash(&self) -> H {
self.hash
}
}
impl<T: Encode, H: HashOrNoHash> From<T> for Immutable<T, H> {
fn from(value: T) -> Self {
let encoded = value.encode().into();
Self::from_value_and_encoded(value, encoded)
}
}
impl<T: Encode, H: HashOrNoHash> Immutable<T, H> {
pub fn modify_and_encode(&mut self, f: impl FnOnce(&mut T)) {
f(&mut self.value);
self.encoded = self.value.encode().into();
self.hash = H::hash(&self.encoded);
}
}
impl<T, H> Deref for Immutable<T, H> {
type Target = T;
fn deref(&self) -> &T {
&self.value
}
}
impl<T, H> Borrow<T> for Immutable<T, H> {
fn borrow(&self) -> &T {
&self.value
}
}
impl<T: Decode, H: HashOrNoHash> Decode for Immutable<T, H> {
fn decode<I: Input>(input: &mut I) -> Result<Self, codec::Error> {
let (value, encoded) = match cast!(input, &mut BytesCursor) {
Ok(input) => {
let mut encoded = input.remaining().clone();
let value = T::decode(input)?;
encoded.truncate(encoded.len() - input.infallible_remaining_len());
(value, encoded)
},
Err(input) => {
let mut input = RecordedInput::new(input);
let value = T::decode(&mut input)?;
debug_assert!(!input.failed());
let encoded: Vec<_> = input.into_record();
(value, encoded.into())
},
};
let hash = H::hash(&encoded);
Ok(Self { value, encoded, hash })
}
fn skip<I: Input>(input: &mut I) -> Result<(), codec::Error> {
T::skip(input)
}
fn encoded_fixed_size() -> Option<usize> {
T::encoded_fixed_size()
}
}
impl<T, H> Encode for Immutable<T, H> {
fn size_hint(&self) -> usize {
self.encoded.len()
}
fn using_encoded<R, F: FnOnce(&[u8]) -> R>(&self, f: F) -> R {
f(&self.encoded)
}
fn encoded_size(&self) -> usize {
self.encoded.len()
}
}
impl<T: MaxEncodedLen, H> MaxEncodedLen for Immutable<T, H> {
fn max_encoded_len() -> usize {
T::max_encoded_len()
}
}
#[derive(Clone, Debug)]
pub struct ImmutableWrap;
impl Wrap for ImmutableWrap {
type Wrap<T> = Immutable<T>;
}
#[cfg(test)]
mod tests {
use super::*;
use codec::DecodeAll;
use jam_types::NoWrap;
#[derive(Encode, Decode, Debug, PartialEq, Eq)]
struct Inner(Bytes);
#[derive(Encode, Decode, Debug, PartialEq, Eq)]
struct WrappedOuter<W: Wrap> {
a: W::Wrap<Inner>,
b: W::Wrap<Inner>,
}
type Outer = WrappedOuter<NoWrap>;
type ImmutableOuter = Immutable<WrappedOuter<ImmutableWrap>, Hash>;
fn contains(outer: &[u8], inner: &[u8]) -> bool {
let outer = outer.as_ptr_range();
let inner = inner.as_ptr_range();
(outer.start <= inner.start) && (inner.end <= outer.end)
}
#[test]
fn basic_operation() {
let outer =
Outer { a: Inner(Bytes::from_static(b"polka")), b: Inner(Bytes::from_static(b"jam!")) };
let encoded_outer = outer.encode();
assert_eq!(outer, Outer::decode_all(&mut encoded_outer.as_slice()).unwrap());
let encoded_outer = Bytes::from(encoded_outer);
let immutable_outer: ImmutableOuter =
BytesCursor::from(encoded_outer.clone()).decode_all().unwrap();
assert_eq!(immutable_outer.a.0, outer.a.0);
assert_eq!(immutable_outer.b.0, outer.b.0);
assert_eq!(immutable_outer.encoded(), &encoded_outer);
assert_eq!(immutable_outer.hash(), hash_raw(&encoded_outer));
assert_eq!(immutable_outer.a.encoded(), &outer.a.encode());
assert_eq!(immutable_outer.b.encoded(), &outer.b.encode());
assert!(contains(&encoded_outer, immutable_outer.encoded()));
assert!(contains(&encoded_outer, immutable_outer.a.encoded()));
assert!(contains(&encoded_outer, immutable_outer.b.encoded()));
assert!(contains(&encoded_outer, &immutable_outer.a.0));
assert!(contains(&encoded_outer, &immutable_outer.b.0));
}
}