dynamo_runtime/
protocols.rs1use std::fmt;
5use std::str::FromStr;
6
7use serde::{Deserialize, Serialize};
8
9pub mod annotated;
10pub mod maybe_error;
11
12pub type LeaseId = i64;
13
14const DEFAULT_NAMESPACE: &str = "NS";
16
17const DEFAULT_COMPONENT: &str = "C";
18
19const DEFAULT_ENDPOINT: &str = "E";
20
21pub const ENDPOINT_SCHEME: &str = "dyn://";
25
26#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
27pub struct Component {
28 pub name: String,
29 pub namespace: String,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)]
42pub struct EndpointId {
43 pub namespace: String,
44 pub component: String,
45 pub name: String,
46}
47
48impl fmt::Display for EndpointId {
49 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50 write!(f, "{}/{}/{}", self.namespace, self.component, self.name)
51 }
52}
53
54impl PartialEq<Vec<&str>> for EndpointId {
55 fn eq(&self, other: &Vec<&str>) -> bool {
56 if other.len() != 3 {
57 return false;
58 }
59
60 self.namespace == other[0] && self.component == other[1] && self.name == other[2]
61 }
62}
63
64impl PartialEq<[&str; 3]> for EndpointId {
65 fn eq(&self, other: &[&str; 3]) -> bool {
66 self.namespace == other[0] && self.component == other[1] && self.name == other[2]
67 }
68}
69
70impl PartialEq<EndpointId> for [&str; 3] {
71 fn eq(&self, other: &EndpointId) -> bool {
72 other == self
73 }
74}
75
76impl PartialEq<EndpointId> for Vec<&str> {
77 fn eq(&self, other: &EndpointId) -> bool {
78 other == self
79 }
80}
81
82impl Default for EndpointId {
83 fn default() -> Self {
84 EndpointId {
85 namespace: DEFAULT_NAMESPACE.to_string(),
86 component: DEFAULT_COMPONENT.to_string(),
87 name: DEFAULT_ENDPOINT.to_string(),
88 }
89 }
90}
91
92impl From<&str> for EndpointId {
93 fn from(s: &str) -> Self {
119 let input = s.strip_prefix(ENDPOINT_SCHEME).unwrap_or(s);
120
121 let mut parts = input
123 .trim_matches([' ', '/', '.'])
124 .split(['.', '/'])
125 .filter(|x| !x.is_empty());
126
127 let p1 = parts.next();
129 let p2 = parts.next();
130 let p3 = parts.next();
131
132 let namespace;
133 let component;
134 let name;
135
136 match (p1, p2, p3) {
137 (None, _, _) => {
138 namespace = DEFAULT_NAMESPACE.to_string();
141 component = DEFAULT_COMPONENT.to_string();
142 name = DEFAULT_ENDPOINT.to_string();
143 }
144 (Some(c), None, _) => {
145 namespace = DEFAULT_NAMESPACE.to_string();
146 component = c.to_string();
147 name = DEFAULT_ENDPOINT.to_string();
148 }
149 (Some(ns), Some(c), None) => {
150 namespace = ns.to_string();
152 component = c.to_string();
153 name = DEFAULT_ENDPOINT.to_string();
154 }
155 (Some(ns), Some(c), Some(ep)) => {
156 namespace = ns.to_string();
157 component = c.to_string();
158
159 let mut endpoint_buf = String::from(ep); for part in parts {
163 endpoint_buf.push('_');
165 endpoint_buf.push_str(part);
166 }
167 name = endpoint_buf;
168 }
169 }
170
171 EndpointId {
172 namespace,
173 component,
174 name,
175 }
176 }
177}
178
179impl FromStr for EndpointId {
180 type Err = core::convert::Infallible;
181
182 fn from_str(s: &str) -> Result<Self, Self::Err> {
203 Ok(EndpointId::from(s))
204 }
205}
206
207impl EndpointId {
208 pub fn as_url(&self) -> String {
210 format!(
211 "{ENDPOINT_SCHEME}{}.{}.{}",
212 self.namespace, self.component, self.name
213 )
214 }
215}
216
217#[cfg(test)]
218mod tests {
219 use super::*;
220 use std::convert::TryFrom;
221 use std::str::FromStr;
222
223 #[test]
224 fn test_valid_endpoint_from() {
225 let input = "namespace1/component1/endpoint1";
226 let endpoint = EndpointId::from(input);
227
228 assert_eq!(endpoint.namespace, "namespace1");
229 assert_eq!(endpoint.component, "component1");
230 assert_eq!(endpoint.name, "endpoint1");
231 }
232
233 #[test]
234 fn test_valid_endpoint_from_str() {
235 let input = "namespace2/component2/endpoint2";
236 let endpoint = EndpointId::from_str(input).unwrap();
237
238 assert_eq!(endpoint.namespace, "namespace2");
239 assert_eq!(endpoint.component, "component2");
240 assert_eq!(endpoint.name, "endpoint2");
241 }
242
243 #[test]
244 fn test_valid_endpoint_parse() {
245 let input = "namespace3/component3/endpoint3";
246 let endpoint: EndpointId = input.parse().unwrap();
247
248 assert_eq!(endpoint.namespace, "namespace3");
249 assert_eq!(endpoint.component, "component3");
250 assert_eq!(endpoint.name, "endpoint3");
251 }
252
253 #[test]
254 fn test_endpoint_from() {
255 let result = EndpointId::from("component");
256 assert_eq!(
257 result,
258 vec![DEFAULT_NAMESPACE, "component", DEFAULT_ENDPOINT]
259 );
260 }
261
262 #[test]
263 fn test_namespace_component_endpoint() {
264 let result = EndpointId::from("namespace.component.endpoint");
265 assert_eq!(result, vec!["namespace", "component", "endpoint"]);
266 }
267
268 #[test]
269 fn test_forward_slash_separator() {
270 let result = EndpointId::from("namespace/component");
271 assert_eq!(result, vec!["namespace", "component", DEFAULT_ENDPOINT]);
272 }
273
274 #[test]
275 fn test_multiple_parts() {
276 let result = EndpointId::from("namespace.component.endpoint.other.parts");
277 assert_eq!(
278 result,
279 vec!["namespace", "component", "endpoint_other_parts"]
280 );
281 }
282
283 #[test]
284 fn test_mixed_separators() {
285 let result: EndpointId = "namespace/component.endpoint".into();
287 assert_eq!(result, vec!["namespace", "component", "endpoint"]);
288 }
289
290 #[test]
291 fn test_empty_string() {
292 let result = EndpointId::from("");
293 assert_eq!(
294 result,
295 vec![DEFAULT_NAMESPACE, DEFAULT_COMPONENT, DEFAULT_ENDPOINT]
296 );
297
298 let result = EndpointId::from(" ");
300 assert_eq!(
301 result,
302 vec![DEFAULT_NAMESPACE, DEFAULT_COMPONENT, DEFAULT_ENDPOINT]
303 );
304 }
305
306 #[test]
307 fn test_parse_with_scheme_and_url_roundtrip() {
308 let input = "dyn://ns/cp/ep";
309 let endpoint: EndpointId = input.parse().unwrap();
310 assert_eq!(endpoint, vec!["ns", "cp", "ep"]);
311 assert_eq!(endpoint.as_url(), "dyn://ns.cp.ep");
312 }
313}