1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
use crate::{Error, MessagePath, Msg, Result};
use lazy_static::lazy_static;
use regex::RegexBuilder;
use serde_derive::{Deserialize, Serialize};
use std::convert::TryFrom;
use std::fmt;
use std::fmt::Formatter;

/// A ROS service parsed from a `srv` file.
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(into = "SrvSerde")]
#[serde(try_from = "SrvSerde")]
pub struct Srv {
    path: MessagePath,
    source: String,
    req: Msg,
    res: Msg,
}

impl fmt::Display for Srv {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        self.source.fmt(f)
    }
}

impl Srv {
    /// Create a service from a passed in path and source.
    ///
    /// # Errors
    ///
    /// Returns an error if there is an error parsing the service source.
    ///
    /// # Examples
    ///
    /// ```
    /// # use ros_message::Srv;
    /// # use std::convert::TryInto;
    /// #
    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
    /// let service = Srv::new(
    ///     "foo/Bar".try_into()?,
    ///     r#"# a comment that is ignored
    ///     Header header
    ///     uint32 a
    ///     byte[16] b
    ///     geometry_msgs/Point[] point
    ///     uint32 FOO=5
    ///     string SOME_TEXT=this is # some text, don't be fooled by the hash
    /// ---
    ///     uint32 a
    ///     geometry_msgs/Point[] point
    ///     uint32 FOO=6
    ///     "#,
    /// )?;
    ///
    /// assert_eq!(service.path(), &"foo/Bar".try_into()?);
    /// assert_eq!(service.request().fields().len(), 6);
    /// assert_eq!(service.response().fields().len(), 3);
    /// # Ok(())
    /// # }
    /// ```
    pub fn new(path: MessagePath, source: impl Into<String>) -> Result<Srv> {
        let source = source.into();
        let (req, res) = Self::build_req_res(&path, &source)?;
        Ok(Srv {
            path,
            source,
            req,
            res,
        })
    }

    /// Returns the path of the service.
    pub fn path(&self) -> &MessagePath {
        &self.path
    }

    /// Returns the original source.
    pub fn source(&self) -> &str {
        &self.source
    }

    /// Returns the request message.
    pub fn request(&self) -> &Msg {
        &self.req
    }

    /// Returns the response message.
    pub fn response(&self) -> &Msg {
        &self.res
    }

    fn build_req_res(path: &MessagePath, source: &str) -> Result<(Msg, Msg)> {
        lazy_static! {
            static ref RE_SPLIT: regex::Regex = RegexBuilder::new("^---$")
                .multi_line(true)
                .build()
                .expect("Invalid regex `^---$`");
        }
        let (req, res) = match RE_SPLIT.split(source).collect::<Vec<_>>().as_slice() {
            &[req] => (req, ""),
            &[req, res] => (req, res),
            &[] => {
                return Err(Error::BadMessageContent(format!(
                    "Service {} does not have any content",
                    path
                )))
            }
            v => {
                return Err(Error::BadMessageContent(format!(
                    "Service {} is split into {} parts",
                    path,
                    v.len()
                )))
            }
        };

        Ok((
            Msg::new(path.peer(format!("{}Req", path.name())), req)?,
            Msg::new(path.peer(format!("{}Res", path.name())), res)?,
        ))
    }
}

#[derive(Serialize, Deserialize)]
struct SrvSerde {
    path: MessagePath,
    source: String,
}

impl TryFrom<SrvSerde> for Srv {
    type Error = Error;

    fn try_from(src: SrvSerde) -> Result<Self> {
        Self::new(src.path, &src.source)
    }
}

impl From<Srv> for SrvSerde {
    fn from(src: Srv) -> Self {
        Self {
            path: src.path,
            source: src.source,
        }
    }
}