1use crate::block::BlockCodec;
4use crate::error::Error;
5use crate::path::{IpfsPath, PathRoot, SlashedPath};
6use crate::repo::DefaultStorage;
7use crate::repo::Repo;
8use crate::{Block, Ipfs};
9use bytes::Bytes;
10use connexa::prelude::PeerId;
11use futures::FutureExt;
12use futures::future::BoxFuture;
13use ipld_core::cid::{Cid, Version};
14use ipld_core::codec::Codec;
15use ipld_core::ipld::Ipld;
16use ipld_core::serde::{from_ipld, to_ipld};
17use multihash_codetable::{Code, MultihashDigest};
18use rust_unixfs::{
19 MaybeResolved,
20 dagpb::{NodeData, wrap_node_data},
21 dir::{Cache, ShardedLookup},
22 resolve,
23};
24use serde::Serialize;
25use serde::de::DeserializeOwned;
26use std::borrow::Borrow;
27use std::convert::TryFrom;
28use std::error::Error as StdError;
29use std::iter::Peekable;
30use std::marker::PhantomData;
31use std::time::Duration;
32use thiserror::Error;
33use tracing::{Instrument, Span};
34
35pub mod car;
36
37#[derive(Debug, Error)]
38pub enum ResolveError {
39 #[error("block loading failed")]
41 Loading(Cid, #[source] crate::Error),
42
43 #[error("unsupported document")]
46 UnsupportedDocument(Cid, #[source] Box<dyn StdError + Send + Sync + 'static>),
47
48 #[error("list index out of range 0..{elements}: {index}")]
50 ListIndexOutOfRange {
51 document: Cid,
53 path: SlashedPath,
55 index: usize,
57 elements: usize,
59 },
60
61 #[error("tried to resolve through an object that had no links")]
63 NoLinks(Cid, SlashedPath),
64
65 #[error("no link named {n:?} under {0}", n = .1.iter().last().unwrap())]
67 NotFound(Cid, SlashedPath),
68
69 #[error("the path neiter contains nor resolves to a Cid")]
71 NoCid(IpfsPath),
72
73 #[error("can't resolve an IPNS path")]
75 IpnsResolutionFailed(IpfsPath),
76
77 #[error("path is not provided or is invalid")]
78 PathNotProvided,
79}
80
81#[derive(Debug, Error)]
82pub enum UnexpectedResolved {
83 #[error("path resolved to unexpected type of document: {:?} or {}", .0, .1.source())]
84 UnexpectedCodec(u64, Box<ResolvedNode>),
85 #[error("path did not resolve to a block on {}", .0.source())]
86 NonBlock(Box<ResolvedNode>),
87}
88
89#[derive(Debug)]
91enum RawResolveLocalError {
92 Loading(Cid, crate::Error),
93 UnsupportedDocument(Cid, Box<dyn StdError + Send + Sync + 'static>),
94 ListIndexOutOfRange {
95 document: Cid,
96 segment_index: usize,
97 index: usize,
98 elements: usize,
99 },
100 InvalidIndex {
101 document: Cid,
102 segment_index: usize,
103 },
104 NoLinks {
105 document: Cid,
106 segment_index: usize,
107 },
108 NotFound {
109 document: Cid,
110 segment_index: usize,
111 },
112}
113
114impl RawResolveLocalError {
115 fn add_starting_point_in_path(&mut self, start: usize) {
119 use RawResolveLocalError::*;
120 match self {
121 ListIndexOutOfRange { segment_index, .. }
122 | InvalidIndex { segment_index, .. }
123 | NoLinks { segment_index, .. }
124 | NotFound { segment_index, .. } => {
125 *segment_index += start;
128 }
129 _ => {}
130 }
131 }
132
133 fn with_path(self, path: IpfsPath) -> ResolveError {
137 use RawResolveLocalError::*;
138
139 match self {
140 Loading(cid, e) => ResolveError::Loading(cid, e),
142 UnsupportedDocument(cid, e) => ResolveError::UnsupportedDocument(cid, e),
143 ListIndexOutOfRange {
144 document,
145 segment_index,
146 index,
147 elements,
148 } => ResolveError::ListIndexOutOfRange {
149 document,
150 path: path.into_truncated(segment_index + 1),
151 index,
152 elements,
153 },
154 NoLinks {
155 document,
156 segment_index,
157 } => ResolveError::NoLinks(document, path.into_truncated(segment_index + 1)),
158 InvalidIndex {
159 document,
160 segment_index,
161 }
162 | NotFound {
163 document,
164 segment_index,
165 } => ResolveError::NotFound(document, path.into_truncated(segment_index + 1)),
166 }
167 }
168}
169
170#[derive(Clone, Debug)]
172pub struct IpldDag {
173 ipfs: Option<Ipfs>,
174 repo: Repo<DefaultStorage>,
175}
176
177impl From<Repo<DefaultStorage>> for IpldDag {
178 fn from(repo: Repo<DefaultStorage>) -> Self {
179 IpldDag { ipfs: None, repo }
180 }
181}
182
183impl IpldDag {
184 pub fn new(ipfs: Ipfs) -> Self {
186 let repo = ipfs.repo().clone();
187 IpldDag {
188 ipfs: Some(ipfs),
189 repo,
190 }
191 }
192
193 pub fn put_dag(&self, ipld: impl Serialize) -> DagPut {
197 self.put().serialize(ipld)
198 }
199
200 pub fn get_dag(&self, path: impl Into<IpfsPath>) -> DagGet {
204 self.get().path(path)
205 }
206
207 pub fn put(&self) -> DagPut {
211 DagPut::new(self.clone())
212 }
213
214 pub fn get(&self) -> DagGet {
218 DagGet::new(self.clone())
219 }
220
221 pub(crate) async fn _get(
222 &self,
223 path: IpfsPath,
224 providers: &[PeerId],
225 local_only: bool,
226 timeout: Option<Duration>,
227 ) -> Result<Ipld, ResolveError> {
228 let resolved_path = resolve_path(self.ipfs.as_ref(), path).await?;
229
230 let cid = match resolved_path.root().cid() {
231 Some(cid) => cid,
232 None => return Err(ResolveError::NoCid(resolved_path)),
233 };
234
235 let mut iter = resolved_path.iter().peekable();
236
237 let (node, _) = match self
238 .resolve0(cid, &mut iter, true, providers, local_only, timeout)
239 .await
240 {
241 Ok(t) => t,
242 Err(e) => {
243 drop(iter);
244 return Err(e.with_path(resolved_path));
245 }
246 };
247
248 Ipld::try_from(node)
249 }
250
251 pub async fn resolve(
263 &self,
264 path: IpfsPath,
265 follow_links: bool,
266 providers: &[PeerId],
267 local_only: bool,
268 ) -> Result<(ResolvedNode, SlashedPath), ResolveError> {
269 self._resolve(path, follow_links, providers, local_only, None)
270 .await
271 }
272
273 pub(crate) async fn _resolve(
274 &self,
275 path: IpfsPath,
276 follow_links: bool,
277 providers: &[PeerId],
278 local_only: bool,
279 timeout: Option<Duration>,
280 ) -> Result<(ResolvedNode, SlashedPath), ResolveError> {
281 let resolved_path = resolve_path(self.ipfs.as_ref(), path).await?;
282
283 let cid = match resolved_path.root().cid() {
284 Some(cid) => cid,
285 None => return Err(ResolveError::NoCid(resolved_path)),
286 };
287
288 let (node, matched_segments) = {
289 let mut iter = resolved_path.iter().peekable();
290 match self
291 .resolve0(cid, &mut iter, follow_links, providers, local_only, timeout)
292 .await
293 {
294 Ok(t) => t,
295 Err(e) => {
296 drop(iter);
297 return Err(e.with_path(resolved_path));
298 }
299 }
300 };
301
302 let remaining_path = resolved_path.into_shifted(matched_segments);
306
307 Ok((node, remaining_path))
308 }
309
310 #[allow(clippy::too_many_arguments)]
312 async fn resolve0<'a>(
313 &self,
314 cid: &Cid,
315 segments: &mut Peekable<impl Iterator<Item = &'a str>>,
316 follow_links: bool,
317 providers: &[PeerId],
318 local_only: bool,
319 timeout: Option<Duration>,
320 ) -> Result<(ResolvedNode, usize), RawResolveLocalError> {
321 use LocallyResolved::*;
322
323 let mut current = *cid;
324 let mut total = 0;
325
326 let mut cache = None;
327
328 loop {
329 let block = match self
330 .repo
331 .get_block(current)
332 .providers(providers)
333 .set_local(local_only)
334 .timeout(timeout)
335 .await
336 {
337 Ok(block) => block,
338 Err(e) => return Err(RawResolveLocalError::Loading(current, e)),
339 };
340
341 let start = total;
342
343 let (resolution, matched) = match resolve_local(block, segments, &mut cache) {
344 Ok(t) => t,
345 Err(mut e) => {
346 e.add_starting_point_in_path(start);
347 return Err(e);
348 }
349 };
350 total += matched;
351
352 let (src, dest) = match resolution {
353 Complete(ResolvedNode::Link(src, dest)) => (src, dest),
354 Incomplete(src, lookup) => match self
355 .resolve_hamt(lookup, &mut cache, providers, local_only)
356 .await
357 {
358 Ok(dest) => (src, dest),
359 Err(e) => return Err(RawResolveLocalError::UnsupportedDocument(src, e.into())),
360 },
361 Complete(other) => {
362 return Ok((other, start));
365 }
366 };
367
368 if !follow_links {
369 return Ok((ResolvedNode::Link(src, dest), total));
371 } else {
372 current = dest;
373 }
374 }
375 }
376
377 async fn resolve_hamt(
380 &self,
381 mut lookup: ShardedLookup<'_>,
382 cache: &mut Option<Cache>,
383 providers: &[PeerId],
384 local_only: bool,
385 ) -> Result<Cid, Error> {
386 use MaybeResolved::*;
387
388 loop {
389 let (next, _) = lookup.pending_links();
390
391 let block = self
392 .repo
393 .get_block(next)
394 .providers(providers)
395 .set_local(local_only)
396 .await?;
397
398 match lookup.continue_walk(block.data(), cache)? {
399 NeedToLoadMore(next) => lookup = next,
400 Found(cid) => return Ok(cid),
401 NotFound => return Err(anyhow::anyhow!("key not found: ???")),
402 }
403 }
404 }
405}
406
407#[must_use = "futures do nothing unless you `.await` or poll them"]
408pub struct DagGet {
409 dag_ipld: IpldDag,
410 path: Option<IpfsPath>,
411 providers: Vec<PeerId>,
412 local: bool,
413 timeout: Option<Duration>,
414 span: Option<Span>,
415}
416
417impl DagGet {
418 pub fn new(dag: IpldDag) -> Self {
419 Self {
420 dag_ipld: dag,
421 path: None,
422 providers: vec![],
423 local: false,
424 timeout: None,
425 span: None,
426 }
427 }
428
429 pub fn path(mut self, path: impl Into<IpfsPath>) -> Self {
431 let path = path.into();
432 self.path = Some(path);
433 self
434 }
435
436 pub fn provider(mut self, peer_id: PeerId) -> Self {
438 if !self.providers.contains(&peer_id) {
439 self.providers.push(peer_id);
440 }
441 self
442 }
443
444 pub fn providers(mut self, providers: &[PeerId]) -> Self {
446 self.providers = providers.into();
447 self
448 }
449
450 pub fn local(mut self) -> Self {
452 self.local = true;
453 self
454 }
455
456 pub fn set_local(mut self, local: bool) -> Self {
458 self.local = local;
459 self
460 }
461
462 pub fn timeout(mut self, timeout: Duration) -> Self {
464 self.timeout = Some(timeout);
465 self
466 }
467
468 pub fn deserialized<D: DeserializeOwned>(self) -> DagGetDeserialize<D> {
470 DagGetDeserialize {
471 dag_get: self,
472 _marker: PhantomData,
473 }
474 }
475
476 pub fn span(mut self, span: Span) -> Self {
478 self.span = Some(span);
479 self
480 }
481}
482
483impl IntoFuture for DagGet {
484 type Output = Result<Ipld, ResolveError>;
485
486 type IntoFuture = BoxFuture<'static, Self::Output>;
487
488 fn into_future(self) -> Self::IntoFuture {
489 let span = self.span.unwrap_or(Span::current());
490 async move {
491 let path = self.path.ok_or(ResolveError::PathNotProvided)?;
492 self.dag_ipld
493 ._get(path, &self.providers, self.local, self.timeout)
494 .await
495 }
496 .instrument(span)
497 .boxed()
498 }
499}
500
501#[must_use = "futures do nothing unless you `.await` or poll them"]
502pub struct DagGetDeserialize<D> {
503 dag_get: DagGet,
504 _marker: PhantomData<D>,
505}
506
507impl<D> std::future::IntoFuture for DagGetDeserialize<D>
508where
509 D: DeserializeOwned,
510{
511 type Output = Result<D, anyhow::Error>;
512
513 type IntoFuture = BoxFuture<'static, Self::Output>;
514
515 fn into_future(self) -> Self::IntoFuture {
516 let fut = self.dag_get.into_future();
517 async move {
518 let document = fut.await?;
519 let data = from_ipld(document)?;
520 Ok(data)
521 }
522 .boxed()
523 }
524}
525
526#[must_use = "futures do nothing unless you `.await` or poll them"]
527pub struct DagPut {
528 dag_ipld: IpldDag,
529 codec: BlockCodec,
530 data: Box<dyn FnOnce() -> anyhow::Result<Ipld> + Send + 'static>,
531 hash: Code,
532 pinned: Option<bool>,
533 span: Span,
534 provide: bool,
535}
536
537impl DagPut {
538 pub fn new(dag: IpldDag) -> Self {
539 Self {
540 dag_ipld: dag,
541 codec: BlockCodec::DagCbor,
542 data: Box::new(|| anyhow::bail!("data not available")),
543 hash: Code::Sha2_256,
544 pinned: None,
545 span: Span::current(),
546 provide: false,
547 }
548 }
549
550 pub fn ipld(self, data: Ipld) -> Self {
552 self.serialize(data)
553 }
554
555 pub fn serialize(mut self, data: impl serde::Serialize) -> Self {
557 let result = to_ipld(data).map_err(anyhow::Error::from);
558 self.data = Box::new(move || result);
559 self
560 }
561
562 pub fn pin(mut self, recursive: bool) -> Self {
564 self.pinned = Some(recursive);
565 self
566 }
567
568 pub fn provide(mut self) -> Self {
570 self.provide = true;
571 self
572 }
573
574 pub fn hash(mut self, code: Code) -> Self {
576 self.hash = code;
577 self
578 }
579
580 pub fn codec(mut self, codec: BlockCodec) -> Self {
582 self.codec = codec;
583 self
584 }
585
586 pub fn span(mut self, span: Span) -> Self {
588 self.span = span;
589 self
590 }
591}
592
593impl IntoFuture for DagPut {
594 type Output = Result<Cid, anyhow::Error>;
595
596 type IntoFuture = BoxFuture<'static, Self::Output>;
597
598 fn into_future(self) -> Self::IntoFuture {
599 let span = self.span;
600 async move {
601 if self.provide && self.dag_ipld.ipfs.is_none() {
602 anyhow::bail!("Ipfs is offline");
603 }
604
605 let _g = self.dag_ipld.repo.gc_guard().await;
606
607 let data = (self.data)()?;
608 let bytes = match self.codec {
609 BlockCodec::Raw => from_ipld(data)?,
610 BlockCodec::DagCbor => {
611 serde_ipld_dagcbor::codec::DagCborCodec::encode_to_vec(&data)?
612 }
613 BlockCodec::DagJson => {
614 serde_ipld_dagjson::codec::DagJsonCodec::encode_to_vec(&data)?
615 }
616 BlockCodec::DagPb => ipld_dagpb::from_ipld(&data)?,
617 };
618
619 let code = self.hash;
620 let hash = code.digest(&bytes);
621 let version = if self.codec == BlockCodec::DagPb {
622 Version::V0
623 } else {
624 Version::V1
625 };
626 let cid = Cid::new(version, self.codec.into(), hash)?;
627 let block = Block::new(cid, bytes)?;
628 let cid = self.dag_ipld.repo.put_block(&block).await?;
629
630 if let Some(opt) = self.pinned {
631 if !self.dag_ipld.repo.is_pinned(&cid).await? {
632 self.dag_ipld.repo.insert_pin(&cid, opt, true).await?;
633 }
634 }
635
636 if self.provide {
637 if let Some(ipfs) = &self.dag_ipld.ipfs {
638 if let Err(e) = ipfs.provide(cid).await {
639 error!("Failed to provide content over DHT: {e}")
640 }
641 }
642 }
643
644 Ok(cid)
645 }
646 .instrument(span)
647 .boxed()
648 }
649}
650
651async fn resolve_path(
652 ipfs: Option<&Ipfs>,
653 path: impl Borrow<IpfsPath>,
654) -> Result<IpfsPath, ResolveError> {
655 let path = path.borrow().clone();
656 let resolved_path = match ipfs {
657 Some(ipfs) => ipfs
658 .resolve_ipns(&path, true)
659 .await
660 .map_err(|_| ResolveError::IpnsResolutionFailed(path))?,
661 None => {
662 if !matches!(path.root(), PathRoot::Ipld(_)) {
663 return Err(ResolveError::IpnsResolutionFailed(path));
664 }
665 path
666 }
667 };
668
669 Ok(resolved_path)
670}
671
672#[derive(Debug, PartialEq)]
677pub enum ResolvedNode {
678 Block(Block),
680 DagPbData(Cid, NodeData<Bytes>),
684 Projection(Cid, Ipld),
686 Link(Cid, Cid),
688}
689
690impl ResolvedNode {
691 pub fn source(&self) -> &Cid {
693 match self {
694 ResolvedNode::Block(block) => block.cid(),
695 ResolvedNode::DagPbData(cid, ..)
696 | ResolvedNode::Projection(cid, ..)
697 | ResolvedNode::Link(cid, ..) => cid,
698 }
699 }
700
701 pub fn into_unixfs_block(self) -> Result<Block, UnexpectedResolved> {
705 let codec = self.source().codec();
706 let dag_pb: u64 = BlockCodec::DagPb.into();
707 let raw: u64 = BlockCodec::Raw.into();
708 if codec != dag_pb && codec != raw {
709 Err(UnexpectedResolved::UnexpectedCodec(dag_pb, Box::new(self)))
710 } else {
711 match self {
712 ResolvedNode::Block(b) => Ok(b),
713 _ => Err(UnexpectedResolved::NonBlock(Box::new(self))),
714 }
715 }
716 }
717}
718
719impl TryFrom<ResolvedNode> for Ipld {
720 type Error = ResolveError;
721 fn try_from(r: ResolvedNode) -> Result<Ipld, Self::Error> {
722 use ResolvedNode::*;
723
724 match r {
725 Block(block) => Ok(block
726 .to_ipld()
727 .map_err(move |e| ResolveError::UnsupportedDocument(*block.cid(), e.into()))?),
728 DagPbData(_, node_data) => Ok(Ipld::Bytes(node_data.node_data().to_vec())),
729 Projection(_, ipld) => Ok(ipld),
730 Link(_, cid) => Ok(Ipld::Link(cid)),
731 }
732 }
733}
734
735#[derive(Debug)]
737enum LocallyResolved<'a> {
738 Complete(ResolvedNode),
740
741 Incomplete(Cid, ShardedLookup<'a>),
744}
745
746#[cfg(test)]
747impl LocallyResolved<'_> {
748 fn unwrap_complete(self) -> ResolvedNode {
749 match self {
750 LocallyResolved::Complete(rn) => rn,
751 x => unreachable!("{:?}", x),
752 }
753 }
754}
755
756impl From<ResolvedNode> for LocallyResolved<'static> {
757 fn from(r: ResolvedNode) -> LocallyResolved<'static> {
758 LocallyResolved::Complete(r)
759 }
760}
761
762fn resolve_local<'a>(
765 block: Block,
766 segments: &mut Peekable<impl Iterator<Item = &'a str>>,
767 cache: &mut Option<Cache>,
768) -> Result<(LocallyResolved<'a>, usize), RawResolveLocalError> {
769 if segments.peek().is_none() {
770 return Ok((LocallyResolved::Complete(ResolvedNode::Block(block)), 0));
771 }
772
773 if block.cid().codec() == <BlockCodec as Into<u64>>::into(BlockCodec::DagPb) {
774 let segment = segments.next().unwrap();
782 let (cid, data) = block.into_inner();
783 Ok(resolve_local_dagpb(
784 cid,
785 data,
786 segment,
787 segments.peek().is_none(),
788 cache,
789 )?)
790 } else {
791 let ipld = match block.to_ipld() {
792 Ok(ipld) => ipld,
793 Err(e) => {
794 return Err(RawResolveLocalError::UnsupportedDocument(
795 *block.cid(),
796 e.into(),
797 ));
798 }
799 };
800 resolve_local_ipld(*block.cid(), ipld, segments)
801 }
802}
803
804fn resolve_local_dagpb<'a>(
808 cid: Cid,
809 data: Bytes,
810 segment: &'a str,
811 is_last: bool,
812 cache: &mut Option<Cache>,
813) -> Result<(LocallyResolved<'a>, usize), RawResolveLocalError> {
814 match resolve(&data, segment, cache) {
815 Ok(MaybeResolved::NeedToLoadMore(lookup)) => {
816 Ok((LocallyResolved::Incomplete(cid, lookup), 0))
817 }
818 Ok(MaybeResolved::Found(dest)) => {
819 Ok((LocallyResolved::Complete(ResolvedNode::Link(cid, dest)), 1))
820 }
821 Ok(MaybeResolved::NotFound) => {
822 if segment == "Data" && is_last {
823 let wrapped = wrap_node_data(data).expect("already deserialized once");
824 return Ok((
825 LocallyResolved::Complete(ResolvedNode::DagPbData(cid, wrapped)),
826 1,
827 ));
828 }
829 Err(RawResolveLocalError::NotFound {
830 document: cid,
831 segment_index: 0,
832 })
833 }
834 Err(rust_unixfs::ResolveError::UnexpectedType(ut)) if ut.is_file() => {
835 Err(RawResolveLocalError::NotFound {
838 document: cid,
839 segment_index: 0,
840 })
841 }
842 Err(e) => Err(RawResolveLocalError::UnsupportedDocument(cid, e.into())),
843 }
844}
845
846fn resolve_local_ipld<'a>(
858 document: Cid,
859 mut ipld: Ipld,
860 segments: &mut Peekable<impl Iterator<Item = &'a str>>,
861) -> Result<(LocallyResolved<'a>, usize), RawResolveLocalError> {
862 let mut matched_count = 0;
863 loop {
864 ipld = match ipld {
865 Ipld::Link(cid) => {
866 if segments.peek() != Some(&".") {
867 return Ok((ResolvedNode::Link(document, cid).into(), matched_count));
870 } else {
871 Ipld::Link(cid)
872 }
873 }
874 ipld => ipld,
875 };
876
877 ipld = match (ipld, segments.next()) {
878 (Ipld::Link(cid), Some(".")) => {
879 return Ok((ResolvedNode::Link(document, cid).into(), matched_count + 1));
880 }
881 (Ipld::Link(_), Some(_)) => {
882 unreachable!("case already handled above before advancing the iterator")
883 }
884 (Ipld::Map(mut map), Some(segment)) => {
885 let found = match map.remove(segment) {
886 Some(f) => f,
887 None => {
888 return Err(RawResolveLocalError::NotFound {
889 document,
890 segment_index: matched_count,
891 });
892 }
893 };
894 matched_count += 1;
895 found
896 }
897 (Ipld::List(mut vec), Some(segment)) => match segment.parse::<usize>() {
898 Ok(index) if index < vec.len() => {
899 matched_count += 1;
900 vec.swap_remove(index)
901 }
902 Ok(index) => {
903 return Err(RawResolveLocalError::ListIndexOutOfRange {
904 document,
905 segment_index: matched_count,
906 index,
907 elements: vec.len(),
908 });
909 }
910 Err(_) => {
911 return Err(RawResolveLocalError::InvalidIndex {
912 document,
913 segment_index: matched_count,
914 });
915 }
916 },
917 (_, Some(_)) => {
918 return Err(RawResolveLocalError::NoLinks {
919 document,
920 segment_index: matched_count,
921 });
922 }
923 (anything, None) => {
925 return Ok((
926 ResolvedNode::Projection(document, anything).into(),
927 matched_count,
928 ));
929 }
930 };
931 }
932}
933
934#[cfg(test)]
935mod tests {
936 use super::*;
937 use crate::Node;
938 use ipld_core::ipld;
939 use serde_ipld_dagcbor::codec::DagCborCodec;
940
941 #[tokio::test]
942 async fn test_resolve_root_cid() {
943 let Node { ipfs, .. } = Node::new("test_node").await;
944 let dag = IpldDag::new(ipfs);
945 let data = ipld!([1, 2, 3]);
946 let cid = dag.put_dag(data.clone()).await.unwrap();
947 let res = dag.get_dag(IpfsPath::from(cid)).await.unwrap();
948 assert_eq!(res, data);
949 }
950
951 #[tokio::test]
952 async fn test_resolve_array_elem() {
953 let Node { ipfs, .. } = Node::new("test_node").await;
954 let dag = IpldDag::new(ipfs);
955 let data = ipld!([1, 2, 3]);
956 let cid = dag.put_dag(data.clone()).await.unwrap();
957 let res = dag
958 .get_dag(IpfsPath::from(cid).sub_path("1").unwrap())
959 .await
960 .unwrap();
961 assert_eq!(res, ipld!(2));
962 }
963
964 #[tokio::test]
965 async fn test_resolve_nested_array_elem() {
966 let Node { ipfs, .. } = Node::new("test_node").await;
967 let dag = IpldDag::new(ipfs);
968 let data = ipld!([1, [2], 3,]);
969 let cid = dag.put_dag(data).await.unwrap();
970 let res = dag
971 .get_dag(IpfsPath::from(cid).sub_path("1/0").unwrap())
972 .await
973 .unwrap();
974 assert_eq!(res, ipld!(2));
975 }
976
977 #[tokio::test]
978 async fn test_resolve_object_elem() {
979 let Node { ipfs, .. } = Node::new("test_node").await;
980 let dag = IpldDag::new(ipfs);
981 let data = ipld!({
982 "key": false,
983 });
984 let cid = dag.put_dag(data).await.unwrap();
985 let res = dag
986 .get_dag(IpfsPath::from(cid).sub_path("key").unwrap())
987 .await
988 .unwrap();
989 assert_eq!(res, ipld!(false));
990 }
991
992 #[tokio::test]
993 async fn test_resolve_cid_elem() {
994 let Node { ipfs, .. } = Node::new("test_node").await;
995 let dag = IpldDag::new(ipfs);
996 let data1 = ipld!([1]);
997 let cid1 = dag.put_dag(data1).await.unwrap();
998 let data2 = ipld!([cid1]);
999 let cid2 = dag.put_dag(data2).await.unwrap();
1000 let res = dag
1001 .get_dag(IpfsPath::from(cid2).sub_path("0/0").unwrap())
1002 .await
1003 .unwrap();
1004 assert_eq!(res, ipld!(1));
1005 }
1006
1007 fn example_doc_and_cid() -> (Cid, Ipld, Cid) {
1010 let cid = Cid::try_from("QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n").unwrap();
1011 let doc = ipld!({
1012 "nested": {
1013 "even": [
1014 {
1015 "more": 5
1016 },
1017 {
1018 "or": "this",
1019 },
1020 {
1021 "or": cid,
1022 },
1023 {
1024 "5": "or",
1025 }
1026 ],
1027 }
1028 });
1029 let root =
1030 Cid::try_from("bafyreielwgy762ox5ndmhx6kpi6go6il3gzahz3ngagb7xw3bj3aazeita").unwrap();
1031 (root, doc, cid)
1032 }
1033
1034 #[test]
1035 fn resolve_cbor_locally_to_end() {
1036 let (root, example_doc, _) = example_doc_and_cid();
1037
1038 let good_examples = [
1039 (
1040 "bafyreielwgy762ox5ndmhx6kpi6go6il3gzahz3ngagb7xw3bj3aazeita/nested/even/0/more",
1041 Ipld::Integer(5),
1042 ),
1043 (
1044 "bafyreielwgy762ox5ndmhx6kpi6go6il3gzahz3ngagb7xw3bj3aazeita/nested/even/1/or",
1045 Ipld::from("this"),
1046 ),
1047 (
1048 "bafyreielwgy762ox5ndmhx6kpi6go6il3gzahz3ngagb7xw3bj3aazeita/nested/even/3/5",
1049 Ipld::from("or"),
1050 ),
1051 ];
1052
1053 for (path, expected) in &good_examples {
1054 let p = IpfsPath::try_from(*path).unwrap();
1055
1056 let (resolved, matched_segments) =
1057 resolve_local_ipld(root, example_doc.clone(), &mut p.iter().peekable()).unwrap();
1058
1059 assert_eq!(matched_segments, 4);
1060
1061 match resolved.unwrap_complete() {
1062 ResolvedNode::Projection(_, p) if &p == expected => {}
1063 x => unreachable!("unexpected {:?}", x),
1064 }
1065
1066 let remaining_path = p.iter().skip(matched_segments).collect::<Vec<&str>>();
1067 assert!(remaining_path.is_empty(), "{remaining_path:?}");
1068 }
1069 }
1070
1071 #[test]
1072 fn resolve_cbor_locally_to_link() {
1073 let (root, example_doc, target) = example_doc_and_cid();
1074
1075 let p = IpfsPath::try_from(
1076 "bafyreielwgy762ox5ndmhx6kpi6go6il3gzahz3ngagb7xw3bj3aazeita/nested/even/2/or/foobar/trailer", )
1078 .unwrap();
1079
1080 let (resolved, matched_segments) =
1081 resolve_local_ipld(root, example_doc, &mut p.iter().peekable()).unwrap();
1082
1083 match resolved.unwrap_complete() {
1084 ResolvedNode::Link(_, cid) if cid == target => {}
1085 x => unreachable!("{:?}", x),
1086 }
1087
1088 assert_eq!(matched_segments, 4);
1089
1090 let remaining_path = p.iter().skip(matched_segments).collect::<Vec<&str>>();
1091 assert_eq!(remaining_path, &["foobar", "trailer"]);
1092 }
1093
1094 #[test]
1095 fn resolve_cbor_locally_to_link_with_dot() {
1096 let (root, example_doc, cid) = example_doc_and_cid();
1097
1098 let p = IpfsPath::try_from(
1099 "bafyreielwgy762ox5ndmhx6kpi6go6il3gzahz3ngagb7xw3bj3aazeita/nested/even/2/or/./foobar/trailer",
1100 )
1102 .unwrap();
1103
1104 let (resolved, matched_segments) =
1105 resolve_local_ipld(root, example_doc, &mut p.iter().peekable()).unwrap();
1106 assert_eq!(resolved.unwrap_complete(), ResolvedNode::Link(root, cid));
1107 assert_eq!(matched_segments, 5);
1108
1109 let remaining_path = p.iter().skip(matched_segments).collect::<Vec<&str>>();
1110 assert_eq!(remaining_path, &["foobar", "trailer"]);
1111 }
1112
1113 #[test]
1114 fn resolve_cbor_locally_not_found_map_key() {
1115 let (root, example_doc, _) = example_doc_and_cid();
1116 let p = IpfsPath::try_from(
1117 "bafyreielwgy762ox5ndmhx6kpi6go6il3gzahz3ngagb7xw3bj3aazeita/foobar/trailer",
1118 )
1119 .unwrap();
1120
1121 let e = resolve_local_ipld(root, example_doc, &mut p.iter().peekable()).unwrap_err();
1122 assert!(
1123 matches!(
1124 e,
1125 RawResolveLocalError::NotFound {
1126 segment_index: 0,
1127 ..
1128 }
1129 ),
1130 "{e:?}"
1131 );
1132 }
1133
1134 #[test]
1135 fn resolve_cbor_locally_too_large_list_index() {
1136 let (root, example_doc, _) = example_doc_and_cid();
1137 let p = IpfsPath::try_from(
1138 "bafyreielwgy762ox5ndmhx6kpi6go6il3gzahz3ngagb7xw3bj3aazeita/nested/even/3000",
1139 )
1140 .unwrap();
1141
1142 let e = resolve_local_ipld(root, example_doc, &mut p.iter().peekable()).unwrap_err();
1143 assert!(
1144 matches!(
1145 e,
1146 RawResolveLocalError::ListIndexOutOfRange {
1147 segment_index: 2,
1148 index: 3000,
1149 elements: 4,
1150 ..
1151 }
1152 ),
1153 "{e:?}"
1154 );
1155 }
1156
1157 #[test]
1158 fn resolve_cbor_locally_non_usize_index() {
1159 let (root, example_doc, _) = example_doc_and_cid();
1160 let p = IpfsPath::try_from(
1161 "bafyreielwgy762ox5ndmhx6kpi6go6il3gzahz3ngagb7xw3bj3aazeita/nested/even/-1",
1162 )
1163 .unwrap();
1164
1165 let e = resolve_local_ipld(root, example_doc, &mut p.iter().peekable()).unwrap_err();
1167 assert!(
1168 matches!(
1169 e,
1170 RawResolveLocalError::InvalidIndex {
1171 segment_index: 2,
1172 ..
1173 }
1174 ),
1175 "{e:?}"
1176 );
1177 }
1178
1179 #[tokio::test]
1180 async fn resolve_through_link() {
1181 let Node { ipfs, .. } = Node::new("test_node").await;
1182 let dag = IpldDag::new(ipfs);
1183 let ipld = ipld!([1]);
1184 let cid1 = dag.put_dag(ipld).await.unwrap();
1185 let ipld = ipld!([cid1]);
1186 let cid2 = dag.put_dag(ipld).await.unwrap();
1187
1188 let prefix = IpfsPath::from(cid2);
1189
1190 let equiv_paths = vec![
1193 prefix.sub_path("0/0").unwrap(),
1194 prefix.sub_path("0/./0").unwrap(),
1195 ];
1196
1197 for p in equiv_paths {
1198 let cloned = p.clone();
1199 match dag.resolve(p, true, &[], false).await.unwrap() {
1200 (ResolvedNode::Projection(_, Ipld::Integer(1)), remaining_path) => {
1201 assert_eq!(remaining_path, ["0"][..], "{cloned}");
1202 }
1203 x => unreachable!("{:?}", x),
1204 }
1205 }
1206 }
1207
1208 #[tokio::test]
1209 async fn fail_resolving_first_segment() {
1210 let Node { ipfs, .. } = Node::new("test_node").await;
1211 let dag = IpldDag::new(ipfs);
1212 let ipld = ipld!([1]);
1213 let cid1 = dag.put_dag(ipld).await.unwrap();
1214 let ipld = ipld!({ "0": cid1 });
1215 let cid2 = dag.put_dag(ipld).await.unwrap();
1216
1217 let path = IpfsPath::from(cid2).sub_path("1/a").unwrap();
1218
1219 let e = dag.resolve(path, true, &[], false).await.unwrap_err();
1221 assert_eq!(e.to_string(), format!("no link named \"1\" under {cid2}"));
1222 }
1223
1224 #[tokio::test]
1225 async fn fail_resolving_last_segment() {
1226 let Node { ipfs, .. } = Node::new("test_node").await;
1227 let dag = IpldDag::new(ipfs);
1228 let ipld = ipld!([1]);
1229 let cid1 = dag.put_dag(ipld).await.unwrap();
1230 let ipld = ipld!([cid1]);
1231 let cid2 = dag.put_dag(ipld).await.unwrap();
1232
1233 let path = IpfsPath::from(cid2).sub_path("0/a").unwrap();
1234
1235 let e = dag.resolve(path, true, &[], false).await.unwrap_err();
1237 assert_eq!(e.to_string(), format!("no link named \"a\" under {cid1}"));
1238 }
1239
1240 #[tokio::test]
1241 async fn fail_resolving_through_file() {
1242 let Node { ipfs, .. } = Node::new("test_node").await;
1243
1244 let mut adder = rust_unixfs::file::adder::FileAdder::builder()
1247 .with_cid_version(Version::V0)
1248 .build();
1249 let (mut blocks, _) = adder.push(b"foobar\n");
1250 assert_eq!(blocks.next(), None);
1251
1252 let mut blocks = adder.finish();
1253
1254 let (cid, data) = blocks.next().unwrap();
1255 assert_eq!(blocks.next(), None);
1256
1257 ipfs.put_block(&Block::new(cid, data).unwrap())
1258 .await
1259 .unwrap();
1260
1261 let path = IpfsPath::from(cid).sub_path("anything-here").unwrap();
1262
1263 let e = ipfs
1264 .dag()
1265 .resolve(path, true, &[], false)
1266 .await
1267 .unwrap_err();
1268
1269 assert_eq!(
1270 e.to_string(),
1271 format!("no link named \"anything-here\" under {cid}")
1272 );
1273 }
1274
1275 #[tokio::test]
1276 async fn fail_resolving_through_dir() {
1277 let Node { ipfs, .. } = Node::new("test_node").await;
1278
1279 let mut adder = rust_unixfs::file::adder::FileAdder::default();
1280 let (mut blocks, _) = adder.push(b"foobar\n");
1281 assert_eq!(blocks.next(), None);
1282
1283 let mut blocks = adder.finish();
1284
1285 let (cid, data) = blocks.next().unwrap();
1286 assert_eq!(blocks.next(), None);
1287
1288 let total_size = data.len();
1289
1290 ipfs.put_block(&Block::new(cid, data).unwrap())
1291 .await
1292 .unwrap();
1293
1294 let mut opts = rust_unixfs::dir::builder::TreeOptions::default();
1295 opts.wrap_with_directory();
1296
1297 let mut tree = rust_unixfs::dir::builder::BufferingTreeBuilder::new(opts);
1298 tree.put_link("something/best-file-in-the-world", cid, total_size as u64)
1299 .unwrap();
1300
1301 let mut iter = tree.build();
1302 let mut cids = Vec::new();
1303
1304 while let Some(node) = iter.next_borrowed() {
1305 let node = node.unwrap();
1306 let block = Block::new(node.cid.to_owned(), node.block.to_vec()).unwrap();
1307
1308 ipfs.put_block(&block).await.unwrap();
1309
1310 cids.push(node.cid.to_owned());
1311 }
1312
1313 cids.reverse();
1315
1316 let path = IpfsPath::from(cids[0].to_owned())
1317 .sub_path("something/second-best-file")
1318 .unwrap();
1319
1320 let e = ipfs
1321 .dag()
1322 .resolve(path, true, &[], false)
1323 .await
1324 .unwrap_err();
1325
1326 assert_eq!(
1327 e.to_string(),
1328 format!("no link named \"second-best-file\" under {}", cids[1])
1329 );
1330 }
1331
1332 #[test]
1333 fn observes_strict_order_of_map_keys() {
1334 let map = ipld!({
1335 "omega": Ipld::Null,
1336 "bar": Ipld::Null,
1337 "alpha": Ipld::Null,
1338 "foo": Ipld::Null,
1339 });
1340
1341 let bytes = DagCborCodec::encode_to_vec(&map).unwrap();
1342
1343 assert_eq!(
1344 bytes.as_slice(),
1345 &[
1346 164, 99, 98, 97, 114, 246, 99, 102, 111, 111, 246, 101, 97, 108, 112, 104, 97, 246,
1347 101, 111, 109, 101, 103, 97, 246
1348 ]
1349 );
1350 }
1351}