Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement Codec trait #28

Merged
merged 2 commits into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
3 changes: 1 addition & 2 deletions examples/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
42 changes: 42 additions & 0 deletions src/codec.rs
Original file line number Diff line number Diff line change
@@ -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<T> Codec<T> for DagCborCodec
where
T: for<'a> Deserialize<'a> + Serialize,
{
const CODE: u64 = 0x71;
type Error = CodecError;

fn decode<R: BufRead>(reader: R) -> Result<T, Self::Error> {
Ok(crate::from_reader(reader)?)
}

fn encode<W: Write>(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<impl Iterator<Item = Cid>, Self::LinksError> {
let mut deserializer = Deserializer::from_slice(data);
Ok(ExtractLinks::deserialize(&mut deserializer)?
.into_vec()
.into_iter())
}
}
13 changes: 11 additions & 2 deletions src/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -84,7 +84,7 @@ where

/// A Serde `Deserialize`r of DAG-CBOR data.
#[derive(Debug)]
struct Deserializer<R> {
pub struct Deserializer<R> {
reader: R,
}

Expand All @@ -95,6 +95,15 @@ impl<R> Deserializer<R> {
}
}

impl<'a> Deserializer<SliceReader<'a>> {
/// 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<R> {
#[allow(clippy::type_complexity)]
#[inline]
Expand Down
67 changes: 62 additions & 5 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand Down Expand Up @@ -196,3 +196,60 @@ impl<E: fmt::Debug> From<cbor4ii::DecodeError<E>> for DecodeError<E> {
}
}
}

/// Encode and Decode error combined.
#[derive(Debug)]
pub enum CodecError {
/// A decoding error.
Decode(DecodeError<Infallible>),
/// An encoding error.
Encode(EncodeError<TryReserveError>),
/// A decoding error.
#[cfg(feature = "std")]
DecodeIo(DecodeError<std::io::Error>),
/// An encoding error.
#[cfg(feature = "std")]
EncodeIo(EncodeError<std::io::Error>),
}

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<DecodeError<Infallible>> for CodecError {
fn from(error: DecodeError<Infallible>) -> Self {
Self::Decode(error)
}
}

#[cfg(feature = "std")]
impl From<DecodeError<std::io::Error>> for CodecError {
fn from(error: DecodeError<std::io::Error>) -> Self {
Self::DecodeIo(error)
}
}

impl From<EncodeError<TryReserveError>> for CodecError {
fn from(error: EncodeError<TryReserveError>) -> Self {
Self::Encode(error)
}
}

#[cfg(feature = "std")]
impl From<EncodeError<std::io::Error>> for CodecError {
fn from(error: EncodeError<std::io::Error>) -> Self {
Self::EncodeIo(error)
}
}
5 changes: 4 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/ser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
10 changes: 3 additions & 7 deletions tests/cid.rs
Original file line number Diff line number Diff line change
@@ -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() {
Expand Down Expand Up @@ -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());
}
66 changes: 66 additions & 0 deletions tests/codec.rs
Original file line number Diff line number Diff line change
@@ -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::<Vec<_>>();

let mut encoded = Vec::new();
DagCborCodec::encode(&mut encoded, &data).unwrap();

let links = DagCborCodec::links(&encoded).unwrap();
assert_eq!(links.collect::<Vec<_>>(), expected);
}

#[test]
fn test_codec_generic() {
fn encode_generic<C, T>(value: T) -> Vec<u8>
where
C: Codec<T>,
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::<DagCborCodec, _>(data);
assert_eq!(encoded, expected);
}