Skip to main content

ros2msg/msg/
types.rs

1/// Core types for ROS2 message parsing
2use std::collections::HashMap;
3use std::fmt;
4
5#[cfg(feature = "serde")]
6use serde::{Deserialize, Serialize};
7
8use super::errors::{ParseError, ParseResult, invalid_resource_name, invalid_type};
9use crate::msg::validation::{
10    ARRAY_UPPER_BOUND_TOKEN, PACKAGE_NAME_MESSAGE_TYPE_SEPARATOR, PRIMITIVE_TYPES, PrimitiveValue,
11    STRING_UPPER_BOUND_TOKEN, is_valid_constant_name, is_valid_field_name, is_valid_message_name,
12    is_valid_package_name, parse_primitive_value_string,
13};
14
15/// Annotations for fields, constants, and messages
16pub type Annotations = HashMap<String, AnnotationValue>;
17
18/// Annotation values can be strings, booleans, or lists of strings
19#[derive(Debug, Clone, PartialEq)]
20#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
21#[allow(missing_docs)]
22pub enum AnnotationValue {
23    String(String),
24    Bool(bool),
25    StringList(Vec<String>),
26}
27
28/// Base type information (without array specifiers)
29#[derive(Debug, Clone, PartialEq, Eq, Hash)]
30#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
31pub struct BaseType {
32    /// Package name for non-primitive types (None for primitive types)
33    pub pkg_name: Option<String>,
34    /// Type name (e.g., "string", "int32", "Pose")
35    pub type_name: String,
36    /// String upper bound for string/wstring types
37    pub string_upper_bound: Option<u32>,
38}
39
40impl BaseType {
41    /// Create a new `BaseType` from a type string
42    ///
43    /// # Errors
44    ///
45    /// Returns an error if:
46    /// - The type string contains invalid format for bounded strings
47    /// - The bound value is invalid or out of range
48    /// - The type string format is invalid or unsupported
49    pub fn new(type_string: &str, context_package_name: Option<&str>) -> ParseResult<Self> {
50        // Check for primitive types
51        if PRIMITIVE_TYPES.contains(&type_string) {
52            return Ok(BaseType {
53                pkg_name: None,
54                type_name: type_string.to_string(),
55                string_upper_bound: None,
56            });
57        }
58
59        // Check for bounded string types
60        if type_string.starts_with("string") && type_string.contains(STRING_UPPER_BOUND_TOKEN) {
61            return Self::parse_bounded_string(type_string, "string");
62        }
63        if type_string.starts_with("wstring") && type_string.contains(STRING_UPPER_BOUND_TOKEN) {
64            return Self::parse_bounded_string(type_string, "wstring");
65        }
66
67        // Parse non-primitive type
68        let parts: Vec<&str> = type_string
69            .split(PACKAGE_NAME_MESSAGE_TYPE_SEPARATOR)
70            .collect();
71
72        let (pkg_name, type_name) = match parts.len() {
73            1 => {
74                // Local type reference
75                match context_package_name {
76                    Some(pkg) => (Some(pkg.to_string()), parts[0].to_string()),
77                    None => {
78                        return Err(invalid_type(
79                            type_string,
80                            "non-primitive type requires package name or context package",
81                        ));
82                    }
83                }
84            }
85            2 => {
86                // Fully qualified type
87                (Some(parts[0].to_string()), parts[1].to_string())
88            }
89            _ => return Err(invalid_type(type_string, "invalid type format")),
90        };
91
92        // Validate package name if present
93        if let Some(ref pkg) = pkg_name
94            && !is_valid_package_name(pkg)
95        {
96            return Err(invalid_resource_name(pkg, "valid package name pattern"));
97        }
98
99        // Validate message name
100        if !is_valid_message_name(&type_name) {
101            return Err(invalid_resource_name(
102                &type_name,
103                "valid message name pattern",
104            ));
105        }
106
107        Ok(BaseType {
108            pkg_name,
109            type_name,
110            string_upper_bound: None,
111        })
112    }
113
114    fn parse_bounded_string(type_string: &str, base_type: &str) -> ParseResult<Self> {
115        let parts: Vec<&str> = type_string.split(STRING_UPPER_BOUND_TOKEN).collect();
116        if parts.len() != 2 {
117            return Err(invalid_type(type_string, "invalid bounded string format"));
118        }
119
120        let upper_bound_str = parts[1];
121        let upper_bound = upper_bound_str.parse::<u32>().map_err(|_| {
122            invalid_type(
123                type_string,
124                "string upper bound must be a valid positive integer",
125            )
126        })?;
127
128        if upper_bound == 0 {
129            return Err(invalid_type(type_string, "string upper bound must be > 0"));
130        }
131
132        Ok(BaseType {
133            pkg_name: None,
134            type_name: base_type.to_string(),
135            string_upper_bound: Some(upper_bound),
136        })
137    }
138
139    /// Check if this is a primitive type
140    #[must_use]
141    pub fn is_primitive_type(&self) -> bool {
142        self.pkg_name.is_none()
143    }
144}
145
146impl fmt::Display for BaseType {
147    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148        if let Some(ref pkg) = self.pkg_name {
149            write!(f, "{}/{}", pkg, self.type_name)
150        } else {
151            write!(f, "{}", self.type_name)?;
152            if let Some(bound) = self.string_upper_bound {
153                write!(f, "{STRING_UPPER_BOUND_TOKEN}{bound}")?;
154            }
155            Ok(())
156        }
157    }
158}
159
160/// Type information including array specifiers
161#[derive(Debug, Clone, PartialEq, Eq, Hash)]
162#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
163pub struct Type {
164    /// Base type information
165    #[cfg_attr(feature = "serde", serde(flatten))]
166    pub base_type: BaseType,
167    /// Whether this is an array type
168    pub is_array: bool,
169    /// Array size (None for dynamic arrays)
170    pub array_size: Option<u32>,
171    /// Whether `array_size` is an upper bound
172    pub is_upper_bound: bool,
173}
174
175impl Type {
176    /// Create a new Type from a type string
177    ///
178    /// # Errors
179    ///
180    /// Returns an error if:
181    /// - The type string contains invalid array syntax
182    /// - Array size bounds are invalid or out of range  
183    /// - The base type string is invalid or unsupported
184    pub fn new(type_string: &str, context_package_name: Option<&str>) -> ParseResult<Self> {
185        // Check for array brackets
186        let is_array = type_string.ends_with(']');
187        let mut array_size = None;
188        let mut is_upper_bound = false;
189        let base_type_string;
190
191        if is_array {
192            // Find the opening bracket
193            let bracket_start = type_string
194                .rfind('[')
195                .ok_or_else(|| invalid_type(type_string, "ends with ']' but missing '['"))?;
196
197            let array_spec = &type_string[bracket_start + 1..type_string.len() - 1];
198            base_type_string = &type_string[..bracket_start];
199
200            // Parse array size if specified
201            if !array_spec.is_empty() {
202                // Check for upper bound specifier
203                if let Some(size_str) = array_spec.strip_prefix(ARRAY_UPPER_BOUND_TOKEN) {
204                    is_upper_bound = true;
205                    array_size = Some(size_str.parse::<u32>().map_err(|_| {
206                        invalid_type(type_string, "array size must be a valid positive integer")
207                    })?);
208                } else {
209                    array_size = Some(array_spec.parse::<u32>().map_err(|_| {
210                        invalid_type(type_string, "array size must be a valid positive integer")
211                    })?);
212                }
213
214                // Validate array size
215                if let Some(size) = array_size
216                    && size == 0
217                {
218                    return Err(invalid_type(type_string, "array size must be > 0"));
219                }
220            }
221        } else {
222            base_type_string = type_string;
223        }
224
225        let base_type = BaseType::new(base_type_string, context_package_name)?;
226
227        Ok(Type {
228            base_type,
229            is_array,
230            array_size,
231            is_upper_bound,
232        })
233    }
234
235    /// Check if this is a primitive type
236    #[must_use]
237    pub fn is_primitive_type(&self) -> bool {
238        self.base_type.is_primitive_type()
239    }
240
241    /// Check if this is a dynamic array (array without fixed size)
242    #[must_use]
243    pub fn is_dynamic_array(&self) -> bool {
244        self.is_array && self.array_size.is_none()
245    }
246
247    /// Check if this is a bounded array (array with upper bound)
248    #[must_use]
249    pub fn is_bounded_array(&self) -> bool {
250        self.is_array && self.is_upper_bound
251    }
252}
253
254impl fmt::Display for Type {
255    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
256        write!(f, "{}", self.base_type)?;
257        if self.is_array {
258            write!(f, "[")?;
259            if self.is_upper_bound {
260                write!(f, "{ARRAY_UPPER_BOUND_TOKEN}")?;
261            }
262            if let Some(size) = self.array_size {
263                write!(f, "{size}")?;
264            }
265            write!(f, "]")?;
266        }
267        Ok(())
268    }
269}
270
271/// Value that can be assigned to fields or constants
272#[derive(Debug, Clone, PartialEq)]
273#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
274pub enum Value {
275    /// Single primitive value
276    Primitive(PrimitiveValue),
277    /// Array of primitive values
278    Array(Vec<PrimitiveValue>),
279}
280
281impl fmt::Display for Value {
282    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
283        match self {
284            Value::Primitive(v) => write!(f, "{v}"),
285            Value::Array(values) => {
286                write!(f, "[")?;
287                for (i, v) in values.iter().enumerate() {
288                    if i > 0 {
289                        write!(f, ", ")?;
290                    }
291                    write!(f, "{v}")?;
292                }
293                write!(f, "]")
294            }
295        }
296    }
297}
298
299/// Constant definition
300#[derive(Debug, Clone, PartialEq)]
301#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
302pub struct Constant {
303    /// Primitive type of the constant
304    pub type_name: String,
305    /// Name of the constant
306    pub name: String,
307    /// Value of the constant
308    pub value: PrimitiveValue,
309    /// Annotations attached to this constant
310    pub annotations: Annotations,
311}
312
313impl Constant {
314    /// Create a new constant
315    ///
316    /// # Errors
317    ///
318    /// Returns an error if:
319    /// - The primitive type is not a valid ROS2 primitive type
320    /// - The constant name doesn't follow valid naming conventions
321    /// - The value string cannot be parsed for the given primitive type
322    pub fn new(primitive_type: &str, name: &str, value_string: &str) -> ParseResult<Self> {
323        if !PRIMITIVE_TYPES.contains(&primitive_type) {
324            return Err(invalid_type(
325                primitive_type,
326                "constant type must be primitive",
327            ));
328        }
329
330        if !is_valid_constant_name(name) {
331            return Err(invalid_resource_name(name, "valid constant name pattern"));
332        }
333
334        let value = parse_primitive_value_string(primitive_type, value_string)?;
335
336        Ok(Constant {
337            type_name: primitive_type.to_string(),
338            name: name.to_string(),
339            value,
340            annotations: HashMap::new(),
341        })
342    }
343}
344
345impl fmt::Display for Constant {
346    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
347        write!(f, "{} {}={}", self.type_name, self.name, self.value)
348    }
349}
350
351/// Field definition
352#[derive(Debug, Clone, PartialEq)]
353#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
354pub struct Field {
355    /// Type of the field
356    pub field_type: Type,
357    /// Name of the field
358    pub name: String,
359    /// Default value (if any)
360    pub default_value: Option<Value>,
361    /// Annotations attached to this field
362    pub annotations: Annotations,
363}
364
365impl Field {
366    /// Create a new field
367    ///
368    /// # Errors
369    ///
370    /// Returns an error if:
371    /// - The field name doesn't follow valid naming conventions
372    /// - The default value string cannot be parsed for the field's type
373    pub fn new(
374        field_type: Type,
375        name: &str,
376        default_value_string: Option<&str>,
377    ) -> ParseResult<Self> {
378        if !is_valid_field_name(name) {
379            return Err(invalid_resource_name(name, "valid field name pattern"));
380        }
381
382        let default_value = if let Some(value_str) = default_value_string {
383            Some(parse_value_string(&field_type, value_str)?)
384        } else {
385            None
386        };
387
388        Ok(Field {
389            field_type,
390            name: name.to_string(),
391            default_value,
392            annotations: HashMap::new(),
393        })
394    }
395}
396
397impl fmt::Display for Field {
398    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
399        write!(f, "{} {}", self.field_type, self.name)?;
400        if let Some(ref value) = self.default_value {
401            write!(f, " {value}")?;
402        }
403        Ok(())
404    }
405}
406
407/// Parse value string for a given type
408fn parse_value_string(type_: &Type, value_string: &str) -> ParseResult<Value> {
409    if type_.is_primitive_type() && !type_.is_array {
410        // Single primitive value
411        let value = parse_primitive_value_string(&type_.base_type.type_name, value_string)?;
412        Ok(Value::Primitive(value))
413    } else if type_.is_primitive_type() && type_.is_array {
414        // Array of primitive values
415        let trimmed = value_string.trim();
416        if !trimmed.starts_with('[') || !trimmed.ends_with(']') {
417            return Err(ParseError::InvalidValue {
418                value: value_string.to_string(),
419                type_info: type_.to_string(),
420                reason: "array value must start with '[' and end with ']'".to_string(),
421            });
422        }
423
424        let elements_string = &trimmed[1..trimmed.len() - 1];
425        let value_strings: Vec<&str> = if elements_string.is_empty() {
426            Vec::new()
427        } else {
428            elements_string.split(',').collect()
429        };
430
431        // Validate array size constraints
432        if let Some(array_size) = type_.array_size {
433            if !type_.is_upper_bound && value_strings.len() != array_size as usize {
434                return Err(ParseError::InvalidValue {
435                    value: value_string.to_string(),
436                    type_info: type_.to_string(),
437                    reason: format!(
438                        "array must have exactly {} elements, not {}",
439                        array_size,
440                        value_strings.len()
441                    ),
442                });
443            }
444            if type_.is_upper_bound && value_strings.len() > array_size as usize {
445                return Err(ParseError::InvalidValue {
446                    value: value_string.to_string(),
447                    type_info: type_.to_string(),
448                    reason: format!(
449                        "array must have not more than {} elements, not {}",
450                        array_size,
451                        value_strings.len()
452                    ),
453                });
454            }
455        }
456
457        // Parse individual elements
458        let mut values = Vec::new();
459        for element_str in value_strings {
460            let element_str = element_str.trim();
461            let value = parse_primitive_value_string(&type_.base_type.type_name, element_str)?;
462            values.push(value);
463        }
464
465        Ok(Value::Array(values))
466    } else {
467        Err(ParseError::InvalidValue {
468            value: value_string.to_string(),
469            type_info: type_.to_string(),
470            reason: "only primitive types and primitive arrays can have default values".to_string(),
471        })
472    }
473}
474
475#[cfg(test)]
476mod tests {
477    use super::*;
478
479    #[test]
480    fn test_base_type_creation() {
481        // Primitive type
482        let base_type = BaseType::new("int32", None).unwrap();
483        assert!(base_type.is_primitive_type());
484        assert_eq!(base_type.type_name, "int32");
485
486        // Bounded string
487        let base_type = BaseType::new("string<=10", None).unwrap();
488        assert!(base_type.is_primitive_type());
489        assert_eq!(base_type.string_upper_bound, Some(10));
490
491        // Fully qualified type
492        let base_type = BaseType::new("geometry_msgs/Pose", None).unwrap();
493        assert!(!base_type.is_primitive_type());
494        assert_eq!(base_type.pkg_name, Some("geometry_msgs".to_string()));
495        assert_eq!(base_type.type_name, "Pose");
496    }
497
498    #[test]
499    fn test_type_creation() {
500        // Simple array
501        let type_ = Type::new("int32[5]", None).unwrap();
502        assert!(type_.is_array);
503        assert_eq!(type_.array_size, Some(5));
504        assert!(!type_.is_upper_bound);
505
506        // Bounded array
507        let type_ = Type::new("float64[<=10]", None).unwrap();
508        assert!(type_.is_array);
509        assert_eq!(type_.array_size, Some(10));
510        assert!(type_.is_upper_bound);
511
512        // Dynamic array
513        let type_ = Type::new("string[]", None).unwrap();
514        assert!(type_.is_array);
515        assert!(type_.is_dynamic_array());
516    }
517
518    #[test]
519    fn test_constant_creation() {
520        let constant = Constant::new("int32", "MAX_VALUE", "100").unwrap();
521        assert_eq!(constant.name, "MAX_VALUE");
522        assert_eq!(constant.value, PrimitiveValue::Int32(100));
523    }
524
525    #[test]
526    fn test_field_creation() {
527        let type_ = Type::new("string", None).unwrap();
528        let field = Field::new(type_, "name", Some("\"default\"")).unwrap();
529        assert_eq!(field.name, "name");
530        assert!(field.default_value.is_some());
531    }
532
533    #[test]
534    fn test_base_type_display() {
535        let bt = BaseType::new("int32", None).unwrap();
536        assert_eq!(bt.to_string(), "int32");
537
538        let bt = BaseType::new("geometry_msgs/Pose", None).unwrap();
539        assert_eq!(bt.to_string(), "geometry_msgs/Pose");
540
541        let bt = BaseType::new("string<=50", None).unwrap();
542        assert_eq!(bt.to_string(), "string<=50");
543    }
544
545    #[test]
546    fn test_type_display() {
547        let t = Type::new("int32", None).unwrap();
548        assert_eq!(t.to_string(), "int32");
549
550        let t = Type::new("int32[10]", None).unwrap();
551        assert_eq!(t.to_string(), "int32[10]");
552
553        let t = Type::new("int32[]", None).unwrap();
554        assert_eq!(t.to_string(), "int32[]");
555
556        let t = Type::new("int32[<=100]", None).unwrap();
557        assert_eq!(t.to_string(), "int32[<=100]");
558    }
559
560    #[test]
561    fn test_annotation_value_variants() {
562        let val = AnnotationValue::String("test".to_string());
563        assert!(matches!(val, AnnotationValue::String(_)));
564
565        let val = AnnotationValue::Bool(true);
566        assert!(matches!(val, AnnotationValue::Bool(true)));
567
568        let val = AnnotationValue::StringList(vec!["a".to_string(), "b".to_string()]);
569        assert!(matches!(val, AnnotationValue::StringList(_)));
570    }
571
572    #[test]
573    fn test_value_display() {
574        let val = Value::Primitive(PrimitiveValue::Int32(42));
575        assert_eq!(val.to_string(), "42");
576
577        let val = Value::Array(vec![PrimitiveValue::Int32(1), PrimitiveValue::Int32(2)]);
578        assert_eq!(val.to_string(), "[1, 2]");
579    }
580
581    #[test]
582    fn test_type_is_methods() {
583        let t = Type::new("int32[]", None).unwrap();
584        assert!(t.is_dynamic_array());
585        assert!(!t.is_bounded_array());
586
587        let t = Type::new("int32[<=10]", None).unwrap();
588        assert!(t.is_bounded_array());
589        assert!(!t.is_dynamic_array());
590    }
591}