Skip to content

Commit 6d11125

Browse files
authored
Add serde support to messages (#131)
1 parent d191419 commit 6d11125

File tree

8 files changed

+265
-3
lines changed

8 files changed

+265
-3
lines changed

rosidl_generator_rs/cmake/rosidl_generator_rs_generate_interfaces.cmake

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,10 +133,13 @@ file(MAKE_DIRECTORY "${_output_path}")
133133
set(_target_suffix "__rs")
134134

135135
set(CRATES_DEPENDENCIES "rosidl_runtime_rs = \"*\"")
136+
set(SERDE_FEATURES "[\"dep:serde\", \"rosidl_runtime_rs/serde\"")
136137
foreach(_pkg_name ${rosidl_generate_interfaces_DEPENDENCY_PACKAGE_NAMES})
137138
find_package(${_pkg_name} REQUIRED)
138139
set(CRATES_DEPENDENCIES "${CRATES_DEPENDENCIES}\n${_pkg_name} = \"*\"")
140+
set(SERDE_FEATURES "${SERDE_FEATURES}, \"${_pkg_name}/serde\"")
139141
endforeach()
142+
set(SERDE_FEATURES "${SERDE_FEATURES}]")
140143
ament_index_register_resource("rust_packages")
141144

142145

rosidl_generator_rs/resource/Cargo.toml.in

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,8 @@ edition = "2021"
55

66
[dependencies]
77
libc = "0.2"
8-
@CRATES_DEPENDENCIES@
8+
serde = { version = "1", optional = true, features = ["derive"] }
9+
@CRATES_DEPENDENCIES@
10+
11+
[features]
12+
serde = @SERDE_FEATURES@

rosidl_generator_rs/resource/msg.rs.em

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ from rosidl_parser.definition import UnboundedString
1010
from rosidl_parser.definition import UnboundedWString
1111
}@
1212
pub mod rmw {
13+
#[cfg(feature = "serde")]
14+
use serde::{Deserialize, Serialize};
1315
@[for subfolder, msg_spec in msg_specs]@
1416
@{
1517
type_name = msg_spec.structure.namespaced_type.name
@@ -31,6 +33,7 @@ extern "C" {
3133
@# it just calls the drop/fini functions of all fields
3234
// Corresponds to @(package_name)__@(subfolder)__@(type_name)
3335
#[repr(C)]
36+
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
3437
#[derive(Clone, Debug, PartialEq, PartialOrd)]
3538
pub struct @(type_name) {
3639
@[for member in msg_spec.structure.members]@
@@ -87,11 +90,14 @@ impl rosidl_runtime_rs::RmwMessage for @(type_name) where Self: Sized {
8790
@# ############ Idiomatic message types ############
8891
@# #################################################
8992
@# These types use standard Rust containers where possible.
93+
#[cfg(feature = "serde")]
94+
use serde::{Deserialize, Serialize};
9095
@[for subfolder, msg_spec in msg_specs]@
9196
@{
9297
type_name = msg_spec.structure.namespaced_type.name
9398
}@
9499

100+
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
95101
#[derive(Clone, Debug, PartialEq, PartialOrd)]
96102
pub struct @(type_name) {
97103
@[for member in msg_spec.structure.members]@

rosidl_runtime_rs/Cargo.toml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,13 @@ path = "src/lib.rs"
1212
[dependencies]
1313
# Needed for FFI
1414
libc = "0.2"
15+
# Optional dependency for making it possible to convert messages to and from
16+
# formats such as JSON, YAML, Pickle, etc.
17+
serde = { version = "1", optional = true }
1518

1619
[dev-dependencies]
1720
# Needed for writing property tests
18-
quickcheck = "1"
21+
quickcheck = "1"
22+
# Needed for testing serde support
23+
serde_json = "1"
24+

rosidl_runtime_rs/src/sequence.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ use std::hash::{Hash, Hasher};
44
use std::iter::{Extend, FromIterator, FusedIterator};
55
use std::ops::{Deref, DerefMut};
66

7+
#[cfg(feature = "serde")]
8+
mod serde;
9+
710
use crate::traits::SequenceAlloc;
811

912
/// An unbounded sequence.
@@ -674,7 +677,20 @@ macro_rules! seq {
674677
#[cfg(test)]
675678
mod tests {
676679
use super::*;
677-
use quickcheck::quickcheck;
680+
use quickcheck::{quickcheck, Arbitrary, Gen};
681+
682+
impl<T: Arbitrary + SequenceAlloc> Arbitrary for Sequence<T> {
683+
fn arbitrary(g: &mut Gen) -> Self {
684+
Vec::arbitrary(g).into()
685+
}
686+
}
687+
688+
impl<T: Arbitrary + SequenceAlloc> Arbitrary for BoundedSequence<T, 256> {
689+
fn arbitrary(g: &mut Gen) -> Self {
690+
let len = u8::arbitrary(g);
691+
(0..len).map(|_| T::arbitrary(g)).collect()
692+
}
693+
}
678694

679695
quickcheck! {
680696
fn test_extend(xs: Vec<i32>, ys: Vec<i32>) -> bool {
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
use serde::{de::Error, ser::SerializeSeq, Deserialize, Deserializer, Serialize, Serializer};
2+
3+
use super::{BoundedSequence, Sequence};
4+
use crate::traits::SequenceAlloc;
5+
6+
impl<'de, T: Deserialize<'de> + SequenceAlloc> Deserialize<'de> for Sequence<T> {
7+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
8+
where
9+
D: Deserializer<'de>,
10+
{
11+
let v: Vec<_> = Deserialize::deserialize(deserializer)?;
12+
Ok(Self::from(v))
13+
}
14+
}
15+
16+
impl<T: Serialize + SequenceAlloc> Serialize for Sequence<T> {
17+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
18+
where
19+
S: Serializer,
20+
{
21+
let mut seq = serializer.serialize_seq(Some(self.len()))?;
22+
for element in self.iter() {
23+
seq.serialize_element(element)?;
24+
}
25+
seq.end()
26+
}
27+
}
28+
29+
impl<'de, T: Deserialize<'de> + SequenceAlloc, const N: usize> Deserialize<'de>
30+
for BoundedSequence<T, N>
31+
{
32+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
33+
where
34+
D: Deserializer<'de>,
35+
{
36+
let v: Vec<_> = Deserialize::deserialize(deserializer)?;
37+
Self::try_from(v).map_err(D::Error::custom)
38+
}
39+
}
40+
41+
impl<T: Serialize + SequenceAlloc, const N: usize> Serialize for BoundedSequence<T, N> {
42+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
43+
where
44+
S: Serializer,
45+
{
46+
self.inner.serialize(serializer)
47+
}
48+
}
49+
50+
#[cfg(test)]
51+
mod tests {
52+
use crate::{BoundedSequence, Sequence};
53+
use quickcheck::quickcheck;
54+
55+
quickcheck! {
56+
fn test_json_roundtrip_sequence(xs: Sequence<i32>) -> bool {
57+
let value = serde_json::to_value(xs.clone()).unwrap();
58+
let recovered = serde_json::from_value(value).unwrap();
59+
xs == recovered
60+
}
61+
}
62+
63+
quickcheck! {
64+
fn test_json_roundtrip_bounded_sequence(xs: BoundedSequence<i32, 256>) -> bool {
65+
let value = serde_json::to_value(xs.clone()).unwrap();
66+
let recovered = serde_json::from_value(value).unwrap();
67+
xs == recovered
68+
}
69+
}
70+
}

rosidl_runtime_rs/src/string.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ use std::fmt::{self, Debug, Display};
44
use std::hash::{Hash, Hasher};
55
use std::ops::{Deref, DerefMut};
66

7+
#[cfg(feature = "serde")]
8+
mod serde;
9+
710
use crate::sequence::Sequence;
811
use crate::traits::SequenceAlloc;
912

@@ -449,3 +452,37 @@ impl Display for StringExceedsBoundsError {
449452
}
450453

451454
impl std::error::Error for StringExceedsBoundsError {}
455+
456+
#[cfg(test)]
457+
mod tests {
458+
use super::*;
459+
use quickcheck::{Arbitrary, Gen};
460+
461+
impl Arbitrary for String {
462+
fn arbitrary(g: &mut Gen) -> Self {
463+
std::string::String::arbitrary(g).as_str().into()
464+
}
465+
}
466+
467+
impl Arbitrary for WString {
468+
fn arbitrary(g: &mut Gen) -> Self {
469+
std::string::String::arbitrary(g).as_str().into()
470+
}
471+
}
472+
473+
impl Arbitrary for BoundedString<256> {
474+
fn arbitrary(g: &mut Gen) -> Self {
475+
let len = u8::arbitrary(g);
476+
let s: std::string::String = (0..len).map(|_| char::arbitrary(g)).collect();
477+
s.as_str().try_into().unwrap()
478+
}
479+
}
480+
481+
impl Arbitrary for BoundedWString<256> {
482+
fn arbitrary(g: &mut Gen) -> Self {
483+
let len = u8::arbitrary(g);
484+
let s: std::string::String = (0..len).map(|_| char::arbitrary(g)).collect();
485+
s.as_str().try_into().unwrap()
486+
}
487+
}
488+
}

rosidl_runtime_rs/src/string/serde.rs

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};
2+
use std::ops::Deref;
3+
4+
use super::{BoundedString, BoundedWString, String, WString};
5+
6+
impl<'de> Deserialize<'de> for String {
7+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
8+
where
9+
D: Deserializer<'de>,
10+
{
11+
std::string::String::deserialize(deserializer).map(|s| Self::from(s.as_str()))
12+
}
13+
}
14+
15+
impl Serialize for String {
16+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
17+
where
18+
S: Serializer,
19+
{
20+
// Not particularly efficient
21+
let s = std::string::String::from_utf8_lossy(self.deref());
22+
serializer.serialize_str(&s)
23+
}
24+
}
25+
26+
impl<'de> Deserialize<'de> for WString {
27+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
28+
where
29+
D: Deserializer<'de>,
30+
{
31+
std::string::String::deserialize(deserializer).map(|s| Self::from(s.as_str()))
32+
}
33+
}
34+
35+
impl Serialize for WString {
36+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
37+
where
38+
S: Serializer,
39+
{
40+
// Not particularly efficient
41+
let s = std::string::String::from_utf16_lossy(self.deref());
42+
serializer.serialize_str(&s)
43+
}
44+
}
45+
46+
impl<'de, const N: usize> Deserialize<'de> for BoundedString<N> {
47+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
48+
where
49+
D: Deserializer<'de>,
50+
{
51+
std::string::String::deserialize(deserializer)
52+
.and_then(|s| Self::try_from(s.as_str()).map_err(D::Error::custom))
53+
}
54+
}
55+
56+
impl<const N: usize> Serialize for BoundedString<N> {
57+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
58+
where
59+
S: Serializer,
60+
{
61+
self.inner.serialize(serializer)
62+
}
63+
}
64+
65+
impl<'de, const N: usize> Deserialize<'de> for BoundedWString<N> {
66+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
67+
where
68+
D: Deserializer<'de>,
69+
{
70+
std::string::String::deserialize(deserializer)
71+
.and_then(|s| Self::try_from(s.as_str()).map_err(D::Error::custom))
72+
}
73+
}
74+
75+
impl<const N: usize> Serialize for BoundedWString<N> {
76+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
77+
where
78+
S: Serializer,
79+
{
80+
self.inner.serialize(serializer)
81+
}
82+
}
83+
84+
#[cfg(test)]
85+
mod tests {
86+
use crate::{BoundedString, BoundedWString, String, WString};
87+
use quickcheck::quickcheck;
88+
89+
quickcheck! {
90+
fn test_json_roundtrip_string(s: String) -> bool {
91+
let value = serde_json::to_value(s.clone()).unwrap();
92+
let recovered = serde_json::from_value(value).unwrap();
93+
s == recovered
94+
}
95+
}
96+
97+
quickcheck! {
98+
fn test_json_roundtrip_wstring(s: WString) -> bool {
99+
let value = serde_json::to_value(s.clone()).unwrap();
100+
let recovered = serde_json::from_value(value).unwrap();
101+
s == recovered
102+
}
103+
}
104+
105+
quickcheck! {
106+
fn test_json_roundtrip_bounded_string(s: BoundedString<256>) -> bool {
107+
let value = serde_json::to_value(s.clone()).unwrap();
108+
let recovered = serde_json::from_value(value).unwrap();
109+
s == recovered
110+
}
111+
}
112+
113+
quickcheck! {
114+
fn test_json_roundtrip_bounded_wstring(s: BoundedWString<256>) -> bool {
115+
let value = serde_json::to_value(s.clone()).unwrap();
116+
let recovered = serde_json::from_value(value).unwrap();
117+
s == recovered
118+
}
119+
}
120+
}

0 commit comments

Comments
 (0)