1use 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
15pub type Annotations = HashMap<String, AnnotationValue>;
17
18#[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#[derive(Debug, Clone, PartialEq, Eq, Hash)]
30#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
31pub struct BaseType {
32 pub pkg_name: Option<String>,
34 pub type_name: String,
36 pub string_upper_bound: Option<u32>,
38}
39
40impl BaseType {
41 pub fn new(type_string: &str, context_package_name: Option<&str>) -> ParseResult<Self> {
50 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 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 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 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 (Some(parts[0].to_string()), parts[1].to_string())
88 }
89 _ => return Err(invalid_type(type_string, "invalid type format")),
90 };
91
92 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 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 #[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#[derive(Debug, Clone, PartialEq, Eq, Hash)]
162#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
163pub struct Type {
164 #[cfg_attr(feature = "serde", serde(flatten))]
166 pub base_type: BaseType,
167 pub is_array: bool,
169 pub array_size: Option<u32>,
171 pub is_upper_bound: bool,
173}
174
175impl Type {
176 pub fn new(type_string: &str, context_package_name: Option<&str>) -> ParseResult<Self> {
185 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 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 if !array_spec.is_empty() {
202 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 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 #[must_use]
237 pub fn is_primitive_type(&self) -> bool {
238 self.base_type.is_primitive_type()
239 }
240
241 #[must_use]
243 pub fn is_dynamic_array(&self) -> bool {
244 self.is_array && self.array_size.is_none()
245 }
246
247 #[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#[derive(Debug, Clone, PartialEq)]
273#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
274pub enum Value {
275 Primitive(PrimitiveValue),
277 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#[derive(Debug, Clone, PartialEq)]
301#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
302pub struct Constant {
303 pub type_name: String,
305 pub name: String,
307 pub value: PrimitiveValue,
309 pub annotations: Annotations,
311}
312
313impl Constant {
314 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#[derive(Debug, Clone, PartialEq)]
353#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
354pub struct Field {
355 pub field_type: Type,
357 pub name: String,
359 pub default_value: Option<Value>,
361 pub annotations: Annotations,
363}
364
365impl Field {
366 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
407fn parse_value_string(type_: &Type, value_string: &str) -> ParseResult<Value> {
409 if type_.is_primitive_type() && !type_.is_array {
410 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 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 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 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 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 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 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 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 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 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}