actr_protocol/
actr_ext.rs1use crate::{ActrId, ActrType, Realm, name::Name};
7use std::str::FromStr;
8use thiserror::Error;
9
10#[derive(Error, Debug, PartialEq, Eq)]
12pub enum ActrError {
13 #[error(
14 "Invalid Actor ID format: '{0}'. Expected format: <serial_number>@<realm_id>:<manufacturer>+<name>"
15 )]
16 InvalidFormat(String),
17
18 #[error("Invalid component in actor identity: {0}")]
19 InvalidComponent(String),
20
21 #[error("Invalid actor type format: '{0}'. Expected format: <manufacturer>+<name>")]
22 InvalidTypeFormat(String),
23
24 #[error("Failed to decode protobuf message: {message}")]
26 DecodeFailure { message: String },
27
28 #[error("Unknown route key: {route_key}")]
30 UnknownRoute { route_key: String },
31
32 #[error("Gate not initialized: {message}")]
34 GateNotInitialized { message: String },
35
36 #[error("Feature not yet implemented: {feature}")]
38 NotImplemented { feature: String },
39
40 #[error("Permission denied: {message}")]
42 PermissionDenied { message: String },
43}
44
45pub trait ActrTypeExt: Sized {
47 fn to_string_repr(&self) -> String;
49
50 fn from_string_repr(s: &str) -> Result<Self, ActrError>;
52}
53
54impl ActrTypeExt for ActrType {
55 fn to_string_repr(&self) -> String {
56 format!("{}+{}", self.manufacturer, self.name)
57 }
58
59 fn from_string_repr(s: &str) -> Result<Self, ActrError> {
60 let (manufacturer, name) = s
61 .split_once('+')
62 .ok_or_else(|| ActrError::InvalidTypeFormat(s.to_string()))?;
63
64 Name::new(manufacturer.to_string())
66 .map_err(|e| ActrError::InvalidComponent(format!("Invalid manufacturer: {e}")))?;
67 Name::new(name.to_string())
68 .map_err(|e| ActrError::InvalidComponent(format!("Invalid type name: {e}")))?;
69
70 Ok(ActrType {
71 manufacturer: manufacturer.to_string(),
72 name: name.to_string(),
73 })
74 }
75}
76
77pub trait ActrIdExt: Sized {
79 fn to_string_repr(&self) -> String;
81
82 fn from_string_repr(s: &str) -> Result<Self, ActrError>;
84}
85
86impl ActrIdExt for ActrId {
87 fn to_string_repr(&self) -> String {
88 format!(
89 "{:x}@{}:{}+{}",
90 self.serial_number, self.realm.realm_id, self.r#type.manufacturer, self.r#type.name
91 )
92 }
93
94 fn from_string_repr(s: &str) -> Result<Self, ActrError> {
95 let (serial_part, rest) = s
96 .split_once('@')
97 .ok_or_else(|| ActrError::InvalidFormat("Missing '@' separator".to_string()))?;
98
99 let serial_number = u64::from_str_radix(serial_part, 16).map_err(|_e| {
100 ActrError::InvalidComponent(format!("Invalid serial number hex: {serial_part}"))
101 })?;
102
103 let (realm_part, type_part) = rest
104 .split_once(':')
105 .ok_or_else(|| ActrError::InvalidFormat("Missing ':' separator".to_string()))?;
106
107 let realm_id = u32::from_str(realm_part)
108 .map_err(|_e| ActrError::InvalidComponent(format!("Invalid realm ID: {realm_part}")))?;
109
110 let actr_type = ActrType::from_string_repr(type_part)?;
111
112 Ok(ActrId {
113 realm: Realm { realm_id },
114 serial_number,
115 r#type: actr_type,
116 })
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 #[test]
125 fn test_actor_id_string_repr_roundtrip() {
126 let original_id = ActrId {
127 realm: Realm { realm_id: 101 },
128 serial_number: 0x1a2b3c,
129 r#type: ActrType {
130 manufacturer: "acme".to_string(),
131 name: "echo-service".to_string(),
132 },
133 };
134
135 let string_repr = original_id.to_string_repr();
136 assert_eq!(string_repr, "1a2b3c@101:acme+echo-service");
137
138 let parsed_id = ActrId::from_string_repr(&string_repr).unwrap();
139 assert_eq!(original_id.realm.realm_id, parsed_id.realm.realm_id);
140 assert_eq!(original_id.serial_number, parsed_id.serial_number);
141 assert_eq!(
142 original_id.r#type.manufacturer,
143 parsed_id.r#type.manufacturer
144 );
145 assert_eq!(original_id.r#type.name, parsed_id.r#type.name);
146 }
147
148 #[test]
149 fn test_invalid_format_parsing() {
150 assert!(matches!(
151 ActrId::from_string_repr("invalid-string"),
152 Err(ActrError::InvalidFormat(_))
153 ));
154 assert!(matches!(
155 ActrId::from_string_repr("123@101:acme"),
156 Err(ActrError::InvalidTypeFormat(_))
157 ));
158 assert!(matches!(
159 ActrId::from_string_repr("123@acme+echo"),
160 Err(ActrError::InvalidFormat(_))
161 ));
162 assert!(matches!(
163 ActrId::from_string_repr("xyz@101:acme+echo"),
164 Err(ActrError::InvalidComponent(_))
165 ));
166 }
167
168 #[test]
169 fn test_actr_type_roundtrip_and_validation() {
170 let s = "acme+echo";
171 let ty = ActrType::from_string_repr(s).unwrap();
172 assert_eq!(ty.to_string_repr(), s);
173
174 assert!(matches!(
176 ActrType::from_string_repr("acme-echo"),
177 Err(ActrError::InvalidTypeFormat(_))
178 ));
179
180 assert!(matches!(
182 ActrType::from_string_repr("1acme+echo"),
183 Err(ActrError::InvalidComponent(_))
184 ));
185
186 assert!(matches!(
188 ActrType::from_string_repr("acme+echo!"),
189 Err(ActrError::InvalidComponent(_))
190 ));
191 }
192}