ftl_protocol/protocol/
command.rs1use 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}