haproxy_stats/
command.rs

1use core::{fmt, ops::ControlFlow, str::FromStr};
2
3//
4pub(crate) const SEMI_COLON: char = ';';
5const BACKSLASH: char = '\\';
6
7//
8#[derive(Debug, Clone)]
9pub struct Command {
10    inner: Box<str>,
11}
12
13impl Command {
14    pub fn new(command: impl AsRef<str>) -> Result<Self, CommandParseError> {
15        let command = command.as_ref();
16
17        let control_flow = command.chars().try_fold(None, |prev, x| {
18            if x == SEMI_COLON {
19                if prev == Some(BACKSLASH) {
20                    ControlFlow::Continue(Some(x))
21                } else {
22                    ControlFlow::Break(())
23                }
24            } else {
25                ControlFlow::Continue(Some(x))
26            }
27        });
28
29        match control_flow {
30            ControlFlow::Continue(_) => {}
31            ControlFlow::Break(_) => return Err(CommandParseError::RequireEscapeSemiColon),
32        }
33
34        Ok(Self {
35            inner: command.into(),
36        })
37    }
38
39    pub fn as_str(&self) -> &str {
40        &self.inner
41    }
42
43    pub fn to_write_bytes(&self) -> Vec<u8> {
44        format!("{}\r\n", self.as_str()).as_bytes().to_vec()
45    }
46}
47
48impl Command {
49    pub fn show_info() -> Self {
50        Self::new("show info").expect("")
51    }
52
53    pub fn show_stat() -> Self {
54        Self::new("show stat").expect("")
55    }
56
57    pub fn show_env() -> Self {
58        Self::new("show env").expect("")
59    }
60}
61
62//
63#[derive(Debug)]
64pub enum CommandParseError {
65    RequireEscapeSemiColon,
66}
67
68impl fmt::Display for CommandParseError {
69    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70        write!(f, "{:?}", self)
71    }
72}
73
74impl std::error::Error for CommandParseError {}
75
76//
77impl fmt::Display for Command {
78    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79        write!(f, "{}", self.as_str())
80    }
81}
82
83//
84impl FromStr for Command {
85    type Err = CommandParseError;
86
87    fn from_str(s: &str) -> Result<Self, Self::Err> {
88        Self::new(s)
89    }
90}
91
92//
93//
94//
95#[derive(Debug, Clone)]
96pub struct Commands<'a>(pub &'a [Command]);
97
98impl<'a> Commands<'a> {
99    pub fn new(inner: &'a [Command]) -> Self {
100        Self(inner)
101    }
102
103    fn internal_to_string(&self) -> String {
104        self.0
105            .iter()
106            .map(|x| x.as_str())
107            .collect::<Vec<_>>()
108            .join(SEMI_COLON.to_string().as_str())
109    }
110
111    pub fn to_write_bytes(&self) -> Vec<u8> {
112        format!("{}\r\n", self.internal_to_string())
113            .as_bytes()
114            .to_vec()
115    }
116}
117
118//
119impl<'a> fmt::Display for Commands<'a> {
120    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121        write!(f, "{}", self.internal_to_string())
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128
129    #[test]
130    fn test_command_new() {
131        assert_eq!(Command::show_stat().as_str(), "show stat");
132
133        //
134        match Command::new("show stat;") {
135            Err(CommandParseError::RequireEscapeSemiColon) => {}
136            x => panic!("{:?}", x),
137        }
138    }
139}