Skip to content

Commit

Permalink
proto: Expose well-known types Any, Duration and Timestamp
Browse files Browse the repository at this point in the history
  • Loading branch information
romac committed Jul 30, 2024
1 parent 61e6899 commit f861448
Show file tree
Hide file tree
Showing 9 changed files with 322 additions and 69 deletions.
1 change: 1 addition & 0 deletions proto/src/google/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod protobuf;
147 changes: 147 additions & 0 deletions proto/src/google/protobuf/any.rs
Original file line number Diff line number Diff line change
@@ -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::<Foo>()?;
/// 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 <https://github.com/influxdata/pbjson/issues/2> 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": <string>,
/// "lastName": <string>
/// }
/// ```
///
/// 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<u8>,
}

impl Any {
/// Serialize the given message type `M` as [`Any`].
pub fn from_msg<M>(msg: &M) -> Result<Self, EncodeError>
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<M>(&self) -> Result<M, DecodeError>
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::<Self>()
}
}
38 changes: 38 additions & 0 deletions proto/src/google/protobuf/duration.rs
Original file line number Diff line number Diff line change
@@ -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::<Self>()
}
}
12 changes: 12 additions & 0 deletions proto/src/google/protobuf/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
47 changes: 47 additions & 0 deletions proto/src/google/protobuf/timestamp.rs
Original file line number Diff line number Diff line change
@@ -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::<Self>()
}
}
72 changes: 72 additions & 0 deletions proto/src/google/protobuf/type_url.rs
Original file line number Diff line number Diff line change
@@ -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<Self> {
// 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<T: Name>() -> 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);
}
}
9 changes: 2 additions & 7 deletions proto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
59 changes: 0 additions & 59 deletions proto/src/protobuf.rs

This file was deleted.

Loading

0 comments on commit f861448

Please sign in to comment.