use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct NodeName {
namespace: String,
base_name: String,
}
impl NodeName {
pub fn new(namespace: &str, base_name: &str) -> Result<NodeName, NameError> {
match base_name.chars().next() {
None => return Err(NameError::Empty),
Some(c) if c.is_ascii_alphabetic() => { }
Some(_other) => return Err(NameError::BadChar),
}
if base_name
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '_')
{
} else {
return Err(NameError::BadChar);
}
match namespace.chars().next() {
None => { } Some(c) if c.is_ascii_alphabetic() || c == '/' => { }
Some(_other) => return Err(NameError::BadChar),
}
if namespace
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '/')
{
} else {
return Err(NameError::BadChar);
}
if namespace.ends_with('/') {
return Err(NameError::BadSlash);
}
Ok(NodeName {
namespace: namespace.to_owned(),
base_name: base_name.to_owned(),
})
}
pub fn namespace(&self) -> &str {
&self.namespace
}
pub fn base_name(&self) -> &str {
&self.base_name
}
pub fn fully_qualified_name(&self) -> String {
let mut fqn = self.namespace.clone();
fqn.push('/');
fqn.push_str(&self.base_name);
fqn
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NameError {
Empty,
BadChar,
BadSlash,
}
impl fmt::Display for NameError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
NameError::Empty => write!(f, "Base name must not be empty"),
NameError::BadChar => write!(f, "Bad chracters in Name"),
NameError::BadSlash => write!(f, "Invalid placement of seprator slashes"),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Name {
base_name: String, preceeding_tokens: Vec<String>, absolute: bool, }
impl Name {
pub fn new(namespace: &str, base_name: &str) -> Result<Name, NameError> {
let (namespace_rel, absolute) = if let Some(rel) = namespace.strip_prefix('/') {
(rel, true)
} else {
(namespace, false)
};
if base_name.is_empty() {
return Err(NameError::Empty);
}
let ok_start_char = |c: char| c.is_ascii_alphabetic() || c == '_';
let no_multi_underscore = |s: &str| !s.contains("__");
if base_name
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '_')
&& base_name.starts_with(ok_start_char)
&& no_multi_underscore(base_name)
{ } else {
return Err(NameError::BadChar);
}
let preceeding_tokens = if namespace_rel.is_empty() {
Vec::new()
} else {
namespace_rel
.split('/')
.map(str::to_owned)
.collect::<Vec<String>>()
};
if preceeding_tokens.iter().any(String::is_empty) {
return Err(NameError::BadSlash);
}
if preceeding_tokens.iter().all(|tok| {
tok.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
&& tok.starts_with(ok_start_char)
&& no_multi_underscore(tok)
}) { } else {
return Err(NameError::BadChar);
}
Ok(Name {
base_name: base_name.to_owned(),
preceeding_tokens,
absolute,
})
}
pub fn parse(full_name: &str) -> Result<Name, NameError> {
match full_name.rsplit_once('/') {
None => Name::new("", full_name),
Some(("", "")) => Err(NameError::Empty),
Some((_, "")) => Err(NameError::BadSlash),
Some(("", base)) => Name::new("/", base),
Some((prefix, base)) => {
if prefix.ends_with('/') {
Err(NameError::BadSlash)
} else {
Name::new(prefix, base)
}
}
}
}
pub fn to_dds_name(&self, kind_prefix: &str, node: &NodeName, suffix: &str) -> String {
let mut result = kind_prefix.to_owned();
assert!(!result.ends_with('/')); if self.absolute {
} else {
result.push_str(node.namespace()); }
result.push('/'); self.preceeding_tokens.iter().for_each(|tok| {
result.push_str(tok);
result.push('/');
});
result.push_str(&self.base_name);
result.push_str(suffix);
result
}
pub(crate) fn push(&self, new_suffix: &str) -> Name {
let mut preceeding_tokens = self.preceeding_tokens.clone();
preceeding_tokens.push(self.base_name.to_string());
Name {
base_name: new_suffix.to_string(),
preceeding_tokens,
absolute: self.absolute,
}
}
pub fn is_absolute(&self) -> bool {
self.absolute
}
}
impl fmt::Display for Name {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.absolute {
write!(f, "/")?;
}
for t in &self.preceeding_tokens {
write!(f, "{t}/")?;
}
write!(f, "{}", self.base_name)
}
}
pub struct MessageTypeName {
prefix: String, ros2_package_name: String, ros2_type_name: String,
}
impl MessageTypeName {
pub fn new(package_name: &str, type_name: &str) -> Self {
MessageTypeName {
prefix: "msg".to_string(),
ros2_package_name: package_name.to_owned(),
ros2_type_name: type_name.to_owned(),
}
}
pub(crate) fn new_prefix(package_name: &str, type_name: &str, prefix: String) -> Self {
MessageTypeName {
prefix,
ros2_package_name: package_name.to_owned(),
ros2_type_name: type_name.to_owned(),
}
}
pub fn package_name(&self) -> &str {
self.ros2_package_name.as_str()
}
pub fn type_name(&self) -> &str {
self.ros2_type_name.as_str()
}
pub fn dds_msg_type(&self) -> String {
slash_to_colons(
self.ros2_package_name.clone() + "/" + &self.prefix + "/dds_/" + &self.ros2_type_name + "_",
)
}
}
fn slash_to_colons(s: String) -> String {
s.replace('/', "::")
}
pub struct ServiceTypeName {
prefix: String,
msg: MessageTypeName,
}
impl ServiceTypeName {
pub fn new(package_name: &str, type_name: &str) -> Self {
ServiceTypeName {
prefix: "srv".to_string(),
msg: MessageTypeName::new(package_name, type_name),
}
}
pub(crate) fn new_prefix(package_name: &str, type_name: &str, prefix: String) -> Self {
ServiceTypeName {
prefix,
msg: MessageTypeName::new(package_name, type_name),
}
}
pub fn package_name(&self) -> &str {
self.msg.package_name()
}
pub fn type_name(&self) -> &str {
self.msg.type_name()
}
pub(crate) fn dds_request_type(&self) -> String {
slash_to_colons(
self.package_name().to_owned()
+ "/"
+ &self.prefix
+ "/dds_/"
+ self.type_name()
+ "_Request_",
)
}
pub(crate) fn dds_response_type(&self) -> String {
slash_to_colons(
self.package_name().to_owned()
+ "/"
+ &self.prefix
+ "/dds_/"
+ self.type_name()
+ "_Response_",
)
}
}
pub struct ActionTypeName(MessageTypeName);
impl ActionTypeName {
pub fn new(package_name: &str, type_name: &str) -> Self {
ActionTypeName(MessageTypeName::new(package_name, type_name))
}
pub fn package_name(&self) -> &str {
self.0.package_name()
}
pub fn type_name(&self) -> &str {
self.0.type_name()
}
pub(crate) fn dds_action_topic(&self, topic: &str) -> MessageTypeName {
MessageTypeName::new_prefix(
self.package_name(),
&(self.type_name().to_owned() + topic),
"action".to_owned(),
)
}
pub(crate) fn dds_action_service(&self, srv: &str) -> ServiceTypeName {
ServiceTypeName::new_prefix(
self.package_name(),
&(self.type_name().to_owned() + srv),
"action".to_owned(),
)
}
}
#[test]
fn test_name() {
assert!(Name::new("", "").is_err());
assert!(Name::new("", "/").is_err());
assert!(Name::new("a", "b").is_ok());
assert!(Name::new("a", "_b").is_ok());
assert!(Name::new("a", "b_b").is_ok()); assert!(Name::new("a", "b__b").is_err()); assert!(Name::new("a2//a", "b").is_err()); }
#[test]
fn test_name_parse() {
assert!(Name::parse("").is_err()); assert!(Name::parse("/").is_err()); assert!(Name::parse("a/").is_err()); assert!(Name::parse("a/b/").is_err());
assert!(Name::parse("2").is_err()); assert!(Name::parse("2/a").is_err()); assert!(Name::parse("a2/a").is_ok());
assert!(Name::parse("_a2/a").is_ok()); assert!(Name::parse("some_name/a").is_ok()); assert!(Name::parse("__a2/a").is_err()); assert!(Name::parse("a2//a").is_err()); assert_eq!(Name::parse("a/nn").unwrap(), Name::new("a", "nn").unwrap());
assert_eq!(
Name::parse("a/b/c/nn").unwrap(),
Name::new("a/b/c", "nn").unwrap()
);
assert_eq!(
Name::parse("/a/b/c/nn").unwrap(),
Name::new("/a/b/c", "nn").unwrap()
);
assert_eq!(Name::parse("a/nn").unwrap().is_absolute(), false);
assert_eq!(Name::parse("/a/nn").unwrap().is_absolute(), true);
}