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}