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: <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    /// 消息解码失败
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
41/// Helpers for `ActrType` string conversions
42pub trait ActrTypeExt: Sized {
43    /// Convert to stable string representation: "<manufacturer>:<name>".
44    fn to_string_repr(&self) -> String;
45
46    /// Parse from string representation. Performs validation on both parts.
47    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        // Reuse generic name validation to keep rules consistent across the project
61        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
73/// Helpers for `ActrId` string conversions
74pub trait ActrIdExt: Sized {
75    /// Convert to "<manufacturer>:<name>@<serial_number_hex>:<realm_id>"
76    fn to_string_repr(&self) -> String;
77
78    /// Parse from string representation.
79    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        // 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}