ros_message/
message_path.rs

1use crate::{Error, Result};
2use lazy_static::lazy_static;
3use regex::Regex;
4use serde_derive::{Deserialize, Serialize};
5use std::convert::TryFrom;
6use std::fmt::{Display, Formatter};
7use std::hash::Hash;
8
9/// Path to a ROS message with naming conventions tested.
10#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
11#[serde(into = "String")]
12#[serde(try_from = "&str")]
13pub struct MessagePath {
14    package: String,
15    name: String,
16}
17
18pub fn is_valid_package_name(package: &str) -> bool {
19    lazy_static! {
20        static ref RE_PACKAGE_CORRECT_CHAR_SET_AND_LENGTH: Regex =
21            Regex::new("^[a-z][a-z0-9_]+$").unwrap();
22        static ref RE_PACKAGE_CONSECUTIVE_UNDERSCORE: Regex = Regex::new("__").unwrap();
23    }
24    RE_PACKAGE_CORRECT_CHAR_SET_AND_LENGTH.is_match(package)
25        && !RE_PACKAGE_CONSECUTIVE_UNDERSCORE.is_match(package)
26}
27
28impl MessagePath {
29    /// Create full message path, with naming rules checked
30    ///
31    /// Naming rules are based on [REP 144](https://www.ros.org/reps/rep-0144.html).
32    ///
33    /// # Errors
34    ///
35    /// An error will be returned if naming conventions are not met.
36    ///
37    /// # Examples
38    ///
39    /// ```
40    /// # use ros_message::MessagePath;
41    /// #
42    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
43    /// let message_path = MessagePath::new("foo", "Bar")?;
44    ///
45    /// assert_eq!(message_path.package(), "foo");
46    /// assert_eq!(message_path.name(), "Bar");
47    ///
48    /// assert!(MessagePath::new("0foo", "Bar").is_err());
49    /// # Ok(())
50    /// # }
51    /// ```
52    pub fn new(package: impl Into<String>, name: impl Into<String>) -> Result<Self> {
53        let package = package.into();
54        let name = name.into();
55        if !is_valid_package_name(&package) {
56            return Err(Error::InvalidMessagePath  {
57                name: format!("{}/{}",package,name),
58                  reason: "package name needs to follow REP 144 rules (https://www.ros.org/reps/rep-0144.html)".into(),
59            });
60        }
61        Ok(Self { package, name })
62    }
63
64    /// Create a new message path inside the same package.
65    ///
66    /// Prevents the need for constant error checking of package names.
67    ///
68    /// # Examples
69    ///
70    /// ```
71    /// # use ros_message::MessagePath;
72    /// #
73    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
74    /// let message_path = MessagePath::new("foo", "Bar")?.peer("Baz");
75    ///
76    /// assert_eq!(message_path.package(), "foo");
77    /// assert_eq!(message_path.name(), "Baz");
78    /// # Ok(())
79    /// # }
80    /// ```
81    pub fn peer(&self, name: impl Into<String>) -> Self {
82        Self {
83            package: self.package.clone(),
84            name: name.into(),
85        }
86    }
87
88    fn from_combined(input: &str) -> Result<Self> {
89        let parts = input.splitn(3, '/').collect::<Vec<&str>>();
90        match parts[..] {
91            [package, name] => Self::new(package, name),
92            _ => Err(Error::InvalidMessagePath {
93                name: input.into(),
94                reason: "string needs to be in `package/name` format".into(),
95            }),
96        }
97    }
98
99    /// Package that the message is located in.
100    pub fn package(&self) -> &str {
101        &self.package
102    }
103
104    /// Name of the message inside the package.
105    pub fn name(&self) -> &str {
106        &self.name
107    }
108}
109
110impl Display for MessagePath {
111    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
112        write!(f, "{}/{}", self.package(), self.name())
113    }
114}
115
116impl<'a> TryFrom<&'a str> for MessagePath {
117    type Error = Error;
118
119    fn try_from(value: &'a str) -> Result<Self> {
120        Self::from_combined(value)
121    }
122}
123
124impl From<MessagePath> for String {
125    fn from(src: MessagePath) -> Self {
126        format!("{}", src)
127    }
128}