1use crate::config::{ClientConfig, MaskedString, Platform};
2use anyhow::{bail, Context, Result};
3use std::str::FromStr;
4
5macro_rules! config_options {
6 ($(
7 $variant:ident => $key:literal,
8 get: $getter:expr,
9 set: $setter:expr
10 ),* $(,)?) => {
11 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
12 pub enum ConfigOption {
13 $($variant,)*
14 }
15
16 impl ConfigOption {
17 pub fn all() -> &'static [Self] {
18 &[$(Self::$variant,)*]
19 }
20
21 pub fn as_str(&self) -> &'static str {
22 match self {
23 $(Self::$variant => $key,)*
24 }
25 }
26
27
28 pub fn get_value(&self, config: &ClientConfig) -> Result<String> {
29 match self {
30 $(Self::$variant => $getter(config),)*
31 }
32 }
33
34 pub fn set_value(&self, config: &mut ClientConfig, value: &str) -> Result<()> {
35 match self {
36 $(Self::$variant => $setter(config, value)?,)*
37 }
38 Ok(())
39 }
40 }
41
42 impl FromStr for ConfigOption {
43 type Err = anyhow::Error;
44
45 fn from_str(s: &str) -> Result<Self, Self::Err> {
46 match s {
47 $($key => Ok(Self::$variant),)*
48 _ => bail!("Unknown config option: {}", s),
49 }
50 }
51 }
52 };
53}
54
55config_options! {
56 Server => "server",
57 get: |config: &ClientConfig| Ok(config.server.to_string()),
58 set: |config: &mut ClientConfig, value: &str| -> Result<(), anyhow::Error> {
59 config.server = value.parse().context("Invalid server URL")?;
60 Ok(())
61 },
62
63 Token => "token",
64 get: |config: &ClientConfig| Ok(config.token.as_ref().map_or("".to_string(), |t| t.to_string())),
65 set: |config: &mut ClientConfig, value: &str| -> Result<(), anyhow::Error> {
66 if let Some((secret, server)) = value.rsplit_once('@') {
70 let server = if server.contains("://") {
71 server.to_string()
72 } else {
73 format!("https://{}", server)
74 };
75 config.server = server.parse().context("Invalid server address in token")?;
76 config.token = Some(MaskedString::from(secret));
77 } else {
78 config.token = Some(MaskedString::from(value));
79 }
80 Ok(())
81 },
82
83 Hwid => "hwid",
84 get: |config: &ClientConfig| Ok(config.get_hwid()),
85 set: |config: &mut ClientConfig, value: &str| -> Result<(), anyhow::Error> {
86 config.hwid = Some(value.to_string());
87 Ok(())
88 },
89
90 HeartbeatTimeout => "heartbeat_timeout",
91 get: |config: &ClientConfig| Ok(config.heartbeat_timeout.to_string()),
92 set: |config: &mut ClientConfig, value: &str| -> Result<(), anyhow::Error> {
93 config.heartbeat_timeout = value.parse().context("Invalid heartbeat_timeout")?;
94 Ok(())
95 },
96
97 OneCHome => "1c_home",
98 get: |config: &ClientConfig| Ok(config.one_c_home.clone().unwrap_or_default()),
99 set: |config: &mut ClientConfig, value: &str| -> Result<(), anyhow::Error> {
100 config.one_c_home = if value.is_empty() { None } else { Some(value.to_string()) };
101 Ok(())
102 },
103
104 OneCPlatform => "1c_platform",
105 get: |config: &ClientConfig| Ok(config.one_c_platform.as_ref().map(|p| p.to_string()).unwrap_or_default()),
106 set: |config: &mut ClientConfig, value: &str| -> Result<(), anyhow::Error> {
107 config.one_c_platform = if value.is_empty() {
108 None
109 } else {
110 Some(value.parse::<Platform>().context("Invalid platform")?)
111 };
112 Ok(())
113 },
114
115 OneCPublishDir => "1c_publish_dir",
116 get: |config: &ClientConfig| Ok(config.one_c_publish_dir.clone().unwrap_or_default()),
117 set: |config: &mut ClientConfig, value: &str| -> Result<(), anyhow::Error> {
118 config.one_c_publish_dir = if value.is_empty() { None } else { Some(value.to_string()) };
119 Ok(())
120 },
121
122 MinecraftServer => "minecraft_server",
123 get: |config: &ClientConfig| Ok(config.minecraft_server.clone().unwrap_or_default()),
124 set: |config: &mut ClientConfig, value: &str| -> Result<(), anyhow::Error> {
125 config.minecraft_server = if value.is_empty() { None } else { Some(value.to_string()) };
126 Ok(())
127 },
128
129 MinecraftJavaOpts => "minecraft_java_opts",
130 get: |config: &ClientConfig| Ok(config.minecraft_java_opts.clone().unwrap_or_default()),
131 set: |config: &mut ClientConfig, value: &str| -> Result<(), anyhow::Error> {
132 config.minecraft_java_opts = if value.is_empty() { None } else { Some(value.to_string()) };
133 Ok(())
134 },
135
136 MinimizeToTrayOnClose => "minimize_to_tray_on_close",
137 get: |config: &ClientConfig| Ok(config.minimize_to_tray_on_close.map_or("false".to_string(), |v| v.to_string())),
138 set: |config: &mut ClientConfig, value: &str| -> Result<(), anyhow::Error> {
139 config.minimize_to_tray_on_close = Some(value.parse().context("Invalid boolean value")?);
140 Ok(())
141 },
142
143 MinimizeToTrayOnStart => "minimize_to_tray_on_start",
144 get: |config: &ClientConfig| Ok(config.minimize_to_tray_on_start.map_or("false".to_string(), |v| v.to_string())),
145 set: |config: &mut ClientConfig, value: &str| -> Result<(), anyhow::Error> {
146 config.minimize_to_tray_on_start = Some(value.parse().context("Invalid boolean value")?);
147 Ok(())
148 },
149
150 UnsafeTls => "unsafe_tls",
151 get: |config: &ClientConfig| Ok(config.transport.tls.as_ref().map_or("".to_string(), |tls| {
152 tls.danger_ignore_certificate_verification.map_or("".to_string(), |v| v.to_string())
153 })),
154 set: |config: &mut ClientConfig, value: &str| -> Result<(), anyhow::Error> {
155 let allow = value.parse().context("Invalid boolean value")?;
156 if config.transport.tls.is_none() {
157 config.transport.tls = Some(Default::default());
158 }
159 if let Some(tls) = config.transport.tls.as_mut() {
160 tls.danger_ignore_certificate_verification = Some(allow);
161 }
162 Ok(())
163 },
164}
165
166impl ClientConfig {
167 pub fn get_option(&self, option: ConfigOption) -> Result<String> {
168 option.get_value(self)
169 }
170
171 pub fn set_option(&mut self, option: ConfigOption, value: &str) -> Result<()> {
172 option.set_value(self, value)?;
173 self.save()
174 }
175
176 pub fn get_all_config_options(&self) -> Vec<(String, String)> {
177 ConfigOption::all()
178 .iter()
179 .filter_map(|&option| {
180 option
181 .get_value(self)
182 .ok()
183 .map(|value| (option.as_str().to_string(), value))
184 })
185 .collect()
186 }
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192
193 #[test]
194 fn test_set_token_plain() {
195 let mut config = ClientConfig::default();
196 let server_before = config.server.clone();
197 ConfigOption::Token
198 .set_value(&mut config, "secret123")
199 .unwrap();
200 assert_eq!(config.token.as_deref(), Some("secret123"));
201 assert_eq!(config.server, server_before);
202 }
203
204 #[test]
205 fn test_set_token_with_server() {
206 let mut config = ClientConfig::default();
207 ConfigOption::Token
208 .set_value(&mut config, "secret123@my.server.ru:8443")
209 .unwrap();
210 assert_eq!(config.token.as_deref(), Some("secret123"));
211 assert_eq!(config.server.to_string(), "https://my.server.ru:8443/");
212 }
213
214 #[test]
215 fn test_set_token_with_server_url() {
216 let mut config = ClientConfig::default();
217 ConfigOption::Token
218 .set_value(&mut config, "secret123@https://my.server.ru")
219 .unwrap();
220 assert_eq!(config.token.as_deref(), Some("secret123"));
221 assert_eq!(config.server.to_string(), "https://my.server.ru/");
222 }
223
224 #[test]
225 fn test_set_token_with_invalid_server() {
226 let mut config = ClientConfig::default();
227 assert!(ConfigOption::Token
228 .set_value(&mut config, "secret123@ not a server ")
229 .is_err());
230 }
231}