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
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 #[test]
132 fn test_actor_id_string_repr_roundtrip() {
133 let original_id = ActrId {
134 realm: Realm { realm_id: 101 },
135 serial_number: 0x1a2b3c,
136 r#type: ActrType {
137 manufacturer: "acme".to_string(),
138 name: "echo-service".to_string(),
139 },
140 };
141
142 let string_repr = original_id.to_string_repr();
143 assert_eq!(string_repr, "1a2b3c@101:acme+echo-service");
144
145 let parsed_id = ActrId::from_string_repr(&string_repr).unwrap();
146 assert_eq!(original_id.realm.realm_id, parsed_id.realm.realm_id);
147 assert_eq!(original_id.serial_number, parsed_id.serial_number);
148 assert_eq!(
149 original_id.r#type.manufacturer,
150 parsed_id.r#type.manufacturer
151 );
152 assert_eq!(original_id.r#type.name, parsed_id.r#type.name);
153 }
154
155 #[test]
156 fn test_invalid_format_parsing() {
157 assert!(matches!(
158 ActrId::from_string_repr("invalid-string"),
159 Err(ActrError::InvalidFormat(_))
160 ));
161 assert!(matches!(
162 ActrId::from_string_repr("123@101:acme"),
163 Err(ActrError::InvalidTypeFormat(_))
164 ));
165 assert!(matches!(
166 ActrId::from_string_repr("123@acme+echo"),
167 Err(ActrError::InvalidFormat(_))
168 ));
169 assert!(matches!(
170 ActrId::from_string_repr("xyz@101:acme+echo"),
171 Err(ActrError::InvalidComponent(_))
172 ));
173 }
174
175 #[test]
176 fn test_actr_type_roundtrip_and_validation() {
177 let s = "acme+echo";
178 let ty = ActrType::from_string_repr(s).unwrap();
179 assert_eq!(ty.to_string_repr(), s);
180
181 assert!(matches!(
183 ActrType::from_string_repr("acme-echo"),
184 Err(ActrError::InvalidTypeFormat(_))
185 ));
186
187 assert!(matches!(
189 ActrType::from_string_repr("1acme+echo"),
190 Err(ActrError::InvalidComponent(_))
191 ));
192
193 assert!(matches!(
195 ActrType::from_string_repr("acme+echo!"),
196 Err(ActrError::InvalidComponent(_))
197 ));
198 }
199}