1pub mod action;
9pub mod errors;
11pub mod message;
13pub mod service;
15pub mod types;
17pub mod validation;
19
20pub use action::{
22 ActionSpecification, create_feedback_message, parse_action_file, parse_action_string,
23};
24pub use errors::{ParseError, ParseResult};
25pub use message::{MessageSpecification, parse_message_file, parse_message_string};
26pub use service::{
27 ServiceSpecification, create_service_event_message, parse_service_file, parse_service_string,
28};
29pub use types::{AnnotationValue, Annotations, BaseType, Constant, Field, Type, Value};
30pub use validation::{
31 PRIMITIVE_TYPES, PrimitiveValue, is_valid_constant_name, is_valid_field_name,
32 is_valid_message_name, is_valid_package_name, parse_primitive_value_string,
33};
34
35#[derive(Debug, Clone, PartialEq)]
37#[allow(clippy::large_enum_variant)]
38pub enum InterfaceSpecification {
39 Message(MessageSpecification),
41 Service(ServiceSpecification),
43 Action(ActionSpecification),
45}
46
47impl InterfaceSpecification {
48 #[must_use]
50 pub fn package_name(&self) -> &str {
51 match self {
52 InterfaceSpecification::Message(spec) => &spec.pkg_name,
53 InterfaceSpecification::Service(spec) => &spec.pkg_name,
54 InterfaceSpecification::Action(spec) => &spec.pkg_name,
55 }
56 }
57
58 #[must_use]
60 pub fn interface_name(&self) -> &str {
61 match self {
62 InterfaceSpecification::Message(spec) => &spec.msg_name,
63 InterfaceSpecification::Service(spec) => &spec.srv_name,
64 InterfaceSpecification::Action(spec) => &spec.action_name,
65 }
66 }
67
68 #[must_use]
70 pub fn full_name(&self) -> String {
71 format!("{}/{}", self.package_name(), self.interface_name())
72 }
73
74 #[must_use]
76 pub fn is_message(&self) -> bool {
77 matches!(self, InterfaceSpecification::Message(_))
78 }
79
80 #[must_use]
82 pub fn is_service(&self) -> bool {
83 matches!(self, InterfaceSpecification::Service(_))
84 }
85
86 #[must_use]
88 pub fn is_action(&self) -> bool {
89 matches!(self, InterfaceSpecification::Action(_))
90 }
91
92 #[must_use]
94 pub fn as_message(&self) -> Option<&MessageSpecification> {
95 match self {
96 InterfaceSpecification::Message(spec) => Some(spec),
97 _ => None,
98 }
99 }
100
101 #[must_use]
103 pub fn as_service(&self) -> Option<&ServiceSpecification> {
104 match self {
105 InterfaceSpecification::Service(spec) => Some(spec),
106 _ => None,
107 }
108 }
109
110 #[must_use]
112 pub fn as_action(&self) -> Option<&ActionSpecification> {
113 match self {
114 InterfaceSpecification::Action(spec) => Some(spec),
115 _ => None,
116 }
117 }
118}
119
120impl std::fmt::Display for InterfaceSpecification {
121 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122 match self {
123 InterfaceSpecification::Message(msg) => write!(f, "{msg}"),
124 InterfaceSpecification::Service(srv) => write!(f, "{srv}"),
125 InterfaceSpecification::Action(action) => write!(f, "{action}"),
126 }
127 }
128}
129
130pub fn parse_interface_file(
153 pkg_name: &str,
154 file_path: &std::path::Path,
155) -> ParseResult<InterfaceSpecification> {
156 let extension = file_path
157 .extension()
158 .and_then(|ext| ext.to_str())
159 .ok_or_else(|| ParseError::InvalidType {
160 type_string: "file extension".to_string(),
161 reason: "File must have an extension".to_string(),
162 })?;
163
164 match extension {
165 "msg" => {
166 let msg_spec = parse_message_file(pkg_name, file_path)?;
167 Ok(InterfaceSpecification::Message(msg_spec))
168 }
169 "srv" => {
170 let srv_spec = parse_service_file(pkg_name, file_path)?;
171 Ok(InterfaceSpecification::Service(srv_spec))
172 }
173 "action" => {
174 let action_spec = parse_action_file(pkg_name, file_path)?;
175 Ok(InterfaceSpecification::Action(action_spec))
176 }
177 _ => Err(ParseError::InvalidType {
178 type_string: extension.to_string(),
179 reason: "Expected .msg, .srv, or .action extension".to_string(),
180 }),
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187
188 #[test]
189 fn test_interface_specification_message_methods() {
190 let msg = parse_message_string("test_pkg", "TestMsg", "int32 x\n").unwrap();
191 let iface = InterfaceSpecification::Message(msg);
192
193 assert_eq!(iface.package_name(), "test_pkg");
194 assert_eq!(iface.interface_name(), "TestMsg");
195 assert_eq!(iface.full_name(), "test_pkg/TestMsg");
196 assert!(iface.is_message());
197 assert!(!iface.is_service());
198 assert!(!iface.is_action());
199 assert!(iface.as_message().is_some());
200 assert!(iface.as_service().is_none());
201 assert!(iface.as_action().is_none());
202 }
203
204 #[test]
205 fn test_interface_specification_service_methods() {
206 let srv = parse_service_string("test_pkg", "TestSrv", "int32 a\n---\nint32 b\n").unwrap();
207 let iface = InterfaceSpecification::Service(srv);
208
209 assert_eq!(iface.package_name(), "test_pkg");
210 assert_eq!(iface.interface_name(), "TestSrv");
211 assert_eq!(iface.full_name(), "test_pkg/TestSrv");
212 assert!(!iface.is_message());
213 assert!(iface.is_service());
214 assert!(!iface.is_action());
215 assert!(iface.as_message().is_none());
216 assert!(iface.as_service().is_some());
217 assert!(iface.as_action().is_none());
218 }
219
220 #[test]
221 fn test_interface_specification_action_methods() {
222 let action = parse_action_string(
223 "test_pkg",
224 "TestAction",
225 "int32 x\n---\nint32 y\n---\nint32 z\n",
226 )
227 .unwrap();
228 let iface = InterfaceSpecification::Action(action);
229
230 assert_eq!(iface.package_name(), "test_pkg");
231 assert_eq!(iface.interface_name(), "TestAction");
232 assert_eq!(iface.full_name(), "test_pkg/TestAction");
233 assert!(!iface.is_message());
234 assert!(!iface.is_service());
235 assert!(iface.is_action());
236 assert!(iface.as_message().is_none());
237 assert!(iface.as_service().is_none());
238 assert!(iface.as_action().is_some());
239 }
240
241 #[test]
242 fn test_interface_specification_display() {
243 let msg = parse_message_string("pkg", "Msg", "int32 x\n").unwrap();
244 let iface = InterfaceSpecification::Message(msg);
245 let display = format!("{iface}");
246 assert!(display.contains("Msg"));
247 }
248
249 #[test]
250 fn test_parse_interface_file_msg() {
251 let temp_dir = std::env::temp_dir();
252 let path = temp_dir.join("Test.msg");
253 std::fs::write(&path, "int32 value\n").unwrap();
254
255 let result = parse_interface_file("test_pkg", &path);
256 assert!(result.is_ok());
257 assert!(result.unwrap().is_message());
258
259 std::fs::remove_file(&path).ok();
260 }
261
262 #[test]
263 fn test_parse_interface_file_srv() {
264 let temp_dir = std::env::temp_dir();
265 let path = temp_dir.join("Test.srv");
266 std::fs::write(&path, "int32 a\n---\nint32 b\n").unwrap();
267
268 let result = parse_interface_file("test_pkg", &path);
269 assert!(result.is_ok());
270 assert!(result.unwrap().is_service());
271
272 std::fs::remove_file(&path).ok();
273 }
274
275 #[test]
276 fn test_parse_interface_file_action() {
277 let temp_dir = std::env::temp_dir();
278 let path = temp_dir.join("Test.action");
279 std::fs::write(&path, "int32 x\n---\nint32 y\n---\nint32 z\n").unwrap();
280
281 let result = parse_interface_file("test_pkg", &path);
282 assert!(result.is_ok());
283 assert!(result.unwrap().is_action());
284
285 std::fs::remove_file(&path).ok();
286 }
287
288 #[test]
289 fn test_parse_interface_file_invalid_extension() {
290 let temp_dir = std::env::temp_dir();
291 let path = temp_dir.join("test.txt");
292 std::fs::write(&path, "int32 x\n").unwrap();
293
294 let result = parse_interface_file("test_pkg", &path);
295 assert!(result.is_err());
296
297 std::fs::remove_file(&path).ok();
298 }
299
300 #[test]
301 fn test_parse_interface_file_no_extension() {
302 let temp_dir = std::env::temp_dir();
303 let path = temp_dir.join("test");
304 std::fs::write(&path, "int32 x\n").unwrap();
305
306 let result = parse_interface_file("test_pkg", &path);
307 assert!(result.is_err());
308
309 std::fs::remove_file(&path).ok();
310 }
311}