Skip to content

Commit 91a6317

Browse files
Improve string serde serialization/deserialization (#415)
* mavlink: Add nulstr Signed-off-by: Patrick José Pereira <[email protected]> * mavlink-bindgen: parser: Deal with string serialization and deserialization Signed-off-by: Patrick José Pereira <[email protected]> * mavlink-bindgen: tests: snapshots: Update to use new nulstr Signed-off-by: Patrick José Pereira <[email protected]> * mavlink: tests: serde: Update to use new string serialization Signed-off-by: Patrick José Pereira <[email protected]> * mavlink: tests: serde: Test serde input Signed-off-by: Patrick José Pereira <[email protected]> * mavlink : tests: serde: Update to deserialize as string over tuple Signed-off-by: Patrick José Pereira <[email protected]> --------- Signed-off-by: Patrick José Pereira <[email protected]>
1 parent fcc7f2d commit 91a6317

File tree

5 files changed

+132
-24
lines changed

5 files changed

+132
-24
lines changed

mavlink-bindgen/src/parser.rs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -710,11 +710,23 @@ impl MavMessage {
710710
quote!()
711711
};
712712

713-
let serde_with_attr = if matches!(field.mavtype, MavType::Array(_, _)) {
714-
quote!(
715-
#[cfg_attr(feature = "serde", serde(with = "serde_arrays"))]
716-
#[cfg_attr(feature = "ts", ts(type = "Array<number>"))]
717-
)
713+
let serde_with_attr = if let MavType::Array(_, size) = field.mavtype {
714+
if field.mavtype.primitive_type() == "char" {
715+
let format_serialize = format!("crate::nulstr::serialize::<_, {}>", size);
716+
let format_deserialize = format!("crate::nulstr::deserialize::<_, {}>", size);
717+
quote!(
718+
#[cfg_attr(feature = "serde", serde(
719+
serialize_with = #format_serialize,
720+
deserialize_with = #format_deserialize
721+
))]
722+
#[cfg_attr(feature = "ts", ts(type = "string"))]
723+
)
724+
} else {
725+
quote!(
726+
#[cfg_attr(feature = "serde", serde(with = "serde_arrays"))]
727+
#[cfg_attr(feature = "ts", ts(type = "Array<number>"))]
728+
)
729+
}
718730
} else {
719731
quote!()
720732
};

mavlink-bindgen/tests/snapshots/[email protected]

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,14 @@ pub struct PARAM_REQUEST_READ_DATA {
156156
#[doc = "Component ID"]
157157
pub target_component: u8,
158158
#[doc = "Onboard parameter id, terminated by NULL if the length is less than 16 human-readable chars and WITHOUT null termination (NULL) byte if the length is exactly 16 chars - applications have to provide 16+1 bytes storage if the ID is stored as string"]
159-
#[cfg_attr(feature = "serde", serde(with = "serde_arrays"))]
160-
#[cfg_attr(feature = "ts", ts(type = "Array<number>"))]
159+
#[cfg_attr(
160+
feature = "serde",
161+
serde(
162+
serialize_with = "crate::nulstr::serialize::<_, 16>",
163+
deserialize_with = "crate::nulstr::deserialize::<_, 16>"
164+
)
165+
)]
166+
#[cfg_attr(feature = "ts", ts(type = "string"))]
161167
pub param_id: [u8; 16],
162168
}
163169
impl PARAM_REQUEST_READ_DATA {
@@ -251,8 +257,14 @@ pub struct PARAM_SET_DATA {
251257
#[doc = "Component ID"]
252258
pub target_component: u8,
253259
#[doc = "Onboard parameter id, terminated by NULL if the length is less than 16 human-readable chars and WITHOUT null termination (NULL) byte if the length is exactly 16 chars - applications have to provide 16+1 bytes storage if the ID is stored as string"]
254-
#[cfg_attr(feature = "serde", serde(with = "serde_arrays"))]
255-
#[cfg_attr(feature = "ts", ts(type = "Array<number>"))]
260+
#[cfg_attr(
261+
feature = "serde",
262+
serde(
263+
serialize_with = "crate::nulstr::serialize::<_, 16>",
264+
deserialize_with = "crate::nulstr::deserialize::<_, 16>"
265+
)
266+
)]
267+
#[cfg_attr(feature = "ts", ts(type = "string"))]
256268
pub param_id: [u8; 16],
257269
#[doc = "Onboard parameter type."]
258270
pub param_type: MavParamType,
@@ -356,8 +368,14 @@ pub struct PARAM_VALUE_DATA {
356368
#[doc = "Index of this onboard parameter"]
357369
pub param_index: u16,
358370
#[doc = "Onboard parameter id, terminated by NULL if the length is less than 16 human-readable chars and WITHOUT null termination (NULL) byte if the length is exactly 16 chars - applications have to provide 16+1 bytes storage if the ID is stored as string"]
359-
#[cfg_attr(feature = "serde", serde(with = "serde_arrays"))]
360-
#[cfg_attr(feature = "ts", ts(type = "Array<number>"))]
371+
#[cfg_attr(
372+
feature = "serde",
373+
serde(
374+
serialize_with = "crate::nulstr::serialize::<_, 16>",
375+
deserialize_with = "crate::nulstr::deserialize::<_, 16>"
376+
)
377+
)]
378+
#[cfg_attr(feature = "ts", ts(type = "string"))]
361379
pub param_id: [u8; 16],
362380
#[doc = "Onboard parameter type."]
363381
pub param_type: MavParamType,

