actr_protocol/
actr_ext.rs

1//! Actor identity formatting and parsing utilities
2//!
3//! This module provides string formatting and parsing helpers for `ActrType` and `ActrId`.
4//! String forms are stable for logging, configuration, and CLI interactions.
5
6use crate::{ActrId, ActrType, Realm, name::Name};
7use std::str::FromStr;
8use thiserror::Error;
9
10/// Errors for actor identity parsing and formatting
11#[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    /// 消息解码失败
25    #[error("Failed to decode protobuf message: {message}")]
26    DecodeFailure { message: String },
27
28    /// 未知的路由键
29    #[error("Unknown route key: {route_key}")]
30    UnknownRoute { route_key: String },
31
32    /// OutGate 尚未初始化
33    #[error("Gate not initialized: {message}")]
34    GateNotInitialized { message: String },
35
36    /// Feature not yet implemented
37    #[error("Feature not yet implemented: {feature}")]
38    NotImplemented { feature: String },
39
40    /// ACL 权限拒绝
41    #[error("Permission denied: {message}")]
42    PermissionDenied { message: String },
43}
44
45/// Helpers for `ActrType` string conversions
46pub trait ActrTypeExt: Sized {
47    /// Convert to stable string representation: "<manufacturer>+<name>".
48    fn to_string_repr(&self) -> String;
49
50    /// Parse from string representation. Performs validation on both parts.
51    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        // Reuse generic name validation to keep rules consistent across the project
65        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
77/// Helpers for `ActrId` string conversions
78pub trait ActrIdExt: Sized {
79    /// Convert to "<serial_number_hex>@<realm_id>:<manufacturer>+<name>"
80    fn to_string_repr(&self) -> String;
81
82    /// Parse from string representation.
83    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        // invalid format
175        assert!(matches!(
176            ActrType::from_string_repr("acme-echo"),
177            Err(ActrError::InvalidTypeFormat(_))
178        ));
179
180        // invalid manufacturer via Name validation
181        assert!(matches!(
182            ActrType::from_string_repr("1acme+echo"),
183            Err(ActrError::InvalidComponent(_))
184        ));
185
186        // invalid name via Name validation
187        assert!(matches!(
188            ActrType::from_string_repr("acme+echo!"),
189            Err(ActrError::InvalidComponent(_))
190        ));
191    }
192}