diff --git a/Cargo.toml b/Cargo.toml index 62372b0..f733b6c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,17 +16,16 @@ edition = "2018" [dependencies] cbor4ii = { version = "0.2.14", default-features = false, features = ["use_alloc"] } -cid = { version = "0.11.0", default-features = false, features = ["serde-codec"] } +ipld-core = { version = "0.3.2", default-features = false, features = ["serde"] } scopeguard = "1.1.0" serde = { version = "1.0.164", default-features = false, features = ["alloc"] } [dev-dependencies] -ipld-core = { version = "0.2.0", features = ["serde"] } serde_derive = { version = "1.0.164", default-features = false } serde_bytes = { version = "0.11.9", default-features = false, features = ["alloc"]} [features] default = ["std"] -std = ["cbor4ii/use_std", "cid/std", "serde/std", "serde_bytes/std"] +std = ["cbor4ii/use_std", "ipld-core/std", "serde/std", "serde_bytes/std"] # Prevent deserializing CIDs as bytes as much as possible. no-cid-as-bytes = [] diff --git a/examples/enums.rs b/examples/enums.rs index bf03219..7b2a95c 100644 --- a/examples/enums.rs +++ b/examples/enums.rs @@ -3,8 +3,7 @@ /// file also contains an example for a kinded enum. use std::convert::{TryFrom, TryInto}; -use cid::Cid; -use ipld_core::ipld::Ipld; +use ipld_core::{cid::Cid, ipld::Ipld}; use serde::{de, Deserialize}; use serde_bytes::ByteBuf; use serde_derive::Deserialize; diff --git a/src/codec.rs b/src/codec.rs new file mode 100644 index 0000000..110647e --- /dev/null +++ b/src/codec.rs @@ -0,0 +1,42 @@ +//! Implementation of ipld-core's `Codec` trait. + +use std::io::{BufRead, Write}; + +use ipld_core::{ + cid::Cid, + codec::{Codec, Links}, + serde::ExtractLinks, +}; +use serde::{de::Deserialize, ser::Serialize}; + +use crate::{de::Deserializer, error::CodecError}; + +/// DAG-CBOR implementation of ipld-core's `Codec` trait. +pub struct DagCborCodec; + +impl Codec for DagCborCodec +where + T: for<'a> Deserialize<'a> + Serialize, +{ + const CODE: u64 = 0x71; + type Error = CodecError; + + fn decode(reader: R) -> Result { + Ok(crate::from_reader(reader)?) + } + + fn encode(writer: W, data: &T) -> Result<(), Self::Error> { + Ok(crate::to_writer(writer, data)?) + } +} + +impl Links for DagCborCodec { + type LinksError = CodecError; + + fn links(data: &[u8]) -> Result, Self::LinksError> { + let mut deserializer = Deserializer::from_slice(data); + Ok(ExtractLinks::deserialize(&mut deserializer)? + .into_vec() + .into_iter()) + } +} diff --git a/src/de.rs b/src/de.rs index 1191234..5e46c1c 100644 --- a/src/de.rs +++ b/src/de.rs @@ -7,7 +7,7 @@ use std::borrow::Cow; use cbor4ii::core::dec::{self, Decode}; use cbor4ii::core::{major, types, utils::SliceReader}; -use cid::serde::CID_SERDE_PRIVATE_IDENTIFIER; +use ipld_core::cid::serde::CID_SERDE_PRIVATE_IDENTIFIER; use serde::de::{self, Visitor}; use crate::cbor4ii_nonpub::{marker, peek_one, pull_one}; @@ -84,7 +84,7 @@ where /// A Serde `Deserialize`r of DAG-CBOR data. #[derive(Debug)] -struct Deserializer { +pub struct Deserializer { reader: R, } @@ -95,6 +95,15 @@ impl Deserializer { } } +impl<'a> Deserializer> { + /// Constructs a `Deserializer` that reads from a slice. + pub fn from_slice(buf: &'a [u8]) -> Self { + Deserializer { + reader: SliceReader::new(buf), + } + } +} + impl<'de, R: dec::Read<'de>> Deserializer { #[allow(clippy::type_complexity)] #[inline] diff --git a/src/error.rs b/src/error.rs index d6a1d9f..adfdf90 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,10 +1,10 @@ //! When serializing or deserializing DAG-CBOR goes wrong. -use core::fmt; -use core::num::TryFromIntError; - -#[cfg(not(feature = "std"))] -use alloc::string::{String, ToString}; +use alloc::{ + collections::TryReserveError, + string::{String, ToString}, +}; +use core::{convert::Infallible, fmt, num::TryFromIntError}; use serde::{de, ser}; @@ -196,3 +196,60 @@ impl From> for DecodeError { } } } + +/// Encode and Decode error combined. +#[derive(Debug)] +pub enum CodecError { + /// A decoding error. + Decode(DecodeError), + /// An encoding error. + Encode(EncodeError), + /// A decoding error. + #[cfg(feature = "std")] + DecodeIo(DecodeError), + /// An encoding error. + #[cfg(feature = "std")] + EncodeIo(EncodeError), +} + +impl fmt::Display for CodecError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Decode(error) => write!(f, "decode error: {}", error), + Self::Encode(error) => write!(f, "encode error: {}", error), + #[cfg(feature = "std")] + Self::DecodeIo(error) => write!(f, "decode io error: {}", error), + #[cfg(feature = "std")] + Self::EncodeIo(error) => write!(f, "encode io error: {}", error), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for CodecError {} + +impl From> for CodecError { + fn from(error: DecodeError) -> Self { + Self::Decode(error) + } +} + +#[cfg(feature = "std")] +impl From> for CodecError { + fn from(error: DecodeError) -> Self { + Self::DecodeIo(error) + } +} + +impl From> for CodecError { + fn from(error: EncodeError) -> Self { + Self::Encode(error) + } +} + +#[cfg(feature = "std")] +impl From> for CodecError { + fn from(error: EncodeError) -> Self { + Self::EncodeIo(error) + } +} diff --git a/src/lib.rs b/src/lib.rs index c8dffef..6681a91 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -112,10 +112,13 @@ #![deny(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] -#[cfg(not(feature = "std"))] extern crate alloc; mod cbor4ii_nonpub; +// The `Codec` implementation is only available if the `no-cid-as-bytes` feature is disabled, due +// to the links being extracted with a Serde based approach. +#[cfg(all(feature = "std", not(feature = "no-cid-as-bytes")))] +pub mod codec; pub mod de; pub mod error; pub mod ser; diff --git a/src/ser.rs b/src/ser.rs index cf201ff..6d4632a 100644 --- a/src/ser.rs +++ b/src/ser.rs @@ -13,7 +13,7 @@ use cbor4ii::core::{ enc::{self, Encode}, types, }; -use cid::serde::CID_SERDE_PRIVATE_IDENTIFIER; +use ipld_core::cid::serde::CID_SERDE_PRIVATE_IDENTIFIER; use serde::{ser, Serialize}; use crate::error::EncodeError; diff --git a/tests/cid.rs b/tests/cid.rs index ef366f7..a708e9a 100644 --- a/tests/cid.rs +++ b/tests/cid.rs @@ -1,13 +1,11 @@ use std::convert::{TryFrom, TryInto}; -use std::io::Cursor; use std::str::FromStr; -use cid::Cid; -use ipld_core::ipld::Ipld; +use ipld_core::{cid::Cid, ipld::Ipld}; use serde::de; use serde_bytes::ByteBuf; use serde_derive::{Deserialize, Serialize}; -use serde_ipld_dagcbor::{from_reader, from_slice, to_vec}; +use serde_ipld_dagcbor::{from_slice, to_vec}; #[test] fn test_cid_struct() { @@ -352,8 +350,6 @@ fn test_cid_decode_from_reader() { let cid_encoded = [ 0xd8, 0x2a, 0x49, 0x00, 0x01, 0xce, 0x01, 0x9b, 0x01, 0x02, 0x63, 0xc8, ]; - println!("vmx: cid: {:?}", cid_encoded); - let cid_decoded: Cid = from_reader(Cursor::new(&cid_encoded)).unwrap(); - println!("vmx: cid: {:?}", cid_decoded); + let cid_decoded: Cid = from_slice(&cid_encoded).unwrap(); assert_eq!(&cid_encoded[4..], &cid_decoded.to_bytes()); } diff --git a/tests/codec.rs b/tests/codec.rs new file mode 100644 index 0000000..31463cf --- /dev/null +++ b/tests/codec.rs @@ -0,0 +1,66 @@ +#![cfg(all(feature = "std", not(feature = "no-cid-as-bytes")))] + +use core::{convert::TryFrom, iter}; + +use ipld_core::{ + cid::Cid, + codec::{Codec, Links}, + ipld, + ipld::Ipld, +}; +use serde_ipld_dagcbor::codec::DagCborCodec; + +#[test] +fn test_codec_encode() { + let data = "hello world!".to_string(); + let expected = b"\x6chello world!"; + + let mut output = Vec::new(); + DagCborCodec::encode(&mut output, &data).unwrap(); + assert_eq!(output, expected); + + let encoded = DagCborCodec::encode_to_vec(&data).unwrap(); + assert_eq!(encoded, expected); +} + +#[test] +fn test_codec_decode() { + let data = b"\x6chello world!"; + let expected = "hello world!".to_string(); + + let decoded: String = DagCborCodec::decode(&data[..]).unwrap(); + assert_eq!(decoded, expected); + + let decoded_from_slice: String = DagCborCodec::decode_from_slice(data).unwrap(); + assert_eq!(decoded_from_slice, expected); +} + +#[test] +fn test_codec_links() { + let cid = Cid::try_from("bafkreibme22gw2h7y2h7tg2fhqotaqjucnbc24deqo72b6mkl2egezxhvy").unwrap(); + let data: Ipld = ipld!({"some": {"nested": cid}, "or": [cid, cid], "foo": true}); + let expected = iter::repeat(cid).take(3).collect::>(); + + let mut encoded = Vec::new(); + DagCborCodec::encode(&mut encoded, &data).unwrap(); + + let links = DagCborCodec::links(&encoded).unwrap(); + assert_eq!(links.collect::>(), expected); +} + +#[test] +fn test_codec_generic() { + fn encode_generic(value: T) -> Vec + where + C: Codec, + C::Error: std::fmt::Debug, + { + C::encode_to_vec(&value).unwrap() + } + + let data = "hello world!".to_string(); + let expected = b"\x6chello world!"; + + let encoded = encode_generic::(data); + assert_eq!(encoded, expected); +}