1use std::fmt;
8
9#[derive(Debug, Clone, PartialEq, Eq, Hash)]
16pub struct NodeName {
17 namespace: String,
18 base_name: String,
19}
20
21impl NodeName {
22 pub fn new(namespace: &str, base_name: &str) -> Result<NodeName, NameError> {
23 match base_name.chars().next() {
24 None => return Err(NameError::Empty),
25 Some(c) if c.is_ascii_alphabetic() || c == '_' => { }
26 Some(other) => return Err(NameError::BadChar(other)),
27 }
28
29 if let Some(bad) = base_name
30 .chars()
31 .find(|c| !(c.is_ascii_alphanumeric() || *c == '_'))
32 {
33 return Err(NameError::BadChar(bad));
34 }
35
36 match namespace.chars().next() {
37 None => {
38 return Err(NameError::BadSlash(
39 "<empty_namespace>".to_owned(),
40 base_name.to_owned(),
41 ))
42 }
43 Some(c) if c.is_ascii_alphabetic() || c == '/' => { }
44 Some(other) => return Err(NameError::BadChar(other)),
47 }
48
49 if namespace.starts_with('/') {
50 } else {
52 return Err(NameError::BadSlash(
53 namespace.to_owned(),
54 base_name.to_owned(),
55 ));
56 }
57
58 if let Some(bad) = namespace
60 .chars()
61 .find(|c| !(c.is_ascii_alphanumeric() || *c == '_' || *c == '/'))
62 {
63 return Err(NameError::BadChar(bad));
64 }
65
66 if namespace.ends_with('/') && namespace != "/" {
67 return Err(NameError::BadSlash(
68 namespace.to_owned(),
69 base_name.to_owned(),
70 ));
71 }
72
73 Ok(NodeName {
74 namespace: namespace.to_owned(),
75 base_name: base_name.to_owned(),
76 })
77 }
78
79 pub fn namespace(&self) -> &str {
80 &self.namespace
81 }
82 pub fn base_name(&self) -> &str {
83 &self.base_name
84 }
85
86 pub fn fully_qualified_name(&self) -> String {
87 let mut fqn = self.namespace.clone();
88 if fqn.ends_with('/') {
89 } else {
91 fqn.push('/');
92 }
93 fqn.push_str(&self.base_name);
94 fqn
95 }
96}
97
98#[derive(Debug, Clone, PartialEq, Eq)]
99pub enum NameError {
100 Empty,
101 BadChar(char),
102 BadSlash(String, String),
103}
104
105impl fmt::Display for NameError {
106 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
107 match self {
108 NameError::Empty => write!(f, "Base name must not be empty"),
109 NameError::BadChar(c) => write!(f, "Bad chracters in Name: {c:?}"),
110 NameError::BadSlash(ns, n) => write!(
111 f,
112 "Invalid placement of seprator slashes. namespace={ns} name={n}"
113 ),
114 }
115 }
116}
117
118impl std::error::Error for NameError {}
119
120#[derive(Clone, Debug, PartialEq, Eq)]
126pub struct Name {
127 base_name: String, preceeding_tokens: Vec<String>, absolute: bool, }
131
132impl Name {
135 pub fn new(namespace: &str, base_name: &str) -> Result<Name, NameError> {
147 let (namespace_rel, absolute) = if let Some(rel) = namespace.strip_prefix('/') {
149 (rel, true)
150 } else {
151 (namespace, false)
152 };
153
154 if base_name.is_empty() {
155 return Err(NameError::Empty);
156 }
157
158 let ok_start_char = |c: char| c.is_ascii_alphabetic() || c == '_';
159 let no_multi_underscore = |s: &str| !s.contains("__");
160
161 if let Some(bad) = base_name
162 .chars()
163 .find(|c| !(c.is_ascii_alphanumeric() || *c == '_'))
164 {
165 return Err(NameError::BadChar(bad));
166 } else if !base_name.starts_with(ok_start_char) {
167 return Err(NameError::BadChar(base_name.chars().next().unwrap_or('?')));
168 } else if !no_multi_underscore(base_name) {
169 return Err(NameError::BadChar('_'));
170 } else {
171 }
173
174 let preceeding_tokens = if namespace_rel.is_empty() {
175 Vec::new()
177 } else {
178 namespace_rel
179 .split('/')
180 .map(str::to_owned)
181 .collect::<Vec<String>>()
182 };
185
186 if preceeding_tokens.iter().any(String::is_empty) {
187 return Err(NameError::BadSlash(
188 namespace_rel.to_owned(),
189 base_name.to_owned(),
190 ));
191 }
192
193 if preceeding_tokens.iter().all(|tok| {
194 tok.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
195 && tok.starts_with(ok_start_char)
196 && no_multi_underscore(tok)
197 }) { } else {
199 return Err(NameError::BadChar('?')); }
201
202 Ok(Name {
203 base_name: base_name.to_owned(),
204 preceeding_tokens,
205 absolute,
206 })
207 }
208
209 pub fn parse(full_name: &str) -> Result<Name, NameError> {
213 match full_name.rsplit_once('/') {
214 None => Name::new("", full_name),
216
217 Some(("", "")) => Err(NameError::Empty),
220
221 Some((bad, "")) => Err(NameError::BadSlash(bad.to_owned(), "".to_owned())),
223
224 Some(("", base)) => Name::new("/", base),
226
227 Some((prefix, base)) => {
229 if prefix.ends_with('/') {
230 Err(NameError::BadSlash(prefix.to_owned(), base.to_owned()))
232 } else {
233 Name::new(prefix, base)
234 }
235 }
236 }
237 }
238
239 pub fn to_dds_name(&self, kind_prefix: &str, node: &NodeName, suffix: &str) -> String {
240 let mut result = kind_prefix.to_owned();
241 assert!(!result.ends_with('/')); if self.absolute {
243 } else {
245 result.push_str(node.namespace()); }
248 result.push('/'); self.preceeding_tokens.iter().for_each(|tok| {
250 result.push_str(tok);
251 result.push('/');
252 });
253 result.push_str(&self.base_name);
255 result.push_str(suffix);
256 result
257 }
258
259 pub(crate) fn push(&self, new_suffix: &str) -> Name {
260 let mut preceeding_tokens = self.preceeding_tokens.clone();
262 preceeding_tokens.push(self.base_name.to_string());
263 Name {
264 base_name: new_suffix.to_string(),
265 preceeding_tokens,
266 absolute: self.absolute,
267 }
268 }
269
270 pub fn is_absolute(&self) -> bool {
271 self.absolute
272 }
273}
274
275impl fmt::Display for Name {
276 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
277 if self.absolute {
278 write!(f, "/")?;
279 }
280 for t in &self.preceeding_tokens {
281 write!(f, "{t}/")?;
282 }
283 write!(f, "{}", self.base_name)
284 }
285}
286
287#[derive(Clone, Debug)]
295pub struct MessageTypeName {
296 prefix: String, ros2_package_name: String, ros2_type_name: String,
301}
302
303impl MessageTypeName {
304 pub fn new(package_name: &str, type_name: &str) -> Self {
305 MessageTypeName {
307 prefix: "msg".to_string(),
308 ros2_package_name: package_name.to_owned(),
309 ros2_type_name: type_name.to_owned(),
310 }
311 }
312
313 pub(crate) fn new_prefix(package_name: &str, type_name: &str, prefix: String) -> Self {
314 MessageTypeName {
315 prefix,
316 ros2_package_name: package_name.to_owned(),
317 ros2_type_name: type_name.to_owned(),
318 }
319 }
320
321 pub fn package_name(&self) -> &str {
322 self.ros2_package_name.as_str()
323 }
324
325 pub fn type_name(&self) -> &str {
326 self.ros2_type_name.as_str()
327 }
328
329 pub fn dds_msg_type(&self) -> String {
331 slash_to_colons(
332 self.ros2_package_name.clone() + "/" + &self.prefix + "/dds_/" + &self.ros2_type_name + "_",
333 )
334 }
335}
336
337fn slash_to_colons(s: String) -> String {
338 s.replace('/', "::")
339}
340
341#[derive(Clone, Debug)]
343pub struct ServiceTypeName {
344 prefix: String,
345 msg: MessageTypeName,
346}
347
348impl ServiceTypeName {
349 pub fn new(package_name: &str, type_name: &str) -> Self {
350 ServiceTypeName {
351 prefix: "srv".to_string(),
352 msg: MessageTypeName::new(package_name, type_name),
353 }
354 }
355
356 pub(crate) fn new_prefix(package_name: &str, type_name: &str, prefix: String) -> Self {
357 ServiceTypeName {
358 prefix,
359 msg: MessageTypeName::new(package_name, type_name),
360 }
361 }
362
363 pub fn package_name(&self) -> &str {
364 self.msg.package_name()
365 }
366
367 pub fn type_name(&self) -> &str {
368 self.msg.type_name()
369 }
370
371 pub(crate) fn dds_request_type(&self) -> String {
372 slash_to_colons(
373 self.package_name().to_owned()
374 + "/"
375 + &self.prefix
376 + "/dds_/"
377 + self.type_name()
378 + "_Request_",
379 )
380 }
381
382 pub(crate) fn dds_response_type(&self) -> String {
383 slash_to_colons(
384 self.package_name().to_owned()
385 + "/"
386 + &self.prefix
387 + "/dds_/"
388 + self.type_name()
389 + "_Response_",
390 )
391 }
392}
393
394#[derive(Clone, Debug)]
396pub struct ActionTypeName(MessageTypeName);
397
398impl ActionTypeName {
399 pub fn new(package_name: &str, type_name: &str) -> Self {
400 ActionTypeName(MessageTypeName::new(package_name, type_name))
401 }
402
403 pub fn package_name(&self) -> &str {
404 self.0.package_name()
405 }
406
407 pub fn type_name(&self) -> &str {
408 self.0.type_name()
409 }
410
411 pub(crate) fn dds_action_topic(&self, topic: &str) -> MessageTypeName {
412 MessageTypeName::new_prefix(
413 self.package_name(),
414 &(self.type_name().to_owned() + topic),
415 "action".to_owned(),
416 )
417 }
420
421 pub(crate) fn dds_action_service(&self, srv: &str) -> ServiceTypeName {
422 ServiceTypeName::new_prefix(
423 self.package_name(),
424 &(self.type_name().to_owned() + srv),
425 "action".to_owned(),
426 )
427 }
428}
429
430#[test]
434fn test_name() {
435 assert!(Name::new("", "").is_err());
436 assert!(Name::new("", "/").is_err());
437 assert!(Name::new("a", "b").is_ok());
438 assert!(Name::new("a", "_b").is_ok());
439 assert!(Name::new("a", "b_b").is_ok()); assert!(Name::new("a", "b__b").is_err()); assert!(Name::new("a2//a", "b").is_err()); }
444
445#[test]
446fn test_name_parse() {
447 assert!(Name::parse("").is_err()); assert!(Name::parse("/").is_err()); assert!(Name::parse("a/").is_err()); assert!(Name::parse("a/b/").is_err());
453
454 assert!(Name::parse("2").is_err()); assert!(Name::parse("2/a").is_err()); assert!(Name::parse("a2/a").is_ok());
457 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());
463 assert_eq!(
464 Name::parse("a/b/c/nn").unwrap(),
465 Name::new("a/b/c", "nn").unwrap()
466 );
467 assert_eq!(
468 Name::parse("/a/b/c/nn").unwrap(),
469 Name::new("/a/b/c", "nn").unwrap()
470 );
471
472 assert!(!Name::parse("a/nn").unwrap().is_absolute());
473 assert!(Name::parse("/a/nn").unwrap().is_absolute());
474}