Skip to main content

mqd_core/
ids.rs

1use serde::{Deserialize, Serialize};
2use std::fmt;
3
4use crate::errors::{CoreError, Result};
5
6#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
7#[serde(transparent)]
8pub struct EntityPath(String);
9
10impl EntityPath {
11    pub fn new(path: impl Into<String>) -> Result<Self> {
12        let s: String = path.into();
13        if !s.starts_with('/') {
14            return Err(CoreError::InvalidEntityPath {
15                path: s,
16                reason: "must start with '/'",
17            });
18        }
19        if s.len() == 1 {
20            return Err(CoreError::InvalidEntityPath {
21                path: s,
22                reason: "must contain at least one path segment",
23            });
24        }
25        if s.contains("//") {
26            return Err(CoreError::InvalidEntityPath {
27                path: s,
28                reason: "empty segments are not allowed",
29            });
30        }
31        Ok(Self(s))
32    }
33
34    pub fn as_str(&self) -> &str {
35        &self.0
36    }
37}
38
39impl fmt::Display for EntityPath {
40    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41        f.write_str(&self.0)
42    }
43}
44
45#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
46#[serde(transparent)]
47pub struct Timeline(String);
48
49impl Timeline {
50    pub const WALL: &'static str = "wall_time";
51    pub const SIM: &'static str = "sim_time";
52
53    pub fn new(name: impl Into<String>) -> Self {
54        Self(name.into())
55    }
56
57    pub fn wall() -> Self {
58        Self(Self::WALL.to_string())
59    }
60
61    pub fn sim() -> Self {
62        Self(Self::SIM.to_string())
63    }
64
65    pub fn as_str(&self) -> &str {
66        &self.0
67    }
68}
69
70impl fmt::Display for Timeline {
71    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72        f.write_str(&self.0)
73    }
74}
75
76#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
77#[serde(transparent)]
78pub struct ComponentName(String);
79
80impl ComponentName {
81    pub fn new(name: impl Into<String>) -> Self {
82        Self(name.into())
83    }
84
85    pub fn as_str(&self) -> &str {
86        &self.0
87    }
88}
89
90impl fmt::Display for ComponentName {
91    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92        f.write_str(&self.0)
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99
100    #[test]
101    fn entity_path_requires_leading_slash() {
102        assert!(EntityPath::new("robot/base").is_err());
103        assert!(EntityPath::new("/").is_err());
104        assert!(EntityPath::new("/robot//base").is_err());
105        assert!(EntityPath::new("/robot/base").is_ok());
106    }
107
108    #[test]
109    fn entity_path_sorts_lexicographically() {
110        let mut paths = [
111            EntityPath::new("/robot/10").unwrap(),
112            EntityPath::new("/robot/2").unwrap(),
113            EntityPath::new("/agent/1").unwrap(),
114        ];
115        paths.sort();
116        assert_eq!(paths[0].as_str(), "/agent/1");
117        assert_eq!(paths[1].as_str(), "/robot/10");
118        assert_eq!(paths[2].as_str(), "/robot/2");
119    }
120}