ros_message/
field_info.rs

1use crate::{DataType, Error, MessagePath, Result, Value};
2use serde_derive::{Deserialize, Serialize};
3use std::collections::HashMap;
4use std::convert::{TryFrom, TryInto};
5use std::fmt;
6use std::fmt::Formatter;
7use std::hash::{Hash, Hasher};
8
9/// Represents all possible variants of a message field
10#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
11pub enum FieldCase {
12    /// Field of a single item.
13    ///
14    /// Examples: `float32`, `geometry_msgs/Point`.
15    Unit,
16    /// Field of an arbitrary length array.
17    ///
18    /// Examples: `float32[]`, `geometry_msgs/Point[]`.
19    Vector,
20    /// Field of a fixed length array.
21    ///
22    /// The contained number is the array length.
23    ///
24    /// Examples: `float32[64]`, `geometry_msgs/Point[10]`.
25    Array(usize),
26    /// Field describing a constant value.
27    ///
28    /// The contained `String` is the unparsed value.
29    ///
30    /// Example: `float32 FOO=123.4`.
31    Const(String),
32}
33
34#[derive(Clone, Debug)]
35struct Uncompared<T> {
36    inner: T,
37}
38
39impl<T> Hash for Uncompared<T> {
40    fn hash<H: Hasher>(&self, _state: &mut H) {}
41}
42
43impl<T> PartialEq for Uncompared<T> {
44    fn eq(&self, _other: &Self) -> bool {
45        true
46    }
47}
48
49impl<T> Eq for Uncompared<T> {}
50
51/// Full description of one field in a `msg` or `srv` file.
52#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
53#[serde(into = "FieldInfoSerde")]
54#[serde(try_from = "FieldInfoSerde")]
55pub struct FieldInfo {
56    datatype: DataType,
57    name: String,
58    case: FieldCase,
59    const_value: Uncompared<Option<Value>>,
60}
61
62impl fmt::Display for FieldInfo {
63    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
64        match &self.case {
65            FieldCase::Unit => write!(f, "{} {}", self.datatype, self.name),
66            FieldCase::Vector => write!(f, "{}[] {}", self.datatype, self.name),
67            FieldCase::Array(l) => write!(f, "{}[{}] {}", self.datatype, l, self.name),
68            FieldCase::Const(val) => write!(f, "{} {}={}", self.datatype, self.name, val),
69        }
70    }
71}
72
73impl FieldInfo {
74    /// Create a field of the provided type, name and variant.
75    ///
76    /// # Errors
77    ///
78    /// An error will be returned if the data type cannot be parsed, or const data is invalid.
79    ///
80    /// # Examples
81    ///
82    /// ```
83    /// # use ros_message::{DataType, FieldInfo, FieldCase};
84    /// #
85    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
86    /// let field = FieldInfo::new("int16", "foo", FieldCase::Vector)?;
87    ///
88    /// assert_eq!(field.name(), "foo");
89    /// assert_eq!(field.datatype(), &DataType::I16);
90    /// assert_eq!(field.case(), &FieldCase::Vector);
91    /// assert_eq!(format!("{}", field), "int16[] foo");
92    /// # Ok(())
93    /// # }
94    /// ```
95    ///
96    /// ```
97    /// # use ros_message::{FieldInfo, FieldCase};
98    /// assert!(FieldInfo::new("bad/field/type", "foo", FieldCase::Vector).is_err());
99    /// ```
100    pub fn new(datatype: &str, name: impl Into<String>, case: FieldCase) -> Result<FieldInfo> {
101        Self::evaluate(datatype.try_into()?, name.into(), case)
102    }
103
104    fn evaluate(datatype: DataType, name: String, case: FieldCase) -> Result<FieldInfo> {
105        let const_value = match &case {
106            FieldCase::Const(raw_value) => Some(
107                match &datatype {
108                    DataType::Bool => Some(Value::Bool(raw_value != "0")),
109                    DataType::I8(_) => raw_value.parse().ok().map(Value::I8),
110                    DataType::I16 => raw_value.parse().ok().map(Value::I16),
111                    DataType::I32 => raw_value.parse().ok().map(Value::I32),
112                    DataType::I64 => raw_value.parse().ok().map(Value::I64),
113                    DataType::U8(_) => raw_value.parse().ok().map(Value::U8),
114                    DataType::U16 => raw_value.parse().ok().map(Value::U16),
115                    DataType::U32 => raw_value.parse().ok().map(Value::U32),
116                    DataType::U64 => raw_value.parse().ok().map(Value::U64),
117                    DataType::F32 => raw_value.parse().ok().map(Value::F32),
118                    DataType::F64 => raw_value.parse().ok().map(Value::F64),
119                    DataType::String => Some(Value::String(raw_value.clone())),
120                    DataType::Time
121                    | DataType::Duration
122                    | DataType::LocalMessage(_)
123                    | DataType::GlobalMessage(_) => None,
124                }
125                .ok_or_else(|| Error::BadConstant {
126                    name: name.clone(),
127                    datatype: format!("{}", datatype),
128                    value: raw_value.into(),
129                })?,
130            ),
131            FieldCase::Unit | FieldCase::Vector | FieldCase::Array(_) => None,
132        };
133        Ok(FieldInfo {
134            datatype,
135            name,
136            case,
137            const_value: Uncompared { inner: const_value },
138        })
139    }
140
141    /// Returns the data type of the field.
142    pub fn datatype(&self) -> &DataType {
143        &self.datatype
144    }
145
146    /// Returns the name of the field.
147    pub fn name(&self) -> &str {
148        &self.name
149    }
150
151    /// Returns the case of the field.
152    pub fn case(&self) -> &FieldCase {
153        &self.case
154    }
155
156    /// Returns the stored value if a constant field.
157    pub fn const_value(&self) -> Option<&Value> {
158        self.const_value.inner.as_ref()
159    }
160
161    /// Returns true if the field contains a constant value.
162    ///
163    /// # Examples
164    ///
165    /// ```
166    /// # use ros_message::{DataType, FieldInfo, FieldCase};
167    /// #
168    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
169    /// assert!(!FieldInfo::new("int16", "foo", FieldCase::Vector)?.is_constant());
170    /// assert!(FieldInfo::new("int16", "foo", FieldCase::Const("12".into()))?.is_constant());
171    /// # Ok(())
172    /// # }
173    /// ```
174    pub fn is_constant(&self) -> bool {
175        matches!(self.case, FieldCase::Const(..))
176    }
177
178    /// Returns the representation of the data type when constructing the MD5 sum.
179    ///
180    /// For built in types, it is the same as the message row, but with consistent whitespace.
181    ///
182    /// For message types, the type is replaced with the message's MD5 sum,
183    /// which is passed in via the `hashes` argument.
184    ///
185    /// The `package` argument should be the package that the current message is in, to resolve
186    /// global paths of local message dependencies.
187    ///
188    /// # Errors
189    ///
190    /// An error will be returned if a message we depend upon is missing.
191    ///
192    /// # Examples
193    ///
194    /// ```
195    /// # use ros_message::{FieldInfo, FieldCase};
196    /// # use std::convert::TryInto;
197    /// # use std::collections::HashMap;
198    /// #
199    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
200    /// let mut hashes = HashMap::new();
201    /// hashes.insert("foo/Header".try_into()?, "wrong_header".into());
202    /// hashes.insert("std_msgs/Header".try_into()?, "123".into());
203    /// hashes.insert("geometry_msgs/Position".try_into()?, "345".into());
204    /// hashes.insert("foo/Position".try_into()?, "678".into());
205    ///
206    /// assert_eq!(
207    ///     FieldInfo::new("int16", "foo", FieldCase::Unit)?.md5_string("foo", &hashes)?,
208    ///     "int16 foo",
209    /// );
210    /// assert_eq!(
211    ///     FieldInfo::new("float64", "foo", FieldCase::Vector)?.md5_string("foo", &hashes)?,
212    ///     "float64[] foo",
213    /// );
214    /// assert_eq!(
215    ///     FieldInfo::new("byte", "foo", FieldCase::Array(12))?.md5_string("foo", &hashes)?,
216    ///     "byte[12] foo",
217    /// );
218    /// assert_eq!(
219    ///     FieldInfo::new("byte", "FOO", FieldCase::Const("12".into()))?.md5_string("foo", &hashes)?,
220    ///     "byte FOO=12",
221    /// );
222    /// assert_eq!(
223    ///     FieldInfo::new("Header", "foo", FieldCase::Unit)?.md5_string("foo", &hashes)?,
224    ///     "123 foo",
225    /// );
226    /// assert_eq!(
227    ///     FieldInfo::new("Position", "foo", FieldCase::Vector)?.md5_string("foo", &hashes)?,
228    ///     "678 foo",
229    /// );
230    /// assert_eq!(
231    ///     FieldInfo::new("geometry_msgs/Position", "foo", FieldCase::Array(12))?.md5_string("foo", &hashes)?,
232    ///     "345 foo",
233    /// );
234    /// assert!(
235    ///     FieldInfo::new("other_msgs/Position", "foo", FieldCase::Unit)?
236    ///         .md5_string("foo", &hashes)
237    ///         .is_err(),
238    /// );
239    /// # Ok(())
240    /// # }
241    /// ```
242    pub fn md5_string(
243        &self,
244        package: &str,
245        hashes: &HashMap<MessagePath, String>,
246    ) -> Result<String> {
247        let datatype = self.datatype.md5_str(package, hashes)?;
248        Ok(match (self.datatype.is_builtin(), &self.case) {
249            (_, FieldCase::Const(v)) => format!("{} {}={}", datatype, self.name, v),
250            (false, _) | (_, &FieldCase::Unit) => format!("{} {}", datatype, self.name),
251            (true, &FieldCase::Vector) => format!("{}[] {}", datatype, self.name),
252            (true, &FieldCase::Array(l)) => format!("{}[{}] {}", datatype, l, self.name),
253        })
254    }
255
256    /// Returns true if this is a header field.
257    ///
258    /// The header field is special, being a unit value of type `std_msgs/Header`
259    /// and named `header`. Also in this special case, the package can be elided,
260    /// even if we're not in the same package.
261    ///
262    /// If any of those requirements are not met, it is not a header field.
263    ///
264    /// The field is special because ROS channel publishers are allowed to populate it with
265    /// the node and publisher specific data.
266    ///
267    /// # Examples
268    ///
269    /// ```
270    /// # use ros_message::{FieldInfo, FieldCase};
271    /// #
272    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
273    /// assert!(FieldInfo::new("Header", "header", FieldCase::Unit)?.is_header());
274    /// assert!(FieldInfo::new("std_msgs/Header", "header", FieldCase::Unit)?.is_header());
275    /// assert!(!FieldInfo::new("Header", "header", FieldCase::Vector)?.is_header());
276    /// assert!(!FieldInfo::new("Header", "header", FieldCase::Array(5))?.is_header());
277    /// assert!(FieldInfo::new("Header", "header", FieldCase::Const("12".into())).is_err());
278    /// assert!(!FieldInfo::new("Header", "some_field", FieldCase::Unit)?.is_header());
279    /// # Ok(())
280    /// # }
281    /// ```
282    pub fn is_header(&self) -> bool {
283        if self.case != FieldCase::Unit || self.name != "header" {
284            return false;
285        }
286        match &self.datatype {
287            DataType::GlobalMessage(msg) => msg.package() == "std_msgs" && msg.name() == "Header",
288            _ => false,
289        }
290    }
291}
292
293#[derive(Serialize, Deserialize)]
294struct FieldInfoSerde {
295    datatype: DataType,
296    name: String,
297    case: FieldCase,
298}
299
300impl TryFrom<FieldInfoSerde> for FieldInfo {
301    type Error = Error;
302
303    fn try_from(src: FieldInfoSerde) -> Result<Self> {
304        Self::evaluate(src.datatype, src.name, src.case)
305    }
306}
307
308impl From<FieldInfo> for FieldInfoSerde {
309    fn from(src: FieldInfo) -> Self {
310        Self {
311            datatype: src.datatype,
312            name: src.name,
313            case: src.case,
314        }
315    }
316}