actr_protocol/
uri.rs

1//! # Actor-RTC URI 解析库
2//!
3//! 提供 actr:// 协议的标准 URI 解析功能,不包含业务逻辑。
4//!
5//! URI 格式: actr://<realm>:<manufacturer>+<name>@<version>
6//! 例如: actr://101:acme+echo-service@v1
7
8use serde::{Deserialize, Serialize};
9use std::fmt::{Display, Formatter};
10use std::str::FromStr;
11use thiserror::Error;
12
13/// Actor-RTC URI 解析错误
14#[derive(Error, Debug)]
15pub enum ActrUriError {
16    #[error("Invalid URI scheme, expected 'actr' but got '{0}'")]
17    InvalidScheme(String),
18
19    #[error("Missing actor authority in URI")]
20    MissingAuthority,
21
22    #[error("Invalid actor authority format, expected: <realm>:<manufacturer>+<name>@<version>")]
23    InvalidAuthorityFormat(String),
24
25    #[error("Missing version suffix '@v1' in URI")]
26    MissingVersion,
27
28    #[error("Invalid realm ID: {0}")]
29    InvalidRealmId(String),
30
31    #[error("URI parse error: {0}")]
32    ParseError(String),
33}
34
35/// Actor-RTC URI 结构
36/// 格式: actr://<realm>:<manufacturer>+<name>@<version>
37#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
38pub struct ActrUri {
39    /// Realm ID (u32)
40    pub realm: u32,
41    /// Manufacturer name
42    pub manufacturer: String,
43    /// Actor type name
44    pub name: String,
45    /// Version (e.g., "v1")
46    pub version: String,
47}
48
49impl ActrUri {
50    /// 创建新的 Actor-RTC URI
51    pub fn new(realm: u32, manufacturer: String, name: String, version: String) -> Self {
52        Self {
53            realm,
54            manufacturer,
55            name,
56            version,
57        }
58    }
59
60    /// 获取 scheme 信息
61    pub fn scheme(&self) -> &'static str {
62        "actr"
63    }
64
65    /// 获取 actor type 字符串表示 (manufacturer+name)
66    pub fn actor_type(&self) -> String {
67        format!("{}+{}", self.manufacturer, self.name)
68    }
69}
70
71impl Display for ActrUri {
72    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
73        write!(
74            f,
75            "actr://{}:{}+{}@{}",
76            self.realm, self.manufacturer, self.name, self.version
77        )
78    }
79}
80
81impl FromStr for ActrUri {
82    type Err = ActrUriError;
83
84    fn from_str(s: &str) -> Result<Self, Self::Err> {
85        if !s.starts_with("actr://") {
86            return Err(ActrUriError::InvalidScheme(
87                s.split(':').next().unwrap_or("").to_string(),
88            ));
89        }
90
91        let without_scheme = &s[7..];
92
93        // Check for empty authority
94        if without_scheme.is_empty() {
95            return Err(ActrUriError::MissingAuthority);
96        }
97
98        // Check for version suffix '@'
99        let (authority, version) = without_scheme
100            .rsplit_once('@')
101            .ok_or(ActrUriError::MissingVersion)?;
102
103        let version = version.to_string();
104
105        // Parse realm:manufacturer+name
106        let (realm_str, type_part) = authority
107            .split_once(':')
108            .ok_or_else(|| ActrUriError::InvalidAuthorityFormat(authority.to_string()))?;
109
110        let realm = realm_str
111            .parse::<u32>()
112            .map_err(|_| ActrUriError::InvalidRealmId(realm_str.to_string()))?;
113
114        let (manufacturer, name) = type_part
115            .split_once('+')
116            .ok_or_else(|| ActrUriError::InvalidAuthorityFormat(authority.to_string()))?;
117
118        Ok(ActrUri {
119            realm,
120            manufacturer: manufacturer.to_string(),
121            name: name.to_string(),
122            version,
123        })
124    }
125}
126
127/// Actor-RTC URI 构建器
128#[derive(Debug, Default)]
129pub struct ActrUriBuilder {
130    realm: Option<u32>,
131    manufacturer: Option<String>,
132    name: Option<String>,
133    version: Option<String>,
134}
135
136impl ActrUriBuilder {
137    /// 创建新的构建器
138    pub fn new() -> Self {
139        Self::default()
140    }
141
142    /// 设置 Realm ID
143    pub fn realm(mut self, realm: u32) -> Self {
144        self.realm = Some(realm);
145        self
146    }
147
148    /// 设置 Manufacturer
149    pub fn manufacturer<S: Into<String>>(mut self, manufacturer: S) -> Self {
150        self.manufacturer = Some(manufacturer.into());
151        self
152    }
153
154    /// 设置 Actor 类型名称
155    pub fn name<S: Into<String>>(mut self, name: S) -> Self {
156        self.name = Some(name.into());
157        self
158    }
159
160    /// 设置版本
161    pub fn version<S: Into<String>>(mut self, version: S) -> Self {
162        self.version = Some(version.into());
163        self
164    }
165
166    /// 构建 URI
167    pub fn build(self) -> Result<ActrUri, ActrUriError> {
168        let realm = self.realm.ok_or(ActrUriError::MissingAuthority)?;
169        let manufacturer = self
170            .manufacturer
171            .ok_or(ActrUriError::InvalidAuthorityFormat(
172                "missing manufacturer".to_string(),
173            ))?;
174        let name = self.name.ok_or(ActrUriError::InvalidAuthorityFormat(
175            "missing name".to_string(),
176        ))?;
177        let version = self.version.unwrap_or_else(|| "v1".to_string());
178
179        Ok(ActrUri {
180            realm,
181            manufacturer,
182            name,
183            version,
184        })
185    }
186}
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191
192    #[test]
193    fn test_basic_uri_parsing() {
194        let uri = "actr://101:acme+echo-service@v1"
195            .parse::<ActrUri>()
196            .unwrap();
197        assert_eq!(uri.realm, 101);
198        assert_eq!(uri.manufacturer, "acme");
199        assert_eq!(uri.name, "echo-service");
200        assert_eq!(uri.version, "v1");
201    }
202
203    #[test]
204    fn test_uri_builder() {
205        let uri = ActrUriBuilder::new()
206            .realm(101)
207            .manufacturer("acme")
208            .name("order-service")
209            .version("v1")
210            .build()
211            .unwrap();
212
213        assert_eq!(uri.realm, 101);
214        assert_eq!(uri.manufacturer, "acme");
215        assert_eq!(uri.name, "order-service");
216        assert_eq!(uri.version, "v1");
217    }
218
219    #[test]
220    fn test_uri_to_string() {
221        let uri = ActrUri::new(
222            101,
223            "acme".to_string(),
224            "user-service".to_string(),
225            "v1".to_string(),
226        );
227        let uri_string = uri.to_string();
228        assert_eq!(uri_string, "actr://101:acme+user-service@v1");
229    }
230
231    #[test]
232    fn test_invalid_scheme() {
233        let result = "http://101:acme+service@v1".parse::<ActrUri>();
234        assert!(matches!(result, Err(ActrUriError::InvalidScheme(_))));
235    }
236
237    #[test]
238    fn test_missing_authority() {
239        let result = "actr://".parse::<ActrUri>();
240        assert!(matches!(result, Err(ActrUriError::MissingAuthority)));
241    }
242
243    #[test]
244    fn test_missing_version() {
245        let result = "actr://101:acme+service".parse::<ActrUri>();
246        assert!(matches!(result, Err(ActrUriError::MissingVersion)));
247    }
248
249    #[test]
250    fn test_invalid_realm_id() {
251        let result = "actr://abc:acme+service@v1".parse::<ActrUri>();
252        assert!(matches!(result, Err(ActrUriError::InvalidRealmId(_))));
253    }
254
255    #[test]
256    fn test_invalid_authority_format() {
257        let result = "actr://101:acme:service@v1".parse::<ActrUri>();
258        assert!(matches!(
259            result,
260            Err(ActrUriError::InvalidAuthorityFormat(_))
261        ));
262    }
263
264    #[test]
265    fn test_actor_type_method() {
266        let uri = "actr://101:acme+user-service@v1"
267            .parse::<ActrUri>()
268            .unwrap();
269        assert_eq!(uri.actor_type(), "acme+user-service");
270    }
271
272    #[test]
273    fn test_roundtrip() {
274        let uri = ActrUriBuilder::new()
275            .realm(9999)
276            .manufacturer("test")
277            .name("service")
278            .build()
279            .unwrap();
280
281        let uri_str = uri.to_string();
282        let parsed = uri_str.parse::<ActrUri>().unwrap();
283        assert_eq!(uri.realm, parsed.realm);
284        assert_eq!(uri.manufacturer, parsed.manufacturer);
285        assert_eq!(uri.name, parsed.name);
286        assert_eq!(uri.version, parsed.version);
287    }
288}