use crate::cid::Cid;
use crate::error::{BlockError, IpldError};
use crate::ipld::Ipld;
use crate::path::Path;
use crate::store::{Store, StoreIpldExt};
use async_trait::async_trait;
use failure::Fail;
#[derive(Debug, Fail)]
pub enum DagError {
#[fail(display = "Path segment is not a number.")]
NotNumber(std::num::ParseIntError),
#[fail(display = "Cannot index into")]
NotIndexable,
#[fail(display = "{}", _0)]
Ipld(IpldError),
#[fail(display = "{}", _0)]
Block(BlockError),
}
impl From<std::num::ParseIntError> for DagError {
fn from(err: std::num::ParseIntError) -> Self {
Self::NotNumber(err)
}
}
impl From<IpldError> for DagError {
fn from(err: IpldError) -> Self {
Self::Ipld(err)
}
}
impl From<BlockError> for DagError {
fn from(err: BlockError) -> Self {
Self::Block(err)
}
}
#[derive(Clone, Debug, PartialEq, Hash)]
pub struct DagPath<'a>(&'a Cid, Path);
impl<'a> DagPath<'a> {
pub fn new<T: Into<Path>>(cid: &'a Cid, path: T) -> Self {
Self(cid, path.into())
}
}
impl<'a> From<&'a Cid> for DagPath<'a> {
fn from(cid: &'a Cid) -> Self {
Self(cid, Default::default())
}
}
#[async_trait]
pub trait StoreDagExt {
async fn get<'a>(&self, path: &DagPath<'a>) -> Result<Option<Ipld>, DagError>;
}
#[async_trait]
impl<TStore: Store> StoreDagExt for TStore {
async fn get<'a>(&self, path: &DagPath<'a>) -> Result<Option<Ipld>, DagError> {
let root = self.read_ipld(&path.0).await?;
let mut root = if let Some(root) = root {
root
} else {
return Ok(None);
};
let mut ipld = &root;
for segment in path.1.iter() {
if let Some(next) = match ipld {
Ipld::List(_) => {
let index: usize = segment.parse()?;
ipld.get(index)
}
Ipld::Map(_) => ipld.get(segment.as_str()),
_ => return Err(DagError::NotIndexable),
} {
if let Ipld::Link(cid) = next {
if let Some(cid) = self.read_ipld(cid).await? {
root = cid;
ipld = &root;
} else {
return Ok(None);
}
} else {
ipld = next;
}
} else {
return Ok(None);
}
}
Ok(Some(ipld.to_owned()))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ipld;
use crate::store::{BufStore, MemStore, StoreCborExt};
use crate::DefaultHash as H;
use async_std::task;
#[test]
fn test_dag() {
task::block_on(async {
let store = BufStore::new(MemStore::default(), 16, 16);
let ipld1 = ipld!({"a": 3});
let cid = store.write_cbor::<H, _>(&ipld1).await.unwrap();
let ipld2 = ipld!({"root": [{"child": &cid}]});
let root = store.write_cbor::<H, _>(&ipld2).await.unwrap();
let path = DagPath::new(&root, "root/0/child/a");
assert_eq!(store.get(&path).await.unwrap(), Some(Ipld::Integer(3)));
});
}
}