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: <manufacturer>:<name>@<serial_number>:<realm_id>"
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
41pub trait ActrTypeExt: Sized {
43 fn to_string_repr(&self) -> String;
45
46 fn from_string_repr(s: &str) -> Result<Self, ActrError>;
48}
49
50impl ActrTypeExt for ActrType {
51 fn to_string_repr(&self) -> String {
52 format!("{}:{}", self.manufacturer, self.name)
53 }
54
55 fn from_string_repr(s: &str) -> Result<Self, ActrError> {
56 let (manufacturer, name) = s
57 .split_once(':')
58 .ok_or_else(|| ActrError::InvalidTypeFormat(s.to_string()))?;
59
60 Name::new(manufacturer.to_string())
62 .map_err(|e| ActrError::InvalidComponent(format!("Invalid manufacturer: {e}")))?;
63 Name::new(name.to_string())
64 .map_err(|e| ActrError::InvalidComponent(format!("Invalid type name: {e}")))?;
65
66 Ok(ActrType {
67 manufacturer: manufacturer.to_string(),
68 name: name.to_string(),
69 })
70 }
71}
72
73pub trait ActrIdExt: Sized {
75 fn to_string_repr(&self) -> String;
77
78 fn from_string_repr(s: &str) -> Result<Self, ActrError>;
80}
81
82impl ActrIdExt for ActrId {
83 fn to_string_repr(&self) -> String {
84 format!(
85 "{}:{}@{:x}:{}",
86 self.r#type.manufacturer, self.r#type.name, self.serial_number, self.realm.realm_id
87 )
88 }
89
90 fn from_string_repr(s: &str) -> Result<Self, ActrError> {
91 let (type_part, id_part) = s
92 .split_once('@')
93 .ok_or_else(|| ActrError::InvalidFormat("Missing '@' separator".to_string()))?;
94
95 let actr_type = ActrType::from_string_repr(type_part)?;
96
97 let (serial_str, realm_str) = id_part
98 .split_once(':')
99 .ok_or_else(|| ActrError::InvalidFormat("Missing ':' in ID part".to_string()))?;
100
101 let serial_number = u64::from_str_radix(serial_str, 16).map_err(|_e| {
102 ActrError::InvalidComponent(format!("Invalid serial number hex: {serial_str}"))
103 })?;
104
105 let realm_id = u32::from_str(realm_str)
106 .map_err(|_e| ActrError::InvalidComponent(format!("Invalid realm ID: {realm_str}")))?;
107
108 Ok(ActrId {
109 realm: Realm { realm_id },
110 serial_number,
111 r#type: actr_type,
112 })
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[test]
121 fn test_actor_id_string_repr_roundtrip() {
122 let original_id = ActrId {
123 realm: Realm { realm_id: 101 },
124 serial_number: 0x1a2b3c,
125 r#type: ActrType {
126 manufacturer: "acme".to_string(),
127 name: "echo-service".to_string(),
128 },
129 };
130
131 let string_repr = original_id.to_string_repr();
132 assert_eq!(string_repr, "acme:echo-service@1a2b3c:101");
133
134 let parsed_id = ActrId::from_string_repr(&string_repr).unwrap();
135 assert_eq!(original_id.realm.realm_id, parsed_id.realm.realm_id);
136 assert_eq!(original_id.serial_number, parsed_id.serial_number);
137 assert_eq!(
138 original_id.r#type.manufacturer,
139 parsed_id.r#type.manufacturer
140 );
141 assert_eq!(original_id.r#type.name, parsed_id.r#type.name);
142 }
143
144 #[test]
145 fn test_invalid_format_parsing() {
146 assert!(matches!(
147 ActrId::from_string_repr("invalid-string"),
148 Err(ActrError::InvalidFormat(_))
149 ));
150 assert!(matches!(
151 ActrId::from_string_repr("acme:echo@123"),
152 Err(ActrError::InvalidFormat(_))
153 ));
154 assert!(matches!(
155 ActrId::from_string_repr("acme@123:101"),
156 Err(ActrError::InvalidTypeFormat(_))
157 ));
158 assert!(matches!(
159 ActrId::from_string_repr("acme:echo@123:xyz"),
160 Err(ActrError::InvalidComponent(_))
161 ));
162 assert!(matches!(
163 ActrId::from_string_repr("acme:echo@xyz:101"),
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}