mavlink-core/src/utils.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,32 @@ mod tests {
6666
remove_trailing_zeroes(&[]);
6767
}
6868
}
69+
70+
#[cfg(feature = "serde")]
71+
pub mod nulstr {
72+
use serde::de::Deserializer;
73+
use serde::ser::Serializer;
74+
use serde::Deserialize;
75+
use std::str;
76+
77+
pub fn serialize<S, const N: usize>(value: &[u8; N], serializer: S) -> Result<S::Ok, S::Error>
78+
where
79+
S: Serializer,
80+
{
81+
let nul_pos = value.iter().position(|&b| b == 0).unwrap_or(N);
82+
let s = str::from_utf8(&value[..nul_pos]).map_err(serde::ser::Error::custom)?;
83+
serializer.serialize_str(s)
84+
}
85+
86+
pub fn deserialize<'de, D, const N: usize>(deserializer: D) -> Result<[u8; N], D::Error>
87+
where
88+
D: Deserializer<'de>,
89+
{
90+
let s: String = Deserialize::deserialize(deserializer)?;
91+
let mut buf = [0u8; N];
92+
let bytes = s.as_bytes();
93+
let len = bytes.len().min(N);
94+
buf[..len].copy_from_slice(&bytes[..len]);
95+
Ok(buf)
96+
}
97+
}

mavlink/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,7 @@ pub use mavlink_core::*;
6666
#[cfg(feature = "emit-extensions")]
6767
#[allow(unused_imports)]
6868
pub(crate) use mavlink_core::utils::RustDefault;
69+
70+
#[cfg(feature = "serde")]
71+
#[allow(unused_imports)]
72+
pub(crate) use mavlink_core::utils::nulstr;

mavlink/tests/serde_test.rs

Lines changed: 58 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -203,18 +203,7 @@ mod serde_test {
203203
Str("c"),
204204
U8(b'R'),
205205
Str("s"),
206-
Tuple { len: 10 },
207-
U8(b'r'),
208-
U8(b'u'),
209-
U8(b's'),
210-
U8(b't'),
211-
U8(b'm'),
212-
U8(b'a'),
213-
U8(b'v'),
214-
U8(b'l'),
215-
U8(b'i'),
216-
U8(b'n'),
217-
TupleEnd,
206+
Str("rustmavlin"),
218207
Str("u8"),
219208
U8(0),
220209
Str("s8"),
@@ -285,9 +274,65 @@ mod serde_test_json {
285274
"param_index": 0,
286275
"target_system": 0,
287276
"target_component": 0,
288-
"param_id": [84, 69, 83, 84, 95, 80, 65, 82, 65, 77, 0, 0, 0, 0, 0, 0]
277+
"param_id": "TEST_PARAM"
289278
})
290279
.to_string();
291280
assert_eq!(json, expected);
292281
}
282+
283+
#[test]
284+
fn test_serde_input() {
285+
let heartbeat_json = json!({
286+
"type": "HEARTBEAT",
287+
"custom_mode": 0,
288+
"mavtype": { "type": "MAV_TYPE_GENERIC" },
289+
"autopilot": { "type": "MAV_AUTOPILOT_GENERIC" },
290+
"base_mode": "MAV_MODE_FLAG_SAFETY_ARMED",
291+
"system_status": { "type": "MAV_STATE_UNINIT" },
292+
"mavlink_version": 3
293+
});
294+
295+
let heartbeat_message: common::MavMessage = serde_json::from_value(heartbeat_json).unwrap();
296+
297+
match heartbeat_message {
298+
common::MavMessage::HEARTBEAT(data) => {
299+
assert_eq!(data.custom_mode, 0);
300+
assert_eq!(data.mavtype, common::MavType::MAV_TYPE_GENERIC);
301+
assert_eq!(data.autopilot, common::MavAutopilot::MAV_AUTOPILOT_GENERIC);
302+
assert_eq!(
303+
data.base_mode,
304+
common::MavModeFlag::MAV_MODE_FLAG_SAFETY_ARMED
305+
);
306+
assert_eq!(data.system_status, common::MavState::MAV_STATE_UNINIT);
307+
assert_eq!(data.mavlink_version, 3);
308+
}
309+
_ => panic!("Expected HEARTBEAT message"),
310+
}
311+
312+
let param_request_json = json!({
313+
"type": "PARAM_REQUEST_READ",
314+
"param_index": 0,
315+
"target_system": 0,
316+
"target_component": 0,
317+
"param_id": "TEST_PARAM"
318+
});
319+
320+
let param_request_message: common::MavMessage =
321+
serde_json::from_value(param_request_json).unwrap();
322+
323+
match param_request_message {
324+
common::MavMessage::PARAM_REQUEST_READ(data) => {
325+
assert_eq!(data.param_index, 0);
326+
assert_eq!(data.target_system, 0);
327+
assert_eq!(data.target_component, 0);
328+
329+
// Check that param_id string is correctly deserialized
330+
let param_id_str = std::str::from_utf8(&data.param_id)
331+
.unwrap()
332+
.trim_end_matches('\0');
333+
assert_eq!(param_id_str, "TEST_PARAM");
334+
}
335+
_ => panic!("Expected PARAM_REQUEST_READ message"),
336+
}
337+
}
293338
}

0 commit comments

Comments
 (0)