server_watchdog/application/handler/
command.rs1mod alarm;
2
3use std::error::Error;
4use anyhow::anyhow;
5use async_trait::async_trait;
6use log::{debug, trace};
7use crate::application::handler::command::alarm::AlarmCommand;
8use crate::application::handler::command::Command::{Alarm, EventList, HealthCheck, HealthCheckAll, Help, Logs, Nothing};
9use crate::application::handler::GeneralHandler;
10use crate::domain::client::Message;
11
12pub struct CommandMeta {
13 pub name: &'static str,
14 pub description: &'static str,
15 pub usage: &'static str,
16 pub examples: &'static [&'static str],
17}
18
19impl CommandMeta {
20 pub const fn new(
21 name: &'static str,
22 description: &'static str,
23 usage: &'static str,
24 examples: &'static [&'static str],
25 ) -> Self {
26 Self { name, description, usage, examples }
27 }
28}
29
30#[async_trait]
31pub trait Run: Send + Sync {
32 async fn run(&self, handler: &mut GeneralHandler, id: String, message: &Message) -> Result<String, Box<dyn Error + Send + Sync>>;
33}
34
35#[derive(Debug)]
36pub enum Command {
37 Logs(String, i32),
38 HealthCheckAll,
39 HealthCheck(String),
40 Nothing,
41 Alarm(AlarmCommand),
42 EventList,
43 Help(Option<String>),
44}
45
46impl Command {
47 const HELP: CommandMeta = CommandMeta::new(
48 "/help",
49 "커맨드 사용법을 표시합니다",
50 "/help [command]",
51 &["/help", "/help logs"],
52 );
53 const LOGS: CommandMeta = CommandMeta::new(
54 "/logs",
55 "서버의 최근 로그를 가져옵니다",
56 "/logs <server_name> <lines>",
57 &["/logs main 100", "/logs api 50"],
58 );
59 const HEALTH: CommandMeta = CommandMeta::new(
60 "/health",
61 "서버 헬스체크 결과를 확인합니다",
62 "/health [server_name]",
63 &["/health", "/health main"],
64 );
65 const ALARM: CommandMeta = CommandMeta::new(
66 "/alarm",
67 "이벤트 알람을 구독 또는 해제합니다",
68 "/alarm <add|remove|list> [event_name]",
69 &["/alarm list", "/alarm add cpu-high", "/alarm remove cpu-high"],
70 );
71 const EVENT: CommandMeta = CommandMeta::new(
72 "/event",
73 "설정된 이벤트 목록을 표시합니다",
74 "/event [list]",
75 &["/event", "/event list"],
76 );
77
78 pub fn meta(&self) -> Option<&'static CommandMeta> {
79 match self {
80 Help(_) => Some(&Self::HELP),
81 Logs(_, _) => Some(&Self::LOGS),
82 HealthCheck(_) | HealthCheckAll => Some(&Self::HEALTH),
83 Alarm(_) => Some(&Self::ALARM),
84 EventList => Some(&Self::EVENT),
85 Nothing => None,
86 }
87 }
88
89 pub fn all_docs() -> &'static [&'static CommandMeta] {
90 &[&Self::HELP, &Self::LOGS, &Self::HEALTH, &Self::ALARM, &Self::EVENT]
91 }
92
93 pub fn render_help_all() -> String {
94 let list = Self::all_docs()
95 .iter()
96 .map(|m| format!("{} — {}", m.name, m.description))
97 .collect::<Vec<_>>()
98 .join("\n");
99 format!("사용 가능한 커맨드:\n\n{list}\n\n자세한 사용법: /help <command>\n예시: /help logs")
100 }
101
102 pub fn render_help_one(name: &str) -> Option<String> {
103 let meta = Self::all_docs()
104 .iter()
105 .find(|m| m.name.trim_start_matches('/') == name)?;
106 let examples = meta.examples
107 .iter()
108 .map(|e| format!(" {e}"))
109 .collect::<Vec<_>>()
110 .join("\n");
111 Some(format!(
112 "{} — {}\n\n사용법:\n {}\n\n예시:\n{examples}",
113 meta.name, meta.description, meta.usage
114 ))
115 }
116}
117
118#[async_trait]
119impl Run for Command {
120 async fn run(&self, handler: &mut GeneralHandler, id: String, message: &Message) -> Result<String, Box<dyn Error + Send + Sync>> {
121 match self {
122 Command::Logs(name, n) => {
123 handler.server_manager.logs(name.as_str(), *n).await
124 .ok_or_else(|| anyhow!("Logs are not available."))
125 .map_err(Into::into)
126 },
127 Command::HealthCheck(name) => {
128 let health = handler.server_manager.healthcheck(name.as_str()).await;
129 let response = format!("===\nServer: {name}\n Health: {health}");
130 Ok(response)
131 },
132 Command::HealthCheckAll => {
133 let response = handler.server_manager.healthcheck_all()
134 .await
135 .iter().map(|result|{format!("===\nServer: {}\nHealth: {}", result.0, result.1)})
136 .collect::<Vec<String>>()
137 .join("\n");
138 Ok(response)
139 },
140 Command::Alarm(command) => {
141 command.run(handler, id, message).await
142 },
143 Command::EventList => {
144 let events = handler.event_config_use_case.list_event().await?;
145 let event_names = events.iter().map(|e| e.name.clone()).collect::<Vec<String>>().join("\n");
146 Ok(format!("Available events:\n{}", event_names))
147 },
148 Command::Help(name) => match name {
149 None => Ok(Command::render_help_all()),
150 Some(name) => Command::render_help_one(name)
151 .ok_or_else(|| anyhow!("알 수 없는 커맨드: /{name}\n\n{}", Command::render_help_all()).into()),
152 },
153 Command::Nothing => Ok(Command::render_help_all()),
154 }
155 }
156}
157
158impl Command {
159 pub fn parse(text: &str) -> Self {
160 trace!("Command::parse(text: {})", &text);
161 let command = match text.split_whitespace().collect::<Vec<_>>()[..] {
162 ["/help"] => Help(None),
163 ["/help", name] => Help(Some(name.trim_start_matches('/').to_string())),
164 ["/health", name] => HealthCheck(name.to_string()),
165 ["/health"] => HealthCheckAll,
166 ["/logs", name, n] => {
167 match n.parse() {
168 Ok(n) => Logs(name.to_string(), n),
169 Err(_) => Nothing
170 }
171 },
172 ["/alarm", "add", name] => {
173 Alarm(AlarmCommand::Add(String::from(name)))
174 },
175 ["/alarm", "remove", name] => {
176 Alarm(AlarmCommand::Remove(String::from(name)))
177 },
178 ["/alarm", "list"] => {
179 Alarm(AlarmCommand::List)
180 },
181 ["/alarm"] => {
182 Alarm(AlarmCommand::List)
183 },
184 ["/event", "list"] => EventList,
185 ["/event"] => EventList,
186 _ => Nothing
187 };
188 debug!("parsed command: {:?}", &command);
189 command
190 }
191}