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

Blanket implementation for DomainType #571

Merged
merged 4 commits into from
Sep 17, 2020
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
1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ members = [
"light-client",
"light-node",
"proto",
"proto-derive",
"rpc",
"tendermint",
"testgen"
Expand Down
15 changes: 0 additions & 15 deletions proto-derive/Cargo.toml

This file was deleted.

95 changes: 0 additions & 95 deletions proto-derive/src/lib.rs

This file was deleted.

4 changes: 0 additions & 4 deletions proto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,9 @@ description = """
[package.metadata.docs.rs]
all-features = true

[features]
default = ["tendermint-proto-derive"]

[dependencies]
prost = { version = "0.6" }
prost-types = { version = "0.6" }
tendermint-proto-derive = { path = "../proto-derive", optional = true }
bytes = "0.5"
anomaly = "0.2"
thiserror = "1.0"
98 changes: 79 additions & 19 deletions proto/src/domaintype.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
//! The benefits include decoding the wire into a struct that is inherently valid as well as hiding
//! the encoding and decoding details from the developer. This latter is important if/when we decide
//! to exchange the underlying Prost library with something else. (Another protobuf implementation
//! or a completely different encoding.) Encoding is not the core product of Tendermint it's a
//! necessary dependency.
//! or a completely different encoding.) Encoding is not the core product it's a necessary
//! dependency.
//!
//!
//! Decode: bytestream -> Raw -> Domain
Expand All @@ -33,47 +33,107 @@
//! Note that the Prost library and the TryFrom method have their own set of errors. These are
//! merged into a custom Error type defined in this crate for easier handling.
//!
//! Requirements:
//! * The DomainType trait requires the struct to implement the Clone trait.
//! * Any RawType structure implements the prost::Message trait. (protobuf struct)
//! * The DomainType trait requires that the TryFrom<RawType> implemented on the structure has an
//! error type that implements Into<BoxError>. (The current implementations with anomaly are
//! fine.)
//!
//! How to implement a DomainType struct:
//! 1. Implement your struct based on your expectations for the developer
//! 2. Add the derive macro `#[derive(DomainType)]` on top of it
//! 3. Add the Raw type as a parameter of the DomainType trait (`[rawtype(MyRawType)]`)
//! 2. Add `impl DomainType<MyRawType> for MyDomainType {}` blanket implementation of the trait
//! 4. Implement the `TryFrom<MyRawType> for MyDomainType` trait
//! 5. Implement the `From<MyDomainType> for MyRawType` trait
//!
//! Note: the `[rawtype()]` parameter is similar to how `serde` implements serialization through a
//! `[serde(with="")]` interim type.
//!

use crate::Error;
use crate::{Error, Kind};
use anomaly::BoxError;
use bytes::{Buf, BufMut};
use prost::Message;
use prost::{encoding::encoded_len_varint, Message};
use std::convert::{TryFrom, TryInto};

/// DomainType trait allows protobuf encoding and decoding for domain types
pub trait DomainType<T: Message + From<Self>>: Sized {
pub trait DomainType<T: Message + From<Self> + Default>
where
Self: Sized + Clone + TryFrom<T>,
<Self as TryFrom<T>>::Error: Into<BoxError>,
{
/// Encodes the DomainType into a buffer.
///
/// The DomainType will be consumed.
fn encode<B: BufMut>(self, buf: &mut B) -> Result<(), Error>;
/// This function replaces the Prost::Message encode() function for DomainTypes.
fn encode<B: BufMut>(&self, buf: &mut B) -> Result<(), Error> {
T::from(self.clone())
.encode(buf)
.map_err(|e| Kind::EncodeMessage.context(e).into())
}

/// Encodes the DomainType with a length-delimiter to a buffer.
///
/// The DomainType will be consumed.
/// An error will be returned if the buffer does not have sufficient capacity.
fn encode_length_delimited<B: BufMut>(self, buf: &mut B) -> Result<(), Error>;
///
/// This function replaces the Prost::Message encode_length_delimited() function for
/// DomainTypes.
fn encode_length_delimited<B: BufMut>(&self, buf: &mut B) -> Result<(), Error> {
T::from(self.clone())
.encode_length_delimited(buf)
.map_err(|e| Kind::EncodeMessage.context(e).into())
}

/// Decodes an instance of the message from a buffer and then converts it into DomainType.
///
/// The entire buffer will be consumed.
fn decode<B: Buf>(buf: B) -> Result<Self, Error>;
///
/// This function replaces the Prost::Message decode() function for DomainTypes.
fn decode<B: Buf>(buf: B) -> Result<Self, Error> {
T::decode(buf).map_or_else(
|e| Err(Kind::DecodeMessage.context(e).into()),
|t| Self::try_from(t).map_err(|e| Kind::TryIntoDomainType.context(e).into()),
)
}

/// Decodes a length-delimited instance of the message from the buffer.
///
/// The entire buffer will be consumed.
fn decode_length_delimited<B: Buf>(buf: B) -> Result<Self, Error>;
///
/// This function replaces the Prost::Message decode_length_delimited() function for
/// DomainTypes.
fn decode_length_delimited<B: Buf>(buf: B) -> Result<Self, Error> {
T::decode_length_delimited(buf).map_or_else(
|e| Err(Kind::DecodeMessage.context(e).into()),
|t| Self::try_from(t).map_err(|e| Kind::TryIntoDomainType.context(e).into()),
)
}

/// Returns the encoded length of the message without a length delimiter.
///
/// The DomainType will be consumed.
fn encoded_len(self) -> usize;
/// This function replaces the Prost::Message encoded_len() function for DomainTypes.
fn encoded_len(&self) -> usize {
T::from(self.clone()).encoded_len()
}

/// Encodes the DomainType into a protobuf-encoded Vec<u8>
fn encode_vec(&self) -> Result<Vec<u8>, Error> {
let mut wire = Vec::with_capacity(self.encoded_len());
self.encode(&mut wire).map(|_| wire)
}

/// Decodes a protobuf-encoded instance of the message from a Vec<u8> and then converts it into
/// DomainType.
fn decode_vec(v: &[u8]) -> Result<Self, Error> {
Self::decode(v)
}

/// Encodes the DomainType with a length-delimiter to a Vec<u8> protobuf-encoded message.
fn encode_length_delimited_vec(&self) -> Result<Vec<u8>, Error> {
let len = self.encoded_len();
let lenu64 = len.try_into().map_err(|e| Kind::EncodeMessage.context(e))?;
let mut wire = Vec::with_capacity(len + encoded_len_varint(lenu64));
self.encode_length_delimited(&mut wire).map(|_| wire)
}

/// Decodes a protobuf-encoded instance of the message with a length-delimiter from a Vec<u8>
/// and then converts it into DomainType.
fn decode_length_delimited_vec(v: &[u8]) -> Result<Self, Error> {
Self::decode_length_delimited(v)
}
}
6 changes: 0 additions & 6 deletions proto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,6 @@ pub use domaintype::DomainType;
mod error;
pub use error::{Error, Kind};

// Re-export the bytes and prost crates for use within derived code.
#[doc(hidden)]
pub use bytes;
#[doc(hidden)]
pub use prost;

// Re-export the DomainType derive macro #[derive(DomainType)]
#[cfg(feature = "tendermint-proto-derive")]
#[doc(hidden)]
Expand Down
38 changes: 35 additions & 3 deletions proto/tests/unit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ use tendermint_proto::types::BlockId as RawBlockId;
use tendermint_proto::types::PartSetHeader as RawPartSetHeader;
use tendermint_proto::DomainType;

impl DomainType<RawBlockId> for BlockId {}

// Example implementation of a protobuf struct using DomainType.
#[derive(DomainType, Clone)]
#[rawtype(RawBlockId)]
#[derive(Clone, Debug)]
pub struct BlockId {
hash: String,
part_set_header_exists: bool,
Expand Down Expand Up @@ -40,6 +41,13 @@ impl From<BlockId> for RawBlockId {
}
}

// Do any custom implementation for your type
impl PartialEq for BlockId {
fn eq(&self, other: &Self) -> bool {
self.part_set_header_exists == other.part_set_header_exists && self.hash == other.hash
}
}

#[test]
pub fn domaintype_struct_example() {
let my_domain_type = BlockId {
Expand All @@ -48,7 +56,7 @@ pub fn domaintype_struct_example() {
};

let mut wire = vec![];
my_domain_type.clone().encode(&mut wire).unwrap();
my_domain_type.encode(&mut wire).unwrap();
assert_eq!(
wire,
vec![10, 12, 72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33]
Expand Down Expand Up @@ -77,3 +85,27 @@ pub fn domaintype_struct_length_delimited_example() {
assert_eq!(new_domain_type.hash, "Hello world!".to_string());
assert_eq!(new_domain_type.part_set_header_exists, false);
}

#[test]
pub fn domaintype_struct_conveniences_example() {
let my_domain_type = BlockId {
hash: "Hello world!".to_string(),
part_set_header_exists: false,
};

let wire = my_domain_type.encode_vec().unwrap();
assert_eq!(
wire,
vec![10, 12, 72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33]
);
let new_domain_type = BlockId::decode_vec(&wire).unwrap();
assert_eq!(my_domain_type, new_domain_type);

let wire = my_domain_type.encode_length_delimited_vec().unwrap();
assert_eq!(
wire,
vec![14, 10, 12, 72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33]
);
let new_domain_type = BlockId::decode_length_delimited_vec(&wire).unwrap();
assert_eq!(my_domain_type, new_domain_type);
}
Loading