actr_protocol/
uri.rs

1//! # Actor-RTC URI 解析库
2//!
3//! 提供 actr:// 协议的标准 URI 解析功能,不包含业务逻辑。
4
5use serde::{Deserialize, Serialize};
6use std::fmt::{Display, Formatter};
7use std::str::FromStr;
8use thiserror::Error;
9
10/// Actor-RTC URI 解析错误
11#[derive(Error, Debug)]
12pub enum ActrUriError {
13    #[error("Invalid URI scheme, expected 'actr' but got '{0}'")]
14    InvalidScheme(String),
15
16    #[error("Missing actor type in URI")]
17    MissingActorType,
18
19    #[error("URI parse error: {0}")]
20    ParseError(String),
21}
22
23/// Actor-RTC URI 结构
24/// 格式: actr://<actor-type>/<path>?<query-params>
25#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
26pub struct ActrUri {
27    /// Actor 类型(如 "user-service")
28    pub actor_type: String,
29
30    /// 路径(可选,如 "user.proto"、"api/v1" 等)
31    pub path: Option<String>,
32
33    /// 查询参数
34    pub query_params: std::collections::HashMap<String, String>,
35}
36
37impl ActrUri {
38    /// 创建新的 Actor-RTC URI
39    pub fn new(actor_type: String) -> Self {
40        Self {
41            actor_type,
42            path: None,
43            query_params: std::collections::HashMap::new(),
44        }
45    }
46
47    /// 设置路径
48    pub fn with_path(mut self, path: String) -> Self {
49        self.path = Some(path);
50        self
51    }
52
53    /// 添加查询参数
54    pub fn with_query_param(mut self, key: String, value: String) -> Self {
55        self.query_params.insert(key, value);
56        self
57    }
58
59    /// 获取 scheme 信息
60    pub fn scheme(&self) -> &'static str {
61        "actr"
62    }
63}
64
65impl Display for ActrUri {
66    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
67        let mut uri = format!("actr://{}", self.actor_type);
68
69        if let Some(ref path) = self.path {
70            if !path.starts_with('/') {
71                uri.push('/');
72            }
73            uri.push_str(path);
74        } else {
75            uri.push('/');
76        }
77
78        if !self.query_params.is_empty() {
79            uri.push('?');
80            let params: Vec<String> = self
81                .query_params
82                .iter()
83                .map(|(k, v)| format!("{k}={v}"))
84                .collect();
85            uri.push_str(&params.join("&"));
86        }
87
88        write!(f, "{uri}")
89    }
90}
91
92impl FromStr for ActrUri {
93    type Err = ActrUriError;
94
95    fn from_str(s: &str) -> Result<Self, Self::Err> {
96        if !s.starts_with("actr://") {
97            return Err(ActrUriError::InvalidScheme(
98                s.split(':').next().unwrap_or("").to_string(),
99            ));
100        }
101
102        let without_scheme = &s[7..];
103
104        let (base_part, query_part) = if let Some(idx) = without_scheme.find('?') {
105            (&without_scheme[..idx], Some(&without_scheme[idx + 1..]))
106        } else {
107            (without_scheme, None)
108        };
109
110        let (actor_type, path) = if let Some(idx) = base_part.find('/') {
111            let actor_type = &base_part[..idx];
112            let path_part = &base_part[idx + 1..];
113
114            if actor_type.is_empty() {
115                return Err(ActrUriError::MissingActorType);
116            }
117
118            let path = if path_part.is_empty() {
119                None
120            } else {
121                Some(path_part.to_string())
122            };
123
124            (actor_type.to_string(), path)
125        } else {
126            if base_part.is_empty() {
127                return Err(ActrUriError::MissingActorType);
128            }
129            (base_part.to_string(), None)
130        };
131
132        let mut query_params = std::collections::HashMap::new();
133        if let Some(query) = query_part {
134            for param in query.split('&') {
135                if let Some(idx) = param.find('=') {
136                    let key = param[..idx].to_string();
137                    let value = param[idx + 1..].to_string();
138                    query_params.insert(key, value);
139                } else if !param.is_empty() {
140                    query_params.insert(param.to_string(), String::new());
141                }
142            }
143        }
144
145        Ok(ActrUri {
146            actor_type,
147            path,
148            query_params,
149        })
150    }
151}
152
153/// Actor-RTC URI 构建器
154#[derive(Debug, Default)]
155pub struct ActrUriBuilder {
156    actor_type: Option<String>,
157    path: Option<String>,
158    query_params: std::collections::HashMap<String, String>,
159}
160
161impl ActrUriBuilder {
162    /// 创建新的构建器
163    pub fn new() -> Self {
164        Self::default()
165    }
166
167    /// 设置 Actor 类型
168    pub fn actor_type<S: Into<String>>(mut self, actor_type: S) -> Self {
169        self.actor_type = Some(actor_type.into());
170        self
171    }
172
173    /// 设置路径
174    pub fn path<S: Into<String>>(mut self, path: S) -> Self {
175        self.path = Some(path.into());
176        self
177    }
178
179    /// 添加查询参数
180    pub fn query<K: Into<String>, V: Into<String>>(mut self, key: K, value: V) -> Self {
181        self.query_params.insert(key.into(), value.into());
182        self
183    }
184
185    /// 构建 URI
186    pub fn build(self) -> Result<ActrUri, ActrUriError> {
187        let actor_type = self.actor_type.ok_or(ActrUriError::MissingActorType)?;
188
189        Ok(ActrUri {
190            actor_type,
191            path: self.path,
192            query_params: self.query_params,
193        })
194    }
195}
196
197#[cfg(test)]
198mod tests {
199    use super::*;
200
201    #[test]
202    fn test_basic_uri_parsing() {
203        let uri = "actr://user-service/".parse::<ActrUri>().unwrap();
204        assert_eq!(uri.actor_type, "user-service");
205        assert_eq!(uri.path, None);
206        assert!(uri.query_params.is_empty());
207    }
208
209    #[test]
210    fn test_uri_with_path() {
211        let uri = "actr://user-service/api/v1".parse::<ActrUri>().unwrap();
212        assert_eq!(uri.actor_type, "user-service");
213        assert_eq!(uri.path, Some("api/v1".to_string()));
214    }
215
216    #[test]
217    fn test_uri_with_query_params() {
218        let uri = "actr://notification-service/?param1=value1&param2=value2"
219            .parse::<ActrUri>()
220            .unwrap();
221        assert_eq!(uri.actor_type, "notification-service");
222        assert_eq!(uri.path, None);
223        assert_eq!(uri.query_params.get("param1"), Some(&"value1".to_string()));
224        assert_eq!(uri.query_params.get("param2"), Some(&"value2".to_string()));
225    }
226
227    #[test]
228    fn test_uri_without_trailing_slash() {
229        let uri = "actr://payment-service".parse::<ActrUri>().unwrap();
230        assert_eq!(uri.actor_type, "payment-service");
231        assert_eq!(uri.path, None);
232    }
233
234    #[test]
235    fn test_uri_builder() {
236        let uri = ActrUriBuilder::new()
237            .actor_type("order-service")
238            .path("orders/create")
239            .query("timeout", "30s")
240            .build()
241            .unwrap();
242
243        assert_eq!(uri.actor_type, "order-service");
244        assert_eq!(uri.path, Some("orders/create".to_string()));
245        assert_eq!(uri.query_params.get("timeout"), Some(&"30s".to_string()));
246    }
247
248    #[test]
249    fn test_uri_to_string() {
250        let uri = ActrUri::new("user-service".to_string())
251            .with_path("users/profile".to_string())
252            .with_query_param("format".to_string(), "json".to_string());
253
254        let uri_string = uri.to_string();
255        assert!(uri_string.starts_with("actr://user-service"));
256        assert!(uri_string.contains("users/profile"));
257        assert!(uri_string.contains("format=json"));
258    }
259
260    #[test]
261    fn test_invalid_scheme() {
262        let result = "http://user-service/".parse::<ActrUri>();
263        assert!(matches!(result, Err(ActrUriError::InvalidScheme(_))));
264    }
265
266    #[test]
267    fn test_missing_actor_type() {
268        let result = "actr:///".parse::<ActrUri>();
269        assert!(matches!(result, Err(ActrUriError::MissingActorType)));
270    }
271
272    #[test]
273    fn test_empty_query_param() {
274        let uri = "actr://service/?flag".parse::<ActrUri>().unwrap();
275        assert_eq!(uri.query_params.get("flag"), Some(&"".to_string()));
276    }
277
278    #[test]
279    fn test_complex_path() {
280        let uri = "actr://api-gateway/v2/users/123/profile"
281            .parse::<ActrUri>()
282            .unwrap();
283        assert_eq!(uri.actor_type, "api-gateway");
284        assert_eq!(uri.path, Some("v2/users/123/profile".to_string()));
285    }
286}