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 #[error("Dependency '{service_name}' not found: {message}")]
46 DependencyNotFound {
47 service_name: String,
48 message: String,
49 },
50}
51
52pub trait ActrTypeExt: Sized {
54 fn to_string_repr(&self) -> String;
56
57 fn from_string_repr(s: &str) -> Result<Self, ActrError>;
59}
60
61impl ActrTypeExt for ActrType {
62 fn to_string_repr(&self) -> String {
63 format!("{}+{}", self.manufacturer, self.name)
64 }
65
66 fn from_string_repr(s: &str) -> Result<Self, ActrError> {
67 let (manufacturer, name) = s
68 .split_once('+')
69 .ok_or_else(|| ActrError::InvalidTypeFormat(s.to_string()))?;
70
71 Name::new(manufacturer.to_string())
73 .map_err(|e| ActrError::InvalidComponent(format!("Invalid manufacturer: {e}")))?;
74 Name::new(name.to_string())
75 .map_err(|e| ActrError::InvalidComponent(format!("Invalid type name: {e}")))?;
76
77 Ok(ActrType {
78 manufacturer: manufacturer.to_string(),
79 name: name.to_string(),
80 })
81 }
82}
83
84pub trait ActrIdExt: Sized {
86 fn to_string_repr(&self) -> String;
88
89 fn from_string_repr(s: &str) -> Result<Self, ActrError>;
91}
92
93impl ActrIdExt for ActrId {
94 fn to_string_repr(&self) -> String {
95 format!(
96 "{:x}@{}:{}+{}",
97 self.serial_number, self.realm.realm_id, self.r#type.manufacturer, self.r#type.name
98 )
99 }
100
101 fn from_string_repr(s: &str) -> Result<Self, ActrError> {
102 let (serial_part, rest) = s
103 .split_once('@')
104 .ok_or_else(|| ActrError::InvalidFormat("Missing '@' separator".to_string()))?;
105
106 let serial_number = u64::from_str_radix(serial_part, 16).map_err(|_e| {
107 ActrError::InvalidComponent(format!("Invalid serial number hex: {serial_part}"))
108 })?;
109
110 let (realm_part, type_part) = rest
111 .split_once(':')
112 .ok_or_else(|| ActrError::InvalidFormat("Missing ':' separator".to_string()))?;
113
114 let realm_id = u32::from_str(realm_part)
115 .map_err(|_e| ActrError::InvalidComponent(format!("Invalid realm ID: {realm_part}")))?;
116
117 let actr_type = ActrType::from_string_repr(type_part)?;
118
119 Ok(ActrId {
120 realm: Realm { realm_id },
121 serial_number,
122 r#type: actr_type,
123 })
124 }
125}
126
127impl std::fmt::Display for ActrType {
132 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
133 write!(f, "{}+{}", self.manufacturer, self.name)
134 }
135}
136
137impl std::fmt::Display for ActrId {
138 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139 write!(
140 f,
141 "{:x}@{}:{}+{}",
142 self.serial_number, self.realm.realm_id, self.r#type.manufacturer, self.r#type.name
143 )
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150
151 #[test]
152 fn test_actor_id_string_repr_roundtrip() {
153 let original_id = ActrId {
154 realm: Realm { realm_id: 101 },
155 serial_number: 0x1a2b3c,
156 r#type: ActrType {
157 manufacturer: "acme".to_string(),
158 name: "echo-service".to_string(),
159 },
160 };
161
162 let string_repr = original_id.to_string_repr();
163 assert_eq!(string_repr, "1a2b3c@101:acme+echo-service");
164
165 let parsed_id = ActrId::from_string_repr(&string_repr).unwrap();
166 assert_eq!(original_id.realm.realm_id, parsed_id.realm.realm_id);
167 assert_eq!(original_id.serial_number, parsed_id.serial_number);
168 assert_eq!(
169 original_id.r#type.manufacturer,
170 parsed_id.r#type.manufacturer
171 );
172 assert_eq!(original_id.r#type.name, parsed_id.r#type.name);
173 }
174
175 #[test]
176 fn test_invalid_format_parsing() {
177 assert!(matches!(
178 ActrId::from_string_repr("invalid-string"),
179 Err(ActrError::InvalidFormat(_))
180 ));
181 assert!(matches!(
182 ActrId::from_string_repr("123@101:acme"),
183 Err(ActrError::InvalidTypeFormat(_))
184 ));
185 assert!(matches!(
186 ActrId::from_string_repr("123@acme+echo"),
187 Err(ActrError::InvalidFormat(_))
188 ));
189 assert!(matches!(
190 ActrId::from_string_repr("xyz@101:acme+echo"),
191 Err(ActrError::InvalidComponent(_))
192 ));
193 }
194
195 #[test]
196 fn test_actr_type_roundtrip_and_validation() {
197 let s = "acme+echo";
198 let ty = ActrType::from_string_repr(s).unwrap();
199 assert_eq!(ty.to_string_repr(), s);
200
201 assert!(matches!(
203 ActrType::from_string_repr("acme-echo"),
204 Err(ActrError::InvalidTypeFormat(_))
205 ));
206
207 assert!(matches!(
209 ActrType::from_string_repr("1acme+echo"),
210 Err(ActrError::InvalidComponent(_))
211 ));
212
213 assert!(matches!(
215 ActrType::from_string_repr("acme+echo!"),
216 Err(ActrError::InvalidComponent(_))
217 ));
218 }
219}