Skip to content

Commit

Permalink
proto: Rename DomainType trait to Protobuf (#672)
Browse files Browse the repository at this point in the history
* Rename DomainType trait to Protobuf

Signed-off-by: Thane Thomson <connect@thanethomson.com>

* Remove domaintype.rs - no longer necessary

Signed-off-by: Thane Thomson <connect@thanethomson.com>

* Update CHANGELOG

Signed-off-by: Thane Thomson <connect@thanethomson.com>

* Fix clippy error

Signed-off-by: Thane Thomson <connect@thanethomson.com>

* Update documentation to reflect DomainType rename

Signed-off-by: Thane Thomson <connect@thanethomson.com>

* Make error identifier more Protobuf-specific

Signed-off-by: Thane Thomson <connect@thanethomson.com>

* Rename DomainTypeError to ProtobufError

Signed-off-by: Thane Thomson <connect@thanethomson.com>

* Clarify docs for Protobuf trait methods

Signed-off-by: Thane Thomson <connect@thanethomson.com>
  • Loading branch information
thanethomson committed Nov 17, 2020
1 parent fd3efe4 commit bfcbf83
Show file tree
Hide file tree
Showing 30 changed files with 274 additions and 228 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
## Unreleased

### BREAKING CHANGES:

- `[tendermint-proto]` The `DomainType` trait has now been renamed to
`Protobuf` to clarify its purpose throughout the codebase. ([#672])

### BUG FIXES:

- `[tendermint]` (Since v0.17.0-rc2) Fix abci::Data serialization to base64-encoded string. ([#667])
- `[tendermint]` (Since v0.17.0-rc2) Simplify abci::Log serialization ([#667])

[#667]: https://github.com/informalsystems/tendermint-rs/issues/667
[#672]: https://github.com/informalsystems/tendermint-rs/pull/672


## v0.17.0-rc2

Expand Down
139 changes: 0 additions & 139 deletions proto/src/domaintype.rs

This file was deleted.

6 changes: 3 additions & 3 deletions proto/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
//! This module defines the various errors that be raised during DomainType conversions.
//! This module defines the various errors that be raised during Protobuf conversions.

use anomaly::{BoxError, Context};
use thiserror::Error;

/// An error that can be raised by the DomainType conversions.
/// An error that can be raised by the Protobuf conversions.
pub type Error = anomaly::Error<Kind>;

/// Various kinds of errors that can be raised.
#[derive(Clone, Debug, Error)]
pub enum Kind {
/// TryFrom Prost Message failed during decoding
#[error("error converting message type into domain type")]
TryIntoDomainType,
TryFromProtobuf,

/// encoding prost Message into buffer failed
#[error("error encoding message into buffer")]
Expand Down
184 changes: 181 additions & 3 deletions proto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,188 @@ pub mod google {
mod tendermint;
pub use tendermint::*;

mod domaintype;
pub use domaintype::DomainType;

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

pub mod serializers;

/// Allows for easy Google Protocol Buffers encoding and decoding of domain
/// types with validation.
///
/// ## Examples
///
/// ```rust
/// use bytes::BufMut;
/// use prost::Message;
/// use std::convert::TryFrom;
/// use tendermint_proto::Protobuf;
///
/// // This struct would ordinarily be automatically generated by prost.
/// #[derive(Clone, PartialEq, Message)]
/// pub struct MyRawType {
/// #[prost(uint64, tag="1")]
/// pub a: u64,
/// #[prost(string, tag="2")]
/// pub b: String,
/// }
///
/// #[derive(Clone)]
/// pub struct MyDomainType {
/// a: u64,
/// b: String,
/// }
///
/// impl MyDomainType {
/// /// Trivial constructor with basic validation logic.
/// pub fn new(a: u64, b: String) -> Result<Self, String> {
/// if a < 1 {
/// return Err("a must be greater than 0".to_owned());
/// }
/// Ok(Self { a, b })
/// }
/// }
///
/// impl TryFrom<MyRawType> for MyDomainType {
/// type Error = String;
///
/// fn try_from(value: MyRawType) -> Result<Self, Self::Error> {
/// Self::new(value.a, value.b)
/// }
/// }
///
/// impl From<MyDomainType> for MyRawType {
/// fn from(value: MyDomainType) -> Self {
/// Self { a: value.a, b: value.b }
/// }
/// }
///
/// impl Protobuf<MyRawType> for MyDomainType {}
///
///
/// // Simulate an incoming valid raw message
/// let valid_raw = MyRawType { a: 1, b: "Hello!".to_owned() };
/// let mut valid_raw_bytes: Vec<u8> = Vec::new();
/// valid_raw.encode(&mut valid_raw_bytes).unwrap();
/// assert!(!valid_raw_bytes.is_empty());
///
/// // Try to decode the simulated incoming message
/// let valid_domain = MyDomainType::decode(valid_raw_bytes.clone().as_ref()).unwrap();
/// assert_eq!(1, valid_domain.a);
/// assert_eq!("Hello!".to_owned(), valid_domain.b);
///
/// // Encode it to compare the serialized form to what we received
/// let mut valid_domain_bytes: Vec<u8> = Vec::new();
/// valid_domain.encode(&mut valid_domain_bytes).unwrap();
/// assert_eq!(valid_raw_bytes, valid_domain_bytes);
///
/// // Simulate an incoming invalid raw message
/// let invalid_raw = MyRawType { a: 0, b: "Hello!".to_owned() };
/// let mut invalid_raw_bytes: Vec<u8> = Vec::new();
/// invalid_raw.encode(&mut invalid_raw_bytes).unwrap();
///
/// // We expect a validation error here
/// assert!(MyDomainType::decode(invalid_raw_bytes.as_ref()).is_err());
/// ```
pub trait Protobuf<T: Message + From<Self> + Default>
where
Self: Sized + Clone + TryFrom<T>,
<Self as TryFrom<T>>::Error: Into<BoxError>,
{
/// Encode into a buffer in Protobuf format.
///
/// Uses [`prost::Message::encode`] after converting into its counterpart
/// Protobuf data structure.
///
/// [`prost::Message::encode`]: https://docs.rs/prost/*/prost/trait.Message.html#method.encode
fn encode<B: BufMut>(&self, buf: &mut B) -> Result<(), Error> {
T::from(self.clone())
.encode(buf)
.map_err(|e| Kind::EncodeMessage.context(e).into())
}

/// Encode with a length-delimiter to a buffer in Protobuf format.
///
/// An error will be returned if the buffer does not have sufficient capacity.
///
/// Uses [`prost::Message::encode_length_delimited`] after converting into
/// its counterpart Protobuf data structure.
///
/// [`prost::Message::encode_length_delimited`]: https://docs.rs/prost/*/prost/trait.Message.html#method.encode_length_delimited
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())
}

/// Constructor that attempts to decode an instance from a buffer.
///
/// The entire buffer will be consumed.
///
/// Similar to [`prost::Message::decode`] but with additional validation
/// prior to constructing the destination type.
///
/// [`prost::Message::decode`]: https://docs.rs/prost/*/prost/trait.Message.html#method.decode
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::TryFromProtobuf.context(e).into()),
)
}

/// Constructor that attempts to decode a length-delimited instance from
/// the buffer.
///
/// The entire buffer will be consumed.
///
/// Similar to [`prost::Message::decode_length_delimited`] but with
/// additional validation prior to constructing the destination type.
///
/// [`prost::Message::decode_length_delimited`]: https://docs.rs/prost/*/prost/trait.Message.html#method.decode_length_delimited
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::TryFromProtobuf.context(e).into()),
)
}

/// Returns the encoded length of the message without a length delimiter.
///
/// Uses [`prost::Message::encoded_len`] after converting to its
/// counterpart Protobuf data structure.
///
/// [`prost::Message::encoded_len`]: https://docs.rs/prost/*/prost/trait.Message.html#method.encoded_len
fn encoded_len(&self) -> usize {
T::from(self.clone()).encoded_len()
}

/// Encodes 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)
}

/// Constructor that attempts to decode a Protobuf-encoded instance from a
/// `Vec<u8>` (or equivalent).
fn decode_vec(v: &[u8]) -> Result<Self, Error> {
Self::decode(v)
}

/// Encode 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)
}

/// Constructor that attempts to decode a Protobuf-encoded instance with a
/// length-delimiter from a `Vec<u8>` or equivalent.
fn decode_length_delimited_vec(v: &[u8]) -> Result<Self, Error> {
Self::decode_length_delimited(v)
}
}
Loading

0 comments on commit bfcbf83

Please sign in to comment.