Skip to main content

cloudpub_client/
options.rs

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        // A token issued by another instance (on-prem) may embed the server it
67        // belongs to as "<secret>@<server>", so a single `set token` also
68        // points the client at that instance. Plain secrets never contain '@'.
69        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}