use crate::{BlockStore, Storable, utils::CondSync};
use anyhow::Result;
use async_once_cell::OnceCell;
use cid::Cid;
use std::fmt::{self, Debug, Formatter};
pub enum Link<T> {
Encoded { cid: Cid, value_cache: OnceCell<T> },
Decoded { value: T },
}
impl<T: Storable + CondSync> Link<T> {
pub fn from_cid(cid: Cid) -> Self {
Self::Encoded {
cid,
value_cache: OnceCell::new(),
}
}
pub async fn resolve_cid(&self, store: &impl BlockStore) -> Result<Cid> {
match self {
Self::Encoded { cid, .. } => Ok(*cid),
Self::Decoded { value } => value.store(store).await,
}
}
pub async fn resolve_value(&self, store: &impl BlockStore) -> Result<&T> {
match self {
Self::Encoded { cid, value_cache } => {
value_cache.get_or_try_init(T::load(cid, store)).await
}
Self::Decoded { value, .. } => Ok(value),
}
}
pub async fn resolve_value_mut(&mut self, store: &impl BlockStore) -> Result<&mut T> {
match self {
Self::Encoded { cid, value_cache } => {
let value = match value_cache.take() {
Some(v) => v,
None => T::load(cid, store).await?,
};
*self = Self::Decoded { value };
Ok(match self {
Self::Decoded { value } => value,
_ => unreachable!(),
})
}
Self::Decoded { value, .. } => Ok(value),
}
}
pub fn get_cid(&self) -> Option<&Cid> {
match self {
Self::Encoded { cid, .. } => Some(cid),
Self::Decoded { value } => value.persisted_as().and_then(OnceCell::get),
}
}
pub fn get_value(&self) -> Option<&T> {
match self {
Self::Encoded { value_cache, .. } => value_cache.get(),
Self::Decoded { value } => Some(value),
}
}
pub async fn resolve_owned_value(self, store: &impl BlockStore) -> Result<T>
where
T: Storable,
{
match self {
Self::Encoded { cid, value_cache } => match value_cache.into_inner() {
Some(cached) => Ok(cached),
None => Ok(T::load(&cid, store).await?),
},
Self::Decoded { value, .. } => Ok(value),
}
}
pub fn has_cid(&self) -> bool {
self.get_cid().is_some()
}
pub fn has_value(&self) -> bool {
match self {
Self::Encoded { value_cache, .. } => value_cache.get().is_some(),
_ => true,
}
}
pub async fn deep_eq(&self, other: &Link<T>, store: &impl BlockStore) -> Result<bool>
where
T: PartialEq + Storable,
{
if self == other {
return Ok(true);
}
Ok(self.resolve_cid(store).await? == other.resolve_cid(store).await?)
}
}
impl<T: Storable> From<T> for Link<T> {
fn from(value: T) -> Self {
Self::Decoded { value }
}
}
impl<T> Clone for Link<T>
where
T: Clone,
{
fn clone(&self) -> Self {
match self {
Self::Encoded { cid, value_cache } => Self::Encoded {
cid: *cid,
value_cache: value_cache
.get()
.cloned()
.map(OnceCell::new_with)
.unwrap_or_default(),
},
Self::Decoded { value } => Self::Decoded {
value: value.clone(),
},
}
}
}
impl<T: Storable + CondSync> PartialEq for Link<T>
where
T: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Encoded { cid, .. }, Self::Encoded { cid: cid2, .. }) => cid == cid2,
(Self::Decoded { value, .. }, Self::Decoded { value: value2, .. }) => value == value2,
(Self::Encoded { cid, .. }, Self::Decoded { value: value2, .. }) => {
if let Some(cid2) = other.get_cid() {
cid == cid2
} else if let Some(value) = self.get_value() {
value == value2
} else {
false
}
}
(Self::Decoded { value, .. }, Self::Encoded { cid: cid2, .. }) => {
if let Some(cid) = self.get_cid() {
cid == cid2
} else if let Some(value2) = other.get_value() {
value == value2
} else {
false
}
}
}
}
}
impl<T> Debug for Link<T>
where
T: Debug,
{
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::Encoded { cid, value_cache } => f
.debug_struct("Link::Encoded")
.field("cid", &format!("{cid}"))
.field("value_cache", &value_cache.get())
.finish(),
Self::Decoded { value } => f.debug_tuple("Link::Decoded").field(value).finish(),
}
}
}
#[cfg(test)]
mod tests {
use crate::{BlockStore, Link, MemoryBlockStore, Storable};
use anyhow::Result;
use async_once_cell::OnceCell;
use cid::Cid;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
struct Example {
price: u64,
#[serde(skip, default = "OnceCell::new")]
persisted_as: OnceCell<Cid>,
}
impl Storable for Example {
type Serializable = Example;
async fn to_serializable(&self, _store: &impl BlockStore) -> Result<Self::Serializable> {
Ok(self.clone())
}
async fn from_serializable(
cid: Option<&Cid>,
mut serializable: Self::Serializable,
) -> Result<Self> {
serializable.persisted_as = cid.cloned().map(OnceCell::new_with).unwrap_or_default();
Ok(serializable)
}
fn persisted_as(&self) -> Option<&OnceCell<Cid>> {
Some(&self.persisted_as)
}
}
impl Clone for Example {
fn clone(&self) -> Self {
Self {
price: self.price,
persisted_as: self
.persisted_as
.get()
.cloned()
.map(OnceCell::new_with)
.unwrap_or_default(),
}
}
}
impl PartialEq for Example {
fn eq(&self, other: &Self) -> bool {
self.price == other.price
}
}
impl Example {
fn new(price: u64) -> Self {
Self {
price,
persisted_as: OnceCell::new(),
}
}
}
#[async_std::test]
async fn link_value_can_be_resolved() {
let store = &MemoryBlockStore::default();
let example = Example::new(256);
let cid = example.store(store).await.unwrap();
let link = Link::<Example>::from_cid(cid);
let value = link.resolve_value(store).await.unwrap();
assert_eq!(value, &example);
assert!(link.has_value());
}
#[async_std::test]
async fn link_cid_can_be_resolved() {
let example = Example::new(12_000_500);
let store = &MemoryBlockStore::default();
let link = Link::<Example>::from(example.clone());
let cid = link.resolve_cid(store).await.unwrap();
let value = Example::load(&cid, store).await.unwrap();
assert_eq!(value, example);
}
}