1use std::path::PathBuf;
2
3use clap::{Args, Parser, Subcommand, ValueEnum};
4
5#[derive(Debug, Clone, PartialEq, ValueEnum)]
6pub enum ConfigAdapter {
7 Kdl,
8 #[cfg(feature = "config-yaml-legacy")]
9 YamlLegacy,
10 #[cfg(feature = "config-docker-auto")]
11 DockerAuto,
12}
13
14#[derive(ValueEnum, Debug, Clone, PartialEq)]
15pub enum LogOutput {
16 Stdout,
17 Stderr,
18 Off,
19}
20
21#[derive(Args, Debug, Clone, PartialEq)]
22pub struct ServeArgs {
23 #[arg(short, long, default_value = "127.0.0.1")]
25 pub listen_ip: String,
26
27 #[arg(short, long, default_value = "3000")]
29 pub port: u16,
30
31 #[arg(short, long, default_value = ".")]
33 pub root: PathBuf,
34
35 #[arg(short, long)]
40 pub credential: Vec<String>,
41
42 #[arg(long)]
44 pub disable_brute_protection: bool,
45
46 #[arg(long)]
48 pub forward_proxy: bool,
49
50 #[arg(long, default_value = "stdout")]
52 pub log: LogOutput,
53
54 #[arg(long, default_value = "stderr")]
56 pub error_log: LogOutput,
57}
58
59#[derive(Subcommand, Debug, Clone, PartialEq)]
60pub enum Command {
61 Serve(ServeArgs),
63}
64
65#[derive(Parser, Debug, PartialEq)]
67#[command(about, long_about = None)]
68pub struct FerronArgs {
69 #[arg(short, long, default_value = "./ferron.kdl")]
71 pub config: PathBuf,
72
73 #[arg(long)]
75 pub config_string: Option<String>,
76
77 #[arg(long, value_enum)]
79 pub config_adapter: Option<ConfigAdapter>,
80
81 #[arg(long)]
83 pub module_config: bool,
84
85 #[arg(short = 'V', long)]
87 pub version: bool,
88
89 #[command(subcommand)]
90 pub command: Option<Command>,
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96
97 const COMMON_TEST_PASSWORD: &str =
99 "$argon2id$v=19$m=19456,t=2,p=1$emTillHaS3OqFuvITdXxzg$G00heP8QSXk5H/ruTiLt302Xk3uETfU5QO8hBIwUq08";
100
101 #[test]
102 fn test_supported_args() {
103 let args = FerronArgs::parse_from(vec![
104 "ferron",
105 "--config",
106 "/dev/null",
107 "--config-adapter",
108 "kdl",
109 "--module-config",
110 "--version",
111 ]);
112 assert!(args.module_config);
113 assert!(args.version);
114 assert_eq!(PathBuf::from("/dev/null"), args.config);
115 assert_eq!(Some(ConfigAdapter::Kdl), args.config_adapter);
116 assert_eq!(None, args.command);
117 }
118
119 #[test]
120 fn test_supported_args_short_options() {
121 let args = FerronArgs::parse_from(vec![
122 "ferron",
123 "-c",
124 "/dev/null",
125 "--config-adapter",
126 "kdl",
127 "--module-config",
128 "-V",
129 ]);
130 assert!(args.module_config);
131 assert!(args.version);
132 assert_eq!(PathBuf::from("/dev/null"), args.config);
133 assert_eq!(None, args.config_string);
134 assert_eq!(Some(ConfigAdapter::Kdl), args.config_adapter);
135 assert_eq!(None, args.command);
136 }
137
138 #[test]
139 fn test_supported_optional_args() {
140 let args = FerronArgs::parse_from(vec!["ferron"]);
141 assert!(!args.module_config);
142 assert!(!args.version);
143 assert_eq!(PathBuf::from("./ferron.kdl"), args.config);
144 assert_eq!(None, args.config_string);
145 assert_eq!(None, args.config_adapter);
146 assert_eq!(None, args.command);
147 }
148
149 #[test]
150 fn test_supported_config_string_arg() {
151 let expected_string =
152 String::from(":8080 {\n log \"/dev/stderr\"\n error_log \"/dev/stderr\"\n root \"/mnt/www\"\n}");
153 let args = FerronArgs::parse_from(vec!["ferron", "--config-string", &expected_string]);
154 assert!(!args.module_config);
155 assert!(!args.version);
156 assert_eq!(PathBuf::from("./ferron.kdl"), args.config);
157 assert_eq!(Some(expected_string), args.config_string);
158 assert_eq!(None, args.config_adapter);
159 assert_eq!(None, args.command);
160 }
161
162 #[test]
163 fn test_supported_http_serve_default_args() {
164 let args = FerronArgs::parse_from(vec!["ferron", "serve"]);
165 assert!(!args.module_config);
166 assert!(!args.version);
167 assert_eq!(PathBuf::from("./ferron.kdl"), args.config);
168 assert_eq!(None, args.config_string);
169 assert_eq!(None, args.config_adapter);
170 assert!(args.command.is_some());
171 match args.command.unwrap() {
172 Command::Serve(http_serve_args) => {
173 assert_eq!(String::from("127.0.0.1"), http_serve_args.listen_ip);
174 assert_eq!(3000, http_serve_args.port);
175 assert_eq!(PathBuf::from("."), http_serve_args.root);
176 assert_eq!(Vec::<String>::new(), http_serve_args.credential);
177 assert!(!http_serve_args.disable_brute_protection);
178 assert!(!http_serve_args.forward_proxy);
179 assert_eq!(LogOutput::Stdout, http_serve_args.log);
180 assert_eq!(LogOutput::Stderr, http_serve_args.error_log);
181 }
182 }
183 }
184
185 #[test]
186 fn test_supported_http_serve_args() {
187 let args = FerronArgs::parse_from(vec![
188 "ferron",
189 "serve",
190 "--listen-ip",
191 "0.0.0.0",
192 "--port",
193 "8080",
194 "--root",
195 "./wwwroot",
196 "--credential",
197 format!("test:{COMMON_TEST_PASSWORD}").as_str(),
198 "--credential",
199 format!("test2:{COMMON_TEST_PASSWORD}").as_str(),
200 "--disable-brute-protection",
201 "--forward-proxy",
202 "--log",
203 "off",
204 "--error-log",
205 "off",
206 ]);
207 assert!(!args.module_config);
208 assert!(!args.version);
209 assert_eq!(PathBuf::from("./ferron.kdl"), args.config);
210 assert_eq!(None, args.config_string);
211 assert_eq!(None, args.config_adapter);
212 assert!(args.command.is_some());
213 match args.command.unwrap() {
214 Command::Serve(http_serve_args) => {
215 assert_eq!(String::from("0.0.0.0"), http_serve_args.listen_ip);
216 assert_eq!(8080, http_serve_args.port);
217 assert_eq!(PathBuf::from("./wwwroot"), http_serve_args.root);
218 assert_eq!(
219 vec![
220 format!("test:{COMMON_TEST_PASSWORD}"),
221 format!("test2:{COMMON_TEST_PASSWORD}")
222 ],
223 http_serve_args.credential
224 );
225 assert!(http_serve_args.disable_brute_protection);
226 assert!(http_serve_args.forward_proxy);
227 assert_eq!(LogOutput::Off, http_serve_args.log);
228 assert_eq!(LogOutput::Off, http_serve_args.error_log);
229 }
230 }
231 }
232}