use heapless::String;
use super::error::Ros2Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Ros2TopicKind {
Topic,
ServiceRequest,
ServiceReply,
ServiceStatus,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TypeNamespace {
Msg,
Srv,
Action,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TypeSuffix {
Plain,
Request,
Response,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ActionSubtopic {
Feedback,
Status,
}
pub fn encode_action_subtopic(
out: &mut String<256>,
action_name: &str,
sub: ActionSubtopic,
) -> Result<(), Ros2Error> {
let name = action_name.strip_prefix('/').unwrap_or(action_name);
validate_name(name, Ros2Error::InvalidTopicName)?;
out.clear();
push_str(out, "rt/")?;
push_str(out, name)?;
push_str(out, "/_action/")?;
let sub_str = match sub {
ActionSubtopic::Feedback => "feedback",
ActionSubtopic::Status => "status",
};
push_str(out, sub_str)?;
Ok(())
}
fn validate_name(name: &str, err: Ros2Error) -> Result<(), Ros2Error> {
if name.is_empty() {
return Err(err);
}
for ch in name.chars() {
if ch == '\0' || ch.is_whitespace() {
return Err(err);
}
}
Ok(())
}
fn push_str(out: &mut String<256>, s: &str) -> Result<(), Ros2Error> {
out.push_str(s).map_err(|_| Ros2Error::NameBufferTooSmall)
}
pub fn encode_topic_name(
out: &mut String<256>,
ros_name: &str,
kind: Ros2TopicKind,
) -> Result<(), Ros2Error> {
let name = ros_name.strip_prefix('/').unwrap_or(ros_name);
validate_name(name, Ros2Error::InvalidTopicName)?;
out.clear();
let (prefix, suffix) = match kind {
Ros2TopicKind::Topic => ("rt/", ""),
Ros2TopicKind::ServiceRequest => ("rq/", "Request"),
Ros2TopicKind::ServiceReply => ("rr/", "Reply"),
Ros2TopicKind::ServiceStatus => ("rs/", "Reply"),
};
push_str(out, prefix)?;
push_str(out, name)?;
push_str(out, suffix)?;
Ok(())
}
pub fn decode_topic_name(dds_name: &str) -> Option<(Ros2TopicKind, &str)> {
if let Some(rest) = dds_name.strip_prefix("rt/") {
return Some((Ros2TopicKind::Topic, rest));
}
if let Some(rest) = dds_name.strip_prefix("rq/") {
return Some((Ros2TopicKind::ServiceRequest, rest));
}
if let Some(rest) = dds_name.strip_prefix("rr/") {
return Some((Ros2TopicKind::ServiceReply, rest));
}
if let Some(rest) = dds_name.strip_prefix("rs/") {
return Some((Ros2TopicKind::ServiceStatus, rest));
}
None
}
pub fn encode_type_name(
out: &mut String<256>,
pkg: &str,
ns: TypeNamespace,
type_name: &str,
suffix: TypeSuffix,
) -> Result<(), Ros2Error> {
validate_name(pkg, Ros2Error::InvalidTypeName)?;
validate_name(type_name, Ros2Error::InvalidTypeName)?;
let ns_str = match ns {
TypeNamespace::Msg => "msg",
TypeNamespace::Srv => "srv",
TypeNamespace::Action => "action",
};
let suffix_str = match suffix {
TypeSuffix::Plain => "",
TypeSuffix::Request => "_Request",
TypeSuffix::Response => "_Response",
};
out.clear();
push_str(out, pkg)?;
push_str(out, "::")?;
push_str(out, ns_str)?;
push_str(out, "::dds_::")?;
push_str(out, type_name)?;
push_str(out, suffix_str)?;
push_str(out, "_")?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn encode_topic_strips_leading_slash() {
let mut buf = String::new();
encode_topic_name(&mut buf, "/chatter", Ros2TopicKind::Topic).unwrap();
assert_eq!(buf.as_str(), "rt/chatter");
}
#[test]
fn encode_topic_no_leading_slash() {
let mut buf = String::new();
encode_topic_name(&mut buf, "chatter", Ros2TopicKind::Topic).unwrap();
assert_eq!(buf.as_str(), "rt/chatter");
}
#[test]
fn encode_service_request_appends_request() {
let mut buf = String::new();
encode_topic_name(&mut buf, "add_two_ints", Ros2TopicKind::ServiceRequest).unwrap();
assert_eq!(buf.as_str(), "rq/add_two_intsRequest");
}
#[test]
fn encode_service_reply_appends_reply() {
let mut buf = String::new();
encode_topic_name(&mut buf, "add_two_ints", Ros2TopicKind::ServiceReply).unwrap();
assert_eq!(buf.as_str(), "rr/add_two_intsReply");
}
#[test]
fn encode_type_name_msg() {
let mut buf = String::new();
encode_type_name(
&mut buf,
"std_msgs",
TypeNamespace::Msg,
"String",
TypeSuffix::Plain,
)
.unwrap();
assert_eq!(buf.as_str(), "std_msgs::msg::dds_::String_");
}
#[test]
fn encode_topic_rejects_whitespace() {
let mut buf = String::new();
let result = encode_topic_name(&mut buf, "my topic", Ros2TopicKind::Topic);
assert_eq!(result, Err(Ros2Error::InvalidTopicName));
}
#[test]
fn decode_topic_name_rt_prefix() {
let result = decode_topic_name("rt/chatter");
assert_eq!(result, Some((Ros2TopicKind::Topic, "chatter")));
}
#[test]
fn decode_topic_name_rq_prefix() {
let result = decode_topic_name("rq/add_two_intsRequest");
assert_eq!(
result,
Some((Ros2TopicKind::ServiceRequest, "add_two_intsRequest"))
);
}
#[test]
fn decode_topic_name_rr_prefix() {
let result = decode_topic_name("rr/add_two_intsReply");
assert_eq!(
result,
Some((Ros2TopicKind::ServiceReply, "add_two_intsReply"))
);
}
#[test]
fn decode_topic_name_unknown_prefix_returns_none() {
let result = decode_topic_name("chatter");
assert_eq!(result, None);
}
#[test]
fn encode_type_name_srv_request() {
let mut buf = String::new();
encode_type_name(
&mut buf,
"rcl_interfaces",
TypeNamespace::Srv,
"GetParameters",
TypeSuffix::Request,
)
.unwrap();
assert_eq!(
buf.as_str(),
"rcl_interfaces::srv::dds_::GetParameters_Request_"
);
}
#[test]
fn encode_topic_rejects_empty() {
let mut buf = String::new();
let result = encode_topic_name(&mut buf, "", Ros2TopicKind::Topic);
assert_eq!(result, Err(Ros2Error::InvalidTopicName));
}
#[test]
fn encode_type_name_rejects_empty_pkg() {
let mut buf = String::new();
let result = encode_type_name(&mut buf, "", TypeNamespace::Msg, "Log", TypeSuffix::Plain);
assert_eq!(result, Err(Ros2Error::InvalidTypeName));
}
#[test]
fn encode_type_name_srv_response() {
let mut buf = String::new();
encode_type_name(
&mut buf,
"rcl_interfaces",
TypeNamespace::Srv,
"GetParameters",
TypeSuffix::Response,
)
.unwrap();
assert_eq!(
buf.as_str(),
"rcl_interfaces::srv::dds_::GetParameters_Response_"
);
}
#[test]
fn encode_action_type_name() {
let mut buf = String::new();
encode_type_name(
&mut buf,
"example_interfaces",
TypeNamespace::Action,
"Fibonacci_SendGoal",
TypeSuffix::Request,
)
.unwrap();
assert_eq!(
buf.as_str(),
"example_interfaces::action::dds_::Fibonacci_SendGoal_Request_"
);
}
#[test]
fn encode_action_subtopic_feedback() {
let mut buf = String::new();
encode_action_subtopic(&mut buf, "fibonacci", ActionSubtopic::Feedback).unwrap();
assert_eq!(buf.as_str(), "rt/fibonacci/_action/feedback");
}
#[test]
fn encode_action_subtopic_status() {
let mut buf = String::new();
encode_action_subtopic(&mut buf, "/fibonacci", ActionSubtopic::Status).unwrap();
assert_eq!(buf.as_str(), "rt/fibonacci/_action/status");
}
#[test]
fn encode_action_service_topic_send_goal() {
let mut buf = String::new();
encode_topic_name(
&mut buf,
"fibonacci/_action/send_goal",
Ros2TopicKind::ServiceRequest,
)
.unwrap();
assert_eq!(buf.as_str(), "rq/fibonacci/_action/send_goalRequest");
}
}