gurty_cli/
command_handler.rs1use crate::{
2 cli::ServeCommand,
3 config::GurtConfig,
4 server::FileServerBuilder,
5 Result,
6};
7use async_trait::async_trait;
8use colored::Colorize;
9use tracing::{error, info};
10
11#[async_trait]
12pub trait CommandHandler {
13 async fn execute(&self) -> Result<()>;
14}
15
16pub struct CommandHandlerBuilder {
17 logging_initialized: bool,
18 verbose: bool,
19}
20
21impl CommandHandlerBuilder {
22 pub fn new() -> Self {
23 Self {
24 logging_initialized: false,
25 verbose: false,
26 }
27 }
28
29 pub fn with_logging(mut self, verbose: bool) -> Self {
30 self.verbose = verbose;
31 self
32 }
33
34 pub fn initialize_logging(mut self) -> Self {
35 if !self.logging_initialized {
36 let level = if self.verbose {
37 tracing::Level::DEBUG
38 } else {
39 tracing::Level::INFO
40 };
41
42 tracing_subscriber::fmt()
43 .with_max_level(level)
44 .init();
45
46 self.logging_initialized = true;
47 }
48 self
49 }
50
51 pub fn build_serve_handler(self, serve_cmd: ServeCommand) -> ServeCommandHandler {
52 ServeCommandHandler::new(serve_cmd)
53 }
54}
55
56impl Default for CommandHandlerBuilder {
57 fn default() -> Self {
58 Self::new()
59 }
60}
61
62pub struct ServeCommandHandler {
63 serve_cmd: ServeCommand,
64}
65
66impl ServeCommandHandler {
67 pub fn new(serve_cmd: ServeCommand) -> Self {
68 Self { serve_cmd }
69 }
70
71 fn validate_command(&self) -> Result<()> {
72 if !self.serve_cmd.dir.exists() {
73 return Err(crate::ServerError::InvalidPath(
74 format!("Directory does not exist: {}", self.serve_cmd.dir.display())
75 ));
76 }
77
78 if !self.serve_cmd.dir.is_dir() {
79 return Err(crate::ServerError::InvalidPath(
80 format!("Path is not a directory: {}", self.serve_cmd.dir.display())
81 ));
82 }
83
84 Ok(())
85 }
86
87 fn build_server_config(&self) -> Result<GurtConfig> {
88 let mut config_builder = GurtConfig::builder();
89
90 if let Some(config_file) = &self.serve_cmd.config {
91 config_builder = config_builder.from_file(config_file)?;
92 }
93
94 let config = config_builder
95 .merge_cli_args(&self.serve_cmd)
96 .build()?;
97
98 Ok(config)
99 }
100
101 fn display_startup_info(&self, config: &GurtConfig) {
102 println!("{}", "GURT Protocol Server".bright_cyan().bold());
103 println!("{} {}", "Version".bright_blue(), config.server.protocol_version);
104 println!("{} {}", "Listening on".bright_blue(), config.address());
105 println!("{} {}", "Serving from".bright_blue(), config.server.base_directory.display());
106
107 if config.tls.is_some() {
108 println!("{}", "TLS encryption enabled".bright_green());
109 }
110
111 if let Some(logging) = &config.logging {
112 println!("{} {}", "Log level".bright_blue(), logging.level);
113 if logging.log_requests {
114 println!("{}", "Request logging enabled".bright_green());
115 }
116 }
117
118 if let Some(security) = &config.security {
119 println!("{} {} req/min", "Rate limit".bright_blue(), security.rate_limit_requests);
120 if !security.deny_files.is_empty() {
121 println!("{} {} patterns", "File restrictions".bright_blue(), security.deny_files.len());
122 }
123 }
124
125 if let Some(headers) = &config.headers {
126 if !headers.is_empty() {
127 println!("{} {} headers", "Custom headers".bright_blue(), headers.len());
128 }
129 }
130
131 println!("{} {}", "Max connections".bright_blue(), config.server.max_connections);
132 println!("{} {}", "Max message size".bright_blue(), config.server.max_message_size);
133 println!();
134 }
135
136 async fn start_server(&self, config: &GurtConfig) -> Result<()> {
137 let server = FileServerBuilder::new(config.clone()).build()?;
138
139 info!("Starting GURT server on {}", config.address());
140
141 if let Err(e) = server.listen(&config.address()).await {
142 error!("Server error: {}", e);
143 std::process::exit(1);
144 }
145
146 Ok(())
147 }
148}
149
150#[async_trait]
151impl CommandHandler for ServeCommandHandler {
152 async fn execute(&self) -> Result<()> {
153 self.validate_command()?;
154
155 let config = self.build_server_config()?;
156
157 self.display_startup_info(&config);
158 self.start_server(&config).await
159 }
160}