ftl_protocol/protocol/
command.rs

1use std::str::FromStr;
2
3use super::FtlError;
4
5#[derive(Debug, PartialEq)]
6pub enum FtlCommand {
7    HMAC,
8    Connect {
9        channel_id: String,
10        hashed_hmac_payload: String,
11    },
12    Dot,
13    Attribute {
14        key: String,
15        value: String,
16    },
17    Ping {
18        channel_id: String,
19    },
20    Disconnect,
21}
22
23impl FromStr for FtlCommand {
24    type Err = FtlError;
25
26    fn from_str(s: &str) -> Result<Self, Self::Err> {
27        match s {
28            "HMAC" => Ok(FtlCommand::HMAC),
29            "." => Ok(FtlCommand::Dot),
30            "DISCONNECT" => Ok(FtlCommand::Disconnect),
31            s => {
32                if &s[..4] == "PING" {
33                    Ok(FtlCommand::Ping {
34                        channel_id: s[5..].to_string(),
35                    })
36                } else if &s[..7] == "CONNECT" {
37                    let parts = &mut s[8..].split(" ");
38
39                    Ok(FtlCommand::Connect {
40                        channel_id: parts
41                            .next()
42                            .ok_or_else(|| FtlError::MissingPart)?
43                            .to_string(),
44                        hashed_hmac_payload: parts
45                            .next()
46                            .ok_or_else(|| FtlError::MissingPart)?
47                            .to_string(),
48                    })
49                } else {
50                    if s.contains(':') {
51                        let mut parts = s.split(':').map(|v| v.trim());
52
53                        return Ok(FtlCommand::Attribute {
54                            key: parts
55                                .next()
56                                .ok_or_else(|| FtlError::MissingPart)?
57                                .to_string(),
58                            value: parts
59                                .next()
60                                .ok_or_else(|| FtlError::MissingPart)?
61                                .to_string(),
62                        });
63                    }
64
65                    Err(FtlError::UnimplementedCommand)
66                }
67            }
68        }
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use std::str::FromStr;
75
76    use crate::protocol::FtlCommand;
77
78    #[test]
79    fn should_parse_hmac() {
80        let command = FtlCommand::from_str("HMAC").unwrap();
81        assert_eq!(command, FtlCommand::HMAC);
82    }
83
84    #[test]
85    fn should_parse_dot() {
86        let command = FtlCommand::from_str(".").unwrap();
87        assert_eq!(command, FtlCommand::Dot);
88    }
89
90    #[test]
91    fn should_parse_disconnect() {
92        let command = FtlCommand::from_str("DISCONNECT").unwrap();
93        assert_eq!(command, FtlCommand::Disconnect);
94    }
95
96    #[test]
97    fn should_parse_ping() {
98        let command = FtlCommand::from_str("PING channel_id").unwrap();
99        assert_eq!(command, FtlCommand::Ping { channel_id: "channel_id".to_string() });
100    }
101
102    #[test]
103    fn should_parse_connect() {
104        let command = FtlCommand::from_str("CONNECT channel_id hmac").unwrap();
105        assert_eq!(command, FtlCommand::Connect { channel_id: "channel_id".to_string(), hashed_hmac_payload: "hmac".to_string() });
106    }
107
108    #[test]
109    fn should_parse_attribute() {
110        let command = FtlCommand::from_str("ProtocolVersion: 0.9").unwrap();
111        assert_eq!(command, FtlCommand::Attribute { key: "ProtocolVersion".to_string(), value: "0.9".to_string() });
112    }
113
114    #[test]
115    fn should_fail_other() {
116        let command = FtlCommand::from_str("i am invalid data");
117        assert!(command.is_err())
118    }
119
120    #[test]
121    fn doc_test() {
122        use crate::protocol::FtlCommand;
123
124        let command = FtlCommand::from_str(".").unwrap();
125        assert_eq!(command, FtlCommand::Dot);
126
127        let command = FtlCommand::from_str("PING 123").unwrap();
128        assert_eq!(command, FtlCommand::Ping {
129            channel_id: "123".to_string()
130        });
131    }
132}