pub mod dag_cbor;
pub mod dag_json;
pub mod dag_pb;
#[macro_use]
pub mod ipld_macro;
use cid::{Cid, Codec};
use dag_cbor::DagCborCodec;
use dag_json::DagJsonCodec;
use dag_pb::DagPbCodec;
use multihash::Multihash;
use std::collections::BTreeMap;
use thiserror::Error;
#[derive(Clone, Debug, PartialEq)]
pub enum Ipld {
Null,
Bool(bool),
Integer(i128),
Float(f64),
String(String),
Bytes(Vec<u8>),
List(Vec<Ipld>),
Map(BTreeMap<String, Ipld>),
Link(Cid),
}
macro_rules! derive_to_ipld_prim {
($enum:ident, $ty:ty, $fn:ident) => {
impl From<$ty> for Ipld {
fn from(t: $ty) -> Self {
Ipld::$enum(t.$fn() as _)
}
}
};
}
macro_rules! derive_to_ipld {
($enum:ident, $ty:ty, $fn:ident) => {
impl From<$ty> for Ipld {
fn from(t: $ty) -> Self {
Ipld::$enum(t.$fn())
}
}
};
}
derive_to_ipld!(Bool, bool, clone);
derive_to_ipld_prim!(Integer, i8, clone);
derive_to_ipld_prim!(Integer, i16, clone);
derive_to_ipld_prim!(Integer, i32, clone);
derive_to_ipld_prim!(Integer, i64, clone);
derive_to_ipld_prim!(Integer, i128, clone);
derive_to_ipld_prim!(Integer, isize, clone);
derive_to_ipld_prim!(Integer, u8, clone);
derive_to_ipld_prim!(Integer, u16, clone);
derive_to_ipld_prim!(Integer, u32, clone);
derive_to_ipld_prim!(Integer, u64, clone);
derive_to_ipld_prim!(Integer, usize, clone);
derive_to_ipld_prim!(Float, f32, clone);
derive_to_ipld_prim!(Float, f64, clone);
derive_to_ipld!(String, String, into);
derive_to_ipld!(String, &str, to_string);
derive_to_ipld!(Bytes, Vec<u8>, into);
derive_to_ipld!(Bytes, &[u8], to_vec);
derive_to_ipld!(List, Vec<Ipld>, into);
derive_to_ipld!(Map, BTreeMap<String, Ipld>, to_owned);
derive_to_ipld!(Link, Cid, clone);
derive_to_ipld!(Link, &Cid, to_owned);
pub enum IpldIndex<'a> {
List(usize),
Map(String),
MapRef(&'a str),
}
impl<'a> From<usize> for IpldIndex<'a> {
fn from(index: usize) -> Self {
Self::List(index)
}
}
impl<'a> From<String> for IpldIndex<'a> {
fn from(key: String) -> Self {
Self::Map(key)
}
}
impl<'a> From<&'a str> for IpldIndex<'a> {
fn from(key: &'a str) -> Self {
Self::MapRef(key)
}
}
impl Ipld {
pub fn get<'a, T: Into<IpldIndex<'a>>>(&self, index: T) -> Option<&Ipld> {
match self {
Ipld::List(l) => match index.into() {
IpldIndex::List(i) => l.get(i),
_ => None,
},
Ipld::Map(m) => match index.into() {
IpldIndex::Map(ref key) => m.get(key),
IpldIndex::MapRef(key) => m.get(key),
_ => None,
},
_ => None,
}
}
pub fn iter(&self) -> IpldIter<'_> {
IpldIter {
stack: vec![Box::new(vec![self].into_iter())],
}
}
}
pub struct IpldIter<'a> {
stack: Vec<Box<dyn Iterator<Item = &'a Ipld> + 'a>>,
}
impl<'a> Iterator for IpldIter<'a> {
type Item = &'a Ipld;
fn next(&mut self) -> Option<Self::Item> {
loop {
if let Some(iter) = self.stack.last_mut() {
if let Some(ipld) = iter.next() {
match ipld {
Ipld::List(list) => {
self.stack.push(Box::new(list.iter()));
}
Ipld::Map(map) => {
self.stack.push(Box::new(map.values()));
}
_ => {}
}
return Some(ipld);
} else {
self.stack.pop();
}
} else {
return None;
}
}
}
}
#[derive(Debug, Error)]
pub enum IpldError {
#[error("Expected a boolean.")]
NotBool,
#[error("Expected an integer.")]
NotInteger,
#[error("Expected a float.")]
NotFloat,
#[error("Expected a string.")]
NotString,
#[error("Expected bytes.")]
NotBytes,
#[error("Expected a list.")]
NotList,
#[error("Expected a map.")]
NotMap,
#[error("Expected a cid.")]
NotLink,
#[error("Expected a key.")]
NotKey,
#[error("Index not found.")]
IndexNotFound,
#[error("Key not found.")]
KeyNotFound,
}
#[derive(Debug, Error)]
pub enum BlockError {
#[error("Block size {0} exceeds MAX_BLOCK_SIZE.")]
BlockTooLarge(usize),
#[error("Hash does not match the CID.")]
InvalidHash(Multihash),
#[error("Unsupported codec {0:?}.")]
UnsupportedCodec(cid::Codec),
#[error("Unsupported multihash {0:?}.")]
UnsupportedMultihash(multihash::Code),
#[error("Codec error: {0}")]
CodecError(Box<dyn std::error::Error + Send + Sync>),
#[error("{0}")]
Io(std::io::Error),
#[error("{0}")]
Cid(cid::Error),
#[error("Invalid link.")]
InvalidLink,
}
impl From<std::io::Error> for BlockError {
fn from(err: std::io::Error) -> Self {
Self::Io(err)
}
}
impl From<cid::Error> for BlockError {
fn from(err: cid::Error) -> Self {
Self::Cid(err)
}
}
pub const MAX_BLOCK_SIZE: usize = 1_048_576;
pub fn validate(cid: &Cid, data: &[u8]) -> Result<(), BlockError> {
if data.len() > MAX_BLOCK_SIZE {
return Err(BlockError::BlockTooLarge(data.len()));
}
let hash = cid.hash().algorithm().digest(&data);
if hash.as_ref() != cid.hash() {
return Err(BlockError::InvalidHash(hash));
}
Ok(())
}
pub fn encode_ipld(ipld: &Ipld, codec: Codec) -> Result<Box<[u8]>, BlockError> {
let bytes = match codec {
Codec::DagCBOR => DagCborCodec::encode(ipld)?,
Codec::DagProtobuf => DagPbCodec::encode(ipld)?,
Codec::DagJSON => DagJsonCodec::encode(ipld)?,
Codec::Raw => {
if let Ipld::Bytes(bytes) = ipld {
bytes.to_vec().into_boxed_slice()
} else {
return Err(BlockError::CodecError(IpldError::NotBytes.into()));
}
}
_ => return Err(BlockError::UnsupportedCodec(codec)),
};
Ok(bytes)
}
pub fn decode_ipld(cid: &Cid, data: &[u8]) -> Result<Ipld, BlockError> {
let ipld = match cid.codec() {
Codec::DagCBOR => DagCborCodec::decode(data)?,
Codec::DagProtobuf => DagPbCodec::decode(data)?,
Codec::DagJSON => DagJsonCodec::decode(data)?,
Codec::Raw => Ipld::Bytes(data.to_vec()),
_ => return Err(BlockError::UnsupportedCodec(cid.codec())),
};
Ok(ipld)
}