diff --git a/proto/src/google/mod.rs b/proto/src/google/mod.rs new file mode 100644 index 000000000..91e41667b --- /dev/null +++ b/proto/src/google/mod.rs @@ -0,0 +1 @@ +pub mod protobuf; diff --git a/proto/src/google/protobuf/any.rs b/proto/src/google/protobuf/any.rs new file mode 100644 index 000000000..de6e59cf7 --- /dev/null +++ b/proto/src/google/protobuf/any.rs @@ -0,0 +1,147 @@ +use prost::{DecodeError, EncodeError, Message, Name}; + +use crate::prelude::*; + +use super::type_url::{type_url_for, TypeUrl}; +use super::PACKAGE; + +/// `Any` contains an arbitrary serialized protocol buffer message along with a +/// URL that describes the type of the serialized message. +/// +/// Protobuf library provides support to pack/unpack Any values in the form +/// of utility functions or additional generated methods of the Any type. +/// +/// # Example +/// +/// Pack and unpack a message in Rust: +/// +/// ```rust,ignore +/// let foo1 = Foo { ... }; +/// let any = Any::from_msg(&foo1)?; +/// let foo2 = any.to_msg::()?; +/// assert_eq!(foo1, foo2); +/// ``` +/// +/// The pack methods provided by protobuf library will by default use +/// 'type.googleapis.com/full.type.name' as the type URL and the unpack +/// methods only use the fully qualified type name after the last '/' +/// in the type URL, for example "foo.bar.com/x/y.z" will yield type +/// name "y.z". +/// +/// # JSON +/// +/// > JSON serialization of Any cannot be made compatible with the specification, +/// > and is therefore left unimplemented at the moment. +/// > See for more information. +/// +/// The JSON representation of an `Any` value uses the regular +/// representation of the deserialized, embedded message, with an +/// additional field `@type` which contains the type URL. Example: +/// +/// ```text +/// package google.profile; +/// message Person { +/// string first_name = 1; +/// string last_name = 2; +/// } +/// +/// { +/// "@type": "type.googleapis.com/google.profile.Person", +/// "firstName": , +/// "lastName": +/// } +/// ``` +/// +/// If the embedded message type is well-known and has a custom JSON +/// representation, that representation will be embedded adding a field +/// `value` which holds the custom JSON in addition to the `@type` +/// field. Example (for message \[google.protobuf.Duration\]\[\]): +/// +/// ```text +/// { +/// "@type": "type.googleapis.com/google.protobuf.Duration", +/// "value": "1.212s" +/// } +/// ``` +#[derive(Clone, PartialEq, Eq, ::prost::Message)] +pub struct Any { + /// A URL/resource name that uniquely identifies the type of the serialized + /// protocol buffer message. This string must contain at least + /// one "/" character. The last segment of the URL's path must represent + /// the fully qualified name of the type (as in + /// `path/google.protobuf.Duration`). The name should be in a canonical form + /// (e.g., leading "." is not accepted). + /// + /// In practice, teams usually precompile into the binary all types that they + /// expect it to use in the context of Any. However, for URLs which use the + /// scheme `http`, `https`, or no scheme, one can optionally set up a type + /// server that maps type URLs to message definitions as follows: + /// + /// * If no scheme is provided, `https` is assumed. + /// * An HTTP GET on the URL must yield a \[google.protobuf.Type\]\[\] + /// value in binary format, or produce an error. + /// * Applications are allowed to cache lookup results based on the + /// URL, or have them precompiled into a binary to avoid any + /// lookup. Therefore, binary compatibility needs to be preserved + /// on changes to types. (Use versioned type names to manage + /// breaking changes.) + /// + /// Note: this functionality is not currently available in the official + /// protobuf release, and it is not used for type URLs beginning with + /// type.googleapis.com. + /// + /// Schemes other than `http`, `https` (or the empty scheme) might be + /// used with implementation specific semantics. + #[prost(string, tag = "1")] + pub type_url: ::prost::alloc::string::String, + /// Must be a valid serialized protocol buffer of the above specified type. + #[prost(bytes = "vec", tag = "2")] + pub value: ::prost::alloc::vec::Vec, +} + +impl Any { + /// Serialize the given message type `M` as [`Any`]. + pub fn from_msg(msg: &M) -> Result + where + M: Name, + { + let type_url = M::type_url(); + let mut value = Vec::new(); + Message::encode(msg, &mut value)?; + Ok(Any { type_url, value }) + } + + /// Decode the given message type `M` from [`Any`], validating that it has + /// the expected type URL. + pub fn to_msg(&self) -> Result + where + M: Default + Name + Sized, + { + let expected_type_url = M::type_url(); + + if let (Some(expected), Some(actual)) = ( + TypeUrl::new(&expected_type_url), + TypeUrl::new(&self.type_url), + ) { + if expected == actual { + return M::decode(self.value.as_slice()); + } + } + + let mut err = DecodeError::new(format!( + "expected type URL: \"{}\" (got: \"{}\")", + expected_type_url, &self.type_url + )); + err.push("unexpected type URL", "type_url"); + Err(err) + } +} + +impl Name for Any { + const PACKAGE: &'static str = PACKAGE; + const NAME: &'static str = "Any"; + + fn type_url() -> String { + type_url_for::() + } +} diff --git a/proto/src/google/protobuf/duration.rs b/proto/src/google/protobuf/duration.rs new file mode 100644 index 000000000..e9c682fd9 --- /dev/null +++ b/proto/src/google/protobuf/duration.rs @@ -0,0 +1,38 @@ +use prost::Name; + +use crate::prelude::*; + +use super::type_url::type_url_for; +use super::PACKAGE; + +/// A Duration represents a signed, fixed-length span of time represented +/// as a count of seconds and fractions of seconds at nanosecond +/// resolution. It is independent of any calendar and concepts like "day" +/// or "month". It is related to Timestamp in that the difference between +/// two Timestamp values is a Duration and it can be added or subtracted +/// from a Timestamp. Range is approximately +-10,000 years. +#[derive(Copy, Clone, PartialEq, ::prost::Message, ::serde::Deserialize, ::serde::Serialize)] +pub struct Duration { + /// Signed seconds of the span of time. Must be from -315,576,000,000 + /// to +315,576,000,000 inclusive. Note: these bounds are computed from: + /// 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years + #[prost(int64, tag = "1")] + pub seconds: i64, + /// Signed fractions of a second at nanosecond resolution of the span + /// of time. Durations less than one second are represented with a 0 + /// `seconds` field and a positive or negative `nanos` field. For durations + /// of one second or more, a non-zero value for the `nanos` field must be + /// of the same sign as the `seconds` field. Must be from -999,999,999 + /// to +999,999,999 inclusive. + #[prost(int32, tag = "2")] + pub nanos: i32, +} + +impl Name for Duration { + const PACKAGE: &'static str = PACKAGE; + const NAME: &'static str = "Duration"; + + fn type_url() -> String { + type_url_for::() + } +} diff --git a/proto/src/google/protobuf/mod.rs b/proto/src/google/protobuf/mod.rs new file mode 100644 index 000000000..92710470a --- /dev/null +++ b/proto/src/google/protobuf/mod.rs @@ -0,0 +1,12 @@ +pub const PACKAGE: &str = "google.protobuf"; + +mod any; +pub use any::Any; + +mod duration; +pub use duration::Duration; + +mod timestamp; +pub use timestamp::Timestamp; + +mod type_url; diff --git a/proto/src/google/protobuf/timestamp.rs b/proto/src/google/protobuf/timestamp.rs new file mode 100644 index 000000000..9215d7b76 --- /dev/null +++ b/proto/src/google/protobuf/timestamp.rs @@ -0,0 +1,47 @@ +use prost::Name; + +use crate::prelude::*; + +use super::type_url::type_url_for; +use super::PACKAGE; + +/// A Timestamp represents a point in time independent of any time zone or local +/// calendar, encoded as a count of seconds and fractions of seconds at +/// nanosecond resolution. The count is relative to an epoch at UTC midnight on +/// January 1, 1970, in the proleptic Gregorian calendar which extends the +/// Gregorian calendar backwards to year one. +/// +/// All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap +/// second table is needed for interpretation, using a +/// [24-hour linear smear](https://developers.google.com/time/smear). +/// +/// The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By +/// restricting to that range, we ensure that we can convert to and from +/// [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings. +#[derive(Copy, Clone, PartialEq, ::prost::Message, ::serde::Deserialize, ::serde::Serialize)] +#[serde( + from = "crate::serializers::timestamp::Rfc3339", + into = "crate::serializers::timestamp::Rfc3339" +)] +pub struct Timestamp { + /// Represents seconds of UTC time since Unix epoch + /// 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to + /// 9999-12-31T23:59:59Z inclusive. + #[prost(int64, tag = "1")] + pub seconds: i64, + /// Non-negative fractions of a second at nanosecond resolution. Negative + /// second values with fractions must still have non-negative nanos values + /// that count forward in time. Must be from 0 to 999,999,999 + /// inclusive. + #[prost(int32, tag = "2")] + pub nanos: i32, +} + +impl Name for Timestamp { + const PACKAGE: &'static str = PACKAGE; + const NAME: &'static str = "Timestamp"; + + fn type_url() -> String { + type_url_for::() + } +} diff --git a/proto/src/google/protobuf/type_url.rs b/proto/src/google/protobuf/type_url.rs new file mode 100644 index 000000000..1b79bca26 --- /dev/null +++ b/proto/src/google/protobuf/type_url.rs @@ -0,0 +1,72 @@ +use prost::Name; + +use crate::prelude::*; + +/// URL/resource name that uniquely identifies the type of the serialized protocol buffer message, +/// e.g. `type.googleapis.com/google.protobuf.Duration`. +/// +/// This string must contain at least one "/" character. +/// +/// The last segment of the URL's path must represent the fully qualified name of the type (as in +/// `path/google.protobuf.Duration`). The name should be in a canonical form (e.g., leading "." is +/// not accepted). +/// +/// If no scheme is provided, `https` is assumed. +/// +/// Schemes other than `http`, `https` (or the empty scheme) might be used with implementation +/// specific semantics. +#[derive(Debug, Eq, PartialEq)] +pub(crate) struct TypeUrl<'a> { + /// Fully qualified name of the type, e.g. `google.protobuf.Duration` + pub(crate) full_name: &'a str, +} + +impl<'a> TypeUrl<'a> { + pub(crate) fn new(s: &'a str) -> core::option::Option { + // Must contain at least one "/" character. + let slash_pos = s.rfind('/')?; + + // The last segment of the URL's path must represent the fully qualified name + // of the type (as in `path/google.protobuf.Duration`) + let full_name = s.get((slash_pos + 1)..)?; + + // The name should be in a canonical form (e.g., leading "." is not accepted). + if full_name.starts_with('.') { + return None; + } + + Some(Self { full_name }) + } +} + +/// Compute the type URL for the given `google.protobuf` type, using `type.googleapis.com` as the +/// authority for the URL. +pub(crate) fn type_url_for() -> String { + format!("type.googleapis.com/{}.{}", T::PACKAGE, T::NAME) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn check_type_url_parsing() { + let example_type_name = "google.protobuf.Duration"; + + let url = TypeUrl::new("type.googleapis.com/google.protobuf.Duration").unwrap(); + assert_eq!(url.full_name, example_type_name); + + let full_url = + TypeUrl::new("https://type.googleapis.com/google.protobuf.Duration").unwrap(); + assert_eq!(full_url.full_name, example_type_name); + + let relative_url = TypeUrl::new("/google.protobuf.Duration").unwrap(); + assert_eq!(relative_url.full_name, example_type_name); + + // The name should be in a canonical form (e.g., leading "." is not accepted). + assert_eq!(TypeUrl::new("/.google.protobuf.Duration"), None); + + // Must contain at least one "/" character. + assert_eq!(TypeUrl::new("google.protobuf.Duration"), None); + } +} diff --git a/proto/src/lib.rs b/proto/src/lib.rs index 54944fff9..86cb6750a 100644 --- a/proto/src/lib.rs +++ b/proto/src/lib.rs @@ -22,13 +22,8 @@ pub mod serializers; use prelude::*; -/// Built-in prost_types with slight customization to enable JSON-encoding -pub mod google { - pub mod protobuf { - // custom Timeout and Duration types that have valid doctest documentation texts - include!("protobuf.rs"); - } -} +/// Built-in `prost_types` with slight customization to enable JSON-encoding. +pub mod google; /// Allows for easy Google Protocol Buffers encoding and decoding of domain /// types with validation. diff --git a/proto/src/protobuf.rs b/proto/src/protobuf.rs deleted file mode 100644 index 7a587c209..000000000 --- a/proto/src/protobuf.rs +++ /dev/null @@ -1,59 +0,0 @@ -// Google protobuf Timestamp and Duration types reimplemented because their comments are turned -// into invalid documentation texts and doctest chokes on them. See https://github.com/danburkert/prost/issues/374 -// Prost does not seem to have a way yet to remove documentations defined in protobuf files. -// These structs are defined in gogoproto v1.3.1 at https://github.com/gogo/protobuf/tree/v1.3.1/protobuf/google/protobuf - -/// A Timestamp represents a point in time independent of any time zone or local -/// calendar, encoded as a count of seconds and fractions of seconds at -/// nanosecond resolution. The count is relative to an epoch at UTC midnight on -/// January 1, 1970, in the proleptic Gregorian calendar which extends the -/// Gregorian calendar backwards to year one. -/// -/// All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap -/// second table is needed for interpretation, using a [24-hour linear -/// smear](https://developers.google.com/time/smear). -/// -/// The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By -/// restricting to that range, we ensure that we can convert to and from [RFC -/// 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings. -#[derive(Copy, Clone, PartialEq, ::prost::Message, ::serde::Deserialize, ::serde::Serialize)] -#[serde( - from = "crate::serializers::timestamp::Rfc3339", - into = "crate::serializers::timestamp::Rfc3339" -)] -pub struct Timestamp { - /// Represents seconds of UTC time since Unix epoch - /// 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to - /// 9999-12-31T23:59:59Z inclusive. - #[prost(int64, tag = "1")] - pub seconds: i64, - /// Non-negative fractions of a second at nanosecond resolution. Negative - /// second values with fractions must still have non-negative nanos values - /// that count forward in time. Must be from 0 to 999,999,999 - /// inclusive. - #[prost(int32, tag = "2")] - pub nanos: i32, -} - -/// A Duration represents a signed, fixed-length span of time represented -/// as a count of seconds and fractions of seconds at nanosecond -/// resolution. It is independent of any calendar and concepts like "day" -/// or "month". It is related to Timestamp in that the difference between -/// two Timestamp values is a Duration and it can be added or subtracted -/// from a Timestamp. Range is approximately +-10,000 years. -#[derive(Copy, Clone, PartialEq, ::prost::Message, ::serde::Deserialize, ::serde::Serialize)] -pub struct Duration { - /// Signed seconds of the span of time. Must be from -315,576,000,000 - /// to +315,576,000,000 inclusive. Note: these bounds are computed from: - /// 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years - #[prost(int64, tag = "1")] - pub seconds: i64, - /// Signed fractions of a second at nanosecond resolution of the span - /// of time. Durations less than one second are represented with a 0 - /// `seconds` field and a positive or negative `nanos` field. For durations - /// of one second or more, a non-zero value for the `nanos` field must be - /// of the same sign as the `seconds` field. Must be from -999,999,999 - /// to +999,999,999 inclusive. - #[prost(int32, tag = "2")] - pub nanos: i32, -} diff --git a/tools/proto-compiler/src/main.rs b/tools/proto-compiler/src/main.rs index 29c7b4a5a..4d1439a14 100644 --- a/tools/proto-compiler/src/main.rs +++ b/tools/proto-compiler/src/main.rs @@ -96,9 +96,8 @@ fn main() { for field_attribute in CUSTOM_FIELD_ATTRIBUTES { pb.field_attribute(field_attribute.0, field_attribute.1); } - // The below in-place path redirection replaces references to the Duration - // and Timestamp WKTs with our own versions that have valid doctest comments. - // See also https://github.com/danburkert/prost/issues/374 . + // The below in-place path redirection replaces references to the + // Duration, Timestamp, and Any "well-know types" with our own versions. pb.extern_path( ".google.protobuf.Duration", "crate::google::protobuf::Duration", @@ -107,6 +106,7 @@ fn main() { ".google.protobuf.Timestamp", "crate::google::protobuf::Timestamp", ); + pb.extern_path(".google.protobuf.Any", "crate::google::protobuf::Any"); println!("[info] => Creating structs and interfaces."); let builder = tonic_build::configure()