Skip to main content

hiroz_schema/
type_description.rs

1//! ROS 2 TypeDescription types
2//!
3//! These types match the ROS 2 `type_description_interfaces` package exactly,
4//! enabling wire-format compatibility for type description services.
5
6use serde::{Deserialize, Serialize};
7
8/// ROS2 TypeDescription message structure
9///
10/// This is the top-level type description containing the main type
11/// and all referenced (nested) type descriptions.
12#[derive(Debug, Clone, Serialize, Deserialize, Default)]
13pub struct TypeDescriptionMsg {
14    /// Description of the main type
15    pub type_description: TypeDescription,
16    /// Descriptions of all referenced types (nested messages)
17    pub referenced_type_descriptions: Vec<TypeDescription>,
18}
19
20/// Type description for a single message type
21///
22/// Matches `type_description_interfaces/msg/IndividualTypeDescription`
23#[derive(Debug, Clone, Serialize, Deserialize, Default)]
24pub struct TypeDescription {
25    /// Fully qualified type name (e.g., "std_msgs/msg/String")
26    pub type_name: String,
27    /// Fields in this type
28    pub fields: Vec<FieldDescription>,
29}
30
31/// Field description in a type
32///
33/// Matches `type_description_interfaces/msg/Field`
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct FieldDescription {
36    /// Field name
37    pub name: String,
38    /// Field type information
39    #[serde(rename = "type")]
40    pub field_type: FieldTypeDescription,
41    /// Default value (empty string for RIHS01 hash computation)
42    #[serde(default)]
43    pub default_value: String,
44}
45
46/// Field type description with type ID
47///
48/// Matches `type_description_interfaces/msg/FieldType`
49#[derive(Debug, Clone, Serialize, Deserialize, Default)]
50pub struct FieldTypeDescription {
51    /// Type ID (1-166, see `TypeId` constants)
52    pub type_id: u8,
53    /// Array/sequence capacity (0 for single values and unbounded sequences)
54    pub capacity: u64,
55    /// String capacity for bounded strings (0 otherwise)
56    pub string_capacity: u64,
57    /// Nested type name (empty for primitives)
58    pub nested_type_name: String,
59}
60
61impl FieldTypeDescription {
62    /// Create a primitive type description
63    pub fn primitive(type_id: u8) -> Self {
64        Self {
65            type_id,
66            capacity: 0,
67            string_capacity: 0,
68            nested_type_name: String::new(),
69        }
70    }
71
72    /// Create a nested type description
73    pub fn nested(type_id: u8, nested_type_name: impl Into<String>) -> Self {
74        Self {
75            type_id,
76            capacity: 0,
77            string_capacity: 0,
78            nested_type_name: nested_type_name.into(),
79        }
80    }
81
82    /// Create an array type description
83    pub fn array(type_id: u8, capacity: u64) -> Self {
84        Self {
85            type_id,
86            capacity,
87            string_capacity: 0,
88            nested_type_name: String::new(),
89        }
90    }
91
92    /// Create a nested array type description
93    pub fn nested_array(type_id: u8, capacity: u64, nested_type_name: impl Into<String>) -> Self {
94        Self {
95            type_id,
96            capacity,
97            string_capacity: 0,
98            nested_type_name: nested_type_name.into(),
99        }
100    }
101}
102
103/// Field description for hash computation (excludes default_value)
104#[derive(Serialize)]
105pub struct FieldDescriptionForHash<'a> {
106    /// Field name
107    pub name: &'a str,
108    /// Field type
109    #[serde(rename = "type")]
110    pub field_type: &'a FieldTypeDescription,
111}
112
113/// Type description for hash computation
114#[derive(Serialize)]
115pub struct TypeDescriptionForHash<'a> {
116    /// Type name
117    pub type_name: &'a str,
118    /// Fields
119    pub fields: Vec<FieldDescriptionForHash<'a>>,
120}
121
122/// TypeDescriptionMsg for hash computation
123#[derive(Serialize)]
124pub struct TypeDescriptionMsgForHash<'a> {
125    /// Main type description
126    pub type_description: TypeDescriptionForHash<'a>,
127    /// Referenced type descriptions
128    pub referenced_type_descriptions: Vec<TypeDescriptionForHash<'a>>,
129}
130
131/// Convert TypeDescriptionMsg to hash-computation version (excludes default_value)
132pub fn to_hash_version(msg: &TypeDescriptionMsg) -> TypeDescriptionMsgForHash<'_> {
133    TypeDescriptionMsgForHash {
134        type_description: TypeDescriptionForHash {
135            type_name: &msg.type_description.type_name,
136            fields: msg
137                .type_description
138                .fields
139                .iter()
140                .map(|f| FieldDescriptionForHash {
141                    name: &f.name,
142                    field_type: &f.field_type,
143                })
144                .collect(),
145        },
146        referenced_type_descriptions: msg
147            .referenced_type_descriptions
148            .iter()
149            .map(|td| TypeDescriptionForHash {
150                type_name: &td.type_name,
151                fields: td
152                    .fields
153                    .iter()
154                    .map(|f| FieldDescriptionForHash {
155                        name: &f.name,
156                        field_type: &f.field_type,
157                    })
158                    .collect(),
159            })
160            .collect(),
161    }
162}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167
168    #[test]
169    fn test_type_description_serialization() {
170        let td = TypeDescription {
171            type_name: "std_msgs/msg/String".to_string(),
172            fields: vec![FieldDescription {
173                name: "data".to_string(),
174                field_type: FieldTypeDescription::primitive(17), // STRING
175                default_value: String::new(),
176            }],
177        };
178
179        let json = serde_json::to_string(&td).unwrap();
180        assert!(json.contains("std_msgs/msg/String"));
181        assert!(json.contains("data"));
182    }
183
184    #[test]
185    fn test_field_type_description_builders() {
186        let prim = FieldTypeDescription::primitive(6); // INT32
187        assert_eq!(prim.type_id, 6);
188        assert_eq!(prim.capacity, 0);
189        assert!(prim.nested_type_name.is_empty());
190
191        let nested = FieldTypeDescription::nested(1, "geometry_msgs/msg/Point");
192        assert_eq!(nested.type_id, 1);
193        assert_eq!(nested.nested_type_name, "geometry_msgs/msg/Point");
194
195        let arr = FieldTypeDescription::array(54, 10); // INT32_ARRAY
196        assert_eq!(arr.type_id, 54);
197        assert_eq!(arr.capacity, 10);
198    }
199}