1use std::fmt;
10
11#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct Route {
16 pub key: String,
17 pub session: Option<String>,
18}
19
20impl Route {
21 pub fn new(key: impl Into<String>, session: impl Into<String>) -> Self {
24 let session = session.into();
25 Route {
26 key: key.into(),
27 session: (!session.is_empty()).then_some(session),
28 }
29 }
30
31 pub fn bare(key: impl Into<String>) -> Self {
33 Route {
34 key: key.into(),
35 session: None,
36 }
37 }
38
39 pub fn parse(s: &str) -> Self {
42 match s.split_once('#') {
43 Some((key, session)) if !session.is_empty() => Route {
44 key: key.to_string(),
45 session: Some(session.to_string()),
46 },
47 _ => Route::bare(s),
48 }
49 }
50
51 pub fn session(&self) -> Option<&str> {
53 self.session.as_deref()
54 }
55}
56
57impl fmt::Display for Route {
58 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59 match &self.session {
60 Some(s) => write!(f, "{}#{}", self.key, s),
61 None => write!(f, "{}", self.key),
62 }
63 }
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69
70 #[test]
71 fn round_trips_session_scoped() {
72 let r = Route::new("KEY", "s12");
73 assert_eq!(r.to_string(), "KEY#s12");
74 assert_eq!(Route::parse("KEY#s12"), r);
75 assert_eq!(r.session(), Some("s12"));
76 }
77
78 #[test]
79 fn empty_session_is_bare() {
80 assert_eq!(Route::new("KEY", ""), Route::bare("KEY"));
81 assert_eq!(Route::new("KEY", "").to_string(), "KEY");
82 assert_eq!(Route::parse("KEY").session(), None);
83 assert_eq!(Route::parse("KEY#").session(), None, "trailing # is bare");
84 }
85
86 #[test]
87 fn parse_splits_on_the_separator() {
88 let r = Route::parse("KEY#abcd");
89 assert_eq!(r.key, "KEY");
90 assert_eq!(r.session(), Some("abcd"));
91 }
92}