diff --git a/grpc/Cargo.toml b/grpc/Cargo.toml index 352e65219..4dfd03c1c 100644 --- a/grpc/Cargo.toml +++ b/grpc/Cargo.toml @@ -15,7 +15,9 @@ allowed_external_types = [ ] [features] -default = ["dns", "_runtime-tokio", "tls-rustls"] +default = ["dns", "_runtime-tokio","tls-rustls", "protobuf"] +protobuf = ["dep:protobuf"] +prost = ["dep:prost"] dns = ["dep:hickory-resolver", "_runtime-tokio"] # The following feature is used to ensure all modules use the runtime # abstraction instead of using tokio directly. @@ -41,6 +43,8 @@ tls-rustls = [ ] [dependencies] +protobuf = { version = "4.33.0-release", optional = true } +prost = { version = "0.14.0", optional = true } bytes = "1.10.1" hickory-resolver = { version = "0.25.1", optional = true } http = "1.1.0" @@ -89,3 +93,4 @@ tonic = { version = "0.14.0", path = "../tonic", default-features = false, featu "router", ] } tonic-prost = { version = "0.14.0", path = "../tonic-prost" } +protobuf-well-known-types = "4.33.0-release" diff --git a/grpc/src/server/message.rs b/grpc/src/server/message.rs new file mode 100644 index 000000000..6331698c1 --- /dev/null +++ b/grpc/src/server/message.rs @@ -0,0 +1,20 @@ +//! Traits for accessing view and mutable view types of messages. +//! +//! These traits are needed to support Protobuf's design decision to prefer view and mut proxy types +//! over references. See +//! for more details. +//! +//! These traits allow for support of view and mut types while defaulting to regular references for +//! non-Protobuf classes that need gRPC support. + +pub mod as_mut; +pub mod as_view; + +pub use as_mut::AsMut; +pub use as_view::AsView; + +#[cfg(feature = "protobuf")] +pub mod protobuf; + +#[cfg(all(feature = "prost", not(feature = "protobuf")))] +pub mod prost; diff --git a/grpc/src/server/message/as_mut.rs b/grpc/src/server/message/as_mut.rs new file mode 100644 index 000000000..b43d89a7b --- /dev/null +++ b/grpc/src/server/message/as_mut.rs @@ -0,0 +1,78 @@ +use crate::server::message::AsView; + +pub trait AsMut { + /// We let this be whatever the library uses (e.g. protobuf::Mut). + type Mut<'a>: Send + AsView + 'a; + + fn as_mut(&mut self) -> Self::Mut<'_>; + + /// Implement `reborrow` to make it feel it feel like a mut reference. + /// Since we can't attach .reborrow() to the alias 'Self::Mut', + /// we define the logic here as a static utility function. + #[doc(hidden)] + fn reborrow_view<'a, 'b>(view: &'b mut Self::Mut<'a>) -> Self::Mut<'b> + where + 'a: 'b; +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_custom_as_mut() { + struct TestMsg { + val: i32, + } + + struct TestMsgMut<'a> { + val: &'a mut i32, + } + + impl AsView for TestMsgMut<'_> { + type View<'msg> + = i32 + where + Self: 'msg; + fn as_view(&self) -> Self::View<'_> { + *self.val + } + } + + impl AsMut for TestMsg { + type Mut<'a> = TestMsgMut<'a>; + + fn as_mut(&mut self) -> Self::Mut<'_> { + TestMsgMut { val: &mut self.val } + } + + fn reborrow_view<'a, 'b>(view: &'b mut Self::Mut<'a>) -> Self::Mut<'b> + where + 'a: 'b, + { + TestMsgMut { val: view.val } + } + } + + let mut msg = TestMsg { val: 10 }; + + // 1. Test as_mut() + let mut view = msg.as_mut(); + assert_eq!(*view.val, 10); + *view.val = 20; + + // 2. Test reborrow_view() + { + let reborrowed = ::reborrow_view(&mut view); + assert_eq!(*reborrowed.val, 20); + *reborrowed.val = 30; + } + + // 3. Verify view is still usable (reborrow, not move) + assert_eq!(*view.val, 30); + *view.val = 40; + + // Verify changes persisted + assert_eq!(msg.val, 40); + } +} diff --git a/grpc/src/server/message/as_view.rs b/grpc/src/server/message/as_view.rs new file mode 100644 index 000000000..875eee754 --- /dev/null +++ b/grpc/src/server/message/as_view.rs @@ -0,0 +1,37 @@ +pub trait AsView { + /// A GAT for the view type. + type View<'msg>: Send + Copy + where + Self: 'msg; + + /// Creates a view of the object. + fn as_view(&self) -> Self::View<'_>; +} + +#[cfg(test)] +mod tests { + use super::AsView; + + #[test] + fn test_non_protobuf_view() { + #[derive(Debug, PartialEq)] + struct TestMessage { + id: i32, + } + + impl AsView for TestMessage { + type View<'msg> + = &'msg TestMessage + where + Self: 'msg; + fn as_view(&self) -> Self::View<'_> { + self + } + } + + let msg = TestMessage { id: 42 }; + let view = AsView::as_view(&msg); + assert_eq!(view.id, 42); + assert!(std::ptr::eq(view, &msg)); + } +} diff --git a/grpc/src/server/message/prost.rs b/grpc/src/server/message/prost.rs new file mode 100644 index 000000000..74adfe28c --- /dev/null +++ b/grpc/src/server/message/prost.rs @@ -0,0 +1,83 @@ +use super::{AsMut, AsView}; + +impl AsView for T +where + T: prost::Message + Sync + Send, +{ + type View<'msg> + = &'msg T + where + Self: 'msg; + + fn as_view(&self) -> Self::View<'_> { + self + } +} + +impl AsMut for T +where + T: prost::Message + Send + 'static, +{ + type Mut<'a> = &'a mut T; + + fn as_mut(&mut self) -> Self::Mut<'_> { + self + } + + fn reborrow_view<'a, 'b>(view: &'b mut Self::Mut<'a>) -> Self::Mut<'b> + where + 'a: 'b, + { + &mut **view + } +} + +#[cfg(test)] +mod tests { + use crate::server::message::{AsMut, AsView}; + use prost::Message; + + // A simple prost message for testing + #[derive(Clone, PartialEq, Message)] + struct ValidateRequest { + #[prost(string, tag = "1")] + pub service_name: String, + } + + #[test] + fn test_prost_view() { + let msg = ValidateRequest { + service_name: "test_service".to_string(), + }; + let view = AsView::as_view(&msg); + assert_eq!(view.service_name, "test_service"); + // For prost, the view is just a reference + assert_eq!(view as *const _, &msg as *const _); + } + + #[test] + fn test_prost_as_mut() { + let mut msg = ValidateRequest { + service_name: "test_service".to_string(), + }; + + // 1. Test as_mut() + let mut view = AsMut::as_mut(&mut msg); + assert_eq!(view.service_name, "test_service"); + view.service_name = "updated_service".to_string(); + + // 2. Test reborrow_view() + { + let reborrowed = ::reborrow_view(&mut view); + assert_eq!(reborrowed.service_name, "updated_service"); + reborrowed.service_name = "reborrowed_service".to_string(); + } + + // 3. Verify view is still usable + assert_eq!(view.service_name, "reborrowed_service"); + view.service_name = "final_service".to_string(); + + // Verify changes persisted + assert_eq!(msg.service_name, "final_service"); + } +} diff --git a/grpc/src/server/message/protobuf.rs b/grpc/src/server/message/protobuf.rs new file mode 100644 index 000000000..e3b2e39f0 --- /dev/null +++ b/grpc/src/server/message/protobuf.rs @@ -0,0 +1,79 @@ +use super::{AsMut, AsView}; + +impl AsView for T +where + T: protobuf::AsView + Sync, + // Require `Copy` to emulate "all shared references are copyable" + for<'msg> protobuf::View<'msg, ::Proxied>: Copy, +{ + type View<'msg> + = protobuf::View<'msg, ::Proxied> + where + Self: 'msg; + + fn as_view(&self) -> Self::View<'_> { + self.as_view() + } +} + +impl AsMut for T +where + T: protobuf::AsMut + Send + 'static, + for<'msg> protobuf::Mut<'msg, ::MutProxied>: Send + AsView, +{ + type Mut<'a> = protobuf::Mut<'a, ::MutProxied>; + + fn as_mut(&mut self) -> Self::Mut<'_> { + protobuf::AsMut::as_mut(self) + } + + fn reborrow_view<'a, 'b>(view: &'b mut Self::Mut<'a>) -> Self::Mut<'b> + where + 'a: 'b, + { + protobuf::AsMut::as_mut(view) + } +} + +#[cfg(test)] +mod tests { + use crate::server::message::{AsMut, AsView}; + + #[test] + fn test_protobuf_view() { + use protobuf_well_known_types::Timestamp; + let mut ts = Timestamp::new(); + ts.set_seconds(1234567890); + let view = AsView::as_view(&ts); + assert_eq!(view.seconds(), 1234567890); + } + + #[test] + fn test_protobuf_as_mut() { + use protobuf_well_known_types::Timestamp; + + let mut msg = Timestamp::new(); + msg.set_seconds(123); + msg.set_nanos(456); + + // 1. Test as_mut() + let mut view = AsMut::as_mut(&mut msg); + assert_eq!(view.seconds(), 123); + view.set_seconds(789); + + // 2. Test reborrow_view() + { + let mut reborrowed = ::reborrow_view(&mut view); + assert_eq!(reborrowed.seconds(), 789); + reborrowed.set_nanos(999); + } + + // 3. Verify view is still usable (reborrow, not move) + assert_eq!(view.nanos(), 999); + view.set_seconds(111); + + // Verify changes persisted + assert_eq!(msg.seconds(), 111); + assert_eq!(msg.nanos(), 999); + } +} diff --git a/grpc/src/server/mod.rs b/grpc/src/server/mod.rs index afef95dd2..dc61decd4 100644 --- a/grpc/src/server/mod.rs +++ b/grpc/src/server/mod.rs @@ -34,6 +34,8 @@ use crate::service::Request; use crate::service::Response; use crate::service::Service; +pub mod message; + pub struct Server { handler: Option>, }