ros_message/
srv.rs

1use crate::{Error, MessagePath, Msg, Result};
2use lazy_static::lazy_static;
3use regex::RegexBuilder;
4use serde_derive::{Deserialize, Serialize};
5use std::convert::TryFrom;
6use std::fmt;
7use std::fmt::Formatter;
8
9/// A ROS service parsed from a `srv` file.
10#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
11#[serde(into = "SrvSerde")]
12#[serde(try_from = "SrvSerde")]
13pub struct Srv {
14    path: MessagePath,
15    source: String,
16    req: Msg,
17    res: Msg,
18}
19
20impl fmt::Display for Srv {
21    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
22        self.source.fmt(f)
23    }
24}
25
26impl Srv {
27    /// Create a service from a passed in path and source.
28    ///
29    /// # Errors
30    ///
31    /// Returns an error if there is an error parsing the service source.
32    ///
33    /// # Examples
34    ///
35    /// ```
36    /// # use ros_message::Srv;
37    /// # use std::convert::TryInto;
38    /// #
39    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
40    /// let service = Srv::new(
41    ///     "foo/Bar".try_into()?,
42    ///     r#"# a comment that is ignored
43    ///     Header header
44    ///     uint32 a
45    ///     byte[16] b
46    ///     geometry_msgs/Point[] point
47    ///     uint32 FOO=5
48    ///     string SOME_TEXT=this is # some text, don't be fooled by the hash
49    /// ---
50    ///     uint32 a
51    ///     geometry_msgs/Point[] point
52    ///     uint32 FOO=6
53    ///     "#,
54    /// )?;
55    ///
56    /// assert_eq!(service.path(), &"foo/Bar".try_into()?);
57    /// assert_eq!(service.request().fields().len(), 6);
58    /// assert_eq!(service.response().fields().len(), 3);
59    /// # Ok(())
60    /// # }
61    /// ```
62    pub fn new(path: MessagePath, source: impl Into<String>) -> Result<Srv> {
63        let source = source.into();
64        let (req, res) = Self::build_req_res(&path, &source)?;
65        Ok(Srv {
66            path,
67            source,
68            req,
69            res,
70        })
71    }
72
73    /// Returns the path of the service.
74    pub fn path(&self) -> &MessagePath {
75        &self.path
76    }
77
78    /// Returns the original source.
79    pub fn source(&self) -> &str {
80        &self.source
81    }
82
83    /// Returns the request message.
84    pub fn request(&self) -> &Msg {
85        &self.req
86    }
87
88    /// Returns the response message.
89    pub fn response(&self) -> &Msg {
90        &self.res
91    }
92
93    fn build_req_res(path: &MessagePath, source: &str) -> Result<(Msg, Msg)> {
94        lazy_static! {
95            static ref RE_SPLIT: regex::Regex = RegexBuilder::new("^---$")
96                .multi_line(true)
97                .build()
98                .expect("Invalid regex `^---$`");
99        }
100        let (req, res) = match RE_SPLIT.split(source).collect::<Vec<_>>().as_slice() {
101            &[req] => (req, ""),
102            &[req, res] => (req, res),
103            &[] => {
104                return Err(Error::BadMessageContent(format!(
105                    "Service {} does not have any content",
106                    path
107                )))
108            }
109            v => {
110                return Err(Error::BadMessageContent(format!(
111                    "Service {} is split into {} parts",
112                    path,
113                    v.len()
114                )))
115            }
116        };
117
118        Ok((
119            Msg::new(path.peer(format!("{}Req", path.name())), req)?,
120            Msg::new(path.peer(format!("{}Res", path.name())), res)?,
121        ))
122    }
123}
124
125#[derive(Serialize, Deserialize)]
126struct SrvSerde {
127    path: MessagePath,
128    source: String,
129}
130
131impl TryFrom<SrvSerde> for Srv {
132    type Error = Error;
133
134    fn try_from(src: SrvSerde) -> Result<Self> {
135        Self::new(src.path, &src.source)
136    }
137}
138
139impl From<Srv> for SrvSerde {
140    fn from(src: Srv) -> Self {
141        Self {
142            path: src.path,
143            source: src.source,
144        }
145    }
146}