wnfs_common/
link.rs

1use crate::{BlockStore, Storable, utils::CondSync};
2use anyhow::Result;
3use async_once_cell::OnceCell;
4use cid::Cid;
5use std::fmt::{self, Debug, Formatter};
6
7//--------------------------------------------------------------------------------------------------
8// Type Definitions
9//--------------------------------------------------------------------------------------------------
10
11/// A data structure that represents a link in the IPLD graph. Basically it is a "link" to some content addressable value of `T`.
12///
13/// It supports representing the "link" with a Cid or the deserialized value itself.
14///
15/// Link needs a `BlockStore` to be able to resolve Cids to corresponding values of `T` and vice versa.
16pub enum Link<T> {
17    /// A variant of `Link` that started out as a `Cid`.
18    /// If the decoded value is resolved using `resolve_value`, then the `value_cache` gets populated and
19    /// further calls to `resolve_value` will just return from that cache.
20    Encoded { cid: Cid, value_cache: OnceCell<T> },
21    /// A variant of `Link` that started out as a deserialized value `T`.
22    /// If the cid is resolved using `resolve_cid`, then `T`'s `.persisted_as` from the
23    /// `Storable` trait is called and that `OnceCell<Cid>` is populated, preventing
24    /// further calls to `resolve_cid` from duplicating work.
25    Decoded { value: T },
26}
27
28//--------------------------------------------------------------------------------------------------
29// Implementations
30//--------------------------------------------------------------------------------------------------
31
32impl<T: Storable + CondSync> Link<T> {
33    /// Creates a new `Link` that starts out as a Cid.
34    pub fn from_cid(cid: Cid) -> Self {
35        Self::Encoded {
36            cid,
37            value_cache: OnceCell::new(),
38        }
39    }
40
41    /// Gets the Cid stored in type. It attempts to get it from the store if it is not present in type.
42    pub async fn resolve_cid(&self, store: &impl BlockStore) -> Result<Cid> {
43        match self {
44            Self::Encoded { cid, .. } => Ok(*cid),
45            Self::Decoded { value } => value.store(store).await,
46        }
47    }
48
49    /// Gets the value stored in link. It attempts to get it from the store if it is not present in link.
50    pub async fn resolve_value(&self, store: &impl BlockStore) -> Result<&T> {
51        match self {
52            Self::Encoded { cid, value_cache } => {
53                value_cache.get_or_try_init(T::load(cid, store)).await
54            }
55            Self::Decoded { value, .. } => Ok(value),
56        }
57    }
58
59    /// Gets mut value stored in link. It attempts to get it from the store if it is not present in link.
60    pub async fn resolve_value_mut(&mut self, store: &impl BlockStore) -> Result<&mut T> {
61        match self {
62            Self::Encoded { cid, value_cache } => {
63                let value = match value_cache.take() {
64                    Some(v) => v,
65                    None => T::load(cid, store).await?,
66                };
67
68                *self = Self::Decoded { value };
69
70                Ok(match self {
71                    Self::Decoded { value } => value,
72                    _ => unreachable!(),
73                })
74            }
75            Self::Decoded { value, .. } => Ok(value),
76        }
77    }
78
79    /// Gets the cid data stored in type.
80    ///
81    /// NOTE: This does not attempt to get it from the store if it does not exist..
82    pub fn get_cid(&self) -> Option<&Cid> {
83        match self {
84            Self::Encoded { cid, .. } => Some(cid),
85            Self::Decoded { value } => value.persisted_as().and_then(OnceCell::get),
86        }
87    }
88
89    /// Gets the value stored in type.
90    ///
91    /// NOTE: This does not attempt to get it from the store if it does not exist.
92    pub fn get_value(&self) -> Option<&T> {
93        match self {
94            Self::Encoded { value_cache, .. } => value_cache.get(),
95            Self::Decoded { value } => Some(value),
96        }
97    }
98
99    /// Gets an owned value from type. It attempts to it get from the store if it is not present in type.
100    pub async fn resolve_owned_value(self, store: &impl BlockStore) -> Result<T>
101    where
102        T: Storable,
103    {
104        match self {
105            Self::Encoded { cid, value_cache } => match value_cache.into_inner() {
106                Some(cached) => Ok(cached),
107                None => Ok(T::load(&cid, store).await?),
108            },
109            Self::Decoded { value, .. } => Ok(value),
110        }
111    }
112
113    /// Checks if there is a Cid cached in link.
114    pub fn has_cid(&self) -> bool {
115        self.get_cid().is_some()
116    }
117
118    /// Checks if there is a value stored in link.
119    pub fn has_value(&self) -> bool {
120        match self {
121            Self::Encoded { value_cache, .. } => value_cache.get().is_some(),
122            _ => true,
123        }
124    }
125
126    /// Compares two links for equality. Attempts to get them from store if they are not already cached.
127    pub async fn deep_eq(&self, other: &Link<T>, store: &impl BlockStore) -> Result<bool>
128    where
129        T: PartialEq + Storable,
130    {
131        if self == other {
132            return Ok(true);
133        }
134
135        Ok(self.resolve_cid(store).await? == other.resolve_cid(store).await?)
136    }
137}
138
139impl<T: Storable> From<T> for Link<T> {
140    fn from(value: T) -> Self {
141        Self::Decoded { value }
142    }
143}
144
145impl<T> Clone for Link<T>
146where
147    T: Clone,
148{
149    fn clone(&self) -> Self {
150        match self {
151            Self::Encoded { cid, value_cache } => Self::Encoded {
152                cid: *cid,
153                value_cache: value_cache
154                    .get()
155                    .cloned()
156                    .map(OnceCell::new_with)
157                    .unwrap_or_default(),
158            },
159            Self::Decoded { value } => Self::Decoded {
160                value: value.clone(),
161            },
162        }
163    }
164}
165
166impl<T: Storable + CondSync> PartialEq for Link<T>
167where
168    T: PartialEq,
169{
170    fn eq(&self, other: &Self) -> bool {
171        match (self, other) {
172            (Self::Encoded { cid, .. }, Self::Encoded { cid: cid2, .. }) => cid == cid2,
173            (Self::Decoded { value, .. }, Self::Decoded { value: value2, .. }) => value == value2,
174            (Self::Encoded { cid, .. }, Self::Decoded { value: value2, .. }) => {
175                if let Some(cid2) = other.get_cid() {
176                    cid == cid2
177                } else if let Some(value) = self.get_value() {
178                    value == value2
179                } else {
180                    false
181                }
182            }
183            (Self::Decoded { value, .. }, Self::Encoded { cid: cid2, .. }) => {
184                if let Some(cid) = self.get_cid() {
185                    cid == cid2
186                } else if let Some(value2) = other.get_value() {
187                    value == value2
188                } else {
189                    false
190                }
191            }
192        }
193    }
194}
195
196impl<T> Debug for Link<T>
197where
198    T: Debug,
199{
200    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
201        match self {
202            Self::Encoded { cid, value_cache } => f
203                .debug_struct("Link::Encoded")
204                .field("cid", &format!("{cid}"))
205                .field("value_cache", &value_cache.get())
206                .finish(),
207            Self::Decoded { value } => f.debug_tuple("Link::Decoded").field(value).finish(),
208        }
209    }
210}
211
212//--------------------------------------------------------------------------------------------------
213// Tests
214//--------------------------------------------------------------------------------------------------
215
216#[cfg(test)]
217mod tests {
218    use crate::{BlockStore, Link, MemoryBlockStore, Storable};
219    use anyhow::Result;
220    use async_once_cell::OnceCell;
221    use cid::Cid;
222    use serde::{Deserialize, Serialize};
223
224    #[derive(Debug, Serialize, Deserialize)]
225    struct Example {
226        price: u64,
227        #[serde(skip, default = "OnceCell::new")]
228        persisted_as: OnceCell<Cid>,
229    }
230
231    impl Storable for Example {
232        type Serializable = Example;
233
234        async fn to_serializable(&self, _store: &impl BlockStore) -> Result<Self::Serializable> {
235            Ok(self.clone())
236        }
237
238        async fn from_serializable(
239            cid: Option<&Cid>,
240            mut serializable: Self::Serializable,
241        ) -> Result<Self> {
242            serializable.persisted_as = cid.cloned().map(OnceCell::new_with).unwrap_or_default();
243            Ok(serializable)
244        }
245
246        fn persisted_as(&self) -> Option<&OnceCell<Cid>> {
247            Some(&self.persisted_as)
248        }
249    }
250
251    impl Clone for Example {
252        fn clone(&self) -> Self {
253            Self {
254                price: self.price,
255                persisted_as: self
256                    .persisted_as
257                    .get()
258                    .cloned()
259                    .map(OnceCell::new_with)
260                    .unwrap_or_default(),
261            }
262        }
263    }
264
265    impl PartialEq for Example {
266        fn eq(&self, other: &Self) -> bool {
267            self.price == other.price
268        }
269    }
270
271    impl Example {
272        fn new(price: u64) -> Self {
273            Self {
274                price,
275                persisted_as: OnceCell::new(),
276            }
277        }
278    }
279
280    #[async_std::test]
281    async fn link_value_can_be_resolved() {
282        let store = &MemoryBlockStore::default();
283        let example = Example::new(256);
284        let cid = example.store(store).await.unwrap();
285        let link = Link::<Example>::from_cid(cid);
286
287        let value = link.resolve_value(store).await.unwrap();
288        assert_eq!(value, &example);
289        assert!(link.has_value());
290    }
291
292    #[async_std::test]
293    async fn link_cid_can_be_resolved() {
294        let example = Example::new(12_000_500);
295        let store = &MemoryBlockStore::default();
296        let link = Link::<Example>::from(example.clone());
297
298        let cid = link.resolve_cid(store).await.unwrap();
299        let value = Example::load(&cid, store).await.unwrap();
300
301        assert_eq!(value, example);
302    }
303}