From 24c26ea24eb0f69f2d506ab6bbc267e3a0fd8157 Mon Sep 17 00:00:00 2001 From: yuanyuyuan Date: Mon, 9 Mar 2026 13:05:17 +0000 Subject: [PATCH 01/13] refactor(msg): decouple serialization from ZMessage via ZSerdes trait Replace the ZMessage::Serdes associated type with a ZSerdes type parameter on ZPub/ZSub/ZClient/ZServer builders. This allows external message ecosystems (roslibrust) to use ros-z pub/sub directly without wrapper types, and enables future formats (FlatBuffers, Protobuf) at the call site rather than baked into the message type. - ZMessage becomes a plain Send+Sync+'static marker (no associated type) - NativeCdrSerdes/SerdeCdrSerdes become unit structs implementing ZSerdes - ZPub/ZSub default to NativeCdrSerdes; builders expose .with_serdes::() - All action, dynamic, and service call sites updated to explicit ZSerdes API - roslibrust_ros2: drops RosMessageWrapper and WrapperSerdes, uses SerdeCdrSerdes directly via create_pub_impl/create_sub_impl --- crates/rmw-zenoh-rs/src/msg.rs | 57 +- .../ros-z-codegen/src/protobuf_generator.rs | 4 +- .../src/python_msgspec_generator.rs | 8 +- .../ros-z-msgs/tests/shm_size_estimation.rs | 16 +- .../tests/size_estimation_performance.rs | 29 +- crates/ros-z-py/src/service.rs | 35 +- .../ros-z/examples/dynamic_message/interop.rs | 16 +- .../ros-z/examples/protobuf_demo/src/types.rs | 14 +- crates/ros-z/examples/z_custom_message.rs | 12 +- crates/ros-z/src/action/client.rs | 34 +- crates/ros-z/src/action/driver.rs | 30 +- crates/ros-z/src/action/macros.rs | 23 +- crates/ros-z/src/action/messages.rs | 37 +- crates/ros-z/src/action/server.rs | 50 +- crates/ros-z/src/dynamic/error.rs | 10 + crates/ros-z/src/dynamic/mod.rs | 6 +- crates/ros-z/src/dynamic/serdes.rs | 61 ++- .../ros-z/src/dynamic/tests/interop_tests.rs | 24 +- .../ros-z/src/dynamic/tests/pubsub_tests.rs | 11 +- .../src/dynamic/type_description_service.rs | 20 +- crates/ros-z/src/msg.rs | 505 ++++++++++-------- crates/ros-z/src/node.rs | 8 +- crates/ros-z/src/pubsub.rs | 91 ++-- crates/ros-z/src/service.rs | 68 ++- crates/ros-z/tests/action/communication.rs | 11 +- crates/ros-z/tests/pubsub.rs | 4 +- crates/ros-z/tests/service.rs | 8 +- 27 files changed, 677 insertions(+), 515 deletions(-) diff --git a/crates/rmw-zenoh-rs/src/msg.rs b/crates/rmw-zenoh-rs/src/msg.rs index 0eaf58e7..d9f04ae1 100644 --- a/crates/rmw-zenoh-rs/src/msg.rs +++ b/crates/rmw-zenoh-rs/src/msg.rs @@ -1,6 +1,6 @@ use crate::type_support::MessageTypeSupport; use ros_z::entity::{TypeHash, TypeInfo}; -use ros_z::msg::{ZDeserializer, ZMessage, ZSerializer, ZService}; +use ros_z::msg::{ZDeserializer, ZMessage, ZSerdes, ZSerializer, ZService}; use ros_z::ros_msg::WithTypeInfo; use ros_z::{MessageTypeInfo, ServiceTypeInfo}; @@ -79,10 +79,61 @@ impl ZSerializer for RosSerdes { } } -impl ZMessage for RosMessage { - type Serdes = RosSerdes; +/// `ZSerdes` implementation for `RosSerdes`. +/// +/// Serialization uses the C FFI path (`ts.serialize_message`). +/// Deserialization via the `ZSerdes` interface is not supported — the RMW path +/// deserializes directly into a pre-allocated C++ message via `ts.deserialize_message` +/// in `SubscriptionImpl::take`. Calling `deserialize` on this impl returns an error. +impl ZSerdes for RosSerdes { + type Error = std::io::Error; + + fn serialize(msg: &RosMessage) -> zenoh_buffers::ZBuf { + let RosMessage { msg: ptr, ts } = msg; + let bytes = unsafe { ts.serialize_message(*ptr) }; + zenoh_buffers::ZBuf::from(bytes) + } + + fn serialize_with_hint(msg: &RosMessage, _capacity_hint: usize) -> zenoh_buffers::ZBuf { + Self::serialize(msg) + } + + fn serialize_to_vec(msg: &RosMessage) -> Vec { + let RosMessage { msg: ptr, ts } = msg; + unsafe { ts.serialize_message(*ptr) } + } + + fn serialize_to_shm( + msg: &RosMessage, + _estimated_size: usize, + provider: &zenoh::shm::ShmProvider, + ) -> zenoh::Result<(zenoh_buffers::ZBuf, usize)> { + let data = Self::serialize_to_vec(msg); + let actual_size = data.len(); + + use zenoh::Wait; + use zenoh::shm::{BlockOn, GarbageCollect}; + + let mut shm_buf = provider + .alloc(actual_size) + .with_policy::>() + .wait() + .map_err(|e| zenoh::Error::from(format!("SHM allocation failed: {}", e)))?; + + shm_buf[0..actual_size].copy_from_slice(&data); + Ok((zenoh_buffers::ZBuf::from(shm_buf), actual_size)) + } + + fn deserialize(_buf: &[u8]) -> Result { + Err(std::io::Error::new( + std::io::ErrorKind::Unsupported, + "RosMessage deserialization requires a pre-allocated target; use SubscriptionImpl::take instead", + )) + } } +impl ZMessage for RosMessage {} + impl MessageTypeInfo for RosMessage { // Static methods return placeholder values since RosMessage is a generic wrapper // The actual type info varies per instance diff --git a/crates/ros-z-codegen/src/protobuf_generator.rs b/crates/ros-z-codegen/src/protobuf_generator.rs index dc2b8afd..34d50f5f 100644 --- a/crates/ros-z-codegen/src/protobuf_generator.rs +++ b/crates/ros-z-codegen/src/protobuf_generator.rs @@ -311,9 +311,7 @@ impl ProtobufMessageGenerator { impl ::ros_z::WithTypeInfo for {proto_type} {{}} -impl ::ros_z::msg::ZMessage for {proto_type} {{ - type Serdes = ::ros_z::msg::ProtobufSerdes<{proto_type}>; -}} +impl ::ros_z::msg::ZMessage for {proto_type} {{}} "# )); diff --git a/crates/ros-z-codegen/src/python_msgspec_generator.rs b/crates/ros-z-codegen/src/python_msgspec_generator.rs index 350853ba..2d687447 100644 --- a/crates/ros-z-codegen/src/python_msgspec_generator.rs +++ b/crates/ros-z-codegen/src/python_msgspec_generator.rs @@ -644,7 +644,7 @@ fn generate_serialize_to_zbuf( match_arms.push(quote! { #full_name => { let rust_msg = ::from_py(msg)?; - Ok(rust_msg.serialize_to_zbuf()) + Ok(::ros_z::msg::NativeCdrSerdes::serialize(&rust_msg)) } }); } @@ -660,7 +660,7 @@ fn generate_serialize_to_zbuf( match_arms.push(quote! { #req_full_name => { let rust_msg = ::from_py(msg)?; - Ok(rust_msg.serialize_to_zbuf()) + Ok(::ros_z::msg::NativeCdrSerdes::serialize(&rust_msg)) } }); @@ -670,7 +670,7 @@ fn generate_serialize_to_zbuf( match_arms.push(quote! { #resp_full_name => { let rust_msg = ::from_py(msg)?; - Ok(rust_msg.serialize_to_zbuf()) + Ok(::ros_z::msg::NativeCdrSerdes::serialize(&rust_msg)) } }); } @@ -679,7 +679,7 @@ fn generate_serialize_to_zbuf( /// Direct ZBuf serialization - bypasses Python registry for zero-copy performance pub fn serialize_to_zbuf(type_name: &str, msg: &Bound<'_, PyAny>) -> PyResult<::zenoh_buffers::ZBuf> { use ::ros_z::python_bridge::FromPyMessage; - use ::ros_z::msg::ZMessage; + use ::ros_z::msg::ZSerdes as _; match type_name { #(#match_arms)* _ => Err(pyo3::exceptions::PyValueError::new_err( diff --git a/crates/ros-z-msgs/tests/shm_size_estimation.rs b/crates/ros-z-msgs/tests/shm_size_estimation.rs index 2ae000a4..a1334a3b 100644 --- a/crates/ros-z-msgs/tests/shm_size_estimation.rs +++ b/crates/ros-z-msgs/tests/shm_size_estimation.rs @@ -9,7 +9,7 @@ use std::sync::Arc; use ros_z::{ ZBuf, - msg::{ZMessage, ZSerializer}, + msg::{NativeCdrSerdes, ZMessage, ZSerdes}, shm::ShmProviderBuilder, }; use zenoh_buffers::buffer::Buffer; @@ -82,7 +82,8 @@ fn test_pointcloud2_shm_serialization_with_accurate_estimate() { ); // Serialize to SHM - should not panic! - let result = ::Serdes::serialize_to_shm(&cloud, estimated, &provider); + let result = + >::serialize_to_shm(&cloud, estimated, &provider); assert!(result.is_ok(), "SHM serialization should succeed"); @@ -148,7 +149,8 @@ fn test_image_shm_serialization_with_accurate_estimate() { .expect("Failed to create SHM provider"), ); - let result = ::Serdes::serialize_to_shm(&image, estimated, &provider); + let result = + >::serialize_to_shm(&image, estimated, &provider); assert!(result.is_ok(), "SHM serialization should succeed"); let (zbuf, actual_size) = result.unwrap(); @@ -205,7 +207,8 @@ fn test_laserscan_shm_serialization_with_accurate_estimate() { .expect("Failed to create SHM provider"), ); - let result = ::Serdes::serialize_to_shm(&scan, estimated, &provider); + let result = + >::serialize_to_shm(&scan, estimated, &provider); assert!(result.is_ok(), "SHM serialization should succeed"); let (zbuf, actual_size) = result.unwrap(); @@ -256,7 +259,7 @@ fn test_compressed_image_shm_serialization() { ); let result = - ::Serdes::serialize_to_shm(&img, estimated, &provider); + >::serialize_to_shm(&img, estimated, &provider); assert!(result.is_ok(), "SHM serialization should succeed"); let (zbuf, actual_size) = result.unwrap(); @@ -298,7 +301,8 @@ fn test_multiple_messages_share_shm_pool() { }; let estimated = image.estimated_serialized_size(); - let result = ::Serdes::serialize_to_shm(&image, estimated, &provider); + let result = + >::serialize_to_shm(&image, estimated, &provider); assert!( result.is_ok(), diff --git a/crates/ros-z-msgs/tests/size_estimation_performance.rs b/crates/ros-z-msgs/tests/size_estimation_performance.rs index f21645f5..ea3a6f6c 100644 --- a/crates/ros-z-msgs/tests/size_estimation_performance.rs +++ b/crates/ros-z-msgs/tests/size_estimation_performance.rs @@ -5,7 +5,10 @@ use std::time::Instant; -use ros_z::{ZBuf, msg::ZMessage}; +use ros_z::{ + ZBuf, + msg::{NativeCdrSerdes, ZMessage, ZSerdes}, +}; use ros_z_msgs::{builtin_interfaces::Time, sensor_msgs::*, std_msgs::Header}; use zenoh_buffers::buffer::Buffer; @@ -30,13 +33,13 @@ fn test_pointcloud2_serialization_performance() { // Warm up for _ in 0..5 { - let _ = cloud.serialize_to_zbuf(); + let _ = NativeCdrSerdes::serialize(&cloud); } // Benchmark with accurate size estimation (current implementation) let start = Instant::now(); for _ in 0..100 { - let zbuf = cloud.serialize_to_zbuf(); + let zbuf = NativeCdrSerdes::serialize(&cloud); assert!(zbuf.len() > 1_000_000); } let with_estimation = start.elapsed(); @@ -68,13 +71,13 @@ fn test_image_serialization_performance() { // Warm up for _ in 0..5 { - let _ = image.serialize_to_zbuf(); + let _ = NativeCdrSerdes::serialize(&image); } // Benchmark let start = Instant::now(); for _ in 0..100 { - let zbuf = image.serialize_to_zbuf(); + let zbuf = NativeCdrSerdes::serialize(&image); assert!(zbuf.len() > 920_000); } let elapsed = start.elapsed(); @@ -105,7 +108,7 @@ fn test_estimated_size_matches_actual() { }; let estimated = cloud.estimated_serialized_size(); - let zbuf = cloud.serialize_to_zbuf(); + let zbuf = NativeCdrSerdes::serialize(&cloud); let actual = zbuf.len(); println!("PointCloud2: estimated={}, actual={}", estimated, actual); @@ -130,7 +133,7 @@ fn test_estimated_size_matches_actual() { }; let estimated = image.estimated_serialized_size(); - let zbuf = image.serialize_to_zbuf(); + let zbuf = NativeCdrSerdes::serialize(&image); let actual = zbuf.len(); println!("Image: estimated={}, actual={}", estimated, actual); @@ -158,7 +161,7 @@ fn test_estimated_size_matches_actual() { }; let estimated = scan.estimated_serialized_size(); - let zbuf = scan.serialize_to_zbuf(); + let zbuf = NativeCdrSerdes::serialize(&scan); let actual = zbuf.len(); println!("LaserScan: estimated={}, actual={}", estimated, actual); @@ -171,8 +174,6 @@ fn test_estimated_size_matches_actual() { #[test] fn test_capacity_hint_api() { - use ros_z::msg::{SerdeCdrSerdes, ZSerializer}; - let cloud = PointCloud2 { header: Header { stamp: Time { sec: 0, nanosec: 0 }, @@ -188,14 +189,14 @@ fn test_capacity_hint_api() { is_dense: true, }; - // Test the low-level API with explicit hint + // Test serialize_with_hint (capacity hint API) let hint = cloud.estimated_serialized_size(); - let zbuf = SerdeCdrSerdes::::serialize_to_zbuf_with_hint(&cloud, hint); + let zbuf = >::serialize_with_hint(&cloud, hint); assert!(zbuf.len() > 50_000); println!("Serialized with explicit hint: {} bytes", zbuf.len()); - // Test that ZMessage::serialize_to_zbuf uses the hint automatically - let zbuf2 = cloud.serialize_to_zbuf(); + // Both serialize paths should produce the same length + let zbuf2 = NativeCdrSerdes::serialize(&cloud); assert_eq!(zbuf.len(), zbuf2.len()); } diff --git a/crates/ros-z-py/src/service.rs b/crates/ros-z-py/src/service.rs index 1399988f..19590c2a 100644 --- a/crates/ros-z-py/src/service.rs +++ b/crates/ros-z-py/src/service.rs @@ -1,7 +1,7 @@ use anyhow::Result; use pyo3::prelude::*; use pyo3::types::PyDict; -use ros_z::msg::{ZDeserializer, ZMessage, ZService}; +use ros_z::msg::{SerdeCdrSerdes, ZMessage, ZSerdes, ZService}; use ros_z::service::{QueryKey, ZClient, ZServer}; use std::time::Duration; @@ -26,16 +26,12 @@ impl ZClientWrapper { impl RawClient for ZClientWrapper where T: ZService + Send + Sync + 'static, - T::Request: ZMessage + Send + Sync + 'static, - T::Response: ZMessage + Send + Sync + 'static, - for<'a> ::Serdes: - ZDeserializer = &'a [u8]>, - for<'a> ::Serdes: - ZDeserializer = &'a [u8]>, + T::Request: ZMessage + serde::Serialize + serde::de::DeserializeOwned + Send + Sync + 'static, + T::Response: ZMessage + serde::Serialize + serde::de::DeserializeOwned + Send + Sync + 'static, { fn send_request_serialized(&self, data: &[u8]) -> Result<()> { // Deserialize the request from CDR bytes - let request = ::deserialize(data) + let request = >::deserialize(data) .map_err(|e| anyhow::anyhow!("Failed to deserialize request: {:?}", e))?; // Send the request (this is async in Rust, but we'll block here for Python) @@ -61,13 +57,17 @@ where .map_err(|e| anyhow::anyhow!("Failed to receive response: {}", e))?; // Serialize the response to CDR bytes - Ok(response.serialize()) + Ok(>::serialize_to_vec( + &response, + )) } fn try_take_response_serialized(&self) -> Result>> { // Use a minimal timeout for non-blocking behavior match self.inner.take_response_timeout(Duration::from_millis(1)) { - Ok(response) => Ok(Some(response.serialize())), + Ok(response) => Ok(Some( + >::serialize_to_vec(&response), + )), Err(e) => { let err_str = e.to_string(); if err_str.contains("timeout") @@ -106,12 +106,8 @@ impl ZServerWrapper { impl RawServer for ZServerWrapper where T: ZService + Send + Sync + 'static, - T::Request: ZMessage + Send + Sync + 'static, - T::Response: ZMessage + Send + Sync + 'static, - for<'a> ::Serdes: - ZDeserializer = &'a [u8]>, - for<'a> ::Serdes: - ZDeserializer = &'a [u8]>, + T::Request: ZMessage + serde::Serialize + serde::de::DeserializeOwned + Send + Sync + 'static, + T::Response: ZMessage + serde::Serialize + serde::de::DeserializeOwned + Send + Sync + 'static, { fn take_request_serialized(&self) -> Result<(QueryKey, Vec)> { let mut server = self @@ -124,12 +120,15 @@ where .map_err(|e| anyhow::anyhow!("Failed to receive request: {}", e))?; // Serialize the request to CDR bytes - Ok((key, request.serialize())) + Ok(( + key, + >::serialize_to_vec(&request), + )) } fn send_response_serialized(&self, data: &[u8], key: &QueryKey) -> Result<()> { // Deserialize the response from CDR bytes - let response = ::deserialize(data) + let response = >::deserialize(data) .map_err(|e| anyhow::anyhow!("Failed to deserialize response: {:?}", e))?; let mut server = self diff --git a/crates/ros-z/examples/dynamic_message/interop.rs b/crates/ros-z/examples/dynamic_message/interop.rs index 5dfb3ec7..b13f8d4f 100644 --- a/crates/ros-z/examples/dynamic_message/interop.rs +++ b/crates/ros-z/examples/dynamic_message/interop.rs @@ -7,7 +7,7 @@ use ros_z::{ dynamic::{DynamicMessage, FieldType, MessageSchema}, - msg::ZMessage, + msg::{NativeCdrSerdes, ZMessage, ZSerdes}, }; use ros_z_msgs::{ geometry_msgs::{Point, Twist, Vector3}, @@ -52,7 +52,7 @@ fn main() -> Result<(), Box> { ); // Serialize static to CDR, then deserialize as dynamic - let cdr_bytes = static_point.serialize(); + let cdr_bytes = NativeCdrSerdes::serialize_to_vec(&static_point); let dynamic_point = DynamicMessage::from_cdr(&cdr_bytes, &point_schema)?; println!( "Dynamic Point: x={}, y={}, z={}\n", @@ -76,7 +76,7 @@ fn main() -> Result<(), Box> { // Serialize dynamic to CDR, then deserialize as static let cdr_bytes = dynamic_point.to_cdr()?; - let static_point = Point::deserialize(&cdr_bytes)?; + let static_point = >::deserialize(&cdr_bytes)?; println!( "Static Point: x={}, y={}, z={}\n", static_point.x, static_point.y, static_point.z @@ -89,7 +89,7 @@ fn main() -> Result<(), Box> { }; println!("Static String: \"{}\"", static_string.data); - let cdr_bytes = static_string.serialize(); + let cdr_bytes = NativeCdrSerdes::serialize_to_vec(&static_string); let dynamic_string = DynamicMessage::from_cdr(&cdr_bytes, &string_schema)?; println!( "Dynamic String: \"{}\"\n", @@ -120,7 +120,7 @@ fn main() -> Result<(), Box> { static_twist.angular.x, static_twist.angular.y, static_twist.angular.z ); - let cdr_bytes = static_twist.serialize(); + let cdr_bytes = NativeCdrSerdes::serialize_to_vec(&static_twist); let dynamic_twist = DynamicMessage::from_cdr(&cdr_bytes, &twist_schema)?; println!("Dynamic Twist:"); println!( @@ -148,7 +148,7 @@ fn main() -> Result<(), Box> { dynamic_msg.set("y", 2.5f64)?; dynamic_msg.set("z", 3.5f64)?; - let static_cdr = static_msg.serialize(); + let static_cdr = NativeCdrSerdes::serialize_to_vec(&static_msg); let dynamic_cdr = dynamic_msg.to_cdr()?; println!("Static CDR: {} bytes", static_cdr.len()); @@ -175,10 +175,10 @@ fn main() -> Result<(), Box> { ); // Static → CDR → Dynamic → CDR → Static - let cdr1 = original.serialize(); + let cdr1 = NativeCdrSerdes::serialize_to_vec(&original); let dynamic = DynamicMessage::from_cdr(&cdr1, &point_schema)?; let cdr2 = dynamic.to_cdr()?; - let recovered = Point::deserialize(&cdr2)?; + let recovered = >::deserialize(&cdr2)?; println!( "Recovered static: ({}, {}, {})", diff --git a/crates/ros-z/examples/protobuf_demo/src/types.rs b/crates/ros-z/examples/protobuf_demo/src/types.rs index 253eb72c..593b3a8e 100644 --- a/crates/ros-z/examples/protobuf_demo/src/types.rs +++ b/crates/ros-z/examples/protobuf_demo/src/types.rs @@ -26,9 +26,7 @@ impl MessageTypeInfo for SensorData { impl WithTypeInfo for SensorData {} -impl ZMessage for SensorData { - type Serdes = ros_z::msg::SerdeCdrSerdes; -} +impl ZMessage for SensorData {} // SensorData uses serde/CDR for backward compatibility with the original pub/sub demo @@ -46,10 +44,7 @@ impl MessageTypeInfo for CalculateRequest { impl WithTypeInfo for CalculateRequest {} -// Explicitly implement ZMessage to use ProtobufSerdes for pure protobuf serialization -impl ZMessage for CalculateRequest { - type Serdes = ProtobufSerdes; -} +impl ZMessage for CalculateRequest {} // ========== CalculateResponse Trait Implementations ========== @@ -65,10 +60,7 @@ impl MessageTypeInfo for CalculateResponse { impl WithTypeInfo for CalculateResponse {} -// Explicitly implement ZMessage to use ProtobufSerdes for pure protobuf serialization -impl ZMessage for CalculateResponse { - type Serdes = ProtobufSerdes; -} +impl ZMessage for CalculateResponse {} // ========== Calculate Service Definition ========== diff --git a/crates/ros-z/examples/z_custom_message.rs b/crates/ros-z/examples/z_custom_message.rs index 9f3905bb..301e0ea3 100644 --- a/crates/ros-z/examples/z_custom_message.rs +++ b/crates/ros-z/examples/z_custom_message.rs @@ -29,9 +29,7 @@ impl MessageTypeInfo for RobotStatus { impl ros_z::WithTypeInfo for RobotStatus {} -impl ros_z::msg::ZMessage for RobotStatus { - type Serdes = ros_z::msg::SerdeCdrSerdes; -} +impl ros_z::msg::ZMessage for RobotStatus {} // Custom service request #[derive(Debug, Clone, Serialize, Deserialize, Default)] @@ -53,9 +51,7 @@ impl MessageTypeInfo for NavigateToRequest { impl ros_z::WithTypeInfo for NavigateToRequest {} -impl ros_z::msg::ZMessage for NavigateToRequest { - type Serdes = ros_z::msg::SerdeCdrSerdes; -} +impl ros_z::msg::ZMessage for NavigateToRequest {} // Custom service response #[derive(Debug, Clone, Serialize, Deserialize, Default)] @@ -77,9 +73,7 @@ impl MessageTypeInfo for NavigateToResponse { impl ros_z::WithTypeInfo for NavigateToResponse {} -impl ros_z::msg::ZMessage for NavigateToResponse { - type Serdes = ros_z::msg::SerdeCdrSerdes; -} +impl ros_z::msg::ZMessage for NavigateToResponse {} // Service type definition pub struct NavigateTo; diff --git a/crates/ros-z/src/action/client.rs b/crates/ros-z/src/action/client.rs index 477ea282..fd6e2e1e 100644 --- a/crates/ros-z/src/action/client.rs +++ b/crates/ros-z/src/action/client.rs @@ -11,7 +11,12 @@ use tokio::sync::{mpsc, watch}; use zenoh::Result; use super::{GoalId, GoalInfo, GoalStatus, Time, ZAction, messages::*}; -use crate::{Builder, msg::ZMessage, qos::QosProfile, topic_name::qualify_topic_name}; +use crate::{ + Builder, + msg::{NativeCdrSerdes, SerdeCdrSerdes, ZSerdes}, + qos::QosProfile, + topic_name::qualify_topic_name, +}; /// Type states for goal handles. pub mod goal_state { @@ -178,6 +183,8 @@ impl<'a, A: ZAction> Builder for ZActionClientBuilder<'a, A> { if let Some(qos) = self.feedback_topic_qos { feedback_sub_builder.entity.qos = qos.to_protocol_qos(); } + // FeedbackMessage uses serde (not native CDR), so use SerdeCdrSerdes. + let feedback_sub_builder = feedback_sub_builder.with_serdes::(); tracing::debug!( "Creating feedback subscriber with callback for {}", feedback_topic_name @@ -309,9 +316,8 @@ pub struct ZActionClient { goal_client: Arc>>, result_client: Arc>>, cancel_client: Arc>>, - feedback_sub: - Arc, (), as ZMessage>::Serdes>>, - status_sub: Arc::Serdes>>, + feedback_sub: Arc, (), SerdeCdrSerdes>>, + status_sub: Arc>, goal_board: Arc>, } @@ -403,7 +409,7 @@ impl ZActionClient { payload.len(), payload ); - let response = ::deserialize(&payload) + let response = >::deserialize(&payload) .map_err(|e| zenoh::Error::from(e.to_string()))?; // 5. Check if accepted @@ -430,8 +436,9 @@ impl ZActionClient { self.cancel_client.send_request(&request).await?; let sample = self.cancel_client.rx.recv_async().await?; let payload = sample.payload().to_bytes(); - let response = ::deserialize(&payload) - .map_err(|e| zenoh::Error::from(e.to_string()))?; + let response = + >::deserialize(&payload) + .map_err(|e| zenoh::Error::from(e.to_string()))?; Ok(response) } @@ -447,8 +454,9 @@ impl ZActionClient { self.cancel_client.send_request(&request).await?; let sample = self.cancel_client.rx.recv_async().await?; let payload = sample.payload().to_bytes(); - let response = ::deserialize(&payload) - .map_err(|e| zenoh::Error::from(e.to_string()))?; + let response = + >::deserialize(&payload) + .map_err(|e| zenoh::Error::from(e.to_string()))?; Ok(response) } @@ -477,7 +485,7 @@ impl ZActionClient { self.result_client.send_request(&request).await?; let sample = self.result_client.rx.recv_async().await?; let payload = sample.payload().to_bytes(); - let response = as ZMessage>::deserialize(&payload) + let response = >>::deserialize(&payload) .map_err(|e| zenoh::Error::from(e.to_string()))?; Ok(response.result) @@ -493,7 +501,7 @@ impl ZActionClient { pub async fn recv_goal_response_low(&self) -> Result { let sample = self.goal_client.rx.recv_async().await?; let payload = sample.payload().to_bytes(); - ::deserialize(&payload) + >::deserialize(&payload) .map_err(|e| zenoh::Error::from(e.to_string())) } @@ -506,7 +514,7 @@ impl ZActionClient { pub async fn recv_cancel_response_low(&self) -> Result { let sample = self.cancel_client.rx.recv_async().await?; let payload = sample.payload().to_bytes(); - ::deserialize(&payload) + >::deserialize(&payload) .map_err(|e| zenoh::Error::from(e.to_string())) } @@ -519,7 +527,7 @@ impl ZActionClient { pub async fn recv_result_response_low(&self) -> Result> { let sample = self.result_client.rx.recv_async().await?; let payload = sample.payload().to_bytes(); - as ZMessage>::deserialize(&payload) + >>::deserialize(&payload) .map_err(|e| zenoh::Error::from(e.to_string())) } } diff --git a/crates/ros-z/src/action/driver.rs b/crates/ros-z/src/action/driver.rs index 5c9e45c3..74940e19 100644 --- a/crates/ros-z/src/action/driver.rs +++ b/crates/ros-z/src/action/driver.rs @@ -21,7 +21,10 @@ use super::{ server::{Executing, GoalHandle, InnerServer, Requested, ZActionServer}, state::ServerGoalState, }; -use crate::{attachment::Attachment, msg::ZMessage}; +use crate::{ + attachment::Attachment, + msg::{NativeCdrSerdes, SerdeCdrSerdes, ZSerdes}, +}; /// Runs the unified driver loop for an action server with automatic goal handling. /// @@ -132,7 +135,7 @@ async fn handle_goal_request( { tracing::debug!("Received goal request"); let payload = query.payload().unwrap().to_bytes(); - let request = match as ZMessage>::deserialize(&payload) { + let request = match >>::deserialize(&payload) { Ok(r) => r, Err(e) => { tracing::error!("Failed to deserialize goal request: {}", e); @@ -169,13 +172,14 @@ async fn handle_cancel_request( ) { tracing::debug!("Received cancel request"); let payload = query.payload().unwrap().to_bytes(); - let request = match ::deserialize(&payload) { - Ok(r) => r, - Err(e) => { - tracing::error!("Failed to deserialize cancel request: {}", e); - return; - } - }; + let request = + match >::deserialize(&payload) { + Ok(r) => r, + Err(e) => { + tracing::error!("Failed to deserialize cancel request: {}", e); + return; + } + }; // Mark goal as canceling using the atomic flag let cancelled = inner.goal_manager.read(|manager| { @@ -199,7 +203,8 @@ async fn handle_cancel_request( }, }; - let response_bytes = ::serialize(&response); + let response_bytes = + >::serialize_to_vec(&response); let attachment: Attachment = query.attachment().unwrap().try_into().unwrap(); // FIXME: address the result let _ = query @@ -217,7 +222,7 @@ async fn handle_result_request( ) { tracing::debug!("Received result request"); let payload = query.payload().unwrap().to_bytes(); - let request = match ::deserialize(&payload) { + let request = match >::deserialize(&payload) { Ok(r) => r, Err(e) => { tracing::error!("Failed to deserialize result request: {}", e); @@ -291,7 +296,8 @@ async fn handle_result_request( status: status as i8, result, }; - let response_bytes = as ZMessage>::serialize(&response); + let response_bytes = + >>::serialize_to_vec(&response); let attachment: Attachment = query.attachment().unwrap().try_into().unwrap(); let _ = query .reply(query.key_expr().clone(), response_bytes) diff --git a/crates/ros-z/src/action/macros.rs b/crates/ros-z/src/action/macros.rs index 2c19f4b0..8a0752cc 100644 --- a/crates/ros-z/src/action/macros.rs +++ b/crates/ros-z/src/action/macros.rs @@ -67,27 +67,6 @@ macro_rules! define_action { } } - // Provide ZMessage impls via the serde (SerdeCdrSerdes) path for types that - // do not implement the CDR traits. Types that do implement CdrSerialize + - // CdrDeserialize + CdrSerializedSize get ZMessage automatically from the - // blanket impl in ros_z::msg and should NOT use define_action!. - impl $crate::msg::ZMessage for $goal_type - where - $goal_type: Send + Sync + 'static, - { - type Serdes = $crate::msg::SerdeCdrSerdes<$goal_type>; - } - impl $crate::msg::ZMessage for $result_type - where - $result_type: Send + Sync + 'static, - { - type Serdes = $crate::msg::SerdeCdrSerdes<$result_type>; - } - impl $crate::msg::ZMessage for $feedback_type - where - $feedback_type: Send + Sync + 'static, - { - type Serdes = $crate::msg::SerdeCdrSerdes<$feedback_type>; - } + // ZMessage is covered by the blanket impl in ros_z::msg. }; } diff --git a/crates/ros-z/src/action/messages.rs b/crates/ros-z/src/action/messages.rs index 787b1d81..ada2c3dc 100644 --- a/crates/ros-z/src/action/messages.rs +++ b/crates/ros-z/src/action/messages.rs @@ -540,39 +540,4 @@ impl CdrSerializedSize for CancelGoalServiceResponse { } } -// ── Generic types: still use serde path until ZAction gains CDR bounds ──────── - -impl crate::msg::ZMessage for GoalRequest -where - A::Goal: Send + Sync + serde::Serialize + for<'de> serde::Deserialize<'de> + 'static, -{ - type Serdes = crate::msg::SerdeCdrSerdes>; -} - -impl crate::msg::ZMessage for ResultResponse -where - A::Result: Send + Sync + serde::Serialize + for<'de> serde::Deserialize<'de> + 'static, -{ - type Serdes = crate::msg::SerdeCdrSerdes>; -} - -impl crate::msg::ZMessage for FeedbackMessage -where - A::Feedback: Send + Sync + serde::Serialize + for<'de> serde::Deserialize<'de> + 'static, -{ - type Serdes = crate::msg::SerdeCdrSerdes>; -} - -impl crate::msg::ZMessage for SendGoalRequest -where - A::Goal: Send + Sync + serde::Serialize + for<'de> serde::Deserialize<'de> + 'static, -{ - type Serdes = crate::msg::SerdeCdrSerdes>; -} - -impl crate::msg::ZMessage for GetResultResponse -where - A::Result: Send + Sync + serde::Serialize + for<'de> serde::Deserialize<'de> + 'static, -{ - type Serdes = crate::msg::SerdeCdrSerdes>; -} +// ZMessage is covered by the blanket impl in ros_z::msg for all Send + Sync + 'static types. diff --git a/crates/ros-z/src/action/server.rs b/crates/ros-z/src/action/server.rs index 2e51863a..c4abcb8e 100644 --- a/crates/ros-z/src/action/server.rs +++ b/crates/ros-z/src/action/server.rs @@ -21,7 +21,12 @@ use super::{ messages::*, state::{SafeGoalManager, ServerGoalState}, }; -use crate::{Builder, attachment::Attachment, msg::ZMessage, topic_name::qualify_topic_name}; +use crate::{ + Builder, + attachment::Attachment, + msg::{NativeCdrSerdes, SerdeCdrSerdes, ZSerdes}, + topic_name::qualify_topic_name, +}; /// Private implementation holding the actual server state. /// This is wrapped by the public `ZActionServer` handle. @@ -29,10 +34,8 @@ pub(crate) struct InnerServer { pub(crate) goal_server: Arc>>, pub(crate) result_server: Arc>>, pub(crate) cancel_server: Arc>>, - pub(crate) feedback_pub: - Arc, as ZMessage>::Serdes>>, - pub(crate) status_pub: - Arc::Serdes>>, + pub(crate) feedback_pub: Arc, SerdeCdrSerdes>>, + pub(crate) status_pub: Arc>, pub(crate) goal_manager: Arc>, /// Token to cancel the default result handler when switching to full driver mode pub(crate) result_handler_token: CancellationToken, @@ -149,7 +152,7 @@ async fn handle_result_requests_legacy_inner( ) { tracing::debug!("Received result request"); let payload = query.payload().unwrap().to_bytes(); - let request = match ::deserialize(&payload) { + let request = match >::deserialize(&payload) { Ok(r) => r, Err(e) => { tracing::error!("Failed to deserialize result request: {}", e); @@ -180,7 +183,8 @@ async fn handle_result_requests_legacy_inner( status: status as i8, result, }; - let response_bytes = as ZMessage>::serialize(&response); + let response_bytes = + >>::serialize(&response); let attachment: Attachment = query.attachment().unwrap().try_into().unwrap(); //FIXME: address the result let _ = query @@ -271,7 +275,9 @@ impl<'a, A: ZAction> Builder for ZActionServerBuilder<'a, A> { feedback_pub_builder.entity.qos = qos.to_protocol_qos(); } // Keep attachments enabled for RMW-Zenoh compatibility - let feedback_pub = feedback_pub_builder.build()?; + let feedback_pub = feedback_pub_builder + .with_serdes::() + .build()?; // Create status publisher using node API for proper graph registration // Use the action's status_type_info for proper ROS 2 interop @@ -388,16 +394,11 @@ impl ZActionServer { &self.inner.cancel_server } - fn feedback_pub( - &self, - ) -> &Arc, as ZMessage>::Serdes>> - { + fn feedback_pub(&self) -> &Arc, SerdeCdrSerdes>> { &self.inner.feedback_pub } - fn status_pub( - &self, - ) -> &Arc::Serdes>> { + fn status_pub(&self) -> &Arc> { &self.inner.status_pub } @@ -447,7 +448,7 @@ impl ZActionServer { pub async fn recv_goal(&self) -> Result> { let query = self.goal_server().queue().recv_async().await; let payload = query.payload().unwrap().to_bytes(); - let request = as ZMessage>::deserialize(&payload) + let request = >>::deserialize(&payload) .map_err(|e| zenoh::Error::from(e.to_string()))?; Ok(GoalHandle { @@ -463,7 +464,7 @@ impl ZActionServer { pub async fn recv_cancel(&self) -> Result<(CancelGoalServiceRequest, zenoh::query::Query)> { let query = self.cancel_server().queue().recv_async().await; let payload = query.payload().unwrap().to_bytes(); - let request = ::deserialize(&payload) + let request = >::deserialize(&payload) .map_err(|e| zenoh::Error::from(e.to_string()))?; Ok((request, query)) } @@ -490,7 +491,7 @@ impl ZActionServer { pub async fn recv_result_request(&self) -> Result<(GoalId, zenoh::query::Query)> { let query = self.result_server().queue().recv_async().await; let payload = query.payload().unwrap().to_bytes(); - let request = ::deserialize(&payload) + let request = >::deserialize(&payload) .map_err(|e| zenoh::Error::from(e.to_string()))?; Ok((request.goal_id, query)) } @@ -501,7 +502,7 @@ impl ZActionServer { query: &zenoh::query::Query, response: &GoalResponse, ) -> Result<()> { - let response_bytes = ::serialize(response); + let response_bytes = >::serialize(response); let attachment: Attachment = query.attachment().unwrap().try_into().unwrap(); let _ = query .reply(query.key_expr().clone(), response_bytes) @@ -516,7 +517,7 @@ impl ZActionServer { ) -> Result<(CancelGoalServiceRequest, zenoh::query::Query)> { let query = self.cancel_server().queue().recv_async().await; let payload = query.payload().unwrap().to_bytes(); - let request = ::deserialize(&payload) + let request = >::deserialize(&payload) .map_err(|e| zenoh::Error::from(e.to_string()))?; Ok((request, query)) } @@ -526,7 +527,8 @@ impl ZActionServer { query: &zenoh::query::Query, response: &CancelGoalServiceResponse, ) -> Result<()> { - let response_bytes = ::serialize(response); + let response_bytes = + >::serialize(response); let attachment: Attachment = query.attachment().unwrap().try_into().unwrap(); let _ = query .reply(query.key_expr().clone(), response_bytes) @@ -541,7 +543,7 @@ impl ZActionServer { query: &zenoh::query::Query, response: &GetResultResponse, ) -> Result<()> { - let response_bytes = as ZMessage>::serialize(response); + let response_bytes = >>::serialize(response); let attachment: Attachment = query.attachment().unwrap().try_into().unwrap(); let _ = query .reply(query.key_expr().clone(), response_bytes) @@ -762,7 +764,7 @@ impl GoalHandle { stamp_sec: self.info.stamp.sec, stamp_nanosec: self.info.stamp.nanosec, }; - let response_bytes = ::serialize(&response); + let response_bytes = >::serialize(&response); if let Some(query) = self.query.take() { let attachment: Attachment = query.attachment().unwrap().try_into().unwrap(); @@ -809,7 +811,7 @@ impl GoalHandle { stamp_sec: 0, stamp_nanosec: 0, }; - let response_bytes = ::serialize(&response); + let response_bytes = >::serialize(&response); if let Some(query) = self.query.take() { // FIXME: Address the unwrap usage diff --git a/crates/ros-z/src/dynamic/error.rs b/crates/ros-z/src/dynamic/error.rs index 80175412..76a99c09 100644 --- a/crates/ros-z/src/dynamic/error.rs +++ b/crates/ros-z/src/dynamic/error.rs @@ -46,6 +46,9 @@ pub enum DynamicError { /// Bounded string/sequence exceeded maximum size BoundExceeded { max: usize, actual: usize }, + + /// Deserialization requires a schema (use the specialized recv() on DynamicMessage subscriber) + SchemaMissing, } impl fmt::Display for DynamicError { @@ -117,6 +120,13 @@ impl fmt::Display for DynamicError { max, actual ) } + DynamicError::SchemaMissing => { + write!( + f, + "DynamicMessage deserialization requires a schema; use .with_dyn_schema() \ + when building the subscriber and call recv() on ZSub" + ) + } } } } diff --git a/crates/ros-z/src/dynamic/mod.rs b/crates/ros-z/src/dynamic/mod.rs index 2e028604..ee191d9e 100644 --- a/crates/ros-z/src/dynamic/mod.rs +++ b/crates/ros-z/src/dynamic/mod.rs @@ -87,13 +87,9 @@ pub use value::{DynamicValue, FromDynamic, IntoDynamic}; use zenoh::sample::Sample; -use crate::msg::ZMessage; use crate::pubsub::{ZPub, ZPubBuilder, ZSub, ZSubBuilder}; -// Implement ZMessage for DynamicMessage -impl ZMessage for DynamicMessage { - type Serdes = DynamicSerdeCdrSerdes; -} +// ZMessage covered by blanket impl in ros_z::msg for all Send + Sync + 'static types. // Type aliases for convenience /// Type alias for a dynamic message publisher. diff --git a/crates/ros-z/src/dynamic/serdes.rs b/crates/ros-z/src/dynamic/serdes.rs index a1178955..18c3920f 100644 --- a/crates/ros-z/src/dynamic/serdes.rs +++ b/crates/ros-z/src/dynamic/serdes.rs @@ -8,7 +8,7 @@ use std::sync::Arc; use zenoh_buffers::ZBuf; -use crate::msg::{ZDeserializer, ZSerializer}; +use crate::msg::{ZDeserializer, ZSerdes, ZSerializer}; use super::error::DynamicError; use super::message::DynamicMessage; @@ -108,6 +108,57 @@ impl ZDeserializer for DynamicSerdeCdrSerdes { } } +/// `ZSerdes` implementation for `DynamicSerdeCdrSerdes`. +/// +/// The `deserialize` method on this impl requires a schema at runtime; when called +/// without one (plain byte-slice path) it returns an error. The real deserialization +/// path is the specialized `ZSub` impl which calls +/// `DynamicSerdeCdrSerdes::deserialize((&bytes, schema))` directly. +impl ZSerdes for DynamicSerdeCdrSerdes { + type Error = DynamicError; + + fn serialize(msg: &DynamicMessage) -> ZBuf { + msg.to_cdr_zbuf() + .expect("DynamicMessage CDR serialization failed") + } + + fn serialize_with_hint(msg: &DynamicMessage, _capacity_hint: usize) -> ZBuf { + >::serialize(msg) + } + + fn serialize_to_shm( + msg: &DynamicMessage, + _estimated_size: usize, + provider: &zenoh::shm::ShmProvider, + ) -> zenoh::Result<(ZBuf, usize)> { + let data = msg.to_cdr().map_err(|e| { + zenoh::Error::from(format!("DynamicMessage serialization failed: {}", e)) + })?; + let actual_size = data.len(); + + use zenoh::Wait; + use zenoh::shm::{BlockOn, GarbageCollect}; + + let mut shm_buf = provider + .alloc(actual_size) + .with_policy::>() + .wait() + .map_err(|e| zenoh::Error::from(format!("SHM allocation failed: {}", e)))?; + + shm_buf[0..actual_size].copy_from_slice(&data); + Ok((ZBuf::from(shm_buf), actual_size)) + } + + fn serialize_to_vec(msg: &DynamicMessage) -> Vec { + msg.to_cdr() + .expect("DynamicMessage CDR serialization failed") + } + + fn deserialize(_buf: &[u8]) -> Result { + Err(DynamicError::SchemaMissing) + } +} + #[cfg(test)] mod tests { use super::*; @@ -144,10 +195,12 @@ mod tests { msg.set("z", 3.5f64).unwrap(); // Serialize - let bytes = DynamicSerdeCdrSerdes::serialize(&msg); + let bytes = ::serialize(&msg); // Deserialize - let deserialized = DynamicSerdeCdrSerdes::deserialize((&bytes, &schema)).unwrap(); + let deserialized = + ::deserialize((&bytes, &schema)) + .unwrap(); assert_eq!(deserialized.get::("x").unwrap(), 1.5); assert_eq!(deserialized.get::("y").unwrap(), 2.5); @@ -166,7 +219,7 @@ mod tests { DynamicSerdeCdrSerdes::serialize_to_buf(&msg, &mut buffer); // Should match serialize() output - let direct = DynamicSerdeCdrSerdes::serialize(&msg); + let direct = ::serialize(&msg); assert_eq!(buffer, direct); } } diff --git a/crates/ros-z/src/dynamic/tests/interop_tests.rs b/crates/ros-z/src/dynamic/tests/interop_tests.rs index a5fc1b02..f4c90895 100644 --- a/crates/ros-z/src/dynamic/tests/interop_tests.rs +++ b/crates/ros-z/src/dynamic/tests/interop_tests.rs @@ -8,7 +8,7 @@ use std::sync::Arc; use crate::dynamic::message::DynamicMessage; use crate::dynamic::schema::{FieldType, MessageSchema}; -use crate::msg::ZMessage; +use crate::msg::{NativeCdrSerdes, ZMessage, ZSerdes}; use ros_z_msgs::geometry_msgs::{Point, Twist, Vector3}; use ros_z_msgs::std_msgs::String as StdString; @@ -61,7 +61,7 @@ fn test_static_point_to_dynamic() { }; // Serialize to CDR bytes - let cdr_bytes = static_msg.serialize(); + let cdr_bytes = NativeCdrSerdes::serialize_to_vec(&static_msg); // Deserialize as dynamic message let schema = create_point_schema(); @@ -86,7 +86,7 @@ fn test_dynamic_point_to_static() { let cdr_bytes = dynamic_msg.to_cdr().unwrap(); // Deserialize as static message - let static_msg = Point::deserialize(&cdr_bytes).unwrap(); + let static_msg = >::deserialize(&cdr_bytes).unwrap(); // Verify values match assert_eq!(static_msg.x, 10.0); @@ -102,7 +102,7 @@ fn test_static_string_to_dynamic() { }; // Serialize to CDR bytes - let cdr_bytes = static_msg.serialize(); + let cdr_bytes = NativeCdrSerdes::serialize_to_vec(&static_msg); // Deserialize as dynamic message let schema = create_string_schema(); @@ -126,7 +126,7 @@ fn test_dynamic_string_to_static() { let cdr_bytes = dynamic_msg.to_cdr().unwrap(); // Deserialize as static message - let static_msg = StdString::deserialize(&cdr_bytes).unwrap(); + let static_msg = >::deserialize(&cdr_bytes).unwrap(); // Verify value matches assert_eq!(static_msg.data, "Hello from dynamic!"); @@ -149,7 +149,7 @@ fn test_static_twist_to_dynamic() { }; // Serialize to CDR bytes - let cdr_bytes = static_msg.serialize(); + let cdr_bytes = NativeCdrSerdes::serialize_to_vec(&static_msg); // Deserialize as dynamic message let schema = create_twist_schema(); @@ -180,7 +180,7 @@ fn test_dynamic_twist_to_static() { let cdr_bytes = dynamic_msg.to_cdr().unwrap(); // Deserialize as static message - let static_msg = Twist::deserialize(&cdr_bytes).unwrap(); + let static_msg = >::deserialize(&cdr_bytes).unwrap(); // Verify values match assert_eq!(static_msg.linear.x, 0.5); @@ -201,13 +201,13 @@ fn test_roundtrip_static_dynamic_static() { }; // Static → CDR → Dynamic - let cdr_bytes = original.serialize(); + let cdr_bytes = NativeCdrSerdes::serialize_to_vec(&original); let schema = create_point_schema(); let dynamic_msg = DynamicMessage::from_cdr(&cdr_bytes, &schema).unwrap(); // Dynamic → CDR → Static let cdr_bytes2 = dynamic_msg.to_cdr().unwrap(); - let recovered = Point::deserialize(&cdr_bytes2).unwrap(); + let recovered = >::deserialize(&cdr_bytes2).unwrap(); // Verify original matches recovered assert_eq!(original.x, recovered.x); @@ -226,10 +226,10 @@ fn test_roundtrip_dynamic_static_dynamic() { // Dynamic → CDR → Static let cdr_bytes = original.to_cdr().unwrap(); - let static_msg = Point::deserialize(&cdr_bytes).unwrap(); + let static_msg = >::deserialize(&cdr_bytes).unwrap(); // Static → CDR → Dynamic - let cdr_bytes2 = static_msg.serialize(); + let cdr_bytes2 = NativeCdrSerdes::serialize_to_vec(&static_msg); let recovered = DynamicMessage::from_cdr(&cdr_bytes2, &schema).unwrap(); // Verify original matches recovered @@ -262,7 +262,7 @@ fn test_cdr_bytes_identical() { dynamic_msg.set("y", 2.0f64).unwrap(); dynamic_msg.set("z", 3.0f64).unwrap(); - let static_bytes = static_msg.serialize(); + let static_bytes = NativeCdrSerdes::serialize_to_vec(&static_msg); let dynamic_bytes = dynamic_msg.to_cdr().unwrap(); // CDR bytes should be identical diff --git a/crates/ros-z/src/dynamic/tests/pubsub_tests.rs b/crates/ros-z/src/dynamic/tests/pubsub_tests.rs index f4e33071..a804b24a 100644 --- a/crates/ros-z/src/dynamic/tests/pubsub_tests.rs +++ b/crates/ros-z/src/dynamic/tests/pubsub_tests.rs @@ -121,17 +121,18 @@ fn test_dynamic_cdr_serdes_to_buf() { #[test] fn test_zmessage_impl_for_dynamic_message() { - use crate::msg::ZMessage; - let schema = create_point_schema(); let mut msg = DynamicMessage::new(&schema); msg.set("x", 5.0f64).unwrap(); msg.set("y", 6.0f64).unwrap(); msg.set("z", 7.0f64).unwrap(); - // Use ZMessage trait method (must use fully qualified syntax because - // DynamicMessage has its own serialize method that shadows the trait method) - let bytes = ::serialize(&msg); + // Use DynamicSerdeCdrSerdes for serialization (ZMessage is now a marker trait) + use crate::dynamic::DynamicSerdeCdrSerdes; + use crate::msg::ZSerdes; + let zbuf = >::serialize(&msg); + use zenoh_buffers::buffer::SplitBuffer; + let bytes = zbuf.contiguous(); assert!(!bytes.is_empty()); // Verify CDR header diff --git a/crates/ros-z/src/dynamic/type_description_service.rs b/crates/ros-z/src/dynamic/type_description_service.rs index 0107ab89..cac469bc 100644 --- a/crates/ros-z/src/dynamic/type_description_service.rs +++ b/crates/ros-z/src/dynamic/type_description_service.rs @@ -591,17 +591,21 @@ impl TypeDescriptionService { /// This is called from the Zenoh callback when a query is received. /// It deserializes the request, looks up the schema, and sends the response. fn handle_query(schemas: &Arc>>, query: Query) { - use crate::msg::{SerdeCdrSerdes, ZDeserializer, ZSerializer}; + use crate::msg::{SerdeCdrSerdes, ZSerdes}; // Deserialize the request let request: GetTypeDescriptionRequest = match query.payload() { - Some(payload) => match SerdeCdrSerdes::deserialize(payload.to_bytes().as_ref()) { - Ok(req) => req, - Err(e) => { - warn!("[TDS] Failed to deserialize request: {}", e); - return; + Some(payload) => { + match >::deserialize( + payload.to_bytes().as_ref(), + ) { + Ok(req) => req, + Err(e) => { + warn!("[TDS] Failed to deserialize request: {}", e); + return; + } } - }, + } None => { warn!("[TDS] Query has no payload"); return; @@ -627,7 +631,7 @@ impl TypeDescriptionService { ); // Serialize and send the response - let bytes = SerdeCdrSerdes::serialize(&response); + let bytes = >::serialize(&response); use zenoh::Wait; if let Err(e) = query.reply(query.key_expr().clone(), bytes).wait() { warn!("[TDS] Failed to send response: {}", e); diff --git a/crates/ros-z/src/msg.rs b/crates/ros-z/src/msg.rs index d898f0c1..edab04fe 100644 --- a/crates/ros-z/src/msg.rs +++ b/crates/ros-z/src/msg.rs @@ -56,7 +56,7 @@ pub trait ZSerializer { /// /// let msg = LargeMsg { data: vec![0; 1_000_000] }; /// let hint = 4 + 4 + 1_000_000; // header + length + data - /// let zbuf = SerdeCdrSerdes::::serialize_to_zbuf_with_hint(&msg, hint); + /// let zbuf = SerdeCdrSerdes::serialize_to_zbuf_with_hint(&msg, hint); /// ``` fn serialize_to_zbuf_with_hint(input: Self::Input<'_>, capacity_hint: usize) -> ZBuf; @@ -95,7 +95,7 @@ pub trait ZSerializer { /// let msg = MyMsg { value: 42 }; /// let provider = ShmProviderBuilder::new(1024 * 1024).build()?; /// - /// let (zbuf, size) = SerdeCdrSerdes::::serialize_to_shm(&msg, 128, &provider)?; + /// let (zbuf, size) = SerdeCdrSerdes::serialize_to_shm(&msg, 128, &provider)?; /// println!("Serialized {} bytes to SHM", size); /// # Ok(()) /// # } @@ -139,28 +139,55 @@ pub trait ZDeserializer { fn deserialize(input: Self::Input<'_>) -> Result; } -// Core Z-Message trait -pub trait ZMessage: Send + Sync + Sized + 'static { - type Serdes: for<'a> ZSerializer = &'a Self> + ZDeserializer; +/// Unified serialization/deserialization trait for ROS-Z messages. +/// +/// `ZSerdes` replaces the old `ZMessage::Serdes` associated type. +/// Implement this trait to define a custom wire format for message type `T`. +/// +/// Two built-in implementations are provided: +/// - [`NativeCdrSerdes`]: fast path using `CdrSerialize`/`CdrDeserialize` traits +/// - [`SerdeCdrSerdes`]: compatibility path using `serde::Serialize`/`Deserialize` +/// +/// # Example +/// +/// ```rust,no_run +/// use ros_z::msg::{ZSerdes, NativeCdrSerdes, SerdeCdrSerdes}; +/// use serde::{Serialize, Deserialize}; +/// +/// #[derive(Serialize, Deserialize)] +/// struct MyMsg { value: u32 } +/// +/// // Use serde path (works with any serde type) +/// fn publish_serde(msg: &MyMsg) -> zenoh_buffers::ZBuf { +/// SerdeCdrSerdes::serialize(msg) +/// } +/// ``` +pub trait ZSerdes: Send + Sync + 'static { + /// Error type returned by deserialization. + type Error: std::error::Error + Send + Sync + 'static; - fn serialize(&self) -> Vec { - Self::Serdes::serialize(self) - } + /// Serialize `msg` to a ZBuf. + fn serialize(msg: &T) -> ZBuf; - fn serialize_to_zbuf(&self) -> ZBuf { - // Use accurate size estimation for optimal buffer allocation - Self::Serdes::serialize_to_zbuf_with_hint(self, self.estimated_serialized_size()) - } + /// Serialize `msg` to a ZBuf with a capacity hint. + fn serialize_with_hint(msg: &T, capacity_hint: usize) -> ZBuf; - fn deserialize( - input: ::Input<'_>, - ) -> Result::Error> - where - Self::Serdes: ZDeserializer, - { - Self::Serdes::deserialize(input) - } + /// Serialize `msg` to a Vec. + fn serialize_to_vec(msg: &T) -> Vec; + /// Serialize `msg` to shared memory. + fn serialize_to_shm( + msg: &T, + estimated_size: usize, + provider: &zenoh::shm::ShmProvider, + ) -> zenoh::Result<(ZBuf, usize)>; + + /// Deserialize a message from a byte slice. + fn deserialize(buf: &[u8]) -> Result; +} + +// Core Z-Message trait +pub trait ZMessage: Send + Sync + Sized + 'static { /// Get an estimated upper bound on the serialized size of this message. /// /// This is used to pre-allocate buffers for optimal serialization performance, @@ -201,29 +228,94 @@ pub trait ZMessage: Send + Sync + Sized + 'static { } } -// Blanket implementation for types with dedicated CDR traits (fast path). -// All generated message types satisfy these bounds; internal ros-z types that -// only have serde get explicit ZMessage impls below using SerdeCdrSerdes instead. -impl ZMessage for T -where - T: Send - + Sync - + ros_z_cdr::CdrSerialize - + ros_z_cdr::CdrDeserialize - + ros_z_cdr::CdrSerializedSize - + 'static, -{ - type Serdes = NativeCdrSerdes; -} +// Blanket implementation: any type satisfying the CDR traits is a ZMessage. +// All generated message types satisfy these bounds. +impl ZMessage for T where T: Send + Sync + 'static {} // ── Serde-based CDR serialization (existing path, kept for non-generated types) ─────────── -pub struct SerdeCdrSerdes(PhantomData); +/// Unit struct implementing CDR serialization via serde. +/// +/// Use this serdes when your type implements `serde::Serialize + serde::de::DeserializeOwned` +/// but not the native `CdrSerialize`/`CdrDeserialize` traits. This includes: +/// - roslibrust message types +/// - Custom user types with `#[derive(Serialize, Deserialize)]` +/// - Internal wire types that haven't been migrated to native CDR +/// +/// Performance: ~15-30% slower than `NativeCdrSerdes` due to serde overhead. +pub struct SerdeCdrSerdes; /// CDR encapsulation header for little-endian encoding pub const CDR_HEADER_LE: [u8; 4] = [0x00, 0x01, 0x00, 0x00]; -impl ZSerializer for SerdeCdrSerdes +impl ZSerdes for SerdeCdrSerdes +where + T: serde::Serialize + serde::de::DeserializeOwned + Send + Sync + 'static, +{ + type Error = CdrError; + + fn serialize(msg: &T) -> ZBuf { + Self::serialize_with_hint(msg, 256) + } + + fn serialize_with_hint(msg: &T, capacity_hint: usize) -> ZBuf { + let mut writer = ZBufWriter::with_capacity(capacity_hint); + writer.extend_from_slice(&CDR_HEADER_LE); + let mut serializer = CdrSerializer::::new(&mut writer); + msg.serialize(&mut serializer).unwrap(); + writer.into_zbuf() + } + + fn serialize_to_vec(msg: &T) -> Vec { + let mut buffer = Vec::new(); + buffer.extend_from_slice(&CDR_HEADER_LE); + let mut fast_ser = CdrSerializer::::new(&mut buffer); + msg.serialize(&mut fast_ser).unwrap(); + buffer + } + + fn serialize_to_shm( + msg: &T, + estimated_size: usize, + provider: &zenoh::shm::ShmProvider, + ) -> zenoh::Result<(ZBuf, usize)> { + let mut writer = crate::shm::ShmWriter::new(provider, estimated_size)?; + writer.extend_from_slice(&CDR_HEADER_LE); + let mut serializer = CdrSerializer::::new(&mut writer); + msg.serialize(&mut serializer) + .map_err(|e| zenoh::Error::from(format!("CDR serialization failed: {}", e)))?; + let actual_size = writer.position(); + let zbuf = writer.into_zbuf()?; + Ok((zbuf, actual_size)) + } + + fn deserialize(buf: &[u8]) -> Result { + if buf.len() < 4 { + return Err(CdrError("CDR data too short for header".into())); + } + let representation_identifier = &buf[0..2]; + if representation_identifier != [0x00, 0x01] { + return Err(CdrError(format!( + "Expected CDR_LE encapsulation ({:?}), found {:?}", + [0x00, 0x01], + representation_identifier + ))); + } + let payload = &buf[4..]; + let x = ros_z_cdr::from_bytes::(payload) + .map_err(|e| CdrError(e.to_string()))?; + Ok(x.0) + } +} + +// Keep old ZSerializer/ZDeserializer impls on a wrapper for backwards compat +// with any callers that use SerdeCdrSerdes::::serialize_to_zbuf() etc. +// We provide a type alias for backwards compat. +/// Backwards-compatible wrapper. Prefer [`SerdeCdrSerdes`] (unit struct). +#[doc(hidden)] +pub struct SerdeCdrSerdesTyped(PhantomData); + +impl ZSerializer for SerdeCdrSerdesTyped where T: Serialize, { @@ -274,7 +366,7 @@ where } } -impl ZDeserializer for SerdeCdrSerdes +impl ZDeserializer for SerdeCdrSerdesTyped where for<'a> T: Deserialize<'a>, { @@ -303,14 +395,78 @@ where // ── Fast CdrSerialize-based CDR serialization (new path for generated types) ──────────── -/// CDR serialization using the `CdrSerialize`/`CdrDeserialize` traits directly. +/// Unit struct implementing CDR serialization via native `CdrSerialize`/`CdrDeserialize` traits. /// /// Generated message types implement these traits and use `NativeCdrSerdes` as their -/// `ZMessage::Serdes` type. This enables the POD bulk fast path for sequences of +/// default serdes. This enables the POD bulk fast path for sequences of /// plain types (e.g., `Vec`, `Vec`). -pub struct NativeCdrSerdes(PhantomData); +/// +/// This is the default serdes for `ZPub`, `ZSub`, `ZClient`, and `ZServer`. +pub struct NativeCdrSerdes; -impl ZSerializer for NativeCdrSerdes +impl ZSerdes for NativeCdrSerdes +where + T: CdrSerialize + CdrSerializedSize + CdrDeserialize + Send + Sync + 'static, +{ + type Error = CdrError; + + fn serialize(msg: &T) -> ZBuf { + let capacity_hint = msg.cdr_serialized_size(0) + 4; + Self::serialize_with_hint(msg, capacity_hint) + } + + fn serialize_with_hint(msg: &T, capacity_hint: usize) -> ZBuf { + let mut writer = ZBufWriter::with_capacity(capacity_hint); + writer.extend_from_slice(&CDR_HEADER_LE); + ros_z_cdr::traits::cdr_to_zbuf_writer(msg, &mut writer); + writer.into_zbuf() + } + + fn serialize_to_vec(msg: &T) -> Vec { + let mut buffer = Vec::new(); + buffer.extend_from_slice(&CDR_HEADER_LE); + let mut cdr_writer = CdrWriter::::new(&mut buffer); + msg.cdr_serialize(&mut cdr_writer); + buffer + } + + fn serialize_to_shm( + msg: &T, + estimated_size: usize, + provider: &zenoh::shm::ShmProvider, + ) -> zenoh::Result<(ZBuf, usize)> { + let mut writer = crate::shm::ShmWriter::new(provider, estimated_size)?; + writer.extend_from_slice(&CDR_HEADER_LE); + let mut cdr_writer = CdrWriter::::new(&mut writer); + msg.cdr_serialize(&mut cdr_writer); + let actual_size = writer.position(); + let zbuf = writer.into_zbuf()?; + Ok((zbuf, actual_size)) + } + + fn deserialize(buf: &[u8]) -> Result { + if buf.len() < 4 { + return Err(CdrError("CDR data too short for header".into())); + } + let representation_identifier = &buf[0..2]; + if representation_identifier != [0x00, 0x01] { + return Err(CdrError(format!( + "Expected CDR_LE encapsulation ({:?}), found {:?}", + [0x00, 0x01], + representation_identifier + ))); + } + let payload = &buf[4..]; + let mut reader = ros_z_cdr::CdrReader::::new(payload); + T::cdr_deserialize(&mut reader).map_err(|e| CdrError(e.to_string())) + } +} + +/// Backwards-compatible typed wrapper for NativeCdrSerdes. +#[doc(hidden)] +pub struct NativeCdrSerdesTyped(PhantomData); + +impl ZSerializer for NativeCdrSerdesTyped where T: CdrSerialize + CdrSerializedSize, { @@ -359,7 +515,7 @@ where } } -impl ZDeserializer for NativeCdrSerdes +impl ZDeserializer for NativeCdrSerdesTyped where T: CdrDeserialize, { @@ -388,10 +544,61 @@ where // Protobuf #[cfg(feature = "protobuf")] -pub struct ProtobufSerdes(PhantomData); +pub struct ProtobufSerdes; #[cfg(feature = "protobuf")] -impl ZSerializer for ProtobufSerdes +impl ZSerdes for ProtobufSerdes +where + T: ProstMessage + Default + Send + Sync + 'static, +{ + type Error = prost::DecodeError; + + fn serialize(msg: &T) -> ZBuf { + ZBuf::from(msg.encode_to_vec()) + } + + fn serialize_with_hint(msg: &T, _capacity_hint: usize) -> ZBuf { + Self::serialize(msg) + } + + fn serialize_to_vec(msg: &T) -> Vec { + msg.encode_to_vec() + } + + fn serialize_to_shm( + msg: &T, + estimated_size: usize, + provider: &zenoh::shm::ShmProvider, + ) -> zenoh::Result<(ZBuf, usize)> { + let data = msg.encode_to_vec(); + let actual_size = data.len(); + + use zenoh::Wait; + use zenoh::shm::{BlockOn, GarbageCollect}; + + let mut shm_buf = provider + .alloc(estimated_size.max(actual_size)) + .with_policy::>() + .wait() + .map_err(|e| zenoh::Error::from(format!("SHM allocation failed: {}", e)))?; + + shm_buf[0..actual_size].copy_from_slice(&data); + + Ok((ZBuf::from(shm_buf), actual_size)) + } + + fn deserialize(buf: &[u8]) -> Result { + T::decode(buf) + } +} + +/// Backwards-compatible typed protobuf wrapper. +#[cfg(feature = "protobuf")] +#[doc(hidden)] +pub struct ProtobufSerdesTyped(PhantomData); + +#[cfg(feature = "protobuf")] +impl ZSerializer for ProtobufSerdesTyped where T: ProstMessage, { @@ -446,7 +653,7 @@ where } #[cfg(feature = "protobuf")] -impl ZDeserializer for ProtobufSerdes +impl ZDeserializer for ProtobufSerdesTyped where T: ProstMessage + Default, { @@ -475,10 +682,6 @@ mod tests { text: String, } - impl ZMessage for SimpleMessage { - type Serdes = SerdeCdrSerdes; - } - #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] struct LargeMessage { data: Vec, @@ -493,14 +696,14 @@ mod tests { text: "Hello, ZBuf!".to_string(), }; - let zbuf = SerdeCdrSerdes::::serialize_to_zbuf(&msg); + let zbuf = SerdeCdrSerdes::serialize(&msg); let bytes = zbuf.contiguous(); // Verify CDR header assert_eq!(&bytes[0..4], &CDR_HEADER_LE); // Verify roundtrip - let deserialized = SerdeCdrSerdes::::deserialize(&bytes).unwrap(); + let deserialized = >::deserialize(&bytes).unwrap(); assert_eq!(deserialized, msg); } @@ -512,45 +715,13 @@ mod tests { }; // Both methods should produce identical bytes - let zbuf = SerdeCdrSerdes::::serialize_to_zbuf(&msg); - let vec = SerdeCdrSerdes::::serialize(&msg); + let zbuf = SerdeCdrSerdes::serialize(&msg); + let vec = SerdeCdrSerdes::serialize_to_vec(&msg); let zbuf_bytes = zbuf.contiguous(); assert_eq!(&*zbuf_bytes, &vec[..]); } - #[test] - fn test_serialize_to_zbuf_reuse() { - let msg1 = SimpleMessage { - value: 1, - text: "first".to_string(), - }; - let msg2 = SimpleMessage { - value: 2, - text: "second".to_string(), - }; - - let mut buffer = Vec::with_capacity(1024); - - // First serialization - let zbuf1 = SerdeCdrSerdes::::serialize_to_zbuf_reuse(&msg1, &mut buffer); - let bytes1 = zbuf1.contiguous(); - - // Buffer should be empty after take - assert!(buffer.is_empty()); - - // Second serialization (buffer will be reallocated) - let zbuf2 = SerdeCdrSerdes::::serialize_to_zbuf_reuse(&msg2, &mut buffer); - let bytes2 = zbuf2.contiguous(); - - // Verify roundtrips - let decoded1 = SerdeCdrSerdes::::deserialize(&bytes1).unwrap(); - let decoded2 = SerdeCdrSerdes::::deserialize(&bytes2).unwrap(); - - assert_eq!(decoded1, msg1); - assert_eq!(decoded2, msg2); - } - #[test] fn test_zmessage_serialize_to_zbuf() { let msg = SimpleMessage { @@ -558,13 +729,13 @@ mod tests { text: "trait test".to_string(), }; - // ZMessage trait provides serialize_to_zbuf method - let zbuf = msg.serialize_to_zbuf(); + // Use SerdeCdrSerdes directly for serde-only types + let zbuf = SerdeCdrSerdes::serialize(&msg); let bytes = zbuf.contiguous(); assert_eq!(&bytes[0..4], &CDR_HEADER_LE); - let deserialized = ::deserialize(&bytes).unwrap(); + let deserialized = >::deserialize(&bytes).unwrap(); assert_eq!(deserialized, msg); } @@ -576,9 +747,10 @@ mod tests { }; // Serialize using both methods - let vec1 = SerdeCdrSerdes::::serialize(&msg); - let mut vec2 = Vec::new(); - SerdeCdrSerdes::::serialize_to_buf(&msg, &mut vec2); + let vec1 = SerdeCdrSerdes::serialize_to_vec(&msg); + let zbuf = SerdeCdrSerdes::serialize(&msg); + let zbuf_bytes = zbuf.contiguous(); + let vec2 = zbuf_bytes.to_vec(); // Results should be identical assert_eq!(vec1, vec2); @@ -587,54 +759,7 @@ mod tests { } #[test] - fn test_cdr_serialize_to_buf_reuses_capacity() { - let msg = SimpleMessage { - value: 123, - text: "test".to_string(), - }; - - let mut buffer = Vec::with_capacity(1024); - SerdeCdrSerdes::::serialize_to_buf(&msg, &mut buffer); - - let capacity_after_first = buffer.capacity(); - assert_eq!(capacity_after_first, 1024); - - // Serialize again - should reuse capacity - SerdeCdrSerdes::::serialize_to_buf(&msg, &mut buffer); - assert_eq!(buffer.capacity(), capacity_after_first); - } - - #[test] - fn test_cdr_serialize_to_buf_clears_previous_data() { - let msg1 = LargeMessage { - data: vec![1; 1000], - count: 100, - nested: vec![], - }; - - let msg2 = SimpleMessage { - value: 1, - text: "x".to_string(), - }; - - let mut buffer = Vec::new(); - - // Serialize large message - SerdeCdrSerdes::::serialize_to_buf(&msg1, &mut buffer); - let len1 = buffer.len(); - assert!(len1 > 100); - - // Serialize small message - should clear buffer first - SerdeCdrSerdes::::serialize_to_buf(&msg2, &mut buffer); - let len2 = buffer.len(); - assert!(len2 < len1); - - // Verify content is correct (not mixed) - assert_eq!(&buffer[0..4], &CDR_HEADER_LE); // CDR header - } - - #[test] - fn test_cdr_roundtrip_with_serialize_to_buf() { + fn test_cdr_roundtrip_with_serialize_to_vec() { let original = LargeMessage { data: vec![1, 2, 3, 4, 5, 6, 7, 8], count: 42, @@ -650,70 +775,17 @@ mod tests { ], }; - // Serialize using serialize_to_buf - let mut buffer = Vec::new(); - SerdeCdrSerdes::::serialize_to_buf(&original, &mut buffer); + // Serialize using serialize_to_vec + let buffer = SerdeCdrSerdes::serialize_to_vec(&original); // Deserialize - let deserialized = - SerdeCdrSerdes::::deserialize(&buffer).expect("Failed to deserialize"); + let deserialized = >::deserialize(&buffer) + .expect("Failed to deserialize"); // Should match original assert_eq!(deserialized, original); } - #[test] - fn test_serialize_to_buf_with_empty_buffer() { - let msg = SimpleMessage { - value: 99, - text: "empty buffer test".to_string(), - }; - - let mut buffer = Vec::new(); - assert_eq!(buffer.capacity(), 0); - - SerdeCdrSerdes::::serialize_to_buf(&msg, &mut buffer); - - assert!(!buffer.is_empty()); - assert!(buffer.capacity() > 0); - assert_eq!(&buffer[0..4], &CDR_HEADER_LE); // CDR header - } - - #[test] - fn test_serialize_to_buf_multiple_messages() { - let messages = vec![ - SimpleMessage { - value: 1, - text: "one".to_string(), - }, - SimpleMessage { - value: 2, - text: "two".to_string(), - }, - SimpleMessage { - value: 3, - text: "three".to_string(), - }, - ]; - - let mut buffer = Vec::new(); - let mut all_serialized = Vec::new(); - - for msg in &messages { - SerdeCdrSerdes::::serialize_to_buf(msg, &mut buffer); - all_serialized.push(buffer.clone()); - - // Verify each serialization is correct - let deserialized = SerdeCdrSerdes::::deserialize(&buffer) - .expect("Failed to deserialize"); - assert_eq!(&deserialized, msg); - } - - // Verify all serializations are different - assert_ne!(all_serialized[0], all_serialized[1]); - assert_ne!(all_serialized[1], all_serialized[2]); - } - #[test] fn test_zmessage_trait_implementation() { let msg = SimpleMessage { @@ -721,20 +793,20 @@ mod tests { text: "trait test".to_string(), }; - // ZMessage trait provides serialize method - let serialized = ZMessage::serialize(&msg); + // Use SerdeCdrSerdes directly for serde-only types + let serialized = SerdeCdrSerdes::serialize_to_vec(&msg); assert!(!serialized.is_empty()); assert_eq!(&serialized[0..4], &CDR_HEADER_LE); - // ZMessage trait provides deserialize method - let deserialized = ::deserialize(&serialized[..]) + // Deserialize using SerdeCdrSerdes + let deserialized = >::deserialize(&serialized[..]) .expect("Failed to deserialize"); assert_eq!(deserialized, msg); } #[cfg(feature = "protobuf")] #[test] - fn test_protobuf_serialize_to_buf() { + fn test_protobuf_serialize_to_vec() { use prost::Message; #[derive(Clone, PartialEq, Message)] @@ -751,9 +823,10 @@ mod tests { }; // Test both serialization methods - let vec1 = ProtobufSerdes::::serialize(&msg); - let mut vec2 = Vec::new(); - ProtobufSerdes::::serialize_to_buf(&msg, &mut vec2); + let vec1 = ProtobufSerdes::serialize_to_vec(&msg); + let zbuf = ProtobufSerdes::serialize(&msg); + let zbuf_bytes = zbuf.contiguous(); + let vec2 = zbuf_bytes.to_vec(); // Results should be identical assert_eq!(vec1, vec2); @@ -778,11 +851,11 @@ mod tests { name: "test".to_string(), }; - let zbuf = ProtobufSerdes::::serialize_to_zbuf(&msg); + let zbuf = ProtobufSerdes::serialize(&msg); let bytes = zbuf.contiguous(); // Verify it matches the Vec serialization - let vec = ProtobufSerdes::::serialize(&msg); + let vec = ProtobufSerdes::serialize_to_vec(&msg); assert_eq!(&*bytes, &vec[..]); } } @@ -927,16 +1000,24 @@ mod fast_cdr_tests { // ── Helpers ─────────────────────────────────────────────────────────────── - fn serde_bytes(value: &T) -> Vec { - SerdeCdrSerdes::::serialize(value) + fn serde_bytes( + value: &T, + ) -> Vec { + SerdeCdrSerdes::serialize_to_vec(value) } - fn fast_bytes(value: &T) -> Vec { - NativeCdrSerdes::::serialize(value) + fn fast_bytes( + value: &T, + ) -> Vec { + NativeCdrSerdes::serialize_to_vec(value) } - fn fast_deserialize(bytes: &[u8]) -> T { - NativeCdrSerdes::::deserialize(bytes).expect("NativeCdrSerdes::deserialize failed") + fn fast_deserialize< + T: CdrSerialize + CdrSerializedSize + CdrDeserialize + Send + Sync + 'static, + >( + bytes: &[u8], + ) -> T { + NativeCdrSerdes::deserialize(bytes).expect("NativeCdrSerdes::deserialize failed") } // ── Tests ───────────────────────────────────────────────────────────────── diff --git a/crates/ros-z/src/node.rs b/crates/ros-z/src/node.rs index 2ad11dee..46a44512 100644 --- a/crates/ros-z/src/node.rs +++ b/crates/ros-z/src/node.rs @@ -208,7 +208,7 @@ impl ZNode { /// - Absolute topics (starting with '/') are used as-is /// - Private topics (starting with '~') are expanded to /// /// - Relative topics are expanded to // - pub fn create_pub(&self, topic: &str) -> ZPubBuilder + pub fn create_pub(&self, topic: &str) -> ZPubBuilder where T: ZMessage + WithTypeInfo, { @@ -221,7 +221,7 @@ impl ZNode { &self, topic: &str, type_info: Option, - ) -> ZPubBuilder + ) -> ZPubBuilder where T: ZMessage, { @@ -255,7 +255,7 @@ impl ZNode { /// - Absolute topics (starting with '/') are used as-is /// - Private topics (starting with '~') are expanded to /// /// - Relative topics are expanded to // - pub fn create_sub(&self, topic: &str) -> ZSubBuilder + pub fn create_sub(&self, topic: &str) -> ZSubBuilder where T: ZMessage + WithTypeInfo, { @@ -268,7 +268,7 @@ impl ZNode { &self, topic: &str, type_info: Option, - ) -> ZSubBuilder + ) -> ZSubBuilder where T: ZMessage, { diff --git a/crates/ros-z/src/pubsub.rs b/crates/ros-z/src/pubsub.rs index ded868c2..8f6922d4 100644 --- a/crates/ros-z/src/pubsub.rs +++ b/crates/ros-z/src/pubsub.rs @@ -16,7 +16,7 @@ use crate::impl_with_type_info; use crate::queue::BoundedQueue; use crate::topic_name; -use crate::msg::{SerdeCdrSerdes, ZDeserializer, ZMessage, ZSerializer}; +use crate::msg::{NativeCdrSerdes, ZMessage, ZSerdes}; use crate::qos::QosProfile; use ros_z_protocol::qos::{QosDurability, QosHistory, QosReliability}; use std::sync::Mutex; @@ -25,7 +25,7 @@ use std::sync::Mutex; /// (synchronous) or [`async_publish`](ZPub::async_publish) (async). /// /// Create a publisher via [`ZNode::create_pub`](crate::node::ZNode::create_pub). -pub struct ZPub { +pub struct ZPub = NativeCdrSerdes> { pub entity: EndpointEntity, // TODO: replace this with the sample sn sn: AtomicUsize, @@ -45,7 +45,7 @@ pub struct ZPub { _phantom_data: PhantomData<(T, S)>, } -impl std::fmt::Debug for ZPub { +impl> std::fmt::Debug for ZPub { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("ZPub") .field("entity", &self.entity) @@ -54,7 +54,7 @@ impl std::fmt::Debug for ZPub { } #[derive(Debug)] -pub struct ZPubBuilder> { +pub struct ZPubBuilder { pub entity: EndpointEntity, pub session: Arc, pub graph: Arc, @@ -235,7 +235,7 @@ impl ZPubBuilder { impl Builder for ZPubBuilder where T: ZMessage + 'static, - S: for<'a> ZSerializer = &'a T> + 'static, + S: ZSerdes + 'static, { type Output = ZPub; @@ -327,7 +327,7 @@ where impl ZPub where T: ZMessage + 'static, - S: for<'a> ZSerializer = &'a T> + 'static, + S: ZSerdes + 'static, { /// Wait until at least `count` subscribers are matched on this publisher's topic, /// or until `timeout` elapses. @@ -410,7 +410,7 @@ where // Only use SHM if estimated size meets threshold if estimated_size >= shm_cfg.threshold() { - match S::serialize_to_shm(msg, estimated_size, shm_cfg.provider()) { + match >::serialize_to_shm(msg, estimated_size, shm_cfg.provider()) { Ok((zbuf, actual_size)) => { tracing::Span::current().record("used_shm", true); debug!( @@ -425,7 +425,7 @@ where "[PUB] Direct SHM serialization failed: {}. Using regular memory", e ); - let zbuf = S::serialize_to_zbuf(msg); + let zbuf = >::serialize(msg); let size = zbuf.len(); (zbuf, size) } @@ -437,13 +437,13 @@ where estimated_size, shm_cfg.threshold() ); - let zbuf = S::serialize_to_zbuf(msg); + let zbuf = >::serialize(msg); let size = zbuf.len(); (zbuf, size) } } else { tracing::Span::current().record("used_shm", false); - let zbuf = S::serialize_to_zbuf(msg); + let zbuf = >::serialize(msg); let size = zbuf.len(); (zbuf, size) }; @@ -478,15 +478,15 @@ where let estimated_size = msg.estimated_serialized_size(); if estimated_size >= shm_cfg.threshold() { - match S::serialize_to_shm(msg, estimated_size, shm_cfg.provider()) { + match >::serialize_to_shm(msg, estimated_size, shm_cfg.provider()) { Ok((zbuf, _actual_size)) => zbuf, - Err(_) => S::serialize_to_zbuf(msg), + Err(_) => >::serialize(msg), } } else { - S::serialize_to_zbuf(msg) + >::serialize(msg) } } else { - S::serialize_to_zbuf(msg) + >::serialize(msg) }; let zbytes = zenoh::bytes::ZBytes::from(zbuf); @@ -555,7 +555,7 @@ impl ZPub } } -pub struct ZSubBuilder> { +pub struct ZSubBuilder { pub entity: EndpointEntity, pub session: Arc, pub(crate) keyexpr_format: ros_z_protocol::KeyExprFormat, @@ -691,7 +691,7 @@ where queue: Option>>, ) -> Result> where - S: ZDeserializer, + S: ZSerdes, { let qualified_topic = topic_name::qualify_topic_name( &self.entity.topic, @@ -786,8 +786,8 @@ where /// A `ZSub` with no internal queue (callback-only mode) pub fn build_with_callback(self, callback: F) -> Result> where - F: Fn(S::Output) + Send + Sync + 'static, - S: for<'a> ZDeserializer = &'a [u8]> + 'static, + F: Fn(T) + Send + Sync + 'static, + S: ZSerdes + 'static, { let expected_encoding = self.expected_encoding.clone(); let callback = Arc::new(move |sample: Sample| { @@ -810,7 +810,7 @@ where } let payload = sample.payload().to_bytes(); - match S::deserialize(&payload) { + match >::deserialize(&payload) { Ok(msg) => callback(msg), Err(e) => tracing::error!("Failed to deserialize message: {}", e), } @@ -823,7 +823,7 @@ where pub fn build_with_notifier(self, notify: F) -> Result> where F: Fn() + Send + Sync + 'static, - S: ZDeserializer, + S: ZSerdes, { let queue_size = match self.entity.qos.history { QosHistory::KeepLast(depth) => depth, @@ -844,7 +844,7 @@ where impl Builder for ZSubBuilder where T: ZMessage + 'static + Sync + Send, - S: ZDeserializer, + S: ZSerdes, { type Output = ZSub; @@ -859,7 +859,7 @@ where } } -pub struct ZSub { +pub struct ZSub = NativeCdrSerdes> { pub entity: EndpointEntity, pub queue: Option>>, _inner: zenoh::pubsub::Subscriber<()>, @@ -873,7 +873,7 @@ pub struct ZSub { _phantom_data: PhantomData<(T, Q, S)>, } -impl std::fmt::Debug for ZSub { +impl> std::fmt::Debug for ZSub { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("ZSub") .field("entity", &self.entity) @@ -884,7 +884,7 @@ impl std::fmt::Debug for ZSub { impl ZSub where T: ZMessage, - S: ZDeserializer, + S: ZSerdes, { /// Receive the next serialized message (raw sample) pub fn recv_serialized(&self) -> Result { @@ -925,14 +925,14 @@ where impl ZSub where T: ZMessage, - S: for<'a> ZDeserializer = &'a [u8]>, + S: ZSerdes, { /// Receive and deserialize the next message (aligned with ROS behavior) #[tracing::instrument(name = "recv", skip(self), fields( topic = %self.entity.topic, payload_len = tracing::field::Empty ))] - pub fn recv(&self) -> Result { + pub fn recv(&self) -> Result { trace!("[SUB] Waiting for message"); let queue = self.queue.as_ref().ok_or_else(|| { @@ -944,10 +944,10 @@ where tracing::Span::current().record("payload_len", payload.len()); debug!("[SUB] Received message"); - S::deserialize(&payload).map_err(|e| zenoh::Error::from(e.to_string())) + >::deserialize(&payload).map_err(|e| zenoh::Error::from(e.to_string())) } - pub fn recv_timeout(&self, timeout: Duration) -> Result { + pub fn recv_timeout(&self, timeout: Duration) -> Result { let queue = self.queue.as_ref().ok_or_else(|| { zenoh::Error::from("Subscriber was built with callback, no queue available") })?; @@ -955,17 +955,17 @@ where .recv_timeout(timeout) .ok_or_else(|| zenoh::Error::from("Receive timed out"))?; let payload = sample.payload().to_bytes(); - S::deserialize(&payload).map_err(|e| zenoh::Error::from(e.to_string())) + >::deserialize(&payload).map_err(|e| zenoh::Error::from(e.to_string())) } /// Async receive and deserialize the next message - pub async fn async_recv(&self) -> Result { + pub async fn async_recv(&self) -> Result { let queue = self.queue.as_ref().ok_or_else(|| { zenoh::Error::from("Subscriber was built with callback, no queue available") })?; let sample = queue.recv_async().await; let payload = sample.payload().to_bytes(); - S::deserialize(&payload).map_err(|e| zenoh::Error::from(e.to_string())) + >::deserialize(&payload).map_err(|e| zenoh::Error::from(e.to_string())) } } @@ -985,7 +985,7 @@ impl ZSub Result { + pub fn recv_dynamic(&self) -> Result { let schema = self.dyn_schema.as_ref().ok_or_else(|| { zenoh::Error::from( "dyn_schema required for DynamicMessage (use .with_dyn_schema() when building)", @@ -1003,12 +1003,17 @@ impl ZSub::deserialize(( + &payload, schema, + )) + .map_err(|e| zenoh::Error::from(e.to_string())) } /// Receive a dynamic message with timeout. - pub fn recv_timeout(&self, timeout: Duration) -> Result { + pub fn recv_timeout_dynamic( + &self, + timeout: Duration, + ) -> Result { let schema = self .dyn_schema .as_ref() @@ -1023,12 +1028,14 @@ impl ZSub::deserialize(( + &payload, schema, + )) + .map_err(|e| zenoh::Error::from(e.to_string())) } /// Async receive a dynamic message. - pub async fn async_recv(&self) -> Result { + pub async fn async_recv_dynamic(&self) -> Result { let schema = self .dyn_schema .as_ref() @@ -1041,8 +1048,10 @@ impl ZSub::deserialize(( + &payload, schema, + )) + .map_err(|e| zenoh::Error::from(e.to_string())) } /// Try to receive a dynamic message without blocking. @@ -1053,7 +1062,7 @@ impl ZSub { let payload = sample.payload().to_bytes(); - let result = crate::dynamic::DynamicSerdeCdrSerdes::deserialize((&payload, schema)) + let result = ::deserialize((&payload, schema)) .map_err(|e| zenoh::Error::from(e.to_string())); Some(result) } diff --git a/crates/ros-z/src/service.rs b/crates/ros-z/src/service.rs index 364e4b43..a5f88b0f 100644 --- a/crates/ros-z/src/service.rs +++ b/crates/ros-z/src/service.rs @@ -16,6 +16,7 @@ use zenoh::{ query::{Query, Reply}, sample::Sample, }; +use zenoh_buffers::buffer::Buffer as _; use std::sync::atomic::Ordering; @@ -28,7 +29,7 @@ use crate::{ common::DataHandler, entity::EndpointEntity, impl_with_type_info, - msg::{SerdeCdrSerdes, ZDeserializer, ZMessage, ZService}, + msg::{SerdeCdrSerdes, ZMessage, ZSerdes, ZService}, qos::QosHistory, queue::BoundedQueue, }; @@ -173,13 +174,13 @@ where // For ROS-Z pub fn take_response(&self) -> Result where - T::Response: ZMessage, - for<'a> ::Serdes: - ZDeserializer = &'a [u8]>, + T::Response: + ZMessage + serde::Serialize + serde::de::DeserializeOwned + Send + Sync + 'static, { let sample = self.take_sample()?; - let msg = ::deserialize(&sample.payload().to_bytes()) - .map_err(|e| zenoh::Error::from(e.to_string()))?; + let msg = + >::deserialize(&sample.payload().to_bytes()) + .map_err(|e| zenoh::Error::from(e.to_string()))?; Ok(msg) } @@ -187,13 +188,12 @@ where /// arrives within the deadline. pub fn take_response_timeout(&self, timeout: Duration) -> Result where - T::Response: ZMessage, - for<'a> ::Serdes: - ZDeserializer = &'a [u8]>, + T::Response: + ZMessage + serde::Serialize + serde::de::DeserializeOwned + Send + Sync + 'static, { let sample = self.take_sample_timeout(timeout)?; let payload_bytes = sample.payload().to_bytes(); - let msg = ::deserialize(&payload_bytes[..]) + let msg = >::deserialize(&payload_bytes[..]) .map_err(|e| zenoh::Error::from(e.to_string()))?; Ok(msg) } @@ -201,13 +201,12 @@ where /// response arrives or the channel is disconnected. pub async fn take_response_async(&self) -> Result where - T::Response: ZMessage, - for<'a> ::Serdes: - ZDeserializer = &'a [u8]>, + T::Response: + ZMessage + serde::Serialize + serde::de::DeserializeOwned + Send + Sync + 'static, { let sample = self.rx.recv_async().await?; let payload_bytes = sample.payload().to_bytes(); - let msg = ::deserialize(&payload_bytes[..]) + let msg = >::deserialize(&payload_bytes[..]) .map_err(|e| zenoh::Error::from(e.to_string()))?; Ok(msg) } @@ -231,8 +230,11 @@ where sn = self.sn.load(Ordering::Acquire), payload_len = tracing::field::Empty ))] - pub async fn send_request(&self, msg: &T::Request) -> Result<()> { - let payload = msg.serialize(); + pub async fn send_request(&self, msg: &T::Request) -> Result<()> + where + T::Request: serde::Serialize + serde::de::DeserializeOwned + Send + Sync + 'static, + { + let payload = >::serialize(msg); tracing::Span::current().record("payload_len", payload.len()); // Log the key expression being queried @@ -275,13 +277,14 @@ where pub fn rmw_send_request(&self, msg: &T::Request, notify: F) -> Result where F: Fn() + Send + Sync + 'static, + T::Request: serde::Serialize + serde::de::DeserializeOwned + Send + Sync + 'static, { let tx = self.tx.clone(); let attachment = self.new_attachment(); let sn = attachment.sequence_number; self.inner .get() - .payload(msg.serialize()) + .payload(>::serialize(msg)) .attachment(attachment) .callback(move |reply| { match reply.into_result() { @@ -515,9 +518,8 @@ where ))] pub fn take_request(&mut self) -> Result<(QueryKey, T::Request)> where - T::Request: ZMessage + Send + Sync + 'static, - for<'a> ::Serdes: - ZDeserializer = &'a [u8]>, + T::Request: + ZMessage + serde::Serialize + serde::de::DeserializeOwned + Send + Sync + 'static, { trace!("[SRV] Waiting for request"); @@ -540,7 +542,7 @@ where debug!("[SRV] Processing request"); - let msg = ::deserialize(&payload_bytes[..]) + let msg = >::deserialize(&payload_bytes[..]) .map_err(|e| zenoh::Error::from(e.to_string()))?; self.map.insert(key.clone(), query); @@ -552,9 +554,8 @@ where /// This method may fail if the message does not deserialize as the requested type. pub async fn take_request_async(&mut self) -> Result<(QueryKey, T::Request)> where - T::Request: ZMessage + Send + Sync + 'static, - for<'a> ::Serdes: - ZDeserializer = &'a [u8]>, + T::Request: + ZMessage + serde::Serialize + serde::de::DeserializeOwned + Send + Sync + 'static, { let queue = self.queue.as_ref().ok_or_else(|| { zenoh::Error::from("Server was built with callback, no queue available") @@ -566,7 +567,7 @@ where return Err("Existing query detected".into()); } let payload_bytes = query.payload().unwrap().to_bytes(); - let msg = ::deserialize(&payload_bytes[..]) + let msg = >::deserialize(&payload_bytes[..]) .map_err(|e| zenoh::Error::from(e.to_string()))?; self.map.insert(key.clone(), query); @@ -582,14 +583,17 @@ where sn = %key.sn, payload_len = tracing::field::Empty ))] - pub fn send_response(&mut self, msg: &T::Response, key: &QueryKey) -> Result<()> { + pub fn send_response(&mut self, msg: &T::Response, key: &QueryKey) -> Result<()> + where + T::Response: serde::Serialize + serde::de::DeserializeOwned + Send + Sync + 'static, + { debug!( "[SRV] Looking for query with key sn:{}, gid:{:?}", key.sn, key.gid ); match self.map.remove(key) { Some(query) => { - let payload = msg.serialize(); + let payload = >::serialize(msg); tracing::Span::current().record("payload_len", payload.len()); debug!("[SRV] Sending response"); @@ -612,13 +616,19 @@ where /// /// - `msg` is the response message to send. /// - `key` is the query key of the request to reply to and is obtained from [take_request](Self::take_request) or [take_request_async](Self::take_request_async) - pub async fn send_response_async(&mut self, msg: &T::Response, key: &QueryKey) -> Result<()> { + pub async fn send_response_async(&mut self, msg: &T::Response, key: &QueryKey) -> Result<()> + where + T::Response: serde::Serialize + serde::de::DeserializeOwned + Send + Sync + 'static, + { match self.map.remove(key) { Some(query) => { // Use the sequence number and GID from the request let attachment = Attachment::new(key.sn, key.gid); query - .reply(&self.key_expr, msg.serialize()) + .reply( + &self.key_expr, + >::serialize(msg), + ) .attachment(attachment) .await } diff --git a/crates/ros-z/tests/action/communication.rs b/crates/ros-z/tests/action/communication.rs index 1679bceb..2765410e 100644 --- a/crates/ros-z/tests/action/communication.rs +++ b/crates/ros-z/tests/action/communication.rs @@ -4,7 +4,12 @@ use std::time::Duration; -use ros_z::{Builder, Result, context::ZContextBuilder, define_action, msg::ZSerializer}; +use ros_z::{ + Builder, Result, + context::ZContextBuilder, + define_action, + msg::{NativeCdrSerdes, ZSerdes}, +}; use serde::{Deserialize, Serialize}; use tokio::time::timeout; use zenoh::Wait; @@ -149,9 +154,9 @@ mod tests { }; // Respond to the cancel request - let response_bytes = ros_z::msg::SerdeCdrSerdes::< + let response_bytes = ::serialize(&cancel_resp); + >>::serialize_to_vec(&cancel_resp); response_tx .reply(response_tx.key_expr().clone(), response_bytes) .wait()?; diff --git a/crates/ros-z/tests/pubsub.rs b/crates/ros-z/tests/pubsub.rs index d4bcabab..2c0a2529 100644 --- a/crates/ros-z/tests/pubsub.rs +++ b/crates/ros-z/tests/pubsub.rs @@ -24,9 +24,7 @@ impl MessageTypeInfo for TestMessage { impl ros_z::ros_msg::WithTypeInfo for TestMessage {} -impl ros_z::msg::ZMessage for TestMessage { - type Serdes = ros_z::msg::SerdeCdrSerdes; -} +impl ros_z::msg::ZMessage for TestMessage {} #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn test_basic_pubsub() { diff --git a/crates/ros-z/tests/service.rs b/crates/ros-z/tests/service.rs index 587d7ff3..0ee1fa4a 100644 --- a/crates/ros-z/tests/service.rs +++ b/crates/ros-z/tests/service.rs @@ -26,9 +26,7 @@ impl MessageTypeInfo for AddTwoIntsRequest { impl ros_z::WithTypeInfo for AddTwoIntsRequest {} -impl ros_z::msg::ZMessage for AddTwoIntsRequest { - type Serdes = ros_z::msg::SerdeCdrSerdes; -} +impl ros_z::msg::ZMessage for AddTwoIntsRequest {} // Simple test service response #[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)] @@ -48,9 +46,7 @@ impl MessageTypeInfo for AddTwoIntsResponse { impl ros_z::WithTypeInfo for AddTwoIntsResponse {} -impl ros_z::msg::ZMessage for AddTwoIntsResponse { - type Serdes = ros_z::msg::SerdeCdrSerdes; -} +impl ros_z::msg::ZMessage for AddTwoIntsResponse {} // Service type definition struct AddTwoInts; From 17c7a7e9ba1efebf1481f0bec7e59eb55672a98d Mon Sep 17 00:00:00 2001 From: yuanyuyuan Date: Mon, 9 Mar 2026 14:10:32 +0000 Subject: [PATCH 02/13] fix: remove explicit impl ZMessage conflicting with blanket impl After introducing the ZMessage blanket impl, all manual 'impl ZMessage for T {}' in examples, tests, and codegen templates became orphan conflicts. Remove them. Also add .with_serdes::() at serde-only pub/sub call sites (z_custom_message example, pubsub tests) where NativeCdrSerdes cannot satisfy the ZSerdes bound. --- crates/rmw-zenoh-rs/src/msg.rs | 4 +--- .../ros-z-codegen/src/protobuf_generator.rs | 4 ---- .../ros-z/examples/dynamic_message/interop.rs | 2 +- .../ros-z/examples/protobuf_demo/src/types.rs | 8 +------ crates/ros-z/examples/z_custom_message.rs | 22 ++++++++++--------- .../ros-z/src/dynamic/tests/interop_tests.rs | 2 +- crates/ros-z/tests/pubsub.rs | 13 ++++++++--- crates/ros-z/tests/service.rs | 4 ---- 8 files changed, 26 insertions(+), 33 deletions(-) diff --git a/crates/rmw-zenoh-rs/src/msg.rs b/crates/rmw-zenoh-rs/src/msg.rs index d9f04ae1..099a668d 100644 --- a/crates/rmw-zenoh-rs/src/msg.rs +++ b/crates/rmw-zenoh-rs/src/msg.rs @@ -1,6 +1,6 @@ use crate::type_support::MessageTypeSupport; use ros_z::entity::{TypeHash, TypeInfo}; -use ros_z::msg::{ZDeserializer, ZMessage, ZSerdes, ZSerializer, ZService}; +use ros_z::msg::{ZDeserializer, ZSerdes, ZSerializer, ZService}; use ros_z::ros_msg::WithTypeInfo; use ros_z::{MessageTypeInfo, ServiceTypeInfo}; @@ -132,8 +132,6 @@ impl ZSerdes for RosSerdes { } } -impl ZMessage for RosMessage {} - impl MessageTypeInfo for RosMessage { // Static methods return placeholder values since RosMessage is a generic wrapper // The actual type info varies per instance diff --git a/crates/ros-z-codegen/src/protobuf_generator.rs b/crates/ros-z-codegen/src/protobuf_generator.rs index 34d50f5f..4ec60250 100644 --- a/crates/ros-z-codegen/src/protobuf_generator.rs +++ b/crates/ros-z-codegen/src/protobuf_generator.rs @@ -209,8 +209,6 @@ impl ProtobufMessageGenerator { combined_output.push_str("#[allow(unused_imports)]\n"); combined_output.push_str("use ros_z::ros_msg::WithTypeInfo;\n"); combined_output.push_str("#[allow(unused_imports)]\n"); - combined_output.push_str("use ros_z::msg::ZMessage;\n"); - combined_output.push_str("#[allow(unused_imports)]\n"); combined_output.push_str("use ros_z::msg::ProtobufSerdes;\n\n"); // Compile all proto files at once to avoid duplicates @@ -311,8 +309,6 @@ impl ProtobufMessageGenerator { impl ::ros_z::WithTypeInfo for {proto_type} {{}} -impl ::ros_z::msg::ZMessage for {proto_type} {{}} - "# )); } diff --git a/crates/ros-z/examples/dynamic_message/interop.rs b/crates/ros-z/examples/dynamic_message/interop.rs index b13f8d4f..08815abc 100644 --- a/crates/ros-z/examples/dynamic_message/interop.rs +++ b/crates/ros-z/examples/dynamic_message/interop.rs @@ -7,7 +7,7 @@ use ros_z::{ dynamic::{DynamicMessage, FieldType, MessageSchema}, - msg::{NativeCdrSerdes, ZMessage, ZSerdes}, + msg::{NativeCdrSerdes, ZSerdes}, }; use ros_z_msgs::{ geometry_msgs::{Point, Twist, Vector3}, diff --git a/crates/ros-z/examples/protobuf_demo/src/types.rs b/crates/ros-z/examples/protobuf_demo/src/types.rs index 593b3a8e..3f795c3b 100644 --- a/crates/ros-z/examples/protobuf_demo/src/types.rs +++ b/crates/ros-z/examples/protobuf_demo/src/types.rs @@ -2,7 +2,7 @@ use ros_z::{ MessageTypeInfo, ServiceTypeInfo, WithTypeInfo, entity::TypeHash, - msg::{ProtobufSerdes, ZMessage, ZService}, + msg::{ProtobufSerdes, ZService}, }; // Include protobuf messages generated from sensor_data.proto @@ -26,8 +26,6 @@ impl MessageTypeInfo for SensorData { impl WithTypeInfo for SensorData {} -impl ZMessage for SensorData {} - // SensorData uses serde/CDR for backward compatibility with the original pub/sub demo // ========== CalculateRequest Trait Implementations ========== @@ -44,8 +42,6 @@ impl MessageTypeInfo for CalculateRequest { impl WithTypeInfo for CalculateRequest {} -impl ZMessage for CalculateRequest {} - // ========== CalculateResponse Trait Implementations ========== impl MessageTypeInfo for CalculateResponse { @@ -60,8 +56,6 @@ impl MessageTypeInfo for CalculateResponse { impl WithTypeInfo for CalculateResponse {} -impl ZMessage for CalculateResponse {} - // ========== Calculate Service Definition ========== pub struct Calculate; diff --git a/crates/ros-z/examples/z_custom_message.rs b/crates/ros-z/examples/z_custom_message.rs index 301e0ea3..111e892b 100644 --- a/crates/ros-z/examples/z_custom_message.rs +++ b/crates/ros-z/examples/z_custom_message.rs @@ -2,8 +2,10 @@ use std::time::Duration; use clap::Parser; use ros_z::{ - Builder, MessageTypeInfo, Result, ServiceTypeInfo, context::ZContextBuilder, entity::TypeHash, - msg::ZService, + Builder, MessageTypeInfo, Result, ServiceTypeInfo, + context::ZContextBuilder, + entity::TypeHash, + msg::{SerdeCdrSerdes, ZService}, }; use serde::{Deserialize, Serialize}; @@ -29,8 +31,6 @@ impl MessageTypeInfo for RobotStatus { impl ros_z::WithTypeInfo for RobotStatus {} -impl ros_z::msg::ZMessage for RobotStatus {} - // Custom service request #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct NavigateToRequest { @@ -51,8 +51,6 @@ impl MessageTypeInfo for NavigateToRequest { impl ros_z::WithTypeInfo for NavigateToRequest {} -impl ros_z::msg::ZMessage for NavigateToRequest {} - // Custom service response #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct NavigateToResponse { @@ -73,8 +71,6 @@ impl MessageTypeInfo for NavigateToResponse { impl ros_z::WithTypeInfo for NavigateToResponse {} -impl ros_z::msg::ZMessage for NavigateToResponse {} - // Service type definition pub struct NavigateTo; @@ -140,7 +136,10 @@ async fn run_status_publisher(robot_id: String) -> Result<()> { let ctx = ZContextBuilder::default().build()?; let node = ctx.create_node("robot_status_publisher").build()?; - let zpub = node.create_pub::("/robot_status").build()?; + let zpub = node + .create_pub::("/robot_status") + .with_serdes::() + .build()?; let mut position_x = 0.0; let mut position_y = 0.0; @@ -191,7 +190,10 @@ async fn run_status_subscriber() -> Result<()> { let ctx = ZContextBuilder::default().build()?; let node = ctx.create_node("robot_status_subscriber").build()?; - let zsub = node.create_sub::("/robot_status").build()?; + let zsub = node + .create_sub::("/robot_status") + .with_serdes::() + .build()?; loop { let status = zsub.async_recv().await?; diff --git a/crates/ros-z/src/dynamic/tests/interop_tests.rs b/crates/ros-z/src/dynamic/tests/interop_tests.rs index f4c90895..e796f449 100644 --- a/crates/ros-z/src/dynamic/tests/interop_tests.rs +++ b/crates/ros-z/src/dynamic/tests/interop_tests.rs @@ -8,7 +8,7 @@ use std::sync::Arc; use crate::dynamic::message::DynamicMessage; use crate::dynamic::schema::{FieldType, MessageSchema}; -use crate::msg::{NativeCdrSerdes, ZMessage, ZSerdes}; +use crate::msg::{NativeCdrSerdes, ZSerdes}; use ros_z_msgs::geometry_msgs::{Point, Twist, Vector3}; use ros_z_msgs::std_msgs::String as StdString; diff --git a/crates/ros-z/tests/pubsub.rs b/crates/ros-z/tests/pubsub.rs index 2c0a2529..ecd0961a 100644 --- a/crates/ros-z/tests/pubsub.rs +++ b/crates/ros-z/tests/pubsub.rs @@ -1,6 +1,9 @@ use std::{thread, time::Duration}; -use ros_z::{Builder, TypeHash, ZBuf, context::ZContextBuilder, ros_msg::MessageTypeInfo}; +use ros_z::{ + Builder, TypeHash, ZBuf, context::ZContextBuilder, msg::SerdeCdrSerdes, + ros_msg::MessageTypeInfo, +}; use ros_z_msgs::std_msgs::ByteMultiArray; use serde::{Deserialize, Serialize}; use serde_json::json; @@ -24,8 +27,6 @@ impl MessageTypeInfo for TestMessage { impl ros_z::ros_msg::WithTypeInfo for TestMessage {} -impl ros_z::msg::ZMessage for TestMessage {} - #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn test_basic_pubsub() { let ctx = ZContextBuilder::default() @@ -38,11 +39,13 @@ async fn test_basic_pubsub() { let publisher = node .create_pub::("/test_topic") + .with_serdes::() .build() .unwrap(); let subscriber = node .create_sub::("/test_topic") + .with_serdes::() .build() .unwrap(); @@ -72,11 +75,13 @@ async fn test_multiple_messages() { let publisher = node .create_pub::("/multi_topic") + .with_serdes::() .build() .unwrap(); let subscriber = node .create_sub::("/multi_topic") + .with_serdes::() .build() .unwrap(); @@ -110,11 +115,13 @@ async fn test_large_payload() { let publisher = node .create_pub::("/large_topic") + .with_serdes::() .build() .unwrap(); let subscriber = node .create_sub::("/large_topic") + .with_serdes::() .build() .unwrap(); diff --git a/crates/ros-z/tests/service.rs b/crates/ros-z/tests/service.rs index 0ee1fa4a..0fee9cf0 100644 --- a/crates/ros-z/tests/service.rs +++ b/crates/ros-z/tests/service.rs @@ -26,8 +26,6 @@ impl MessageTypeInfo for AddTwoIntsRequest { impl ros_z::WithTypeInfo for AddTwoIntsRequest {} -impl ros_z::msg::ZMessage for AddTwoIntsRequest {} - // Simple test service response #[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)] struct AddTwoIntsResponse { @@ -46,8 +44,6 @@ impl MessageTypeInfo for AddTwoIntsResponse { impl ros_z::WithTypeInfo for AddTwoIntsResponse {} -impl ros_z::msg::ZMessage for AddTwoIntsResponse {} - // Service type definition struct AddTwoInts; From 9f090949852e5cd4d7b885b8365cd1d1bb6496c4 Mon Sep 17 00:00:00 2001 From: yuanyuyuan Date: Mon, 9 Mar 2026 17:07:02 +0000 Subject: [PATCH 03/13] docs: update ZMessage references after blanket impl refactor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove misleading 'implement ZMessage' language from actions.md and protobuf_demo — users no longer write impl ZMessage, it's automatic. --- book/src/chapters/actions.md | 2 +- crates/ros-z/examples/protobuf_demo/src/lib.rs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/book/src/chapters/actions.md b/book/src/chapters/actions.md index ca64d31a..6f13de78 100644 --- a/book/src/chapters/actions.md +++ b/book/src/chapters/actions.md @@ -336,7 +336,7 @@ impl ZAction for CountAction { **Key points:** -- `Goal`, `Feedback`, and `Result` can be any struct that implements `Serialize + Deserialize + Clone + Send + Sync + 'static` (blanket implementation via `ZMessage`) +- `Goal`, `Feedback`, and `Result` can be any struct that is `Send + Sync + 'static` — the `ZMessage` trait is satisfied automatically via a blanket impl, no manual `impl ZMessage` needed - `name()` sets the Zenoh key prefix for the action's internal services and topics - The default `send_goal_type_info()`, `get_result_type_info()`, etc. all return `TypeHash::zero()`, which is correct for ros-z-to-ros-z communication. For ROS 2 interop, override these with the correct RIHS01 hashes. diff --git a/crates/ros-z/examples/protobuf_demo/src/lib.rs b/crates/ros-z/examples/protobuf_demo/src/lib.rs index caa6bea3..2a8b44bd 100644 --- a/crates/ros-z/examples/protobuf_demo/src/lib.rs +++ b/crates/ros-z/examples/protobuf_demo/src/lib.rs @@ -140,7 +140,9 @@ pub fn run_service_client( println!("Successfully demonstrated protobuf service calls!"); println!("\nKey points:"); println!("1. Request/Response: Protobuf messages generated from .proto files"); - println!("2. Both implement MessageTypeInfo, WithTypeInfo, and ZMessage traits"); + println!( + "2. Both implement MessageTypeInfo and WithTypeInfo (ZMessage is automatic via blanket impl)" + ); println!("3. Service type implements ZService and ServiceTypeInfo"); println!("4. Works seamlessly with node.create_service() and node.create_client()"); println!("5. Uses ProtobufSerdes for pure protobuf serialization (not CDR!)"); From 2bd99dd3bcc70e502dd2caf65ceecbec62e61084 Mon Sep 17 00:00:00 2001 From: yuanyuyuan Date: Tue, 10 Mar 2026 01:59:52 +0800 Subject: [PATCH 04/13] refactor(msg): rename NativeCdrSerdes->CdrSerdes, SerdeCdrSerdes->CdrCompatSerdes --- .../src/python_msgspec_generator.rs | 6 +- .../src/core/dynamic_subscriber.rs | 4 +- .../ros-z-msgs/tests/shm_size_estimation.rs | 15 ++- .../tests/size_estimation_performance.rs | 20 ++-- crates/ros-z-py/src/service.rs | 12 +-- .../tests/type_description_interop.rs | 4 +- .../ros-z/examples/dynamic_message/interop.rs | 16 ++-- crates/ros-z/examples/z_custom_message.rs | 6 +- crates/ros-z/src/action/client.rs | 30 +++--- crates/ros-z/src/action/driver.rs | 25 +++-- crates/ros-z/src/action/mod.rs | 2 +- crates/ros-z/src/action/server.rs | 36 +++---- crates/ros-z/src/dynamic/mod.rs | 10 +- crates/ros-z/src/dynamic/serdes.rs | 34 +++---- .../ros-z/src/dynamic/tests/interop_tests.rs | 24 ++--- .../ros-z/src/dynamic/tests/pubsub_tests.rs | 32 +++---- .../src/dynamic/type_description_service.rs | 6 +- crates/ros-z/src/msg.rs | 93 ++++++++++--------- crates/ros-z/src/node.rs | 22 ++--- crates/ros-z/src/pubsub.rs | 30 +++--- crates/ros-z/src/service.rs | 20 ++-- crates/ros-z/tests/action/communication.rs | 4 +- crates/ros-z/tests/pubsub.rs | 14 +-- 23 files changed, 231 insertions(+), 234 deletions(-) diff --git a/crates/ros-z-codegen/src/python_msgspec_generator.rs b/crates/ros-z-codegen/src/python_msgspec_generator.rs index 2d687447..83d20ad9 100644 --- a/crates/ros-z-codegen/src/python_msgspec_generator.rs +++ b/crates/ros-z-codegen/src/python_msgspec_generator.rs @@ -644,7 +644,7 @@ fn generate_serialize_to_zbuf( match_arms.push(quote! { #full_name => { let rust_msg = ::from_py(msg)?; - Ok(::ros_z::msg::NativeCdrSerdes::serialize(&rust_msg)) + Ok(::ros_z::msg::CdrSerdes::serialize(&rust_msg)) } }); } @@ -660,7 +660,7 @@ fn generate_serialize_to_zbuf( match_arms.push(quote! { #req_full_name => { let rust_msg = ::from_py(msg)?; - Ok(::ros_z::msg::NativeCdrSerdes::serialize(&rust_msg)) + Ok(::ros_z::msg::CdrSerdes::serialize(&rust_msg)) } }); @@ -670,7 +670,7 @@ fn generate_serialize_to_zbuf( match_arms.push(quote! { #resp_full_name => { let rust_msg = ::from_py(msg)?; - Ok(::ros_z::msg::NativeCdrSerdes::serialize(&rust_msg)) + Ok(::ros_z::msg::CdrSerdes::serialize(&rust_msg)) } }); } diff --git a/crates/ros-z-console/src/core/dynamic_subscriber.rs b/crates/ros-z-console/src/core/dynamic_subscriber.rs index 4d5a55e9..901e36e7 100644 --- a/crates/ros-z-console/src/core/dynamic_subscriber.rs +++ b/crates/ros-z-console/src/core/dynamic_subscriber.rs @@ -7,7 +7,7 @@ use std::{sync::Arc, time::Duration}; use flume::Receiver; use ros_z::{ - dynamic::{DynamicMessage, DynamicSerdeCdrSerdes, MessageSchema}, + dynamic::{DynamicCdrCompatSerdes, DynamicMessage, MessageSchema}, node::ZNode, pubsub::ZSub, }; @@ -25,7 +25,7 @@ pub struct DynamicTopicSubscriber { /// Channel for receiving messages asynchronously message_rx: Receiver, /// Subscriber handle (kept alive to maintain subscription) - _subscriber: Arc>, + _subscriber: Arc>, } impl DynamicTopicSubscriber { diff --git a/crates/ros-z-msgs/tests/shm_size_estimation.rs b/crates/ros-z-msgs/tests/shm_size_estimation.rs index a1334a3b..e5df9370 100644 --- a/crates/ros-z-msgs/tests/shm_size_estimation.rs +++ b/crates/ros-z-msgs/tests/shm_size_estimation.rs @@ -9,7 +9,7 @@ use std::sync::Arc; use ros_z::{ ZBuf, - msg::{NativeCdrSerdes, ZMessage, ZSerdes}, + msg::{CdrSerdes, ZMessage, ZSerdes}, shm::ShmProviderBuilder, }; use zenoh_buffers::buffer::Buffer; @@ -83,7 +83,7 @@ fn test_pointcloud2_shm_serialization_with_accurate_estimate() { // Serialize to SHM - should not panic! let result = - >::serialize_to_shm(&cloud, estimated, &provider); + >::serialize_to_shm(&cloud, estimated, &provider); assert!(result.is_ok(), "SHM serialization should succeed"); @@ -149,8 +149,7 @@ fn test_image_shm_serialization_with_accurate_estimate() { .expect("Failed to create SHM provider"), ); - let result = - >::serialize_to_shm(&image, estimated, &provider); + let result = >::serialize_to_shm(&image, estimated, &provider); assert!(result.is_ok(), "SHM serialization should succeed"); let (zbuf, actual_size) = result.unwrap(); @@ -207,8 +206,7 @@ fn test_laserscan_shm_serialization_with_accurate_estimate() { .expect("Failed to create SHM provider"), ); - let result = - >::serialize_to_shm(&scan, estimated, &provider); + let result = >::serialize_to_shm(&scan, estimated, &provider); assert!(result.is_ok(), "SHM serialization should succeed"); let (zbuf, actual_size) = result.unwrap(); @@ -259,7 +257,7 @@ fn test_compressed_image_shm_serialization() { ); let result = - >::serialize_to_shm(&img, estimated, &provider); + >::serialize_to_shm(&img, estimated, &provider); assert!(result.is_ok(), "SHM serialization should succeed"); let (zbuf, actual_size) = result.unwrap(); @@ -301,8 +299,7 @@ fn test_multiple_messages_share_shm_pool() { }; let estimated = image.estimated_serialized_size(); - let result = - >::serialize_to_shm(&image, estimated, &provider); + let result = >::serialize_to_shm(&image, estimated, &provider); assert!( result.is_ok(), diff --git a/crates/ros-z-msgs/tests/size_estimation_performance.rs b/crates/ros-z-msgs/tests/size_estimation_performance.rs index ea3a6f6c..f8131f95 100644 --- a/crates/ros-z-msgs/tests/size_estimation_performance.rs +++ b/crates/ros-z-msgs/tests/size_estimation_performance.rs @@ -7,7 +7,7 @@ use std::time::Instant; use ros_z::{ ZBuf, - msg::{NativeCdrSerdes, ZMessage, ZSerdes}, + msg::{CdrSerdes, ZMessage, ZSerdes}, }; use ros_z_msgs::{builtin_interfaces::Time, sensor_msgs::*, std_msgs::Header}; use zenoh_buffers::buffer::Buffer; @@ -33,13 +33,13 @@ fn test_pointcloud2_serialization_performance() { // Warm up for _ in 0..5 { - let _ = NativeCdrSerdes::serialize(&cloud); + let _ = CdrSerdes::serialize(&cloud); } // Benchmark with accurate size estimation (current implementation) let start = Instant::now(); for _ in 0..100 { - let zbuf = NativeCdrSerdes::serialize(&cloud); + let zbuf = CdrSerdes::serialize(&cloud); assert!(zbuf.len() > 1_000_000); } let with_estimation = start.elapsed(); @@ -71,13 +71,13 @@ fn test_image_serialization_performance() { // Warm up for _ in 0..5 { - let _ = NativeCdrSerdes::serialize(&image); + let _ = CdrSerdes::serialize(&image); } // Benchmark let start = Instant::now(); for _ in 0..100 { - let zbuf = NativeCdrSerdes::serialize(&image); + let zbuf = CdrSerdes::serialize(&image); assert!(zbuf.len() > 920_000); } let elapsed = start.elapsed(); @@ -108,7 +108,7 @@ fn test_estimated_size_matches_actual() { }; let estimated = cloud.estimated_serialized_size(); - let zbuf = NativeCdrSerdes::serialize(&cloud); + let zbuf = CdrSerdes::serialize(&cloud); let actual = zbuf.len(); println!("PointCloud2: estimated={}, actual={}", estimated, actual); @@ -133,7 +133,7 @@ fn test_estimated_size_matches_actual() { }; let estimated = image.estimated_serialized_size(); - let zbuf = NativeCdrSerdes::serialize(&image); + let zbuf = CdrSerdes::serialize(&image); let actual = zbuf.len(); println!("Image: estimated={}, actual={}", estimated, actual); @@ -161,7 +161,7 @@ fn test_estimated_size_matches_actual() { }; let estimated = scan.estimated_serialized_size(); - let zbuf = NativeCdrSerdes::serialize(&scan); + let zbuf = CdrSerdes::serialize(&scan); let actual = zbuf.len(); println!("LaserScan: estimated={}, actual={}", estimated, actual); @@ -191,12 +191,12 @@ fn test_capacity_hint_api() { // Test serialize_with_hint (capacity hint API) let hint = cloud.estimated_serialized_size(); - let zbuf = >::serialize_with_hint(&cloud, hint); + let zbuf = >::serialize_with_hint(&cloud, hint); assert!(zbuf.len() > 50_000); println!("Serialized with explicit hint: {} bytes", zbuf.len()); // Both serialize paths should produce the same length - let zbuf2 = NativeCdrSerdes::serialize(&cloud); + let zbuf2 = CdrSerdes::serialize(&cloud); assert_eq!(zbuf.len(), zbuf2.len()); } diff --git a/crates/ros-z-py/src/service.rs b/crates/ros-z-py/src/service.rs index 19590c2a..21660c4c 100644 --- a/crates/ros-z-py/src/service.rs +++ b/crates/ros-z-py/src/service.rs @@ -1,7 +1,7 @@ use anyhow::Result; use pyo3::prelude::*; use pyo3::types::PyDict; -use ros_z::msg::{SerdeCdrSerdes, ZMessage, ZSerdes, ZService}; +use ros_z::msg::{CdrCompatSerdes, ZMessage, ZSerdes, ZService}; use ros_z::service::{QueryKey, ZClient, ZServer}; use std::time::Duration; @@ -31,7 +31,7 @@ where { fn send_request_serialized(&self, data: &[u8]) -> Result<()> { // Deserialize the request from CDR bytes - let request = >::deserialize(data) + let request = >::deserialize(data) .map_err(|e| anyhow::anyhow!("Failed to deserialize request: {:?}", e))?; // Send the request (this is async in Rust, but we'll block here for Python) @@ -57,7 +57,7 @@ where .map_err(|e| anyhow::anyhow!("Failed to receive response: {}", e))?; // Serialize the response to CDR bytes - Ok(>::serialize_to_vec( + Ok(>::serialize_to_vec( &response, )) } @@ -66,7 +66,7 @@ where // Use a minimal timeout for non-blocking behavior match self.inner.take_response_timeout(Duration::from_millis(1)) { Ok(response) => Ok(Some( - >::serialize_to_vec(&response), + >::serialize_to_vec(&response), )), Err(e) => { let err_str = e.to_string(); @@ -122,13 +122,13 @@ where // Serialize the request to CDR bytes Ok(( key, - >::serialize_to_vec(&request), + >::serialize_to_vec(&request), )) } fn send_response_serialized(&self, data: &[u8], key: &QueryKey) -> Result<()> { // Deserialize the response from CDR bytes - let response = >::deserialize(data) + let response = >::deserialize(data) .map_err(|e| anyhow::anyhow!("Failed to deserialize response: {:?}", e))?; let mut server = self diff --git a/crates/ros-z-tests/tests/type_description_interop.rs b/crates/ros-z-tests/tests/type_description_interop.rs index 68814c89..d9eedee6 100644 --- a/crates/ros-z-tests/tests/type_description_interop.rs +++ b/crates/ros-z-tests/tests/type_description_interop.rs @@ -24,7 +24,7 @@ use common::*; use ros_z::{ Builder, dynamic::{ - DynamicMessage, DynamicSerdeCdrSerdes, MessageSchema, type_description_msg_to_schema, + DynamicCdrCompatSerdes, DynamicMessage, MessageSchema, type_description_msg_to_schema, }, }; use ros_z_msgs::type_description_interfaces::{ @@ -423,7 +423,7 @@ fn test_dynamic_subscriber_from_type_description() { println!("Step 3: Creating dynamic subscriber..."); let zsub = node .create_sub_impl::("chatter", None) - .with_serdes::() + .with_serdes::() .with_dyn_schema(schema.clone()) .build() .expect("Failed to create dynamic subscriber"); diff --git a/crates/ros-z/examples/dynamic_message/interop.rs b/crates/ros-z/examples/dynamic_message/interop.rs index 08815abc..e6bcd66c 100644 --- a/crates/ros-z/examples/dynamic_message/interop.rs +++ b/crates/ros-z/examples/dynamic_message/interop.rs @@ -7,7 +7,7 @@ use ros_z::{ dynamic::{DynamicMessage, FieldType, MessageSchema}, - msg::{NativeCdrSerdes, ZSerdes}, + msg::{CdrSerdes, ZSerdes}, }; use ros_z_msgs::{ geometry_msgs::{Point, Twist, Vector3}, @@ -52,7 +52,7 @@ fn main() -> Result<(), Box> { ); // Serialize static to CDR, then deserialize as dynamic - let cdr_bytes = NativeCdrSerdes::serialize_to_vec(&static_point); + let cdr_bytes = CdrSerdes::serialize_to_vec(&static_point); let dynamic_point = DynamicMessage::from_cdr(&cdr_bytes, &point_schema)?; println!( "Dynamic Point: x={}, y={}, z={}\n", @@ -76,7 +76,7 @@ fn main() -> Result<(), Box> { // Serialize dynamic to CDR, then deserialize as static let cdr_bytes = dynamic_point.to_cdr()?; - let static_point = >::deserialize(&cdr_bytes)?; + let static_point = >::deserialize(&cdr_bytes)?; println!( "Static Point: x={}, y={}, z={}\n", static_point.x, static_point.y, static_point.z @@ -89,7 +89,7 @@ fn main() -> Result<(), Box> { }; println!("Static String: \"{}\"", static_string.data); - let cdr_bytes = NativeCdrSerdes::serialize_to_vec(&static_string); + let cdr_bytes = CdrSerdes::serialize_to_vec(&static_string); let dynamic_string = DynamicMessage::from_cdr(&cdr_bytes, &string_schema)?; println!( "Dynamic String: \"{}\"\n", @@ -120,7 +120,7 @@ fn main() -> Result<(), Box> { static_twist.angular.x, static_twist.angular.y, static_twist.angular.z ); - let cdr_bytes = NativeCdrSerdes::serialize_to_vec(&static_twist); + let cdr_bytes = CdrSerdes::serialize_to_vec(&static_twist); let dynamic_twist = DynamicMessage::from_cdr(&cdr_bytes, &twist_schema)?; println!("Dynamic Twist:"); println!( @@ -148,7 +148,7 @@ fn main() -> Result<(), Box> { dynamic_msg.set("y", 2.5f64)?; dynamic_msg.set("z", 3.5f64)?; - let static_cdr = NativeCdrSerdes::serialize_to_vec(&static_msg); + let static_cdr = CdrSerdes::serialize_to_vec(&static_msg); let dynamic_cdr = dynamic_msg.to_cdr()?; println!("Static CDR: {} bytes", static_cdr.len()); @@ -175,10 +175,10 @@ fn main() -> Result<(), Box> { ); // Static → CDR → Dynamic → CDR → Static - let cdr1 = NativeCdrSerdes::serialize_to_vec(&original); + let cdr1 = CdrSerdes::serialize_to_vec(&original); let dynamic = DynamicMessage::from_cdr(&cdr1, &point_schema)?; let cdr2 = dynamic.to_cdr()?; - let recovered = >::deserialize(&cdr2)?; + let recovered = >::deserialize(&cdr2)?; println!( "Recovered static: ({}, {}, {})", diff --git a/crates/ros-z/examples/z_custom_message.rs b/crates/ros-z/examples/z_custom_message.rs index 111e892b..7c6bf65f 100644 --- a/crates/ros-z/examples/z_custom_message.rs +++ b/crates/ros-z/examples/z_custom_message.rs @@ -5,7 +5,7 @@ use ros_z::{ Builder, MessageTypeInfo, Result, ServiceTypeInfo, context::ZContextBuilder, entity::TypeHash, - msg::{SerdeCdrSerdes, ZService}, + msg::{CdrCompatSerdes, ZService}, }; use serde::{Deserialize, Serialize}; @@ -138,7 +138,7 @@ async fn run_status_publisher(robot_id: String) -> Result<()> { let node = ctx.create_node("robot_status_publisher").build()?; let zpub = node .create_pub::("/robot_status") - .with_serdes::() + .with_serdes::() .build()?; let mut position_x = 0.0; @@ -192,7 +192,7 @@ async fn run_status_subscriber() -> Result<()> { let node = ctx.create_node("robot_status_subscriber").build()?; let zsub = node .create_sub::("/robot_status") - .with_serdes::() + .with_serdes::() .build()?; loop { diff --git a/crates/ros-z/src/action/client.rs b/crates/ros-z/src/action/client.rs index fd6e2e1e..e39ee916 100644 --- a/crates/ros-z/src/action/client.rs +++ b/crates/ros-z/src/action/client.rs @@ -13,7 +13,7 @@ use zenoh::Result; use super::{GoalId, GoalInfo, GoalStatus, Time, ZAction, messages::*}; use crate::{ Builder, - msg::{NativeCdrSerdes, SerdeCdrSerdes, ZSerdes}, + msg::{CdrCompatSerdes, CdrSerdes, ZSerdes}, qos::QosProfile, topic_name::qualify_topic_name, }; @@ -183,8 +183,8 @@ impl<'a, A: ZAction> Builder for ZActionClientBuilder<'a, A> { if let Some(qos) = self.feedback_topic_qos { feedback_sub_builder.entity.qos = qos.to_protocol_qos(); } - // FeedbackMessage uses serde (not native CDR), so use SerdeCdrSerdes. - let feedback_sub_builder = feedback_sub_builder.with_serdes::(); + // FeedbackMessage uses serde (not native CDR), so use CdrCompatSerdes. + let feedback_sub_builder = feedback_sub_builder.with_serdes::(); tracing::debug!( "Creating feedback subscriber with callback for {}", feedback_topic_name @@ -316,8 +316,8 @@ pub struct ZActionClient { goal_client: Arc>>, result_client: Arc>>, cancel_client: Arc>>, - feedback_sub: Arc, (), SerdeCdrSerdes>>, - status_sub: Arc>, + feedback_sub: Arc, (), CdrCompatSerdes>>, + status_sub: Arc>, goal_board: Arc>, } @@ -409,7 +409,7 @@ impl ZActionClient { payload.len(), payload ); - let response = >::deserialize(&payload) + let response = >::deserialize(&payload) .map_err(|e| zenoh::Error::from(e.to_string()))?; // 5. Check if accepted @@ -436,9 +436,8 @@ impl ZActionClient { self.cancel_client.send_request(&request).await?; let sample = self.cancel_client.rx.recv_async().await?; let payload = sample.payload().to_bytes(); - let response = - >::deserialize(&payload) - .map_err(|e| zenoh::Error::from(e.to_string()))?; + let response = >::deserialize(&payload) + .map_err(|e| zenoh::Error::from(e.to_string()))?; Ok(response) } @@ -454,9 +453,8 @@ impl ZActionClient { self.cancel_client.send_request(&request).await?; let sample = self.cancel_client.rx.recv_async().await?; let payload = sample.payload().to_bytes(); - let response = - >::deserialize(&payload) - .map_err(|e| zenoh::Error::from(e.to_string()))?; + let response = >::deserialize(&payload) + .map_err(|e| zenoh::Error::from(e.to_string()))?; Ok(response) } @@ -485,7 +483,7 @@ impl ZActionClient { self.result_client.send_request(&request).await?; let sample = self.result_client.rx.recv_async().await?; let payload = sample.payload().to_bytes(); - let response = >>::deserialize(&payload) + let response = >>::deserialize(&payload) .map_err(|e| zenoh::Error::from(e.to_string()))?; Ok(response.result) @@ -501,7 +499,7 @@ impl ZActionClient { pub async fn recv_goal_response_low(&self) -> Result { let sample = self.goal_client.rx.recv_async().await?; let payload = sample.payload().to_bytes(); - >::deserialize(&payload) + >::deserialize(&payload) .map_err(|e| zenoh::Error::from(e.to_string())) } @@ -514,7 +512,7 @@ impl ZActionClient { pub async fn recv_cancel_response_low(&self) -> Result { let sample = self.cancel_client.rx.recv_async().await?; let payload = sample.payload().to_bytes(); - >::deserialize(&payload) + >::deserialize(&payload) .map_err(|e| zenoh::Error::from(e.to_string())) } @@ -527,7 +525,7 @@ impl ZActionClient { pub async fn recv_result_response_low(&self) -> Result> { let sample = self.result_client.rx.recv_async().await?; let payload = sample.payload().to_bytes(); - >>::deserialize(&payload) + >>::deserialize(&payload) .map_err(|e| zenoh::Error::from(e.to_string())) } } diff --git a/crates/ros-z/src/action/driver.rs b/crates/ros-z/src/action/driver.rs index 74940e19..9d25363e 100644 --- a/crates/ros-z/src/action/driver.rs +++ b/crates/ros-z/src/action/driver.rs @@ -23,7 +23,7 @@ use super::{ }; use crate::{ attachment::Attachment, - msg::{NativeCdrSerdes, SerdeCdrSerdes, ZSerdes}, + msg::{CdrCompatSerdes, CdrSerdes, ZSerdes}, }; /// Runs the unified driver loop for an action server with automatic goal handling. @@ -135,7 +135,7 @@ async fn handle_goal_request( { tracing::debug!("Received goal request"); let payload = query.payload().unwrap().to_bytes(); - let request = match >>::deserialize(&payload) { + let request = match >>::deserialize(&payload) { Ok(r) => r, Err(e) => { tracing::error!("Failed to deserialize goal request: {}", e); @@ -172,14 +172,13 @@ async fn handle_cancel_request( ) { tracing::debug!("Received cancel request"); let payload = query.payload().unwrap().to_bytes(); - let request = - match >::deserialize(&payload) { - Ok(r) => r, - Err(e) => { - tracing::error!("Failed to deserialize cancel request: {}", e); - return; - } - }; + let request = match >::deserialize(&payload) { + Ok(r) => r, + Err(e) => { + tracing::error!("Failed to deserialize cancel request: {}", e); + return; + } + }; // Mark goal as canceling using the atomic flag let cancelled = inner.goal_manager.read(|manager| { @@ -204,7 +203,7 @@ async fn handle_cancel_request( }; let response_bytes = - >::serialize_to_vec(&response); + >::serialize_to_vec(&response); let attachment: Attachment = query.attachment().unwrap().try_into().unwrap(); // FIXME: address the result let _ = query @@ -222,7 +221,7 @@ async fn handle_result_request( ) { tracing::debug!("Received result request"); let payload = query.payload().unwrap().to_bytes(); - let request = match >::deserialize(&payload) { + let request = match >::deserialize(&payload) { Ok(r) => r, Err(e) => { tracing::error!("Failed to deserialize result request: {}", e); @@ -297,7 +296,7 @@ async fn handle_result_request( result, }; let response_bytes = - >>::serialize_to_vec(&response); + >>::serialize_to_vec(&response); let attachment: Attachment = query.attachment().unwrap().try_into().unwrap(); let _ = query .reply(query.key_expr().clone(), response_bytes) diff --git a/crates/ros-z/src/action/mod.rs b/crates/ros-z/src/action/mod.rs index b200a6fa..b29536f6 100644 --- a/crates/ros-z/src/action/mod.rs +++ b/crates/ros-z/src/action/mod.rs @@ -374,7 +374,7 @@ pub fn transition_goal_state(current: GoalStatus, event: GoalEvent) -> GoalStatu // ── CDR serialization impls ─────────────────────────────────────────────────── // These allow GoalId, GoalStatus, Time, and GoalInfo to satisfy the // CdrSerialize + CdrDeserialize + CdrSerializedSize bounds, which in turn -// lets the action message types use the NativeCdrSerdes blanket ZMessage impl. +// lets the action message types use the CdrSerdes blanket ZMessage impl. impl CdrSerialize for GoalId { #[inline] diff --git a/crates/ros-z/src/action/server.rs b/crates/ros-z/src/action/server.rs index c4abcb8e..62fd0483 100644 --- a/crates/ros-z/src/action/server.rs +++ b/crates/ros-z/src/action/server.rs @@ -24,7 +24,7 @@ use super::{ use crate::{ Builder, attachment::Attachment, - msg::{NativeCdrSerdes, SerdeCdrSerdes, ZSerdes}, + msg::{CdrCompatSerdes, CdrSerdes, ZSerdes}, topic_name::qualify_topic_name, }; @@ -34,8 +34,8 @@ pub(crate) struct InnerServer { pub(crate) goal_server: Arc>>, pub(crate) result_server: Arc>>, pub(crate) cancel_server: Arc>>, - pub(crate) feedback_pub: Arc, SerdeCdrSerdes>>, - pub(crate) status_pub: Arc>, + pub(crate) feedback_pub: Arc, CdrCompatSerdes>>, + pub(crate) status_pub: Arc>, pub(crate) goal_manager: Arc>, /// Token to cancel the default result handler when switching to full driver mode pub(crate) result_handler_token: CancellationToken, @@ -152,7 +152,7 @@ async fn handle_result_requests_legacy_inner( ) { tracing::debug!("Received result request"); let payload = query.payload().unwrap().to_bytes(); - let request = match >::deserialize(&payload) { + let request = match >::deserialize(&payload) { Ok(r) => r, Err(e) => { tracing::error!("Failed to deserialize result request: {}", e); @@ -184,7 +184,7 @@ async fn handle_result_requests_legacy_inner( result, }; let response_bytes = - >>::serialize(&response); + >>::serialize(&response); let attachment: Attachment = query.attachment().unwrap().try_into().unwrap(); //FIXME: address the result let _ = query @@ -276,7 +276,7 @@ impl<'a, A: ZAction> Builder for ZActionServerBuilder<'a, A> { } // Keep attachments enabled for RMW-Zenoh compatibility let feedback_pub = feedback_pub_builder - .with_serdes::() + .with_serdes::() .build()?; // Create status publisher using node API for proper graph registration @@ -394,11 +394,11 @@ impl ZActionServer { &self.inner.cancel_server } - fn feedback_pub(&self) -> &Arc, SerdeCdrSerdes>> { + fn feedback_pub(&self) -> &Arc, CdrCompatSerdes>> { &self.inner.feedback_pub } - fn status_pub(&self) -> &Arc> { + fn status_pub(&self) -> &Arc> { &self.inner.status_pub } @@ -448,7 +448,7 @@ impl ZActionServer { pub async fn recv_goal(&self) -> Result> { let query = self.goal_server().queue().recv_async().await; let payload = query.payload().unwrap().to_bytes(); - let request = >>::deserialize(&payload) + let request = >>::deserialize(&payload) .map_err(|e| zenoh::Error::from(e.to_string()))?; Ok(GoalHandle { @@ -464,7 +464,7 @@ impl ZActionServer { pub async fn recv_cancel(&self) -> Result<(CancelGoalServiceRequest, zenoh::query::Query)> { let query = self.cancel_server().queue().recv_async().await; let payload = query.payload().unwrap().to_bytes(); - let request = >::deserialize(&payload) + let request = >::deserialize(&payload) .map_err(|e| zenoh::Error::from(e.to_string()))?; Ok((request, query)) } @@ -491,7 +491,7 @@ impl ZActionServer { pub async fn recv_result_request(&self) -> Result<(GoalId, zenoh::query::Query)> { let query = self.result_server().queue().recv_async().await; let payload = query.payload().unwrap().to_bytes(); - let request = >::deserialize(&payload) + let request = >::deserialize(&payload) .map_err(|e| zenoh::Error::from(e.to_string()))?; Ok((request.goal_id, query)) } @@ -502,7 +502,7 @@ impl ZActionServer { query: &zenoh::query::Query, response: &GoalResponse, ) -> Result<()> { - let response_bytes = >::serialize(response); + let response_bytes = >::serialize(response); let attachment: Attachment = query.attachment().unwrap().try_into().unwrap(); let _ = query .reply(query.key_expr().clone(), response_bytes) @@ -517,7 +517,7 @@ impl ZActionServer { ) -> Result<(CancelGoalServiceRequest, zenoh::query::Query)> { let query = self.cancel_server().queue().recv_async().await; let payload = query.payload().unwrap().to_bytes(); - let request = >::deserialize(&payload) + let request = >::deserialize(&payload) .map_err(|e| zenoh::Error::from(e.to_string()))?; Ok((request, query)) } @@ -527,8 +527,7 @@ impl ZActionServer { query: &zenoh::query::Query, response: &CancelGoalServiceResponse, ) -> Result<()> { - let response_bytes = - >::serialize(response); + let response_bytes = >::serialize(response); let attachment: Attachment = query.attachment().unwrap().try_into().unwrap(); let _ = query .reply(query.key_expr().clone(), response_bytes) @@ -543,7 +542,8 @@ impl ZActionServer { query: &zenoh::query::Query, response: &GetResultResponse, ) -> Result<()> { - let response_bytes = >>::serialize(response); + let response_bytes = + >>::serialize(response); let attachment: Attachment = query.attachment().unwrap().try_into().unwrap(); let _ = query .reply(query.key_expr().clone(), response_bytes) @@ -764,7 +764,7 @@ impl GoalHandle { stamp_sec: self.info.stamp.sec, stamp_nanosec: self.info.stamp.nanosec, }; - let response_bytes = >::serialize(&response); + let response_bytes = >::serialize(&response); if let Some(query) = self.query.take() { let attachment: Attachment = query.attachment().unwrap().try_into().unwrap(); @@ -811,7 +811,7 @@ impl GoalHandle { stamp_sec: 0, stamp_nanosec: 0, }; - let response_bytes = >::serialize(&response); + let response_bytes = >::serialize(&response); if let Some(query) = self.query.take() { // FIXME: Address the unwrap usage diff --git a/crates/ros-z/src/dynamic/mod.rs b/crates/ros-z/src/dynamic/mod.rs index ee191d9e..7e7c808e 100644 --- a/crates/ros-z/src/dynamic/mod.rs +++ b/crates/ros-z/src/dynamic/mod.rs @@ -73,7 +73,7 @@ pub use error::DynamicError; pub use message::{DynamicMessage, DynamicMessageBuilder}; pub use registry::{SchemaRegistry, get_schema, has_schema, register_schema}; pub use schema::{FieldSchema, FieldType, MessageSchema, MessageSchemaBuilder}; -pub use serdes::DynamicSerdeCdrSerdes; +pub use serdes::DynamicCdrCompatSerdes; pub use serialization::SerializationFormat; pub use type_description::{MessageSchemaTypeDescription, type_description_msg_to_schema}; pub use type_description_client::TypeDescriptionClient; @@ -93,13 +93,13 @@ use crate::pubsub::{ZPub, ZPubBuilder, ZSub, ZSubBuilder}; // Type aliases for convenience /// Type alias for a dynamic message publisher. -pub type DynPub = ZPub; +pub type DynPub = ZPub; /// Type alias for a dynamic message subscriber. -pub type DynSub = ZSub; +pub type DynSub = ZSub; /// Type alias for a dynamic message publisher builder. -pub type DynPubBuilder = ZPubBuilder; +pub type DynPubBuilder = ZPubBuilder; /// Type alias for a dynamic message subscriber builder. -pub type DynSubBuilder = ZSubBuilder; +pub type DynSubBuilder = ZSubBuilder; diff --git a/crates/ros-z/src/dynamic/serdes.rs b/crates/ros-z/src/dynamic/serdes.rs index 18c3920f..6d82fc53 100644 --- a/crates/ros-z/src/dynamic/serdes.rs +++ b/crates/ros-z/src/dynamic/serdes.rs @@ -1,6 +1,6 @@ //! Serialization/deserialization implementations for dynamic messages. //! -//! This module provides `DynamicSerdeCdrSerdes` which implements the `ZSerializer` +//! This module provides `DynamicCdrCompatSerdes` which implements the `ZSerializer` //! and `ZDeserializer` traits, allowing `DynamicMessage` to be used with //! the standard `ZPub`/`ZSub` infrastructure. @@ -22,25 +22,25 @@ use super::schema::MessageSchema; /// # Example /// /// ```ignore -/// use ros_z::dynamic::{DynamicMessage, DynamicSerdeCdrSerdes, MessageSchema}; +/// use ros_z::dynamic::{DynamicMessage, DynamicCdrCompatSerdes, MessageSchema}; /// use ros_z::pubsub::{ZPub, ZSub}; /// /// // Publisher - schema is embedded in DynamicMessage -/// let publisher: ZPub = node +/// let publisher: ZPub = node /// .create_pub("/topic") -/// .with_serdes::() +/// .with_serdes::() /// .build()?; /// /// // Subscriber - schema must be provided via with_dyn_schema() -/// let subscriber: ZSub = node +/// let subscriber: ZSub = node /// .create_sub("/topic") -/// .with_serdes::() +/// .with_serdes::() /// .with_dyn_schema(schema) /// .build()?; /// ``` -pub struct DynamicSerdeCdrSerdes; +pub struct DynamicCdrCompatSerdes; -impl ZSerializer for DynamicSerdeCdrSerdes { +impl ZSerializer for DynamicCdrCompatSerdes { type Input<'a> = &'a DynamicMessage; fn serialize_to_zbuf(input: &DynamicMessage) -> ZBuf { @@ -97,7 +97,7 @@ impl ZSerializer for DynamicSerdeCdrSerdes { } } -impl ZDeserializer for DynamicSerdeCdrSerdes { +impl ZDeserializer for DynamicCdrCompatSerdes { type Input<'a> = (&'a [u8], &'a Arc); type Output = DynamicMessage; type Error = DynamicError; @@ -108,13 +108,13 @@ impl ZDeserializer for DynamicSerdeCdrSerdes { } } -/// `ZSerdes` implementation for `DynamicSerdeCdrSerdes`. +/// `ZSerdes` implementation for `DynamicCdrCompatSerdes`. /// /// The `deserialize` method on this impl requires a schema at runtime; when called /// without one (plain byte-slice path) it returns an error. The real deserialization /// path is the specialized `ZSub` impl which calls -/// `DynamicSerdeCdrSerdes::deserialize((&bytes, schema))` directly. -impl ZSerdes for DynamicSerdeCdrSerdes { +/// `DynamicCdrCompatSerdes::deserialize((&bytes, schema))` directly. +impl ZSerdes for DynamicCdrCompatSerdes { type Error = DynamicError; fn serialize(msg: &DynamicMessage) -> ZBuf { @@ -182,7 +182,7 @@ mod tests { msg.set("y", 2.0f64).unwrap(); msg.set("z", 3.0f64).unwrap(); - let zbuf = DynamicSerdeCdrSerdes::serialize_to_zbuf(&msg); + let zbuf = DynamicCdrCompatSerdes::serialize_to_zbuf(&msg); assert!(zbuf.len() > 0); } @@ -195,11 +195,11 @@ mod tests { msg.set("z", 3.5f64).unwrap(); // Serialize - let bytes = ::serialize(&msg); + let bytes = ::serialize(&msg); // Deserialize let deserialized = - ::deserialize((&bytes, &schema)) + ::deserialize((&bytes, &schema)) .unwrap(); assert_eq!(deserialized.get::("x").unwrap(), 1.5); @@ -216,10 +216,10 @@ mod tests { msg.set("z", 3.0f64).unwrap(); let mut buffer = Vec::new(); - DynamicSerdeCdrSerdes::serialize_to_buf(&msg, &mut buffer); + DynamicCdrCompatSerdes::serialize_to_buf(&msg, &mut buffer); // Should match serialize() output - let direct = ::serialize(&msg); + let direct = ::serialize(&msg); assert_eq!(buffer, direct); } } diff --git a/crates/ros-z/src/dynamic/tests/interop_tests.rs b/crates/ros-z/src/dynamic/tests/interop_tests.rs index e796f449..ac10ae44 100644 --- a/crates/ros-z/src/dynamic/tests/interop_tests.rs +++ b/crates/ros-z/src/dynamic/tests/interop_tests.rs @@ -8,7 +8,7 @@ use std::sync::Arc; use crate::dynamic::message::DynamicMessage; use crate::dynamic::schema::{FieldType, MessageSchema}; -use crate::msg::{NativeCdrSerdes, ZSerdes}; +use crate::msg::{CdrSerdes, ZSerdes}; use ros_z_msgs::geometry_msgs::{Point, Twist, Vector3}; use ros_z_msgs::std_msgs::String as StdString; @@ -61,7 +61,7 @@ fn test_static_point_to_dynamic() { }; // Serialize to CDR bytes - let cdr_bytes = NativeCdrSerdes::serialize_to_vec(&static_msg); + let cdr_bytes = CdrSerdes::serialize_to_vec(&static_msg); // Deserialize as dynamic message let schema = create_point_schema(); @@ -86,7 +86,7 @@ fn test_dynamic_point_to_static() { let cdr_bytes = dynamic_msg.to_cdr().unwrap(); // Deserialize as static message - let static_msg = >::deserialize(&cdr_bytes).unwrap(); + let static_msg = >::deserialize(&cdr_bytes).unwrap(); // Verify values match assert_eq!(static_msg.x, 10.0); @@ -102,7 +102,7 @@ fn test_static_string_to_dynamic() { }; // Serialize to CDR bytes - let cdr_bytes = NativeCdrSerdes::serialize_to_vec(&static_msg); + let cdr_bytes = CdrSerdes::serialize_to_vec(&static_msg); // Deserialize as dynamic message let schema = create_string_schema(); @@ -126,7 +126,7 @@ fn test_dynamic_string_to_static() { let cdr_bytes = dynamic_msg.to_cdr().unwrap(); // Deserialize as static message - let static_msg = >::deserialize(&cdr_bytes).unwrap(); + let static_msg = >::deserialize(&cdr_bytes).unwrap(); // Verify value matches assert_eq!(static_msg.data, "Hello from dynamic!"); @@ -149,7 +149,7 @@ fn test_static_twist_to_dynamic() { }; // Serialize to CDR bytes - let cdr_bytes = NativeCdrSerdes::serialize_to_vec(&static_msg); + let cdr_bytes = CdrSerdes::serialize_to_vec(&static_msg); // Deserialize as dynamic message let schema = create_twist_schema(); @@ -180,7 +180,7 @@ fn test_dynamic_twist_to_static() { let cdr_bytes = dynamic_msg.to_cdr().unwrap(); // Deserialize as static message - let static_msg = >::deserialize(&cdr_bytes).unwrap(); + let static_msg = >::deserialize(&cdr_bytes).unwrap(); // Verify values match assert_eq!(static_msg.linear.x, 0.5); @@ -201,13 +201,13 @@ fn test_roundtrip_static_dynamic_static() { }; // Static → CDR → Dynamic - let cdr_bytes = NativeCdrSerdes::serialize_to_vec(&original); + let cdr_bytes = CdrSerdes::serialize_to_vec(&original); let schema = create_point_schema(); let dynamic_msg = DynamicMessage::from_cdr(&cdr_bytes, &schema).unwrap(); // Dynamic → CDR → Static let cdr_bytes2 = dynamic_msg.to_cdr().unwrap(); - let recovered = >::deserialize(&cdr_bytes2).unwrap(); + let recovered = >::deserialize(&cdr_bytes2).unwrap(); // Verify original matches recovered assert_eq!(original.x, recovered.x); @@ -226,10 +226,10 @@ fn test_roundtrip_dynamic_static_dynamic() { // Dynamic → CDR → Static let cdr_bytes = original.to_cdr().unwrap(); - let static_msg = >::deserialize(&cdr_bytes).unwrap(); + let static_msg = >::deserialize(&cdr_bytes).unwrap(); // Static → CDR → Dynamic - let cdr_bytes2 = NativeCdrSerdes::serialize_to_vec(&static_msg); + let cdr_bytes2 = CdrSerdes::serialize_to_vec(&static_msg); let recovered = DynamicMessage::from_cdr(&cdr_bytes2, &schema).unwrap(); // Verify original matches recovered @@ -262,7 +262,7 @@ fn test_cdr_bytes_identical() { dynamic_msg.set("y", 2.0f64).unwrap(); dynamic_msg.set("z", 3.0f64).unwrap(); - let static_bytes = NativeCdrSerdes::serialize_to_vec(&static_msg); + let static_bytes = CdrSerdes::serialize_to_vec(&static_msg); let dynamic_bytes = dynamic_msg.to_cdr().unwrap(); // CDR bytes should be identical diff --git a/crates/ros-z/src/dynamic/tests/pubsub_tests.rs b/crates/ros-z/src/dynamic/tests/pubsub_tests.rs index a804b24a..aaf07e36 100644 --- a/crates/ros-z/src/dynamic/tests/pubsub_tests.rs +++ b/crates/ros-z/src/dynamic/tests/pubsub_tests.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use crate::dynamic::message::DynamicMessage; use crate::dynamic::schema::{FieldType, MessageSchema}; -use crate::dynamic::serdes::DynamicSerdeCdrSerdes; +use crate::dynamic::serdes::DynamicCdrCompatSerdes; use crate::msg::{ZDeserializer, ZSerializer}; fn create_test_schema() -> Arc { @@ -54,7 +54,7 @@ fn test_complex_schema_for_pubsub() { assert_eq!(twist.fields.len(), 2); } -// Tests for unified pub/sub using DynamicSerdeCdrSerdes +// Tests for unified pub/sub using DynamicCdrCompatSerdes #[test] fn test_dynamic_cdr_serdes_roundtrip() { @@ -64,12 +64,12 @@ fn test_dynamic_cdr_serdes_roundtrip() { msg.set("y", 2.5f64).unwrap(); msg.set("z", 3.5f64).unwrap(); - // Serialize using DynamicSerdeCdrSerdes (ZSerializer trait) - let bytes = DynamicSerdeCdrSerdes::serialize(&msg); + // Serialize using DynamicCdrCompatSerdes (ZSerializer trait) + let bytes = DynamicCdrCompatSerdes::serialize(&msg); assert!(!bytes.is_empty()); - // Deserialize using DynamicSerdeCdrSerdes (ZDeserializer trait) - let deserialized = DynamicSerdeCdrSerdes::deserialize((&bytes, &schema)).unwrap(); + // Deserialize using DynamicCdrCompatSerdes (ZDeserializer trait) + let deserialized = DynamicCdrCompatSerdes::deserialize((&bytes, &schema)).unwrap(); assert_eq!(deserialized.get::("x").unwrap(), 1.5); assert_eq!(deserialized.get::("y").unwrap(), 2.5); @@ -85,12 +85,12 @@ fn test_dynamic_cdr_serdes_zbuf() { msg.set("data", "Hello, unified pubsub!").unwrap(); // Serialize to ZBuf - let zbuf = DynamicSerdeCdrSerdes::serialize_to_zbuf(&msg); + let zbuf = DynamicCdrCompatSerdes::serialize_to_zbuf(&msg); assert!(zbuf.len() > 0); // Convert to bytes and deserialize let bytes: Vec = zbuf.contiguous().to_vec(); - let deserialized = DynamicSerdeCdrSerdes::deserialize((&bytes, &schema)).unwrap(); + let deserialized = DynamicCdrCompatSerdes::deserialize((&bytes, &schema)).unwrap(); assert_eq!( deserialized.get::("data").unwrap(), @@ -108,14 +108,14 @@ fn test_dynamic_cdr_serdes_to_buf() { // Serialize to existing buffer let mut buffer = Vec::new(); - DynamicSerdeCdrSerdes::serialize_to_buf(&msg, &mut buffer); + DynamicCdrCompatSerdes::serialize_to_buf(&msg, &mut buffer); // Should match serialize() output - let direct = DynamicSerdeCdrSerdes::serialize(&msg); + let direct = DynamicCdrCompatSerdes::serialize(&msg); assert_eq!(buffer, direct); // Verify deserialize works - let deserialized = DynamicSerdeCdrSerdes::deserialize((&buffer, &schema)).unwrap(); + let deserialized = DynamicCdrCompatSerdes::deserialize((&buffer, &schema)).unwrap(); assert_eq!(deserialized.get::("x").unwrap(), 10.0); } @@ -127,10 +127,10 @@ fn test_zmessage_impl_for_dynamic_message() { msg.set("y", 6.0f64).unwrap(); msg.set("z", 7.0f64).unwrap(); - // Use DynamicSerdeCdrSerdes for serialization (ZMessage is now a marker trait) - use crate::dynamic::DynamicSerdeCdrSerdes; + // Use DynamicCdrCompatSerdes for serialization (ZMessage is now a marker trait) + use crate::dynamic::DynamicCdrCompatSerdes; use crate::msg::ZSerdes; - let zbuf = >::serialize(&msg); + let zbuf = >::serialize(&msg); use zenoh_buffers::buffer::SplitBuffer; let bytes = zbuf.contiguous(); assert!(!bytes.is_empty()); @@ -189,7 +189,7 @@ fn test_zpub_builder_with_dyn_schema() { #[test] fn test_zpub_builder_with_serdes_preserves_schema() { - use crate::dynamic::{DynamicMessage, DynamicSerdeCdrSerdes}; + use crate::dynamic::{DynamicCdrCompatSerdes, DynamicMessage}; use crate::pubsub::ZPubBuilder; use std::marker::PhantomData; @@ -211,7 +211,7 @@ fn test_zpub_builder_with_serdes_preserves_schema() { }; // Convert serdes type - schema should be preserved - let builder: ZPubBuilder = builder.with_serdes(); + let builder: ZPubBuilder = builder.with_serdes(); assert!(builder.dyn_schema.is_some()); assert_eq!( builder.dyn_schema.as_ref().unwrap().type_name, diff --git a/crates/ros-z/src/dynamic/type_description_service.rs b/crates/ros-z/src/dynamic/type_description_service.rs index cac469bc..7315ef19 100644 --- a/crates/ros-z/src/dynamic/type_description_service.rs +++ b/crates/ros-z/src/dynamic/type_description_service.rs @@ -591,12 +591,12 @@ impl TypeDescriptionService { /// This is called from the Zenoh callback when a query is received. /// It deserializes the request, looks up the schema, and sends the response. fn handle_query(schemas: &Arc>>, query: Query) { - use crate::msg::{SerdeCdrSerdes, ZSerdes}; + use crate::msg::{CdrCompatSerdes, ZSerdes}; // Deserialize the request let request: GetTypeDescriptionRequest = match query.payload() { Some(payload) => { - match >::deserialize( + match >::deserialize( payload.to_bytes().as_ref(), ) { Ok(req) => req, @@ -631,7 +631,7 @@ impl TypeDescriptionService { ); // Serialize and send the response - let bytes = >::serialize(&response); + let bytes = >::serialize(&response); use zenoh::Wait; if let Err(e) = query.reply(query.key_expr().clone(), bytes).wait() { warn!("[TDS] Failed to send response: {}", e); diff --git a/crates/ros-z/src/msg.rs b/crates/ros-z/src/msg.rs index edab04fe..025e3181 100644 --- a/crates/ros-z/src/msg.rs +++ b/crates/ros-z/src/msg.rs @@ -48,7 +48,7 @@ pub trait ZSerializer { /// # Example /// /// ```rust,no_run - /// use ros_z::msg::{ZSerializer, SerdeCdrSerdes}; + /// use ros_z::msg::{ZSerializer, CdrCompatSerdes}; /// use serde::Serialize; /// /// #[derive(Serialize)] @@ -56,7 +56,7 @@ pub trait ZSerializer { /// /// let msg = LargeMsg { data: vec![0; 1_000_000] }; /// let hint = 4 + 4 + 1_000_000; // header + length + data - /// let zbuf = SerdeCdrSerdes::serialize_to_zbuf_with_hint(&msg, hint); + /// let zbuf = CdrCompatSerdes::serialize_to_zbuf_with_hint(&msg, hint); /// ``` fn serialize_to_zbuf_with_hint(input: Self::Input<'_>, capacity_hint: usize) -> ZBuf; @@ -84,7 +84,7 @@ pub trait ZSerializer { /// # Example /// /// ```rust,no_run - /// use ros_z::msg::{ZSerializer, SerdeCdrSerdes}; + /// use ros_z::msg::{ZSerializer, CdrCompatSerdes}; /// use ros_z::shm::ShmProviderBuilder; /// use serde::Serialize; /// @@ -95,7 +95,7 @@ pub trait ZSerializer { /// let msg = MyMsg { value: 42 }; /// let provider = ShmProviderBuilder::new(1024 * 1024).build()?; /// - /// let (zbuf, size) = SerdeCdrSerdes::serialize_to_shm(&msg, 128, &provider)?; + /// let (zbuf, size) = CdrCompatSerdes::serialize_to_shm(&msg, 128, &provider)?; /// println!("Serialized {} bytes to SHM", size); /// # Ok(()) /// # } @@ -145,13 +145,13 @@ pub trait ZDeserializer { /// Implement this trait to define a custom wire format for message type `T`. /// /// Two built-in implementations are provided: -/// - [`NativeCdrSerdes`]: fast path using `CdrSerialize`/`CdrDeserialize` traits -/// - [`SerdeCdrSerdes`]: compatibility path using `serde::Serialize`/`Deserialize` +/// - [`CdrSerdes`]: fast path using `CdrSerialize`/`CdrDeserialize` traits +/// - [`CdrCompatSerdes`]: compatibility path using `serde::Serialize`/`Deserialize` /// /// # Example /// /// ```rust,no_run -/// use ros_z::msg::{ZSerdes, NativeCdrSerdes, SerdeCdrSerdes}; +/// use ros_z::msg::{ZSerdes, CdrSerdes, CdrCompatSerdes}; /// use serde::{Serialize, Deserialize}; /// /// #[derive(Serialize, Deserialize)] @@ -159,7 +159,7 @@ pub trait ZDeserializer { /// /// // Use serde path (works with any serde type) /// fn publish_serde(msg: &MyMsg) -> zenoh_buffers::ZBuf { -/// SerdeCdrSerdes::serialize(msg) +/// CdrCompatSerdes::serialize(msg) /// } /// ``` pub trait ZSerdes: Send + Sync + 'static { @@ -242,13 +242,13 @@ impl ZMessage for T where T: Send + Sync + 'static {} /// - Custom user types with `#[derive(Serialize, Deserialize)]` /// - Internal wire types that haven't been migrated to native CDR /// -/// Performance: ~15-30% slower than `NativeCdrSerdes` due to serde overhead. -pub struct SerdeCdrSerdes; +/// Performance: ~15-30% slower than `CdrSerdes` due to serde overhead. +pub struct CdrCompatSerdes; /// CDR encapsulation header for little-endian encoding pub const CDR_HEADER_LE: [u8; 4] = [0x00, 0x01, 0x00, 0x00]; -impl ZSerdes for SerdeCdrSerdes +impl ZSerdes for CdrCompatSerdes where T: serde::Serialize + serde::de::DeserializeOwned + Send + Sync + 'static, { @@ -309,13 +309,13 @@ where } // Keep old ZSerializer/ZDeserializer impls on a wrapper for backwards compat -// with any callers that use SerdeCdrSerdes::::serialize_to_zbuf() etc. +// with any callers that use CdrCompatSerdes::::serialize_to_zbuf() etc. // We provide a type alias for backwards compat. -/// Backwards-compatible wrapper. Prefer [`SerdeCdrSerdes`] (unit struct). +/// Backwards-compatible wrapper. Prefer [`CdrCompatSerdes`] (unit struct). #[doc(hidden)] -pub struct SerdeCdrSerdesTyped(PhantomData); +pub struct CdrCompatSerdesTyped(PhantomData); -impl ZSerializer for SerdeCdrSerdesTyped +impl ZSerializer for CdrCompatSerdesTyped where T: Serialize, { @@ -366,7 +366,7 @@ where } } -impl ZDeserializer for SerdeCdrSerdesTyped +impl ZDeserializer for CdrCompatSerdesTyped where for<'a> T: Deserialize<'a>, { @@ -397,14 +397,14 @@ where /// Unit struct implementing CDR serialization via native `CdrSerialize`/`CdrDeserialize` traits. /// -/// Generated message types implement these traits and use `NativeCdrSerdes` as their +/// Generated message types implement these traits and use `CdrSerdes` as their /// default serdes. This enables the POD bulk fast path for sequences of /// plain types (e.g., `Vec`, `Vec`). /// /// This is the default serdes for `ZPub`, `ZSub`, `ZClient`, and `ZServer`. -pub struct NativeCdrSerdes; +pub struct CdrSerdes; -impl ZSerdes for NativeCdrSerdes +impl ZSerdes for CdrSerdes where T: CdrSerialize + CdrSerializedSize + CdrDeserialize + Send + Sync + 'static, { @@ -462,11 +462,11 @@ where } } -/// Backwards-compatible typed wrapper for NativeCdrSerdes. +/// Backwards-compatible typed wrapper for CdrSerdes. #[doc(hidden)] -pub struct NativeCdrSerdesTyped(PhantomData); +pub struct CdrSerdesTyped(PhantomData); -impl ZSerializer for NativeCdrSerdesTyped +impl ZSerializer for CdrSerdesTyped where T: CdrSerialize + CdrSerializedSize, { @@ -515,7 +515,7 @@ where } } -impl ZDeserializer for NativeCdrSerdesTyped +impl ZDeserializer for CdrSerdesTyped where T: CdrDeserialize, { @@ -696,14 +696,15 @@ mod tests { text: "Hello, ZBuf!".to_string(), }; - let zbuf = SerdeCdrSerdes::serialize(&msg); + let zbuf = CdrCompatSerdes::serialize(&msg); let bytes = zbuf.contiguous(); // Verify CDR header assert_eq!(&bytes[0..4], &CDR_HEADER_LE); // Verify roundtrip - let deserialized = >::deserialize(&bytes).unwrap(); + let deserialized = + >::deserialize(&bytes).unwrap(); assert_eq!(deserialized, msg); } @@ -715,8 +716,8 @@ mod tests { }; // Both methods should produce identical bytes - let zbuf = SerdeCdrSerdes::serialize(&msg); - let vec = SerdeCdrSerdes::serialize_to_vec(&msg); + let zbuf = CdrCompatSerdes::serialize(&msg); + let vec = CdrCompatSerdes::serialize_to_vec(&msg); let zbuf_bytes = zbuf.contiguous(); assert_eq!(&*zbuf_bytes, &vec[..]); @@ -729,13 +730,14 @@ mod tests { text: "trait test".to_string(), }; - // Use SerdeCdrSerdes directly for serde-only types - let zbuf = SerdeCdrSerdes::serialize(&msg); + // Use CdrCompatSerdes directly for serde-only types + let zbuf = CdrCompatSerdes::serialize(&msg); let bytes = zbuf.contiguous(); assert_eq!(&bytes[0..4], &CDR_HEADER_LE); - let deserialized = >::deserialize(&bytes).unwrap(); + let deserialized = + >::deserialize(&bytes).unwrap(); assert_eq!(deserialized, msg); } @@ -747,8 +749,8 @@ mod tests { }; // Serialize using both methods - let vec1 = SerdeCdrSerdes::serialize_to_vec(&msg); - let zbuf = SerdeCdrSerdes::serialize(&msg); + let vec1 = CdrCompatSerdes::serialize_to_vec(&msg); + let zbuf = CdrCompatSerdes::serialize(&msg); let zbuf_bytes = zbuf.contiguous(); let vec2 = zbuf_bytes.to_vec(); @@ -776,10 +778,10 @@ mod tests { }; // Serialize using serialize_to_vec - let buffer = SerdeCdrSerdes::serialize_to_vec(&original); + let buffer = CdrCompatSerdes::serialize_to_vec(&original); // Deserialize - let deserialized = >::deserialize(&buffer) + let deserialized = >::deserialize(&buffer) .expect("Failed to deserialize"); // Should match original @@ -793,14 +795,15 @@ mod tests { text: "trait test".to_string(), }; - // Use SerdeCdrSerdes directly for serde-only types - let serialized = SerdeCdrSerdes::serialize_to_vec(&msg); + // Use CdrCompatSerdes directly for serde-only types + let serialized = CdrCompatSerdes::serialize_to_vec(&msg); assert!(!serialized.is_empty()); assert_eq!(&serialized[0..4], &CDR_HEADER_LE); - // Deserialize using SerdeCdrSerdes - let deserialized = >::deserialize(&serialized[..]) - .expect("Failed to deserialize"); + // Deserialize using CdrCompatSerdes + let deserialized = + >::deserialize(&serialized[..]) + .expect("Failed to deserialize"); assert_eq!(deserialized, msg); } @@ -860,11 +863,11 @@ mod tests { } } -/// Tests for `NativeCdrSerdes` — the `CdrSerialize`-based CDR fast path. +/// Tests for `CdrSerdes` — the `CdrSerialize`-based CDR fast path. /// /// These tests verify: -/// 1. Byte-identical wire output between `SerdeCdrSerdes` (serde path) and `NativeCdrSerdes` (CDR trait path). -/// 2. Roundtrip correctness for `NativeCdrSerdes`. +/// 1. Byte-identical wire output between `CdrCompatSerdes` (serde path) and `CdrSerdes` (CDR trait path). +/// 2. Roundtrip correctness for `CdrSerdes`. /// 3. POD bulk path produces the same bytes for plain sequences as the element loop. #[cfg(test)] mod fast_cdr_tests { @@ -1003,13 +1006,13 @@ mod fast_cdr_tests { fn serde_bytes( value: &T, ) -> Vec { - SerdeCdrSerdes::serialize_to_vec(value) + CdrCompatSerdes::serialize_to_vec(value) } fn fast_bytes( value: &T, ) -> Vec { - NativeCdrSerdes::serialize_to_vec(value) + CdrSerdes::serialize_to_vec(value) } fn fast_deserialize< @@ -1017,7 +1020,7 @@ mod fast_cdr_tests { >( bytes: &[u8], ) -> T { - NativeCdrSerdes::deserialize(bytes).expect("NativeCdrSerdes::deserialize failed") + CdrSerdes::deserialize(bytes).expect("CdrSerdes::deserialize failed") } // ── Tests ───────────────────────────────────────────────────────────────── diff --git a/crates/ros-z/src/node.rs b/crates/ros-z/src/node.rs index 46a44512..c6eb80ef 100644 --- a/crates/ros-z/src/node.rs +++ b/crates/ros-z/src/node.rs @@ -12,7 +12,7 @@ use crate::{ action::{client::ZActionClientBuilder, server::ZActionServerBuilder}, context::{GlobalCounter, RemapRules}, dynamic::{ - DynamicMessage, DynamicSerdeCdrSerdes, MessageSchema, TypeDescriptionClient, + DynamicCdrCompatSerdes, DynamicMessage, MessageSchema, TypeDescriptionClient, TypeDescriptionService, }, entity::*, @@ -208,7 +208,7 @@ impl ZNode { /// - Absolute topics (starting with '/') are used as-is /// - Private topics (starting with '~') are expanded to /// /// - Relative topics are expanded to // - pub fn create_pub(&self, topic: &str) -> ZPubBuilder + pub fn create_pub(&self, topic: &str) -> ZPubBuilder where T: ZMessage + WithTypeInfo, { @@ -221,7 +221,7 @@ impl ZNode { &self, topic: &str, type_info: Option, - ) -> ZPubBuilder + ) -> ZPubBuilder where T: ZMessage, { @@ -255,7 +255,7 @@ impl ZNode { /// - Absolute topics (starting with '/') are used as-is /// - Private topics (starting with '~') are expanded to /// /// - Relative topics are expanded to // - pub fn create_sub(&self, topic: &str) -> ZSubBuilder + pub fn create_sub(&self, topic: &str) -> ZSubBuilder where T: ZMessage + WithTypeInfo, { @@ -268,7 +268,7 @@ impl ZNode { &self, topic: &str, type_info: Option, - ) -> ZSubBuilder + ) -> ZSubBuilder where T: ZMessage, { @@ -443,7 +443,7 @@ impl ZNode { &self, topic: &str, schema: Arc, - ) -> Result> { + ) -> Result> { // Register schema with type description service if enabled if let Some(service) = &self.type_desc_service { if let Err(e) = service.register_schema(schema.clone()) { @@ -493,7 +493,7 @@ impl ZNode { // Build the publisher self.create_pub_impl::(topic, type_info) - .with_serdes::() + .with_serdes::() .with_dyn_schema(schema) .build() } @@ -534,7 +534,7 @@ impl ZNode { topic: &str, discovery_timeout: Duration, ) -> Result<( - ZSub, + ZSub, Arc, )> { debug!( @@ -583,7 +583,7 @@ impl ZNode { // Build the subscriber with the discovered schema let subscriber = self .create_sub_impl::(topic, type_info) - .with_serdes::() + .with_serdes::() .with_dyn_schema(schema.clone()) .build()?; @@ -614,7 +614,7 @@ impl ZNode { &self, topic: &str, schema: Arc, - ) -> Result> { + ) -> Result> { // Create TypeInfo from schema for proper key expression matching // Convert ROS 2 canonical name to DDS name // "std_msgs/msg/String" → "std_msgs::msg::dds_::String_" @@ -649,7 +649,7 @@ impl ZNode { // Build the subscriber with proper type info self.create_sub_impl::(topic, type_info) - .with_serdes::() + .with_serdes::() .with_dyn_schema(schema) .build() } diff --git a/crates/ros-z/src/pubsub.rs b/crates/ros-z/src/pubsub.rs index 8f6922d4..4675178c 100644 --- a/crates/ros-z/src/pubsub.rs +++ b/crates/ros-z/src/pubsub.rs @@ -16,7 +16,7 @@ use crate::impl_with_type_info; use crate::queue::BoundedQueue; use crate::topic_name; -use crate::msg::{NativeCdrSerdes, ZMessage, ZSerdes}; +use crate::msg::{CdrSerdes, ZMessage, ZSerdes}; use crate::qos::QosProfile; use ros_z_protocol::qos::{QosDurability, QosHistory, QosReliability}; use std::sync::Mutex; @@ -25,7 +25,7 @@ use std::sync::Mutex; /// (synchronous) or [`async_publish`](ZPub::async_publish) (async). /// /// Create a publisher via [`ZNode::create_pub`](crate::node::ZNode::create_pub). -pub struct ZPub = NativeCdrSerdes> { +pub struct ZPub = CdrSerdes> { pub entity: EndpointEntity, // TODO: replace this with the sample sn sn: AtomicUsize, @@ -54,7 +54,7 @@ impl> std::fmt::Debug for ZPub { } #[derive(Debug)] -pub struct ZPubBuilder { +pub struct ZPubBuilder { pub entity: EndpointEntity, pub session: Arc, pub graph: Arc, @@ -191,7 +191,7 @@ impl ZPubBuilder { /// ```ignore /// let publisher = node /// .create_pub_impl::("topic", None) - /// .with_serdes::() + /// .with_serdes::() /// .with_dyn_schema(schema) /// .build()?; /// ``` @@ -546,7 +546,7 @@ where } // Specialized implementation for DynamicMessage publisher -impl ZPub { +impl ZPub { /// Get the dynamic schema used by this publisher. /// /// Returns `None` if the publisher was not created with `.with_dyn_schema()`. @@ -555,7 +555,7 @@ impl ZPub } } -pub struct ZSubBuilder { +pub struct ZSubBuilder { pub entity: EndpointEntity, pub session: Arc, pub(crate) keyexpr_format: ros_z_protocol::KeyExprFormat, @@ -636,7 +636,7 @@ where /// Set the dynamic message schema for runtime-typed messages. /// - /// This is required when using `DynamicMessage` with `DynamicSerdeCdrSerdes`. + /// This is required when using `DynamicMessage` with `DynamicCdrCompatSerdes`. /// The schema will be used to deserialize incoming messages. /// /// # Example @@ -644,7 +644,7 @@ where /// ```ignore /// let subscriber = node /// .create_sub::("/topic") - /// .with_serdes::() + /// .with_serdes::() /// .with_dyn_schema(schema) /// .build()?; /// ``` @@ -859,14 +859,14 @@ where } } -pub struct ZSub = NativeCdrSerdes> { +pub struct ZSub = CdrSerdes> { pub entity: EndpointEntity, pub queue: Option>>, _inner: zenoh::pubsub::Subscriber<()>, _lv_token: LivelinessToken, events_mgr: Arc>, /// Schema for dynamic message deserialization. - /// Required when using `DynamicMessage` with `DynamicSerdeCdrSerdes`. + /// Required when using `DynamicMessage` with `DynamicCdrCompatSerdes`. pub dyn_schema: Option>, /// Expected encoding for validation. pub expected_encoding: Option, @@ -970,7 +970,7 @@ where } // Specialized implementation for DynamicMessage -impl ZSub { +impl ZSub { /// Receive and deserialize the next dynamic message. /// /// This method requires that the subscriber was built with `.with_dyn_schema()`. @@ -1003,7 +1003,7 @@ impl ZSub::deserialize(( + ::deserialize(( &payload, schema, )) .map_err(|e| zenoh::Error::from(e.to_string())) @@ -1028,7 +1028,7 @@ impl ZSub::deserialize(( + ::deserialize(( &payload, schema, )) .map_err(|e| zenoh::Error::from(e.to_string())) @@ -1048,7 +1048,7 @@ impl ZSub::deserialize(( + ::deserialize(( &payload, schema, )) .map_err(|e| zenoh::Error::from(e.to_string())) @@ -1062,7 +1062,7 @@ impl ZSub { let payload = sample.payload().to_bytes(); - let result = ::deserialize((&payload, schema)) + let result = ::deserialize((&payload, schema)) .map_err(|e| zenoh::Error::from(e.to_string())); Some(result) } diff --git a/crates/ros-z/src/service.rs b/crates/ros-z/src/service.rs index a5f88b0f..30930e91 100644 --- a/crates/ros-z/src/service.rs +++ b/crates/ros-z/src/service.rs @@ -29,7 +29,7 @@ use crate::{ common::DataHandler, entity::EndpointEntity, impl_with_type_info, - msg::{SerdeCdrSerdes, ZMessage, ZSerdes, ZService}, + msg::{CdrCompatSerdes, ZMessage, ZSerdes, ZService}, qos::QosHistory, queue::BoundedQueue, }; @@ -179,7 +179,7 @@ where { let sample = self.take_sample()?; let msg = - >::deserialize(&sample.payload().to_bytes()) + >::deserialize(&sample.payload().to_bytes()) .map_err(|e| zenoh::Error::from(e.to_string()))?; Ok(msg) } @@ -193,7 +193,7 @@ where { let sample = self.take_sample_timeout(timeout)?; let payload_bytes = sample.payload().to_bytes(); - let msg = >::deserialize(&payload_bytes[..]) + let msg = >::deserialize(&payload_bytes[..]) .map_err(|e| zenoh::Error::from(e.to_string()))?; Ok(msg) } @@ -206,7 +206,7 @@ where { let sample = self.rx.recv_async().await?; let payload_bytes = sample.payload().to_bytes(); - let msg = >::deserialize(&payload_bytes[..]) + let msg = >::deserialize(&payload_bytes[..]) .map_err(|e| zenoh::Error::from(e.to_string()))?; Ok(msg) } @@ -234,7 +234,7 @@ where where T::Request: serde::Serialize + serde::de::DeserializeOwned + Send + Sync + 'static, { - let payload = >::serialize(msg); + let payload = >::serialize(msg); tracing::Span::current().record("payload_len", payload.len()); // Log the key expression being queried @@ -284,7 +284,7 @@ where let sn = attachment.sequence_number; self.inner .get() - .payload(>::serialize(msg)) + .payload(>::serialize(msg)) .attachment(attachment) .callback(move |reply| { match reply.into_result() { @@ -542,7 +542,7 @@ where debug!("[SRV] Processing request"); - let msg = >::deserialize(&payload_bytes[..]) + let msg = >::deserialize(&payload_bytes[..]) .map_err(|e| zenoh::Error::from(e.to_string()))?; self.map.insert(key.clone(), query); @@ -567,7 +567,7 @@ where return Err("Existing query detected".into()); } let payload_bytes = query.payload().unwrap().to_bytes(); - let msg = >::deserialize(&payload_bytes[..]) + let msg = >::deserialize(&payload_bytes[..]) .map_err(|e| zenoh::Error::from(e.to_string()))?; self.map.insert(key.clone(), query); @@ -593,7 +593,7 @@ where ); match self.map.remove(key) { Some(query) => { - let payload = >::serialize(msg); + let payload = >::serialize(msg); tracing::Span::current().record("payload_len", payload.len()); debug!("[SRV] Sending response"); @@ -627,7 +627,7 @@ where query .reply( &self.key_expr, - >::serialize(msg), + >::serialize(msg), ) .attachment(attachment) .await diff --git a/crates/ros-z/tests/action/communication.rs b/crates/ros-z/tests/action/communication.rs index 2765410e..98d42318 100644 --- a/crates/ros-z/tests/action/communication.rs +++ b/crates/ros-z/tests/action/communication.rs @@ -8,7 +8,7 @@ use ros_z::{ Builder, Result, context::ZContextBuilder, define_action, - msg::{NativeCdrSerdes, ZSerdes}, + msg::{CdrSerdes, ZSerdes}, }; use serde::{Deserialize, Serialize}; use tokio::time::timeout; @@ -154,7 +154,7 @@ mod tests { }; // Respond to the cancel request - let response_bytes = >::serialize_to_vec(&cancel_resp); response_tx diff --git a/crates/ros-z/tests/pubsub.rs b/crates/ros-z/tests/pubsub.rs index ecd0961a..4397a24a 100644 --- a/crates/ros-z/tests/pubsub.rs +++ b/crates/ros-z/tests/pubsub.rs @@ -1,7 +1,7 @@ use std::{thread, time::Duration}; use ros_z::{ - Builder, TypeHash, ZBuf, context::ZContextBuilder, msg::SerdeCdrSerdes, + Builder, TypeHash, ZBuf, context::ZContextBuilder, msg::CdrCompatSerdes, ros_msg::MessageTypeInfo, }; use ros_z_msgs::std_msgs::ByteMultiArray; @@ -39,13 +39,13 @@ async fn test_basic_pubsub() { let publisher = node .create_pub::("/test_topic") - .with_serdes::() + .with_serdes::() .build() .unwrap(); let subscriber = node .create_sub::("/test_topic") - .with_serdes::() + .with_serdes::() .build() .unwrap(); @@ -75,13 +75,13 @@ async fn test_multiple_messages() { let publisher = node .create_pub::("/multi_topic") - .with_serdes::() + .with_serdes::() .build() .unwrap(); let subscriber = node .create_sub::("/multi_topic") - .with_serdes::() + .with_serdes::() .build() .unwrap(); @@ -115,13 +115,13 @@ async fn test_large_payload() { let publisher = node .create_pub::("/large_topic") - .with_serdes::() + .with_serdes::() .build() .unwrap(); let subscriber = node .create_sub::("/large_topic") - .with_serdes::() + .with_serdes::() .build() .unwrap(); From e0bb963ce100ba05d12f5f02e552a9fa04585088 Mon Sep 17 00:00:00 2001 From: yuanyuyuan Date: Tue, 10 Mar 2026 10:24:52 +0800 Subject: [PATCH 05/13] fix(ci): fix compile errors after ZSerdes refactor - protobuf_demo: ProtobufSerdes is now a unit struct, remove type param - rmw-zenoh-rs/msg.rs: disambiguate Self::serialize with fully-qualified syntax - service.rs: make rmw_send_request generic over S: ZSerdes, add rmw_send_response for RMW path that bypasses serde bounds --- crates/rmw-zenoh-rs/src/msg.rs | 2 +- crates/rmw-zenoh-rs/src/service.rs | 9 ++++-- .../ros-z/examples/protobuf_demo/src/lib.rs | 6 ++-- .../ros-z/examples/protobuf_demo/src/types.rs | 6 +--- crates/ros-z/src/service.rs | 32 +++++++++++++++++-- 5 files changed, 41 insertions(+), 14 deletions(-) diff --git a/crates/rmw-zenoh-rs/src/msg.rs b/crates/rmw-zenoh-rs/src/msg.rs index 099a668d..59e4a487 100644 --- a/crates/rmw-zenoh-rs/src/msg.rs +++ b/crates/rmw-zenoh-rs/src/msg.rs @@ -95,7 +95,7 @@ impl ZSerdes for RosSerdes { } fn serialize_with_hint(msg: &RosMessage, _capacity_hint: usize) -> zenoh_buffers::ZBuf { - Self::serialize(msg) + >::serialize(msg) } fn serialize_to_vec(msg: &RosMessage) -> Vec { diff --git a/crates/rmw-zenoh-rs/src/service.rs b/crates/rmw-zenoh-rs/src/service.rs index 3883ad6a..3d136db7 100644 --- a/crates/rmw-zenoh-rs/src/service.rs +++ b/crates/rmw-zenoh-rs/src/service.rs @@ -63,7 +63,9 @@ impl ClientImpl { }; // Send the request with notification callback - let _ = self.inner.rmw_send_request(&req, notify_callback)?; + let _ = self + .inner + .rmw_send_request::(&req, notify_callback)?; // Return the sequence number we tracked unsafe { @@ -317,7 +319,10 @@ impl ServiceImpl { let resp = crate::msg::RosMessage::new(response, self.response_ts.response); // Send response - match self.inner.send_response(&resp, &key) { + match self + .inner + .rmw_send_response::(&resp, &key) + { Ok(_) => { tracing::debug!("[ServiceImpl::send_response] Response sent successfully"); Ok(()) diff --git a/crates/ros-z/examples/protobuf_demo/src/lib.rs b/crates/ros-z/examples/protobuf_demo/src/lib.rs index 2a8b44bd..e9b20fc6 100644 --- a/crates/ros-z/examples/protobuf_demo/src/lib.rs +++ b/crates/ros-z/examples/protobuf_demo/src/lib.rs @@ -34,7 +34,7 @@ pub fn run_pubsub_demo(ctx: ZContext, max_count: Option) -> Result<()> { println!("--- Part 1: ROS geometry_msgs/Vector3 with Protobuf ---"); let ros_pub = node .create_pub::("/vector_proto") - .with_serdes::>() + .with_serdes::() .build()?; println!("Publishing ROS Vector3 messages...\n"); @@ -55,7 +55,7 @@ pub fn run_pubsub_demo(ctx: ZContext, max_count: Option) -> Result<()> { println!("\n--- Part 2: Custom SensorData message (pure protobuf) ---"); let custom_pub = node .create_pub::("/sensor_data") - .with_serdes::>() + .with_serdes::() .build()?; println!("Publishing custom SensorData messages...\n"); @@ -82,7 +82,7 @@ pub fn run_pubsub_demo(ctx: ZContext, max_count: Option) -> Result<()> { println!("\nKey points:"); println!("1. ROS messages (Vector3Proto): Auto-generated from ros-z-msgs with MessageTypeInfo"); println!("2. Custom messages (SensorData): Generated from your own .proto files"); - println!("3. Both use .with_serdes::>() for protobuf serialization"); + println!("3. Both use .with_serdes::() for protobuf serialization"); println!("4. ros-z is transport-agnostic - works with ANY protobuf message!"); Ok(()) diff --git a/crates/ros-z/examples/protobuf_demo/src/types.rs b/crates/ros-z/examples/protobuf_demo/src/types.rs index 3f795c3b..d510957f 100644 --- a/crates/ros-z/examples/protobuf_demo/src/types.rs +++ b/crates/ros-z/examples/protobuf_demo/src/types.rs @@ -1,9 +1,5 @@ /// Shared protobuf type definitions and trait implementations -use ros_z::{ - MessageTypeInfo, ServiceTypeInfo, WithTypeInfo, - entity::TypeHash, - msg::{ProtobufSerdes, ZService}, -}; +use ros_z::{MessageTypeInfo, ServiceTypeInfo, WithTypeInfo, entity::TypeHash, msg::ZService}; // Include protobuf messages generated from sensor_data.proto pub mod generated { diff --git a/crates/ros-z/src/service.rs b/crates/ros-z/src/service.rs index 30930e91..11bbfeed 100644 --- a/crates/ros-z/src/service.rs +++ b/crates/ros-z/src/service.rs @@ -274,17 +274,17 @@ where } #[cfg(feature = "rmw")] - pub fn rmw_send_request(&self, msg: &T::Request, notify: F) -> Result + pub fn rmw_send_request(&self, msg: &T::Request, notify: F) -> Result where + S: ZSerdes, F: Fn() + Send + Sync + 'static, - T::Request: serde::Serialize + serde::de::DeserializeOwned + Send + Sync + 'static, { let tx = self.tx.clone(); let attachment = self.new_attachment(); let sn = attachment.sequence_number; self.inner .get() - .payload(>::serialize(msg)) + .payload(>::serialize(msg)) .attachment(attachment) .callback(move |reply| { match reply.into_result() { @@ -612,6 +612,32 @@ where } } + /// RMW-specific: send response using an explicit serializer type parameter. + /// + /// Use this instead of [`send_response`](Self::send_response) when the response type does not + /// implement `serde::Serialize` (e.g., `RosMessage` in the RMW crate, which is serialized via + /// a C FFI path through `RosSerdes`). + #[cfg(feature = "rmw")] + pub fn rmw_send_response(&mut self, msg: &T::Response, key: &QueryKey) -> Result<()> + where + S: ZSerdes, + { + match self.map.remove(key) { + Some(query) => { + let payload = >::serialize(msg); + let attachment = Attachment::new(key.sn, key.gid); + query + .reply(&self.key_expr, payload) + .attachment(attachment) + .wait() + } + None => { + error!("[SRV] No query found for sn={}", key.sn); + Err("Query map doesn't contain key".into()) + } + } + } + /// Awaits sending the response to a service request. /// /// - `msg` is the response message to send. From 16f96ecf85cfa5aefa1c29125021aaf6f0db77a4 Mon Sep 17 00:00:00 2001 From: yuanyuyuan Date: Tue, 10 Mar 2026 15:23:43 +0800 Subject: [PATCH 06/13] fix(service): add serdes type param S to ZClient/ZServer for non-serde message types --- crates/rmw-zenoh-rs/src/rmw.rs | 6 +- crates/rmw-zenoh-rs/src/service.rs | 14 +- .../ros-z/examples/protobuf_demo/src/lib.rs | 10 +- crates/ros-z/src/service.rs | 131 +++++++++--------- 4 files changed, 86 insertions(+), 75 deletions(-) diff --git a/crates/rmw-zenoh-rs/src/rmw.rs b/crates/rmw-zenoh-rs/src/rmw.rs index 46065355..5fe64151 100644 --- a/crates/rmw-zenoh-rs/src/rmw.rs +++ b/crates/rmw-zenoh-rs/src/rmw.rs @@ -890,7 +890,8 @@ pub extern "C" fn rmw_create_client( let mut zclient_builder = node_impl .inner .create_client::(&qualified_service) - .with_type_info(service_type_support.get_type_info()); + .with_type_info(service_type_support.get_type_info()) + .with_serdes::(); zclient_builder.entity.qos = qos.to_protocol_qos(); let entity = zclient_builder.entity.clone(); @@ -1052,7 +1053,8 @@ pub extern "C" fn rmw_create_service( let mut zserver_builder = node_impl .inner .create_service::(&qualified_service) - .with_type_info(service_type_support.get_type_info()); + .with_type_info(service_type_support.get_type_info()) + .with_serdes::(); zserver_builder.entity.qos = qos.to_protocol_qos(); let entity = zserver_builder.entity.clone(); diff --git a/crates/rmw-zenoh-rs/src/service.rs b/crates/rmw-zenoh-rs/src/service.rs index 3d136db7..fac3419f 100644 --- a/crates/rmw-zenoh-rs/src/service.rs +++ b/crates/rmw-zenoh-rs/src/service.rs @@ -10,7 +10,7 @@ use zenoh::Result; /// Client implementation for RMW pub struct ClientImpl { - pub inner: ros_z::service::ZClient, + pub inner: ros_z::service::ZClient, pub service_name: CString, pub options: rmw_client_options_t, pub request_ts: crate::type_support::ServiceTypeSupport, @@ -63,9 +63,7 @@ impl ClientImpl { }; // Send the request with notification callback - let _ = self - .inner - .rmw_send_request::(&req, notify_callback)?; + let _ = self.inner.rmw_send_request(&req, notify_callback)?; // Return the sequence number we tracked unsafe { @@ -173,7 +171,8 @@ impl ClientImpl { /// Service implementation for RMW pub struct ServiceImpl { - pub inner: ros_z::service::ZServer, + pub inner: + ros_z::service::ZServer, pub service_name: CString, pub request_ts: crate::type_support::ServiceTypeSupport, pub response_ts: crate::type_support::ServiceTypeSupport, @@ -319,10 +318,7 @@ impl ServiceImpl { let resp = crate::msg::RosMessage::new(response, self.response_ts.response); // Send response - match self - .inner - .rmw_send_response::(&resp, &key) - { + match self.inner.rmw_send_response(&resp, &key) { Ok(_) => { tracing::debug!("[ServiceImpl::send_response] Response sent successfully"); Ok(()) diff --git a/crates/ros-z/examples/protobuf_demo/src/lib.rs b/crates/ros-z/examples/protobuf_demo/src/lib.rs index e9b20fc6..dea3b127 100644 --- a/crates/ros-z/examples/protobuf_demo/src/lib.rs +++ b/crates/ros-z/examples/protobuf_demo/src/lib.rs @@ -101,7 +101,10 @@ pub fn run_service_client( ) -> Result<()> { let node = ctx.create_node("calculator_client").build()?; - let client = node.create_client::(service_name).build()?; + let client = node + .create_client::(service_name) + .with_serdes::() + .build()?; println!("Calculator service client started"); println!("Calling service '{}'...\n", service_name); @@ -163,7 +166,10 @@ pub fn run_service_server( ) -> Result<()> { let node = ctx.create_node("calculator_server").build()?; - let mut server = node.create_service::(service_name).build()?; + let mut server = node + .create_service::(service_name) + .with_serdes::() + .build()?; println!("Calculator service server started on '{}'", service_name); println!("Waiting for requests...\n"); diff --git a/crates/ros-z/src/service.rs b/crates/ros-z/src/service.rs index 11bbfeed..a983d957 100644 --- a/crates/ros-z/src/service.rs +++ b/crates/ros-z/src/service.rs @@ -35,15 +35,15 @@ use crate::{ }; #[derive(Debug)] -pub struct ZClientBuilder { +pub struct ZClientBuilder { pub entity: EndpointEntity, pub session: Arc, pub(crate) keyexpr_format: ros_z_protocol::KeyExprFormat, - pub _phantom_data: PhantomData, + pub _phantom_data: PhantomData<(T, S)>, } -impl_with_type_info!(ZClientBuilder); -impl_with_type_info!(ZServerBuilder); +impl_with_type_info!(ZClientBuilder); +impl_with_type_info!(ZServerBuilder); /// A ROS 2-style service client that sends typed requests and receives typed responses. /// @@ -63,7 +63,7 @@ impl_with_type_info!(ZServerBuilder); /// client.send_request(&request).await?; /// let response = client.take_response_timeout(Duration::from_secs(5))?; /// ``` -pub struct ZClient { +pub struct ZClient { // TODO: replace this with the sample sn sn: AtomicUsize, // TODO: replace this with zenoh's global entity id @@ -73,10 +73,10 @@ pub struct ZClient { tx: flume::Sender, pub rx: flume::Receiver, topic: String, - _phantom_data: PhantomData, + _phantom_data: PhantomData<(T, S)>, } -impl std::fmt::Debug for ZClient { +impl std::fmt::Debug for ZClient { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("ZClient") .field("topic", &self.topic) @@ -84,11 +84,26 @@ impl std::fmt::Debug for ZClient { } } -impl Builder for ZClientBuilder +impl ZClientBuilder where T: ZService, { - type Output = ZClient; + /// Override the serialization format used by this client. + pub fn with_serdes(self) -> ZClientBuilder { + ZClientBuilder { + entity: self.entity, + session: self.session, + keyexpr_format: self.keyexpr_format, + _phantom_data: PhantomData, + } + } +} + +impl Builder for ZClientBuilder +where + T: ZService, +{ + type Output = ZClient; #[tracing::instrument(name = "client_build", skip(self), fields( service = %self.entity.topic @@ -145,7 +160,7 @@ where } } -impl ZClient +impl ZClient where T: ZService, { @@ -171,16 +186,13 @@ where /// [`take_response_timeout`](ZClient::take_response_timeout) to wait up to a /// deadline, or [`take_response_async`](ZClient::take_response_async) to await /// indefinitely in an async context. - // For ROS-Z pub fn take_response(&self) -> Result where - T::Response: - ZMessage + serde::Serialize + serde::de::DeserializeOwned + Send + Sync + 'static, + S: ZSerdes, { let sample = self.take_sample()?; - let msg = - >::deserialize(&sample.payload().to_bytes()) - .map_err(|e| zenoh::Error::from(e.to_string()))?; + let msg = >::deserialize(&sample.payload().to_bytes()) + .map_err(|e| zenoh::Error::from(e.to_string()))?; Ok(msg) } @@ -188,34 +200,28 @@ where /// arrives within the deadline. pub fn take_response_timeout(&self, timeout: Duration) -> Result where - T::Response: - ZMessage + serde::Serialize + serde::de::DeserializeOwned + Send + Sync + 'static, + S: ZSerdes, { let sample = self.take_sample_timeout(timeout)?; let payload_bytes = sample.payload().to_bytes(); - let msg = >::deserialize(&payload_bytes[..]) + let msg = >::deserialize(&payload_bytes[..]) .map_err(|e| zenoh::Error::from(e.to_string()))?; Ok(msg) } + /// Asynchronously wait for the next response. Awaits indefinitely until a /// response arrives or the channel is disconnected. pub async fn take_response_async(&self) -> Result where - T::Response: - ZMessage + serde::Serialize + serde::de::DeserializeOwned + Send + Sync + 'static, + S: ZSerdes, { let sample = self.rx.recv_async().await?; let payload_bytes = sample.payload().to_bytes(); - let msg = >::deserialize(&payload_bytes[..]) + let msg = >::deserialize(&payload_bytes[..]) .map_err(|e| zenoh::Error::from(e.to_string()))?; Ok(msg) } -} -impl ZClient -where - T: ZService, -{ /// Send a typed request to the service server. /// /// This is an `async fn` — it must be `.await`ed. The call resolves once the @@ -232,9 +238,9 @@ where ))] pub async fn send_request(&self, msg: &T::Request) -> Result<()> where - T::Request: serde::Serialize + serde::de::DeserializeOwned + Send + Sync + 'static, + S: ZSerdes, { - let payload = >::serialize(msg); + let payload = >::serialize(msg); tracing::Span::current().record("payload_len", payload.len()); // Log the key expression being queried @@ -274,7 +280,7 @@ where } #[cfg(feature = "rmw")] - pub fn rmw_send_request(&self, msg: &T::Request, notify: F) -> Result + pub fn rmw_send_request(&self, msg: &T::Request, notify: F) -> Result where S: ZSerdes, F: Fn() + Send + Sync + 'static, @@ -310,14 +316,14 @@ where } #[derive(Debug)] -pub struct ZServerBuilder { +pub struct ZServerBuilder { pub entity: EndpointEntity, pub session: Arc, pub(crate) keyexpr_format: ros_z_protocol::KeyExprFormat, - pub _phantom_data: PhantomData, + pub _phantom_data: PhantomData<(T, S)>, } -pub struct ZServer { +pub struct ZServer { // NOTE: This is biased toward RMW key_expr: KeyExpr<'static>, // TODO: replace this with the sample sn @@ -328,10 +334,10 @@ pub struct ZServer { lv_token: LivelinessToken, pub queue: Option>>, pub map: HashMap, - _phantom_data: PhantomData, + _phantom_data: PhantomData<(T, S)>, } -impl std::fmt::Debug for ZServer { +impl std::fmt::Debug for ZServer { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("ZServer") .field("key_expr", &self.key_expr.as_str()) @@ -339,7 +345,7 @@ impl std::fmt::Debug for ZServer { } } -impl ZServer +impl ZServer where T: ZService, { @@ -356,16 +362,26 @@ where } } -impl ZServerBuilder +impl ZServerBuilder where T: ZService, { + /// Override the serialization format used by this server. + pub fn with_serdes(self) -> ZServerBuilder { + ZServerBuilder { + entity: self.entity, + session: self.session, + keyexpr_format: self.keyexpr_format, + _phantom_data: PhantomData, + } + } + /// Internal method that all build variants use. fn build_internal( mut self, handler: DataHandler, queue: Option>>, - ) -> Result> { + ) -> Result> { let qualified_service = topic_name::qualify_service_name( &self.entity.topic, &self.entity.node.namespace, @@ -430,7 +446,7 @@ where }) } - pub fn build_with_callback(self, callback: F) -> Result> + pub fn build_with_callback(self, callback: F) -> Result> where F: Fn(Query) + Send + Sync + 'static, { @@ -438,7 +454,7 @@ where } #[cfg(feature = "rmw")] - pub fn build_with_notifier(self, notify: F) -> Result> + pub fn build_with_notifier(self, notify: F) -> Result> where F: Fn() + Send + Sync + 'static, { @@ -457,11 +473,11 @@ where } } -impl Builder for ZServerBuilder +impl Builder for ZServerBuilder where T: ZService, { - type Output = ZServer; + type Output = ZServer; fn build(self) -> Result { let queue_size = match self.entity.qos.history { @@ -488,7 +504,7 @@ impl From for QueryKey { } } -impl ZServer +impl ZServer where T: ZService, { @@ -518,8 +534,7 @@ where ))] pub fn take_request(&mut self) -> Result<(QueryKey, T::Request)> where - T::Request: - ZMessage + serde::Serialize + serde::de::DeserializeOwned + Send + Sync + 'static, + S: ZSerdes, { trace!("[SRV] Waiting for request"); @@ -542,7 +557,7 @@ where debug!("[SRV] Processing request"); - let msg = >::deserialize(&payload_bytes[..]) + let msg = >::deserialize(&payload_bytes[..]) .map_err(|e| zenoh::Error::from(e.to_string()))?; self.map.insert(key.clone(), query); @@ -554,8 +569,7 @@ where /// This method may fail if the message does not deserialize as the requested type. pub async fn take_request_async(&mut self) -> Result<(QueryKey, T::Request)> where - T::Request: - ZMessage + serde::Serialize + serde::de::DeserializeOwned + Send + Sync + 'static, + S: ZSerdes, { let queue = self.queue.as_ref().ok_or_else(|| { zenoh::Error::from("Server was built with callback, no queue available") @@ -567,7 +581,7 @@ where return Err("Existing query detected".into()); } let payload_bytes = query.payload().unwrap().to_bytes(); - let msg = >::deserialize(&payload_bytes[..]) + let msg = >::deserialize(&payload_bytes[..]) .map_err(|e| zenoh::Error::from(e.to_string()))?; self.map.insert(key.clone(), query); @@ -585,7 +599,7 @@ where ))] pub fn send_response(&mut self, msg: &T::Response, key: &QueryKey) -> Result<()> where - T::Response: serde::Serialize + serde::de::DeserializeOwned + Send + Sync + 'static, + S: ZSerdes, { debug!( "[SRV] Looking for query with key sn:{}, gid:{:?}", @@ -593,7 +607,7 @@ where ); match self.map.remove(key) { Some(query) => { - let payload = >::serialize(msg); + let payload = >::serialize(msg); tracing::Span::current().record("payload_len", payload.len()); debug!("[SRV] Sending response"); @@ -612,13 +626,9 @@ where } } - /// RMW-specific: send response using an explicit serializer type parameter. - /// - /// Use this instead of [`send_response`](Self::send_response) when the response type does not - /// implement `serde::Serialize` (e.g., `RosMessage` in the RMW crate, which is serialized via - /// a C FFI path through `RosSerdes`). + /// RMW-specific: send response using the server's serdes type parameter. #[cfg(feature = "rmw")] - pub fn rmw_send_response(&mut self, msg: &T::Response, key: &QueryKey) -> Result<()> + pub fn rmw_send_response(&mut self, msg: &T::Response, key: &QueryKey) -> Result<()> where S: ZSerdes, { @@ -644,17 +654,14 @@ where /// - `key` is the query key of the request to reply to and is obtained from [take_request](Self::take_request) or [take_request_async](Self::take_request_async) pub async fn send_response_async(&mut self, msg: &T::Response, key: &QueryKey) -> Result<()> where - T::Response: serde::Serialize + serde::de::DeserializeOwned + Send + Sync + 'static, + S: ZSerdes, { match self.map.remove(key) { Some(query) => { // Use the sequence number and GID from the request let attachment = Attachment::new(key.sn, key.gid); query - .reply( - &self.key_expr, - >::serialize(msg), - ) + .reply(&self.key_expr, >::serialize(msg)) .attachment(attachment) .await } From f4839ad0ea63ffd60432e695c4dc660a0a288467 Mon Sep 17 00:00:00 2001 From: yuanyuyuan Date: Tue, 10 Mar 2026 15:46:06 +0800 Subject: [PATCH 07/13] fix(tests): update ZSerdes bounds and add ProtobufSerdes to service readiness check --- crates/ros-z-tests/tests/common/mod.rs | 3 ++- crates/ros-z-tests/tests/pubsub_interop.rs | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/ros-z-tests/tests/common/mod.rs b/crates/ros-z-tests/tests/common/mod.rs index edb9a63c..dc07696d 100644 --- a/crates/ros-z-tests/tests/common/mod.rs +++ b/crates/ros-z-tests/tests/common/mod.rs @@ -5,7 +5,7 @@ use std::time::Duration; use nix::sys::signal::{self, Signal}; use nix::unistd::Pid; -use ros_z::Builder; +use ros_z::{Builder, msg::ProtobufSerdes}; use zenoh::Wait; use zenoh::config::WhatAmI; @@ -192,6 +192,7 @@ pub fn wait_for_service_ready( if let Ok(node) = ctx.create_node("service_readiness_checker").build() && let Ok(client) = node .create_client::(service_name) + .with_serdes::() .build() { // Try a simple test request (add 1 + 1 = 2) diff --git a/crates/ros-z-tests/tests/pubsub_interop.rs b/crates/ros-z-tests/tests/pubsub_interop.rs index 2528a89f..a1477c57 100644 --- a/crates/ros-z-tests/tests/pubsub_interop.rs +++ b/crates/ros-z-tests/tests/pubsub_interop.rs @@ -37,7 +37,7 @@ use std::{ use common::*; use ros_z::{ Builder, WithTypeInfo, - msg::{ZDeserializer, ZMessage, ZSerializer}, + msg::{CdrSerdes, ZMessage, ZSerdes}, }; use ros_z_msgs::{ geometry_msgs::{PoseStamped, Twist, TwistStamped}, @@ -110,7 +110,7 @@ const CASES: &[InteropCase] = &[ fn ros_z_pub_to_ros2_sub(case: &InteropCase) where T: ZMessage + WithTypeInfo + Default + 'static, - T::Serdes: for<'a> ZSerializer = &'a T>, + CdrSerdes: ZSerdes, { if !check_ros2_available() { eprintln!("Skipping {}: ros2 CLI not available", case.type_name); @@ -192,7 +192,7 @@ where fn ros2_pub_to_ros_z_sub(case: &InteropCase) where T: ZMessage + WithTypeInfo + 'static, - T::Serdes: for<'a> ZDeserializer = &'a [u8]>, + CdrSerdes: ZSerdes, { if !check_ros2_available() { eprintln!("Skipping {}: ros2 CLI not available", case.type_name); From 2ef2c0590ba3b99f16f9680d5bd2dde7aad950fb Mon Sep 17 00:00:00 2001 From: yuanyuyuan Date: Tue, 10 Mar 2026 18:54:43 +0800 Subject: [PATCH 08/13] fix(dynamic): restore recv/publish on ZSub/ZPub by removing ZSerdes bound overlap --- crates/ros-z/src/dynamic/serdes.rs | 53 +------------- .../ros-z/src/dynamic/tests/pubsub_tests.rs | 6 +- crates/ros-z/src/pubsub.rs | 69 ++++++++++++++----- 3 files changed, 55 insertions(+), 73 deletions(-) diff --git a/crates/ros-z/src/dynamic/serdes.rs b/crates/ros-z/src/dynamic/serdes.rs index 6d82fc53..972e5e35 100644 --- a/crates/ros-z/src/dynamic/serdes.rs +++ b/crates/ros-z/src/dynamic/serdes.rs @@ -8,7 +8,7 @@ use std::sync::Arc; use zenoh_buffers::ZBuf; -use crate::msg::{ZDeserializer, ZSerdes, ZSerializer}; +use crate::msg::{ZDeserializer, ZSerializer}; use super::error::DynamicError; use super::message::DynamicMessage; @@ -108,57 +108,6 @@ impl ZDeserializer for DynamicCdrCompatSerdes { } } -/// `ZSerdes` implementation for `DynamicCdrCompatSerdes`. -/// -/// The `deserialize` method on this impl requires a schema at runtime; when called -/// without one (plain byte-slice path) it returns an error. The real deserialization -/// path is the specialized `ZSub` impl which calls -/// `DynamicCdrCompatSerdes::deserialize((&bytes, schema))` directly. -impl ZSerdes for DynamicCdrCompatSerdes { - type Error = DynamicError; - - fn serialize(msg: &DynamicMessage) -> ZBuf { - msg.to_cdr_zbuf() - .expect("DynamicMessage CDR serialization failed") - } - - fn serialize_with_hint(msg: &DynamicMessage, _capacity_hint: usize) -> ZBuf { - >::serialize(msg) - } - - fn serialize_to_shm( - msg: &DynamicMessage, - _estimated_size: usize, - provider: &zenoh::shm::ShmProvider, - ) -> zenoh::Result<(ZBuf, usize)> { - let data = msg.to_cdr().map_err(|e| { - zenoh::Error::from(format!("DynamicMessage serialization failed: {}", e)) - })?; - let actual_size = data.len(); - - use zenoh::Wait; - use zenoh::shm::{BlockOn, GarbageCollect}; - - let mut shm_buf = provider - .alloc(actual_size) - .with_policy::>() - .wait() - .map_err(|e| zenoh::Error::from(format!("SHM allocation failed: {}", e)))?; - - shm_buf[0..actual_size].copy_from_slice(&data); - Ok((ZBuf::from(shm_buf), actual_size)) - } - - fn serialize_to_vec(msg: &DynamicMessage) -> Vec { - msg.to_cdr() - .expect("DynamicMessage CDR serialization failed") - } - - fn deserialize(_buf: &[u8]) -> Result { - Err(DynamicError::SchemaMissing) - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/ros-z/src/dynamic/tests/pubsub_tests.rs b/crates/ros-z/src/dynamic/tests/pubsub_tests.rs index aaf07e36..abaccb4a 100644 --- a/crates/ros-z/src/dynamic/tests/pubsub_tests.rs +++ b/crates/ros-z/src/dynamic/tests/pubsub_tests.rs @@ -127,10 +127,10 @@ fn test_zmessage_impl_for_dynamic_message() { msg.set("y", 6.0f64).unwrap(); msg.set("z", 7.0f64).unwrap(); - // Use DynamicCdrCompatSerdes for serialization (ZMessage is now a marker trait) + // Use DynamicCdrCompatSerdes for serialization via ZSerializer use crate::dynamic::DynamicCdrCompatSerdes; - use crate::msg::ZSerdes; - let zbuf = >::serialize(&msg); + use crate::msg::ZSerializer; + let zbuf = ::serialize(&msg); use zenoh_buffers::buffer::SplitBuffer; let bytes = zbuf.contiguous(); assert!(!bytes.is_empty()); diff --git a/crates/ros-z/src/pubsub.rs b/crates/ros-z/src/pubsub.rs index 4675178c..492c6eae 100644 --- a/crates/ros-z/src/pubsub.rs +++ b/crates/ros-z/src/pubsub.rs @@ -16,7 +16,7 @@ use crate::impl_with_type_info; use crate::queue::BoundedQueue; use crate::topic_name; -use crate::msg::{CdrSerdes, ZMessage, ZSerdes}; +use crate::msg::{CdrSerdes, ZMessage, ZSerdes, ZSerializer}; use crate::qos::QosProfile; use ros_z_protocol::qos::{QosDurability, QosHistory, QosReliability}; use std::sync::Mutex; @@ -25,7 +25,7 @@ use std::sync::Mutex; /// (synchronous) or [`async_publish`](ZPub::async_publish) (async). /// /// Create a publisher via [`ZNode::create_pub`](crate::node::ZNode::create_pub). -pub struct ZPub = CdrSerdes> { +pub struct ZPub { pub entity: EndpointEntity, // TODO: replace this with the sample sn sn: AtomicUsize, @@ -45,7 +45,7 @@ pub struct ZPub = CdrSerdes> { _phantom_data: PhantomData<(T, S)>, } -impl> std::fmt::Debug for ZPub { +impl std::fmt::Debug for ZPub { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("ZPub") .field("entity", &self.entity) @@ -235,7 +235,6 @@ impl ZPubBuilder { impl Builder for ZPubBuilder where T: ZMessage + 'static, - S: ZSerdes + 'static, { type Output = ZPub; @@ -327,7 +326,6 @@ where impl ZPub where T: ZMessage + 'static, - S: ZSerdes + 'static, { /// Wait until at least `count` subscribers are matched on this publisher's topic, /// or until `timeout` elapses. @@ -390,7 +388,13 @@ where ); Attachment::new(sn as _, self.gid) } +} +impl ZPub +where + T: ZMessage + 'static, + S: ZSerdes + 'static, +{ /// Serialize and publish `msg` on the topic. Blocks until the put completes. /// /// Use [`async_publish`](ZPub::async_publish) when calling from async code to @@ -502,7 +506,12 @@ where } put_builder.await } +} +impl ZPub +where + T: ZMessage + 'static, +{ /// Publish pre-serialized data directly /// /// Accepts any type that implements `Into`: @@ -553,6 +562,37 @@ impl ZPub Option<&crate::dynamic::schema::MessageSchema> { self.dyn_schema.as_ref().map(|s| s.as_ref()) } + + /// Serialize and publish a dynamic message. Blocks until the put completes. + pub fn publish(&self, msg: &crate::dynamic::DynamicMessage) -> Result<()> { + use zenoh::Wait as _; + let zbuf = ::serialize(msg); + let actual_size = zbuf.len(); + let zbytes = zenoh::bytes::ZBytes::from(zbuf); + let mut put_builder = self.inner.put(zbytes); + if let Some(ref enc) = self.encoding { + put_builder = put_builder.encoding((**enc).clone()); + } + if self.with_attachment { + put_builder = put_builder.attachment(self.new_attachment()); + } + tracing::debug!("[PUB] Publishing dynamic message: {}B", actual_size); + put_builder.wait() + } + + /// Serialize and publish a dynamic message asynchronously. + pub async fn async_publish(&self, msg: &crate::dynamic::DynamicMessage) -> Result<()> { + let zbuf = ::serialize(msg); + let zbytes = zenoh::bytes::ZBytes::from(zbuf); + let mut put_builder = self.inner.put(zbytes); + if let Some(ref enc) = self.encoding { + put_builder = put_builder.encoding((**enc).clone()); + } + if self.with_attachment { + put_builder = put_builder.attachment(self.new_attachment()); + } + put_builder.await + } } pub struct ZSubBuilder { @@ -689,10 +729,7 @@ where mut self, handler: DataHandler, queue: Option>>, - ) -> Result> - where - S: ZSerdes, - { + ) -> Result> { let qualified_topic = topic_name::qualify_topic_name( &self.entity.topic, &self.entity.node.namespace, @@ -844,7 +881,6 @@ where impl Builder for ZSubBuilder where T: ZMessage + 'static + Sync + Send, - S: ZSerdes, { type Output = ZSub; @@ -859,7 +895,7 @@ where } } -pub struct ZSub = CdrSerdes> { +pub struct ZSub { pub entity: EndpointEntity, pub queue: Option>>, _inner: zenoh::pubsub::Subscriber<()>, @@ -873,7 +909,7 @@ pub struct ZSub = CdrSerdes> { _phantom_data: PhantomData<(T, Q, S)>, } -impl> std::fmt::Debug for ZSub { +impl std::fmt::Debug for ZSub { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("ZSub") .field("entity", &self.entity) @@ -985,7 +1021,7 @@ impl ZSub Result { + pub fn recv(&self) -> Result { let schema = self.dyn_schema.as_ref().ok_or_else(|| { zenoh::Error::from( "dyn_schema required for DynamicMessage (use .with_dyn_schema() when building)", @@ -1010,10 +1046,7 @@ impl ZSub Result { + pub fn recv_timeout(&self, timeout: Duration) -> Result { let schema = self .dyn_schema .as_ref() @@ -1035,7 +1068,7 @@ impl ZSub Result { + pub async fn async_recv(&self) -> Result { let schema = self .dyn_schema .as_ref() From 479b5ca7d0a96ad2d812e4a557add235e4096b63 Mon Sep 17 00:00:00 2001 From: yuanyuyuan Date: Tue, 10 Mar 2026 19:35:00 +0800 Subject: [PATCH 09/13] fix(scripts): use --rerun-failed instead of --failed for nextest retry --- scripts/test-pure-rust.nu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/test-pure-rust.nu b/scripts/test-pure-rust.nu index 19b16936..acd2e9ed 100755 --- a/scripts/test-pure-rust.nu +++ b/scripts/test-pure-rust.nu @@ -25,7 +25,7 @@ def run-tests [] { if $result.exit_code != 0 { print "\n⚠️ Some tests failed. Retrying failed tests with debug logging..." $env.RUST_LOG = "ros_z=debug,warn" - run-cmd "cargo nextest run --failed" + run-cmd "cargo nextest run --rerun-failed" } } From da64500d43ed5caf868cbe81fa5ba1a97d766131 Mon Sep 17 00:00:00 2001 From: yuanyuyuan Date: Wed, 11 Mar 2026 00:04:07 +0800 Subject: [PATCH 10/13] fix(py): replace serde bounds with CdrCompatSerdes: ZSerdes in service wrappers --- crates/ros-z-py/src/service.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/ros-z-py/src/service.rs b/crates/ros-z-py/src/service.rs index 21660c4c..bf08d020 100644 --- a/crates/ros-z-py/src/service.rs +++ b/crates/ros-z-py/src/service.rs @@ -26,8 +26,9 @@ impl ZClientWrapper { impl RawClient for ZClientWrapper where T: ZService + Send + Sync + 'static, - T::Request: ZMessage + serde::Serialize + serde::de::DeserializeOwned + Send + Sync + 'static, - T::Response: ZMessage + serde::Serialize + serde::de::DeserializeOwned + Send + Sync + 'static, + T::Request: ZMessage + Send + Sync + 'static, + T::Response: ZMessage + Send + Sync + 'static, + CdrCompatSerdes: ZSerdes + ZSerdes, { fn send_request_serialized(&self, data: &[u8]) -> Result<()> { // Deserialize the request from CDR bytes @@ -106,8 +107,9 @@ impl ZServerWrapper { impl RawServer for ZServerWrapper where T: ZService + Send + Sync + 'static, - T::Request: ZMessage + serde::Serialize + serde::de::DeserializeOwned + Send + Sync + 'static, - T::Response: ZMessage + serde::Serialize + serde::de::DeserializeOwned + Send + Sync + 'static, + T::Request: ZMessage + Send + Sync + 'static, + T::Response: ZMessage + Send + Sync + 'static, + CdrCompatSerdes: ZSerdes + ZSerdes, { fn take_request_serialized(&self) -> Result<(QueryKey, Vec)> { let mut server = self From 54d753332d2b1577c21b72829c43ecd5e426c925 Mon Sep 17 00:00:00 2001 From: yuanyuyuan Date: Wed, 11 Mar 2026 00:08:14 +0800 Subject: [PATCH 11/13] ci: add explicit ros-z-py check to local and pure-rust test suites ros-z-py is not in default-members so cargo clippy/build silently skip it. Adding cargo check/clippy -p ros-z-py catches missing deps and type errors locally before they reach CI. --- scripts/check-local.nu | 3 +++ scripts/test-pure-rust.nu | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/scripts/check-local.nu b/scripts/check-local.nu index d01a429f..70dbb2f1 100755 --- a/scripts/check-local.nu +++ b/scripts/check-local.nu @@ -34,6 +34,9 @@ log-header "Running ros-z Pre-Submission Checks" let checks = [ {name: "Formatting (cargo fmt)", cmd: "cargo fmt --check"}, {name: "Clippy (all targets)", cmd: "cargo clippy --all-targets -- -D warnings"}, + # ros-z-py is not in default-members, so clippy above skips it. + # Explicit check here catches missing deps (e.g. serde) before CI does. + {name: "Check ros-z-py", cmd: "cargo check -p ros-z-py"}, {name: "Build (cargo build)", cmd: "cargo build"}, {name: "Tests", cmd: (if (which cargo-nextest | is-not-empty) { "cargo nextest run" diff --git a/scripts/test-pure-rust.nu b/scripts/test-pure-rust.nu index acd2e9ed..4922e19b 100755 --- a/scripts/test-pure-rust.nu +++ b/scripts/test-pure-rust.nu @@ -59,6 +59,14 @@ def check-distro-features [] { run-cmd "cargo check -p ros-z --no-default-features --features kilted" } +def check-py-crate [] { + # ros-z-py is not in default-members so cargo clippy/build skip it by default. + # Explicit check here catches missing deps and type errors before CI does. + log-step "Check ros-z-py (Python bindings)" + run-cmd "cargo check -p ros-z-py" + run-cmd "cargo clippy -p ros-z-py -- -D warnings" +} + def test-shm [] { log-step "Test SHM functionality" @@ -82,6 +90,7 @@ def get-test-map [] { check-console: { check-console } check-examples: { check-examples } check-distro-features: { check-distro-features } + check-py-crate: { check-py-crate } test-shm: { test-shm } } } @@ -94,6 +103,7 @@ def get-test-pipeline [] { "check-console" "check-examples" "check-distro-features" + "check-py-crate" "test-shm" ] } From 90d3814fa524cdc79c2200e4c5a1870b731b6e16 Mon Sep 17 00:00:00 2001 From: yuanyuyuan Date: Wed, 11 Mar 2026 13:19:28 +0800 Subject: [PATCH 12/13] fix(py): remove ZSerializer/ZDeserializer bounds from pub/sub wrappers CdrSerdes implements ZSerdes but not ZSerializer/ZDeserializer directly. The publish_serialized and recv_serialized paths don't need those bounds, so drop them from ZPubWrapper and ZSubWrapper struct/impl definitions. Also add explicit type annotation on zenoh::Error to fix E0282. --- crates/ros-z-py/src/traits.rs | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/crates/ros-z-py/src/traits.rs b/crates/ros-z-py/src/traits.rs index d9aedc93..69a4c6b8 100644 --- a/crates/ros-z-py/src/traits.rs +++ b/crates/ros-z-py/src/traits.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use ros_z::msg::{ZDeserializer, ZMessage, ZSerializer}; +use ros_z::msg::{ZMessage, ZSerdes}; use ros_z::pubsub::{ZPub, ZSub}; use std::time::Duration; use zenoh::bytes::ZBytes; @@ -27,12 +27,15 @@ pub(crate) trait RawSubscriber: Send + Sync { fn try_recv_serialized(&self) -> Result>>; } -/// Wrapper for ZPub that implements RawPublisher -pub struct ZPubWrapper { +/// Wrapper for ZPub that implements RawPublisher. +/// +/// No `S: ZSerializer` bound on the struct — `publish_serialized` only needs +/// `T: ZMessage`, so any serdes type works here including `CdrSerdes`. +pub struct ZPubWrapper { inner: ZPub, } -impl ZPubWrapper { +impl ZPubWrapper { pub fn new(inner: ZPub) -> Self { Self { inner } } @@ -41,7 +44,7 @@ impl ZPubWrapper { impl RawPublisher for ZPubWrapper where T: ZMessage + 'static, - S: for<'a> ZSerializer = &'a T> + Send + Sync + 'static, + S: Send + Sync + 'static, { fn publish(&self, data: ZBytes) -> Result<()> { self.inner @@ -50,12 +53,16 @@ where } } -/// Wrapper for ZSub that implements RawSubscriber -pub struct ZSubWrapper { +/// Wrapper for ZSub that implements RawSubscriber. +/// +/// No `S: ZDeserializer` bound on the struct — the raw-sample path used here +/// only needs `S: ZSerdes` (to access `recv_serialized`), so `CdrSerdes` +/// and other `ZSerdes` implementors work without implementing `ZDeserializer`. +pub struct ZSubWrapper { inner: ZSub, } -impl ZSubWrapper { +impl ZSubWrapper { pub fn new(inner: ZSub) -> Self { Self { inner } } @@ -64,7 +71,7 @@ impl ZSubWrapper { impl RawSubscriber for ZSubWrapper where T: ZMessage + 'static, - S: for<'a> ZDeserializer = &'a [u8]> + Send + Sync + 'static, + S: ZSerdes + Send + Sync + 'static, { fn recv_sample(&self, timeout: Option) -> Result { if let Some(t) = timeout { @@ -75,7 +82,9 @@ where .recv_timeout(t) .ok_or_else(|| anyhow::anyhow!("Receive timeout")) } else { - self.inner.recv_serialized().map_err(|e| anyhow::anyhow!(e)) + self.inner + .recv_serialized() + .map_err(|e: zenoh::Error| anyhow::anyhow!(e)) } } From 67fd9be5410b64c5dd5f72d170b13fa797b25e73 Mon Sep 17 00:00:00 2001 From: yuanyuyuan Date: Wed, 11 Mar 2026 13:52:33 +0800 Subject: [PATCH 13/13] fix(msgs): remove unused ZMessage import from test files --- crates/ros-z-msgs/tests/shm_size_estimation.rs | 2 +- crates/ros-z-msgs/tests/size_estimation_performance.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/ros-z-msgs/tests/shm_size_estimation.rs b/crates/ros-z-msgs/tests/shm_size_estimation.rs index e5df9370..76df6070 100644 --- a/crates/ros-z-msgs/tests/shm_size_estimation.rs +++ b/crates/ros-z-msgs/tests/shm_size_estimation.rs @@ -9,7 +9,7 @@ use std::sync::Arc; use ros_z::{ ZBuf, - msg::{CdrSerdes, ZMessage, ZSerdes}, + msg::{CdrSerdes, ZSerdes}, shm::ShmProviderBuilder, }; use zenoh_buffers::buffer::Buffer; diff --git a/crates/ros-z-msgs/tests/size_estimation_performance.rs b/crates/ros-z-msgs/tests/size_estimation_performance.rs index f8131f95..e537acd4 100644 --- a/crates/ros-z-msgs/tests/size_estimation_performance.rs +++ b/crates/ros-z-msgs/tests/size_estimation_performance.rs @@ -7,7 +7,7 @@ use std::time::Instant; use ros_z::{ ZBuf, - msg::{CdrSerdes, ZMessage, ZSerdes}, + msg::{CdrSerdes, ZSerdes}, }; use ros_z_msgs::{builtin_interfaces::Time, sensor_msgs::*, std_msgs::Header}; use zenoh_buffers::buffer::Buffer;