Skip to main content

edgefirst_schemas/
schema_registry.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright © 2025 Au-Zone Technologies. All Rights Reserved.
3
4//! Schema Registry for EdgeFirst Schemas.
5//!
6//! This module provides functions to work with ROS2-style schema names.
7//!
8//! Schema names follow the ROS2 convention: `package/msg/TypeName`
9//! (e.g., `sensor_msgs/msg/Image`, `geometry_msgs/msg/Pose`)
10//!
11//! # Example
12//!
13//! ```rust
14//! use edgefirst_schemas::schema_registry::{SchemaType, is_supported};
15//! use edgefirst_schemas::sensor_msgs::NavSatStatus;
16//!
17//! // Get schema name from type
18//! assert_eq!(NavSatStatus::SCHEMA_NAME, "sensor_msgs/msg/NavSatStatus");
19//!
20//! // Check if schema is supported
21//! assert!(is_supported("sensor_msgs/msg/Image"));
22//! assert!(!is_supported("unknown_msgs/msg/Foo"));
23//! ```
24
25use crate::{
26    builtin_interfaces, edgefirst_msgs, foxglove_msgs, geometry_msgs, mavros_msgs, nav_msgs,
27    sensor_msgs, std_msgs,
28};
29
30/// Trait for types that have a schema name.
31///
32/// All message types implement this trait to provide their ROS2 schema name.
33pub trait SchemaType {
34    /// The ROS2 schema name (e.g., "sensor_msgs/msg/Image")
35    const SCHEMA_NAME: &'static str;
36
37    /// Returns the schema name for this type.
38    fn schema_name() -> &'static str {
39        Self::SCHEMA_NAME
40    }
41}
42
43/// Parse a schema name into package and type components.
44///
45/// # Arguments
46/// * `schema` - Schema name (e.g., "sensor_msgs/msg/Image")
47///
48/// # Returns
49/// * `Some((package, type_name))` if valid format
50/// * `None` if invalid format
51///
52/// # Example
53/// ```rust
54/// use edgefirst_schemas::schema_registry::parse_schema;
55///
56/// let (pkg, typ) = parse_schema("sensor_msgs/msg/Image").unwrap();
57/// assert_eq!(pkg, "sensor_msgs");
58/// assert_eq!(typ, "Image");
59/// ```
60pub fn parse_schema(schema: &str) -> Option<(&str, &str)> {
61    let parts: Vec<&str> = schema.split('/').collect();
62    if parts.len() == 3 && parts[1] == "msg" {
63        Some((parts[0], parts[2]))
64    } else {
65        None
66    }
67}
68
69/// Check if a schema name is supported by this library.
70///
71/// Uses hierarchical dispatch to the appropriate package module.
72///
73/// # Example
74///
75/// ```rust
76/// use edgefirst_schemas::schema_registry::is_supported;
77///
78/// assert!(is_supported("sensor_msgs/msg/Image"));
79/// assert!(!is_supported("unknown_msgs/msg/Foo"));
80/// ```
81pub fn is_supported(schema: &str) -> bool {
82    let Some((package, type_name)) = parse_schema(schema) else {
83        return false;
84    };
85
86    match package {
87        "builtin_interfaces" => builtin_interfaces::is_type_supported(type_name),
88        "std_msgs" => std_msgs::is_type_supported(type_name),
89        "geometry_msgs" => geometry_msgs::is_type_supported(type_name),
90        "nav_msgs" => nav_msgs::is_type_supported(type_name),
91        "sensor_msgs" => sensor_msgs::is_type_supported(type_name),
92        "foxglove_msgs" => foxglove_msgs::is_type_supported(type_name),
93        "edgefirst_msgs" => edgefirst_msgs::is_type_supported(type_name),
94        "mavros_msgs" => mavros_msgs::is_type_supported(type_name),
95        _ => false,
96    }
97}
98
99/// List all supported schema names.
100///
101/// Returns a vector of all schema names that this library supports.
102pub fn list_schemas() -> Vec<&'static str> {
103    let mut schemas = Vec::new();
104
105    schemas.extend(builtin_interfaces::list_types().iter().copied());
106    schemas.extend(std_msgs::list_types().iter().copied());
107    schemas.extend(geometry_msgs::list_types().iter().copied());
108    schemas.extend(nav_msgs::list_types().iter().copied());
109    schemas.extend(sensor_msgs::list_types().iter().copied());
110    schemas.extend(foxglove_msgs::list_types().iter().copied());
111    schemas.extend(edgefirst_msgs::list_types().iter().copied());
112    schemas.extend(mavros_msgs::list_types().iter().copied());
113
114    schemas
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120
121    #[test]
122    fn test_parse_schema_valid() {
123        let (pkg, typ) = parse_schema("sensor_msgs/msg/Image").unwrap();
124        assert_eq!(pkg, "sensor_msgs");
125        assert_eq!(typ, "Image");
126    }
127
128    #[test]
129    fn test_parse_schema_invalid() {
130        assert!(parse_schema("invalid").is_none());
131        assert!(parse_schema("sensor_msgs/srv/Image").is_none());
132        assert!(parse_schema("sensor_msgs/Image").is_none());
133    }
134
135    #[test]
136    fn test_schema_name_trait() {
137        assert_eq!(
138            sensor_msgs::NavSatStatus::SCHEMA_NAME,
139            "sensor_msgs/msg/NavSatStatus"
140        );
141        assert_eq!(geometry_msgs::Pose::SCHEMA_NAME, "geometry_msgs/msg/Pose");
142        assert_eq!(edgefirst_msgs::Date::SCHEMA_NAME, "edgefirst_msgs/msg/Date");
143    }
144
145    #[test]
146    fn test_schema_name_method() {
147        assert_eq!(
148            sensor_msgs::NavSatStatus::schema_name(),
149            "sensor_msgs/msg/NavSatStatus"
150        );
151    }
152
153    #[test]
154    fn test_is_supported() {
155        assert!(is_supported("sensor_msgs/msg/Image"));
156        assert!(is_supported("geometry_msgs/msg/Pose"));
157        assert!(is_supported("edgefirst_msgs/msg/Box"));
158        assert!(is_supported("foxglove_msgs/msg/CompressedVideo"));
159        assert!(!is_supported("unknown_msgs/msg/Foo"));
160        assert!(!is_supported("sensor_msgs/Image")); // Wrong format
161    }
162
163    #[test]
164    fn test_list_schemas() {
165        let schemas = list_schemas();
166        assert!(schemas.contains(&"sensor_msgs/msg/Image"));
167        assert!(schemas.contains(&"geometry_msgs/msg/Pose"));
168        assert!(schemas.contains(&"edgefirst_msgs/msg/Box"));
169        assert!(!schemas.contains(&"unknown_msgs/msg/Foo"));
170    }
171}