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    SignedOut => "signed_out",
151    get: |config: &ClientConfig| Ok(config.signed_out.map_or("false".to_string(), |v| v.to_string())),
152    set: |config: &mut ClientConfig, value: &str| -> Result<(), anyhow::Error> {
153        config.signed_out = Some(value.parse().context("Invalid boolean value")?);
154        Ok(())
155    },
156
157    UnsafeTls => "unsafe_tls",
158    get: |config: &ClientConfig| Ok(config.transport.tls.as_ref().map_or("".to_string(), |tls| {
159        tls.danger_ignore_certificate_verification.map_or("".to_string(), |v| v.to_string())
160    })),
161    set: |config: &mut ClientConfig, value: &str| -> Result<(), anyhow::Error> {
162        let allow = value.parse().context("Invalid boolean value")?;
163        if config.transport.tls.is_none() {
164            config.transport.tls = Some(Default::default());
165        }
166        if let Some(tls) = config.transport.tls.as_mut() {
167            tls.danger_ignore_certificate_verification = Some(allow);
168        }
169        Ok(())
170    },
171}
172
173impl ClientConfig {
174    pub fn get_option(&self, option: ConfigOption) -> Result<String> {
175        option.get_value(self)
176    }
177
178    pub fn set_option(&mut self, option: ConfigOption, value: &str) -> Result<()> {
179        option.set_value(self, value)?;
180        self.save()
181    }
182
183    pub fn get_all_config_options(&self) -> Vec<(String, String)> {
184        ConfigOption::all()
185            .iter()
186            .filter_map(|&option| {
187                option
188                    .get_value(self)
189                    .ok()
190                    .map(|value| (option.as_str().to_string(), value))
191            })
192            .collect()
193    }
194}
195
196#[cfg(test)]
197mod tests {
198    use super::*;
199
200    #[test]
201    fn test_set_token_plain() {
202        let mut config = ClientConfig::default();
203        let server_before = config.server.clone();
204        ConfigOption::Token
205            .set_value(&mut config, "secret123")
206            .unwrap();
207        assert_eq!(config.token.as_deref(), Some("secret123"));
208        assert_eq!(config.server, server_before);
209    }
210
211    #[test]
212    fn test_set_token_with_server() {
213        let mut config = ClientConfig::default();
214        ConfigOption::Token
215            .set_value(&mut config, "secret123@my.server.ru:8443")
216            .unwrap();
217        assert_eq!(config.token.as_deref(), Some("secret123"));
218        assert_eq!(config.server.to_string(), "https://my.server.ru:8443/");
219    }
220
221    #[test]
222    fn test_set_token_with_server_url() {
223        let mut config = ClientConfig::default();
224        ConfigOption::Token
225            .set_value(&mut config, "secret123@https://my.server.ru")
226            .unwrap();
227        assert_eq!(config.token.as_deref(), Some("secret123"));
228        assert_eq!(config.server.to_string(), "https://my.server.ru/");
229    }
230
231    #[test]
232    fn test_set_token_with_invalid_server() {
233        let mut config = ClientConfig::default();
234        assert!(ConfigOption::Token
235            .set_value(&mut config, "secret123@ not a server ")
236            .is_err());
237    }
238}