diff --git a/Cargo.lock b/Cargo.lock index 87e4d46..03942d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -357,6 +357,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "digest" version = "0.10.7" @@ -436,6 +442,7 @@ dependencies = [ "libipld", "libipld-cbor", "multihash", + "pretty_assertions", "proptest", "thiserror", ] @@ -716,6 +723,16 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "pretty_assertions" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +dependencies = [ + "diff", + "yansi", +] + [[package]] name = "proc-macro-crate" version = "1.1.3" @@ -1305,3 +1322,9 @@ name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" diff --git a/Cargo.toml b/Cargo.toml index 6e05bd9..f89936b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ thiserror = "1.0" [dev-dependencies] criterion = "0.5" libipld-cbor = "0.16" +pretty_assertions = "1.4.0" proptest = "1.4" [[bench]] diff --git a/flake.nix b/flake.nix index 35b08e4..1a4060f 100644 --- a/flake.nix +++ b/flake.nix @@ -98,6 +98,18 @@ ]; commands = [ + { + name = "release"; + help = "[DEFAULT] Release (optimized build) for current native target"; + category = "release"; + command = "release:native"; + } + { + name = "release:native"; + help = "Release for current native target"; + category = "release"; + command = "${pkgs.cargo}/bin/cargo build --release"; + } { name = "build"; help = "[DEFAULT] Build for current native target"; diff --git a/src/extractor.rs b/src/extractor.rs index 2206af9..6cb71a3 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -113,10 +113,10 @@ where #[cfg(test)] mod tests { use super::*; - use libipld::{cid::CidGeneric, ipld}; use libipld_cbor::DagCborCodec; use multihash::Code::Sha2_256; + use pretty_assertions::{assert_eq, assert_ne}; use proptest::prelude::*; use std::collections::BTreeMap; diff --git a/src/inliner/exactly_once.rs b/src/inliner/exactly_once.rs index 97ee2c4..4f50392 100644 --- a/src/inliner/exactly_once.rs +++ b/src/inliner/exactly_once.rs @@ -119,6 +119,7 @@ impl<'a, S: Store + ?Sized> ExactlyOnce<'a, S> { }) } + // FIXME pub fn tryme(&'a mut self) -> Result<(), Stuck<'a, S>> { self.last(); match self.stuck_at { @@ -154,7 +155,9 @@ mod tests { use super::*; use crate::store::memory::MemoryStore; use libipld::ipld; + use pretty_assertions::{assert_eq, assert_ne}; + // FIXME #[test] fn happy_little_test() { let mut store = MemoryStore::new(); diff --git a/src/iterator/post_order.rs b/src/iterator/post_order.rs index fe2b2c5..fb09303 100644 --- a/src/iterator/post_order.rs +++ b/src/iterator/post_order.rs @@ -68,6 +68,7 @@ pub fn is_delimiter_next(poii: &mut Peekable) -> bool { mod tests { use super::*; use libipld::{cid::CidGeneric, ipld}; + use pretty_assertions::{assert_eq, assert_ne}; #[test] fn poii_test() { diff --git a/src/store/traits.rs b/src/store/traits.rs index 6390077..b4c1538 100644 --- a/src/store/traits.rs +++ b/src/store/traits.rs @@ -14,26 +14,144 @@ use multihash::MultihashDigest; use std::collections::BTreeMap; use thiserror::Error; +/// A trait for interacting with content addressed stores pub trait Store { + /// Retrieve a block by CID + /// + /// # Arguments + /// + /// * `self` - The block store + /// * `cid` - The [`Cid`] to look up by + /// + /// # Examples + /// + /// ``` + /// # use ipld_inline::store::traits::Store; + /// # use std::{collections::BTreeMap, str::FromStr}; + /// # use multihash::Code::Sha2_256; + /// # use libipld::{ + /// # cid::Version, + /// # cbor::DagCborCodec, + /// # ipld + /// # }; + /// # + /// let block = ipld!([1, 2, 3]); + /// let mut store = BTreeMap::new(); + /// let cid = store.put(block.clone(), DagCborCodec, &Sha2_256, Version::V1).unwrap(); + /// + /// assert_eq!(Store::get(&store, &cid).unwrap(), &block); + /// ``` fn get(&self, cid: &Cid) -> Result<&Ipld, BlockNotFound>; + + /// Insert a block manually with a user-specified CID + /// + /// Since this method _may_ result in a invalid content address, [`Store::put`] should be preferred where possible. + /// However, [`Store::put_keyed`] is easier to define in a trait implementation. + /// + /// # Arguments + /// + /// * `self` - The block store + /// * `cid` - The explicit [`Cid`] to index this block by + /// * `ipld` - The [`Ipld`] to store + /// + /// # Examples + /// + /// ``` + /// # use ipld_inline::store::traits::Store; + /// # use libipld::{cid, ipld}; + /// # use std::{collections::BTreeMap, str::FromStr}; + /// # + /// # let block = ipld!([1, 2, 3]); + /// # let cid = FromStr::from_str("bafyreickxqyrg7hhhdm2z24kduovd4k4vvbmfmenzn7nc6pxg6qzjm2v44").unwrap(); + /// # + /// let mut store = BTreeMap::new(); + /// store.put_keyed(cid, block.clone()); + /// + /// assert_eq!(Store::get(&store, &cid).unwrap(), &block); + /// ``` fn put_keyed(&mut self, cid: Cid, ipld: Ipld); + /// Insert a block into content addressed storage + /// + /// A variant of this method (`put_default`) is available if the `"sha2"` flag is enabled on [`libipld`]. + /// + /// # Arguments + /// + /// * `self` - The block store + /// * `ipld` - The [`Ipld`] to store + /// * `codec` - The [`Codec`] that the IPLD is encoded as + /// * `digester` - The hash function to use when generating the [`Cid`] + /// * `cid_version` - The [`Cid`] version + /// + /// # Examples + /// + /// ``` + /// # use ipld_inline::store::traits::Store; + /// # use std::{collections::BTreeMap, str::FromStr}; + /// # use multihash::Code::Sha2_256; + /// # use libipld::{ + /// # cid::Version, + /// # cbor::DagCborCodec, + /// # ipld + /// # }; + /// # + /// let block = ipld!([1, 2, 3]); + /// let mut store = BTreeMap::new(); + /// let cid = store.put(block.clone(), DagCborCodec, &Sha2_256, Version::V1).unwrap(); + /// + /// assert_eq!(Store::get(&store, &cid).unwrap(), &block); + /// ``` fn put>( &mut self, + ipld: Ipld, codec: C, digester: &D, - version: Version, - ipld: Ipld, - ) -> Result<(), cid::Error> + cid_version: Version, + ) -> Result where Ipld: Encode, { - // FIXME - let cid = cid::new(&ipld, codec, digester, version)?; - self.put_keyed(cid, ipld); - Ok(()) + let block_cid = cid::new(&ipld, codec, digester, cid_version)?; + self.put_keyed(block_cid, ipld); + Ok(block_cid) + } + + #[cfg(feature = "sha2")] + /// [`Store::put`] but defaults to [`Sha2_256`] and [`DagCborCodec`] + fn put_default(&mut self, ipld: Ipld) -> Result { + use libipld::{ + cbor::DagCborCodec, + cid::{multihash::Sha2_256, Version}, + }; + + self.put(ipld, DagCborCodec, &Sha2_256, Version::V1) } + /// Retrieve a block by CID as a raw vector of bytes. + /// + /// # Arguments + /// + /// * `self` - The block store + /// * `cid` - The [`Cid`] to look up by + /// + /// # Examples + /// + /// ``` + /// # use ipld_inline::store::traits::Store; + /// # use std::{collections::BTreeMap, str::FromStr}; + /// # use multihash::Code::Sha2_256; + /// # use libipld::{ + /// # cid::Version, + /// # cbor::DagCborCodec, + /// # ipld + /// # }; + /// # + /// let mut store = BTreeMap::new(); + /// let cid = store.put(ipld!([1, 2, 3]), DagCborCodec, &Sha2_256, Version::V1).unwrap(); + /// let observed = store.get_raw(&cid).unwrap(); + /// + /// assert_eq!(observed, vec![131, 1, 2, 3]); + /// ``` fn get_raw(&self, cid: &Cid) -> Result, GetRawError> { let ipld = self.get(cid).map_err(GetRawError::NotFound)?; let codec_id: u64 = cid.codec(); @@ -47,10 +165,11 @@ pub trait Store { } fn try_inline(&mut self, ipld: Ipld) -> Result { - ExactlyOnce::new(ipld, self) - .last() - .expect("should have at least the `Ipld` that was passed in") - .clone() + //FIXME attack of the clones, clippy recommends switching to borriwng the Ipld + match ExactlyOnce::new(ipld.clone(), self).last() { + None => Ok(ipld.clone()), + Some(result) => result.clone(), + } } fn inline_at_most_once(&mut self, ipld: Ipld) -> Ipld { @@ -73,22 +192,21 @@ pub trait Store { /// # Arguments /// /// * `self` - Where subgraphs will be stored - /// * `ipld` - The IPLD to extract graphs from - /// * `codec` - The codec to extract with if none is provided by the inline IPLD + /// * `ipld` - The `Ipld` to extract graphs from + /// * `codec` - The [`Codec`] to extract with if none is provided by the inline IPLD /// * `digester` - The digest (hash) function to use if none is specified in the inlined IPLD /// * `cid_version` - The CID version to use is none is specified in the inlined IPLD /// /// # Examples /// /// ``` - /// use ipld_inline::store::traits::Store; - /// - /// use libipld::{ipld, cid::Version}; - /// use libipld_cbor::DagCborCodec; - /// use multihash::Code::Sha2_256; - /// use std::collections::BTreeMap; - /// use std::str::FromStr; - /// + /// # use ipld_inline::store::traits::Store; + /// # + /// # use libipld::{ipld, cid::Version}; + /// # use libipld_cbor::DagCborCodec; + /// # use multihash::Code::Sha2_256; + /// # use std::{collections::BTreeMap, str::FromStr}; + /// # /// let inner = ipld!([4, 5, 6]); /// let inner_cid = FromStr::from_str("bafyreihscx57i276zr5pgnioa5omevods6eseu5h4mllmow6csasju6eqi").unwrap(); /// @@ -120,7 +238,7 @@ pub trait Store { } } -/// Error cases for [`get_raw`][Store::get_raw()]. +/// Error cases for [`Store::get_raw`] #[derive(Debug, Error)] pub enum GetRawError { /// Forwards a (lifted) [BlockNotFound] @@ -132,10 +250,28 @@ pub enum GetRawError { UnknownCodec(#[from] UnsupportedCodec), /// Forwards a (lifted) [libipld::error::Error] + /// Note that these are never comparable #[error("failed to encode to bytes")] EncodeFailed(#[from] libipld::error::Error), } +impl PartialEq for GetRawError { + fn eq(&self, other: &GetRawError) -> bool { + match (self, other) { + ( + &GetRawError::NotFound(BlockNotFound(cid_a)), + &GetRawError::NotFound(BlockNotFound(cid_b)), + ) => cid_a.eq(&cid_b), + ( + &GetRawError::UnknownCodec(UnsupportedCodec(codec_a)), + &GetRawError::UnknownCodec(UnsupportedCodec(codec_b)), + ) => codec_a.eq(&codec_b), + // libipld::error::Error is existentially quantified, and not constrained with PartialEq, so false + _ => false, + } + } +} + impl Store for BTreeMap { fn get(&self, cid: &Cid) -> Result<&Ipld, BlockNotFound> { self.get(cid).ok_or(BlockNotFound(*cid))