cord_message/
message.rs

1use crate::Pattern;
2
3use std::mem;
4
5macro_rules! unwrap_msg {
6    ($variant:tt, $func_name:ident) => {
7        pub fn $func_name(self) -> Pattern {
8            match self {
9                Message::$variant(pattern) => pattern,
10                _ => panic!("Expected Message variant $variant, got {:?}", self),
11            }
12        }
13    };
14}
15
16#[derive(Clone, Debug, Eq, Hash, PartialEq)]
17pub enum Message {
18    Provide(Pattern),
19    Revoke(Pattern),
20    Subscribe(Pattern),
21    Unsubscribe(Pattern),
22    Event(Pattern, String),
23}
24
25impl Message {
26    // Check that the discriminant is in range. The magic number "4" is the maximum
27    // integer assigned to a Message variant. See `poor_mans_discriminant()` for details.
28    // Note that we take a ref u8 instead of an owned u8 to be compatible with
29    // `Option::filter`, which passes all args by ref.
30    #[allow(clippy::trivially_copy_pass_by_ref)]
31    pub fn test_poor_mans_discriminant(discriminant: &u8) -> bool {
32        *discriminant <= 4
33    }
34
35    // This function takes a discriminant and all other data necessary to instantiate a
36    // new Message variant. To avoid panics, first check the discriminant with
37    // `test_poor_mans_discriminant()`.
38    pub fn from_poor_mans_discriminant(
39        discriminant: u8,
40        namespace: Pattern,
41        data: Option<String>,
42    ) -> Self {
43        match discriminant {
44            0 => Message::Provide(namespace),
45            1 => Message::Revoke(namespace),
46            2 => Message::Subscribe(namespace),
47            3 => Message::Unsubscribe(namespace),
48            4 => Message::Event(
49                namespace,
50                data.expect("Data must be present for Message::Event type"),
51            ),
52            _ => panic!("Invalid discriminant {}", discriminant),
53        }
54    }
55
56    // These macro-generated functions will destructure the Message enum, returning an
57    // owned Pattern.
58    unwrap_msg!(Provide, unwrap_provide);
59    unwrap_msg!(Revoke, unwrap_revoke);
60    unwrap_msg!(Subscribe, unwrap_subscribe);
61    unwrap_msg!(Unsubscribe, unwrap_unsubscribe);
62
63    // Return the namespace pattern for any variant
64    pub fn namespace(&self) -> &Pattern {
65        match self {
66            Message::Provide(p) => p,
67            Message::Revoke(p) => p,
68            Message::Subscribe(p) => p,
69            Message::Unsubscribe(p) => p,
70            Message::Event(p, _) => p,
71        }
72    }
73
74    // XXX Unfortunately Rust doesn't provide a way to access the underlying discriminant
75    // value. Thus we have to invent our own. Lame!
76    //
77    // The fn name is going to stay horrible until a better solution is found. Don't want
78    // to get comfortable with this hack :)
79    // https://github.com/rust-lang/rust/issues/34244
80    pub fn poor_mans_discriminant(&self) -> u8 {
81        match self {
82            Message::Provide(_) => 0,
83            Message::Revoke(_) => 1,
84            Message::Subscribe(_) => 2,
85            Message::Unsubscribe(_) => 3,
86            Message::Event(_, _) => 4,
87        }
88    }
89
90    pub fn contains(&self, other: &Message) -> bool {
91        mem::discriminant(self) == mem::discriminant(other)
92            && self.namespace().contains(other.namespace())
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99
100    #[test]
101    fn test_poor_mans_discriminant() {
102        let pattern = Pattern::new("/");
103
104        let provide = Message::Provide(pattern.clone());
105        assert_eq!(
106            Message::from_poor_mans_discriminant(
107                provide.poor_mans_discriminant(),
108                pattern.clone(),
109                None
110            ),
111            provide
112        );
113
114        let revoke = Message::Revoke(pattern.clone());
115        assert_eq!(
116            Message::from_poor_mans_discriminant(
117                revoke.poor_mans_discriminant(),
118                pattern.clone(),
119                None
120            ),
121            revoke
122        );
123
124        let subscribe = Message::Subscribe(pattern.clone());
125        assert_eq!(
126            Message::from_poor_mans_discriminant(
127                subscribe.poor_mans_discriminant(),
128                pattern.clone(),
129                None
130            ),
131            subscribe
132        );
133
134        let unsubscribe = Message::Unsubscribe(pattern.clone());
135        assert_eq!(
136            Message::from_poor_mans_discriminant(
137                unsubscribe.poor_mans_discriminant(),
138                pattern.clone(),
139                None
140            ),
141            unsubscribe
142        );
143
144        let event = Message::Event(pattern.clone(), String::new());
145        assert_eq!(
146            Message::from_poor_mans_discriminant(
147                event.poor_mans_discriminant(),
148                pattern,
149                Some(String::new())
150            ),
151            event
152        );
153    }
154
155    #[test]
156    fn test_message_contains() {
157        let message = Message::Provide("/a".into());
158
159        // Should contain the same namespace
160        assert!(message.contains(&Message::Provide("/a".into())));
161        // Should contain a sub-namespace
162        assert!(message.contains(&Message::Provide("/a/b".into())));
163        // Should not contain a different message type
164        assert!(!message.contains(&Message::Revoke("/a".into())));
165        // Should not contain a different namespace
166        assert!(!message.contains(&Message::Provide("/c".into())));
167
168        // Event messages should not consider their data components
169        assert!(Message::Event("/a".into(), String::new())
170            .contains(&Message::Event("/a".into(), "b".into())));
171
172        // Root namespace should match everything
173        assert!(Message::Provide("/".into()).contains(&Message::Provide("/a/b/c".into())));
174    }
175}