1#![allow(missing_docs)]
3
4use std::{collections::BTreeMap, str::FromStr};
5
6use anyhow::{anyhow, Result};
7use async_trait::async_trait;
8use cid::Cid;
9
10use futures::{pin_mut, StreamExt};
11use libipld_cbor::DagCborCodec;
12use libipld_core::raw::RawCodec;
13use noosphere_storage::{block_deserialize, block_serialize, BlockStore, UcanStore};
14use serde::{de::DeserializeOwned, Deserialize, Serialize};
15use ucan::{store::UcanJwtStore, Ucan};
16
17use crate::{
18 data::{
19 AuthorityIpld, BodyChunkIpld, ChangelogIpld, ContentIpld, ContentType, DelegationIpld,
20 DelegationsIpld, Header, IdentityIpld, MapOperation, MemoIpld, RevocationIpld,
21 RevocationsIpld, VersionedMapIpld, VersionedMapKey, VersionedMapValue,
22 },
23 view::{Sphere, Timeslice},
24};
25
26use super::{AddressBookIpld, IdentitiesIpld, Jwt, Link, LinkRecord};
27
28#[derive(PartialEq, Eq, Debug, Clone, Default, Serialize, Deserialize)]
31pub struct Bundle(BTreeMap<String, Vec<u8>>);
32
33impl Bundle {
34 pub fn len(&self) -> usize {
35 self.0.len()
36 }
37
38 pub fn is_empty(&self) -> bool {
39 self.len() == 0
40 }
41
42 pub fn contains(&self, cid: &Cid) -> bool {
43 self.0.contains_key(&cid.to_string())
44 }
45
46 pub async fn load_into<S: BlockStore>(&self, store: &mut S) -> Result<()> {
47 debug!("Loading {} blocks into store...", self.0.len());
48
49 for (cid_string, block_bytes) in self.0.iter() {
51 let cid = Cid::from_str(cid_string)?;
52
53 store.put_block(&cid, block_bytes).await?;
54
55 match cid.codec() {
56 codec_id if codec_id == u64::from(DagCborCodec) => {
57 store.put_links::<DagCborCodec>(&cid, block_bytes).await?;
58 }
59 codec_id if codec_id == u64::from(RawCodec) => {
60 store.put_links::<RawCodec>(&cid, block_bytes).await?;
61 }
62 codec_id => warn!("Unrecognized codec {}; skipping...", codec_id),
63 }
64
65 }
67
68 Ok(())
69 }
70
71 pub async fn from_timeslice<'a, S: BlockStore>(
72 timeslice: &Timeslice<'a, S>,
73 store: &S,
74 ) -> Result<Bundle> {
75 let stream = timeslice.stream();
76 let mut bundle = Bundle::default();
77
78 pin_mut!(stream);
79
80 while let Some(ancestor) = stream.next().await {
81 let (_, memo) = ancestor?;
82 memo.extend_bundle(&mut bundle, store).await?;
83 }
84
85 Ok(bundle)
86 }
87
88 pub fn add(&mut self, cid: Cid, bytes: Vec<u8>) -> bool {
89 let cid_string = cid.to_string();
90 match self.0.contains_key(&cid_string) {
91 true => false,
92 false => {
93 self.0.insert(cid_string, bytes);
94 true
95 }
96 }
97 }
98
99 pub fn merge(&mut self, mut other: Bundle) {
100 self.0.append(&mut other.0);
101 }
102
103 pub fn map(&self) -> &BTreeMap<String, Vec<u8>> {
104 &self.0
105 }
106
107 pub async fn extend<CanBundle: TryBundle, S: BlockStore>(
108 &mut self,
109 cid: &Cid,
110 store: &S,
111 ) -> Result<()> {
112 CanBundle::extend_bundle_with_cid(cid, self, store).await?;
113 Ok(())
114 }
115}
116
117#[cfg(not(target_arch = "wasm32"))]
118pub trait TryBundleSendSync: Send + Sync {}
119
120#[cfg(not(target_arch = "wasm32"))]
121impl<T> TryBundleSendSync for T where T: Send + Sync {}
122
123#[cfg(target_arch = "wasm32")]
124pub trait TryBundleSendSync {}
125
126#[cfg(target_arch = "wasm32")]
127impl<T> TryBundleSendSync for T {}
128
129#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
130#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
131pub trait TryBundle: TryBundleSendSync + Serialize + DeserializeOwned {
132 async fn extend_bundle<S: BlockStore>(&self, bundle: &mut Bundle, _store: &S) -> Result<()> {
133 let (self_cid, self_bytes) = block_serialize::<DagCborCodec, _>(self)?;
134 bundle.add(self_cid, self_bytes);
135 Ok(())
136 }
137
138 async fn extend_bundle_with_cid<S: BlockStore>(
139 cid: &Cid,
140 bundle: &mut Bundle,
141 store: &S,
142 ) -> Result<()> {
143 let item = store.load::<DagCborCodec, Self>(cid).await?;
144 item.extend_bundle(bundle, store).await?;
145
146 Ok(())
147 }
148
149 async fn bundle<S: BlockStore>(&self, store: &S) -> Result<Bundle> {
150 let mut bundle = Bundle::default();
151 self.extend_bundle(&mut bundle, store).await?;
152 Ok(bundle)
153 }
154
155 async fn bundle_with_cid<S: BlockStore>(cid: &Cid, store: &S) -> Result<Bundle> {
156 let mut bundle = Bundle::default();
157 Self::extend_bundle_with_cid(cid, &mut bundle, store).await?;
158 Ok(bundle)
159 }
160}
161
162#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
163#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
164impl TryBundle for BodyChunkIpld {
165 async fn extend_bundle_with_cid<S: BlockStore>(
166 cid: &Cid,
167 bundle: &mut Bundle,
168 store: &S,
169 ) -> Result<()> {
170 let mut next_cid = Some(*cid);
171
172 while let Some(cid) = next_cid {
173 trace!(?cid, "Bundling BodyChunkIpld...");
174
175 let bytes = store.require_block(&cid).await?;
176 let chunk = block_deserialize::<DagCborCodec, BodyChunkIpld>(&bytes)?;
177 bundle.add(cid, bytes);
178 next_cid = chunk.next;
179 }
180
181 Ok(())
182 }
183}
184
185#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
186#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
187impl<K, V> TryBundle for ChangelogIpld<MapOperation<K, V>>
188where
189 K: VersionedMapKey,
190 V: VersionedMapValue + TryBundle,
191{
192 async fn extend_bundle_with_cid<S: BlockStore>(
193 cid: &Cid,
194 bundle: &mut Bundle,
195 store: &S,
196 ) -> Result<()> {
197 let bytes = store.require_block(cid).await?;
198 let changelog = block_deserialize::<DagCborCodec, Self>(&bytes)?;
199
200 trace!(?cid, "Bundling ChangeLogIpld...");
201
202 bundle.add(*cid, bytes);
203
204 for op in changelog.changes {
205 if let MapOperation::Add { value, key } = op {
206 trace!("...added entry {key}");
207 value.extend_bundle(bundle, store).await?;
208 }
209 }
210
211 Ok(())
212 }
213}
214
215#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
216#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
217impl TryBundle for MemoIpld {
218 async fn extend_bundle<S: BlockStore>(&self, bundle: &mut Bundle, store: &S) -> Result<()> {
219 let (self_cid, self_bytes) = block_serialize::<DagCborCodec, _>(self)?;
220 trace!(cid = ?self_cid, "Bundling MemoIpld....");
221
222 bundle.add(self_cid, self_bytes);
223
224 match self.get_first_header(&Header::ContentType) {
225 Some(value) => {
226 match ContentType::from_str(&value)? {
227 ContentType::Subtext
228 | ContentType::Text
229 | ContentType::Bytes
230 | ContentType::Json
231 | ContentType::Cbor => {
232 bundle.extend::<BodyChunkIpld, _>(&self.body, store).await?;
233 }
234 ContentType::Sphere => {
235 trace!("Bundling sphere revision {self_cid}...");
236
237 let sphere = Sphere::at(&Link::from(self_cid), store);
238 let mutation = sphere.derive_mutation().await?;
239 let sphere_body = sphere.to_body().await?;
240
241 let sphere_body_bytes = store.require_block(&self.body).await?;
242 bundle.add(self.body, sphere_body_bytes);
243
244 if !mutation.content().changes().is_empty() {
245 trace!(cid = ?sphere_body.content, "Bundling content...");
246 ContentIpld::extend_bundle_with_cid(
247 &sphere_body.content,
248 bundle,
249 store,
250 )
251 .await?;
252 }
253
254 if !mutation.delegations().changes().is_empty()
255 || !mutation.revocations().changes().is_empty()
256 {
257 trace!(cid = ?sphere_body.authority, "Bundling authority...");
258 AuthorityIpld::extend_bundle_with_cid(
259 &sphere_body.authority,
260 bundle,
261 store,
262 )
263 .await?;
264 }
265
266 if !mutation.identities().changes().is_empty() {
267 trace!(cid = ?sphere_body.address_book, "Bundling address book...");
268 AddressBookIpld::extend_bundle_with_cid(
269 &sphere_body.address_book,
270 bundle,
271 store,
272 )
273 .await?;
274 }
275 }
276 ContentType::Unknown(content_type) => {
277 warn!("Unrecognized content type {:?}; attempting to bundle as body chunks...", content_type);
278 bundle.extend::<BodyChunkIpld, _>(&self.body, store).await?;
280 }
281 }
282 }
283 None => {
284 warn!("No content type specified; only bundling a single block");
285 bundle.add(
286 self.body,
287 store
288 .get_block(&self.body)
289 .await?
290 .ok_or_else(|| anyhow!("Unable to find block for {}", self.body))?,
291 );
292 }
293 };
294
295 Ok(())
296 }
297
298 async fn extend_bundle_with_cid<S: BlockStore>(
299 cid: &Cid,
300 bundle: &mut Bundle,
301 store: &S,
302 ) -> Result<()> {
303 store
304 .load::<DagCborCodec, MemoIpld>(cid)
305 .await?
306 .extend_bundle(bundle, store)
307 .await?;
308 Ok(())
309 }
310}
311
312#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
313#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
314impl<K, V> TryBundle for VersionedMapIpld<K, V>
315where
316 K: VersionedMapKey,
317 V: VersionedMapValue + TryBundle,
318{
319 async fn extend_bundle<S: BlockStore>(&self, bundle: &mut Bundle, store: &S) -> Result<()> {
320 trace!("Bundling versioned map...");
321 let (self_cid, self_bytes) = block_serialize::<DagCborCodec, _>(self)?;
322
323 ChangelogIpld::<MapOperation<K, V>>::extend_bundle_with_cid(&self.changelog, bundle, store)
324 .await?;
325
326 bundle.add(self_cid, self_bytes);
327
328 Ok(())
329 }
330
331 async fn extend_bundle_with_cid<S: BlockStore>(
332 cid: &Cid,
333 bundle: &mut Bundle,
334 store: &S,
335 ) -> Result<()> {
336 let map: Self = store.load::<DagCborCodec, _>(cid).await?;
337 map.extend_bundle(bundle, store).await?;
338 Ok(())
339 }
340}
341
342#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
343#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
344impl<T> TryBundle for Link<T>
345where
346 T: TryBundle + Clone,
347{
348 async fn extend_bundle<S: BlockStore>(&self, bundle: &mut Bundle, store: &S) -> Result<()> {
349 T::extend_bundle_with_cid(self, bundle, store).await
350 }
351}
352
353#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
354#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
355impl TryBundle for Jwt {
356 async fn extend_bundle_with_cid<S: BlockStore>(
357 cid: &Cid,
358 bundle: &mut Bundle,
359 store: &S,
360 ) -> Result<()> {
361 bundle.add(*cid, store.require_block(cid).await?);
362 Ok(())
363 }
364}
365
366#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
367#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
368impl TryBundle for LinkRecord {
369 async fn extend_bundle<S: BlockStore>(&self, bundle: &mut Bundle, store: &S) -> Result<()> {
370 let cid = self.to_cid(cid::multihash::Code::Blake3_256)?;
371 Self::extend_bundle_with_cid(&cid, bundle, store).await
372 }
373
374 async fn extend_bundle_with_cid<S: BlockStore>(
375 cid: &Cid,
376 bundle: &mut Bundle,
377 store: &S,
378 ) -> Result<()> {
379 trace!("Bundling LinkRecord...");
380
381 let mut remaining = vec![*cid];
382 let ucan_store = UcanStore(store.clone());
383
384 while let Some(cid) = remaining.pop() {
385 trace!("...with proof {cid}");
386
387 let jwt = ucan_store.require_token(&cid).await?;
388 let ucan = Ucan::try_from(jwt.as_str())?;
389
390 bundle.add(cid, store.require_block(&cid).await?);
391
392 if let Some(proofs) = ucan.proofs() {
393 for proof_string in proofs {
394 remaining.push(Cid::try_from(proof_string.as_str())?)
395 }
396 }
397 }
398 Ok(())
399 }
400}
401
402#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
403#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
404impl TryBundle for IdentityIpld {
405 async fn extend_bundle<S: BlockStore>(&self, bundle: &mut Bundle, store: &S) -> Result<()> {
406 trace!("Bundling IdentityIpld...");
407 let (self_cid, self_bytes) = block_serialize::<DagCborCodec, _>(self)?;
408 bundle.add(self_cid, self_bytes);
409 if let Some(cid) = &self.link_record {
410 LinkRecord::extend_bundle_with_cid(cid, bundle, store).await?;
411 };
412 Ok(())
413 }
414}
415
416#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
417#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
418impl TryBundle for DelegationIpld {
419 async fn extend_bundle<S: BlockStore>(&self, bundle: &mut Bundle, store: &S) -> Result<()> {
420 let (self_cid, self_bytes) = block_serialize::<DagCborCodec, _>(self)?;
421 bundle.add(self_cid, self_bytes);
422 bundle.add(self.jwt, store.require_block(&self.jwt).await?);
423 Ok(())
424 }
425}
426
427#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
428#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
429impl TryBundle for RevocationIpld {}
430
431#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
432#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
433impl TryBundle for AuthorityIpld {
434 async fn extend_bundle_with_cid<S: BlockStore>(
435 cid: &Cid,
436 bundle: &mut Bundle,
437 store: &S,
438 ) -> Result<()> {
439 let self_bytes = store.require_block(cid).await?;
440 let authority_ipld = block_deserialize::<DagCborCodec, AuthorityIpld>(&self_bytes)?;
441
442 DelegationsIpld::extend_bundle_with_cid(&authority_ipld.delegations, bundle, store).await?;
443 RevocationsIpld::extend_bundle_with_cid(&authority_ipld.revocations, bundle, store).await?;
444
445 bundle.add(*cid, self_bytes);
446
447 Ok(())
448 }
449}
450
451#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
452#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
453impl TryBundle for AddressBookIpld {
454 async fn extend_bundle_with_cid<S: BlockStore>(
455 cid: &Cid,
456 bundle: &mut Bundle,
457 store: &S,
458 ) -> Result<()> {
459 let self_bytes = store.require_block(cid).await?;
460 let address_book_ipld = block_deserialize::<DagCborCodec, AddressBookIpld>(&self_bytes)?;
461
462 IdentitiesIpld::extend_bundle_with_cid(&address_book_ipld.identities, bundle, store)
463 .await?;
464
465 bundle.add(*cid, self_bytes);
466
467 Ok(())
468 }
469}
470
471#[cfg(test)]
472mod tests {
473 use crate::{helpers::make_valid_link_record, tracing::initialize_tracing};
474
475 use anyhow::Result;
476 use cid::Cid;
477 use libipld_cbor::DagCborCodec;
478 use libipld_core::{ipld::Ipld, raw::RawCodec};
479 use noosphere_storage::{block_serialize, BlockStore, MemoryStore, UcanStore};
480 use ucan::{builder::UcanBuilder, crypto::KeyMaterial};
481
482 #[cfg(target_arch = "wasm32")]
483 use wasm_bindgen_test::wasm_bindgen_test;
484
485 #[cfg(target_arch = "wasm32")]
486 wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
487
488 use crate::{
489 authority::generate_ed25519_key,
490 data::{Bundle, ContentIpld, DelegationIpld, MemoIpld, TryBundle},
491 view::{Sphere, SphereMutation, Timeline},
492 };
493
494 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
495 #[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
496 async fn it_bundles_an_empty_sphere() {
497 let mut store = MemoryStore::default();
498 let owner_key = generate_ed25519_key();
499 let owner_did = owner_key.get_did().await.unwrap();
500
501 let (sphere, _, _) = Sphere::generate(&owner_did, &mut store).await.unwrap();
502 let bundle = MemoIpld::bundle_with_cid(sphere.cid(), &store)
503 .await
504 .unwrap();
505
506 assert!(bundle.contains(sphere.cid()));
507
508 let memo = sphere.to_memo().await.unwrap();
509
510 assert!(bundle.contains(&memo.body));
511 }
512
513 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
514 #[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
515 async fn it_bundles_a_delegation_with_its_associated_jwt() -> Result<()> {
516 let store = MemoryStore::default();
517 let key = generate_ed25519_key();
518 let did = key.get_did().await.unwrap();
519
520 let jwt = UcanBuilder::default()
521 .issued_by(&key)
522 .for_audience(&did)
523 .with_lifetime(100)
524 .with_nonce()
525 .build()?
526 .sign()
527 .await?
528 .encode()?;
529
530 let (jwt_cid, _) = block_serialize::<RawCodec, _>(Ipld::Bytes(jwt.as_bytes().to_vec()))?;
531
532 let delegation = DelegationIpld::register("foo", &jwt, &store).await?;
533
534 let (delegation_cid, _) = block_serialize::<DagCborCodec, _>(&delegation)?;
535
536 let bundle = delegation.bundle(&store).await?;
537
538 assert!(bundle.contains(&delegation_cid));
539 assert!(bundle.contains(&jwt_cid));
540
541 Ok(())
542 }
543
544 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
545 #[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
546 async fn it_bundles_a_link_record_with_its_associated_proofs() -> Result<()> {
547 initialize_tracing(None);
548
549 let store = MemoryStore::default();
550 let (_, link_record, link_record_link) =
551 make_valid_link_record(&mut UcanStore(store.clone())).await?;
552
553 let proof_cid = Cid::try_from(
554 link_record
555 .proofs()
556 .as_ref()
557 .unwrap()
558 .first()
559 .unwrap()
560 .as_str(),
561 )?;
562 let bundle = link_record.bundle(&store).await?;
563
564 assert!(bundle.contains(&link_record_link));
565 assert!(bundle.contains(&proof_cid));
566
567 Ok(())
568 }
569
570 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
571 #[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
572 async fn it_bundles_a_sphere_with_links() {
573 let mut store = MemoryStore::default();
574 let owner_key = generate_ed25519_key();
575 let owner_did = owner_key.get_did().await.unwrap();
576
577 let (sphere, ucan, _) = Sphere::generate(&owner_did, &mut store).await.unwrap();
578
579 let foo_key = String::from("foo");
580 let foo_memo = MemoIpld::for_body(&mut store, b"foo").await.unwrap();
581 let foo_cid = store.save::<DagCborCodec, _>(&foo_memo).await.unwrap();
582
583 let mut mutation = SphereMutation::new(&owner_did);
584 mutation.content_mut().set(&foo_key, &foo_cid.into());
585
586 let mut revision = sphere.apply_mutation(&mutation).await.unwrap();
587 let new_cid = revision.sign(&owner_key, Some(&ucan)).await.unwrap();
588
589 let bundle = MemoIpld::bundle_with_cid(&new_cid, &store).await.unwrap();
590
591 assert_eq!(bundle.map().keys().len(), 6);
592
593 let sphere = Sphere::at(&new_cid, &store);
594
595 assert!(bundle.contains(sphere.cid()));
596
597 let memo = sphere.to_memo().await.unwrap();
598
599 assert!(bundle.contains(&memo.body));
600
601 let sphere_ipld = sphere.to_body().await.unwrap();
602 let links_cid = sphere_ipld.content;
603
604 assert!(bundle.contains(&links_cid));
605
606 let links_ipld = store
607 .load::<DagCborCodec, ContentIpld>(&links_cid)
608 .await
609 .unwrap();
610
611 assert!(bundle.contains(&links_ipld.changelog));
612 assert!(bundle.contains(&foo_cid));
613 }
614
615 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
616 #[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
617 async fn it_bundles_memo_body_content() {
618 let mut store = MemoryStore::default();
619
620 let owner_key = generate_ed25519_key();
621 let owner_did = owner_key.get_did().await.unwrap();
622
623 let (sphere, authorization, _) = Sphere::generate(&owner_did, &mut store).await.unwrap();
624
625 let body_cid = store
626 .save::<RawCodec, _>(Ipld::Bytes(b"foobar".to_vec()))
627 .await
628 .unwrap();
629
630 let memo = MemoIpld {
631 parent: None,
632 headers: Vec::new(),
633 body: body_cid,
634 };
635 let memo_cid = store.save::<DagCborCodec, _>(memo).await.unwrap();
636 let key = "foo".to_string();
637
638 let mut mutation = SphereMutation::new(&owner_did);
639
640 mutation.content_mut().set(&key, &memo_cid.into());
641
642 let mut revision = sphere.apply_mutation(&mutation).await.unwrap();
643
644 let sphere_revision = revision
645 .sign(&owner_key, Some(&authorization))
646 .await
647 .unwrap();
648
649 let bundle = MemoIpld::bundle_with_cid(&sphere_revision, &store)
650 .await
651 .unwrap();
652
653 assert!(bundle.contains(&body_cid));
654 }
655
656 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
657 #[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
658 async fn it_only_bundles_the_revision_delta() {
659 let mut store = MemoryStore::default();
660 let owner_key = generate_ed25519_key();
661 let owner_did = owner_key.get_did().await.unwrap();
662
663 let (sphere, ucan, _) = Sphere::generate(&owner_did, &mut store).await.unwrap();
664
665 let foo_key = String::from("foo");
666 let foo_memo = MemoIpld::for_body(&mut store, b"foo").await.unwrap();
667 let foo_cid = store.save::<DagCborCodec, _>(&foo_memo).await.unwrap();
668 let mut first_mutation = SphereMutation::new(&owner_did);
669 first_mutation.content_mut().set(&foo_key, &foo_cid.into());
670
671 let mut revision = sphere.apply_mutation(&first_mutation).await.unwrap();
672 let new_cid = revision.sign(&owner_key, Some(&ucan)).await.unwrap();
673
674 let sphere = Sphere::at(&new_cid, &store);
675
676 let bar_key = String::from("bar");
677 let bar_memo = MemoIpld::for_body(&mut store, b"bar").await.unwrap();
678 let bar_cid = store.save::<DagCborCodec, _>(&bar_memo).await.unwrap();
679
680 let mut second_mutation = SphereMutation::new(&owner_did);
681 second_mutation.content_mut().set(&bar_key, &bar_cid.into());
682
683 let mut revision = sphere.apply_mutation(&second_mutation).await.unwrap();
684 let new_cid = revision.sign(&owner_key, Some(&ucan)).await.unwrap();
685
686 let bundle = MemoIpld::bundle_with_cid(&new_cid, &store).await.unwrap();
687
688 assert_eq!(bundle.map().keys().len(), 6);
689 assert!(!bundle.contains(&foo_cid));
690 assert!(bundle.contains(&bar_cid));
691 }
692
693 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
694 #[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
695 async fn it_bundles_all_revisions_in_a_timeslice() {
696 let mut store = MemoryStore::default();
697 let owner_key = generate_ed25519_key();
698 let owner_did = owner_key.get_did().await.unwrap();
699
700 let (sphere, ucan, _) = Sphere::generate(&owner_did, &mut store).await.unwrap();
701
702 let original_cid = *sphere.cid();
703
704 let foo_key = String::from("foo");
705 let foo_memo = MemoIpld::for_body(&mut store, b"foo").await.unwrap();
706 let foo_cid = store.save::<DagCborCodec, _>(&foo_memo).await.unwrap();
707 let mut first_mutation = SphereMutation::new(&owner_did);
708 first_mutation.content_mut().set(&foo_key, &foo_cid.into());
709
710 let mut revision = sphere.apply_mutation(&first_mutation).await.unwrap();
711 let second_cid = revision.sign(&owner_key, Some(&ucan)).await.unwrap();
712
713 let sphere = Sphere::at(&second_cid, &store);
714
715 let bar_key = String::from("bar");
716 let bar_memo = MemoIpld::for_body(&mut store, b"bar").await.unwrap();
717 let bar_cid = store.save::<DagCborCodec, _>(&bar_memo).await.unwrap();
718 let mut second_mutation = SphereMutation::new(&owner_did);
719
720 second_mutation.content_mut().set(&bar_key, &bar_cid.into());
721
722 let mut revision = sphere.apply_mutation(&second_mutation).await.unwrap();
723 let final_cid = revision.sign(&owner_key, Some(&ucan)).await.unwrap();
724
725 let timeline = Timeline::new(&store);
726
727 let bundle = Bundle::from_timeslice(&timeline.slice(&final_cid, Some(&second_cid)), &store)
728 .await
729 .unwrap();
730
731 assert_eq!(bundle.map().keys().len(), 12);
732
733 assert!(bundle.contains(&foo_cid));
734 assert!(bundle.contains(&bar_cid));
735 assert!(bundle.contains(&final_cid));
736 assert!(bundle.contains(&second_cid));
737 assert!(!bundle.contains(&original_cid));
738 }
739}