cloudpub-client 3.0.2

CloudPub CLI client for secure tunnel and service publishing
Documentation
use crate::config::{ClientConfig, MaskedString, Platform};
use anyhow::{bail, Context, Result};
use std::str::FromStr;

macro_rules! config_options {
    ($(
        $variant:ident => $key:literal,
        get: $getter:expr,
        set: $setter:expr
    ),* $(,)?) => {
        #[derive(Debug, Clone, Copy, PartialEq, Eq)]
        pub enum ConfigOption {
            $($variant,)*
        }

        impl ConfigOption {
            pub fn all() -> &'static [Self] {
                &[$(Self::$variant,)*]
            }

            pub fn as_str(&self) -> &'static str {
                match self {
                    $(Self::$variant => $key,)*
                }
            }


            pub fn get_value(&self, config: &ClientConfig) -> Result<String> {
                match self {
                    $(Self::$variant => $getter(config),)*
                }
            }

            pub fn set_value(&self, config: &mut ClientConfig, value: &str) -> Result<()> {
                match self {
                    $(Self::$variant => $setter(config, value)?,)*
                }
                Ok(())
            }
        }

        impl FromStr for ConfigOption {
            type Err = anyhow::Error;

            fn from_str(s: &str) -> Result<Self, Self::Err> {
                match s {
                    $($key => Ok(Self::$variant),)*
                    _ => bail!("Unknown config option: {}", s),
                }
            }
        }
    };
}

config_options! {
    Server => "server",
    get: |config: &ClientConfig| Ok(config.server.to_string()),
    set: |config: &mut ClientConfig, value: &str| -> Result<(), anyhow::Error> {
        config.server = value.parse().context("Invalid server URL")?;
        Ok(())
    },

    Token => "token",
    get: |config: &ClientConfig| Ok(config.token.as_ref().map_or("".to_string(), |t| t.to_string())),
    set: |config: &mut ClientConfig, value: &str| -> Result<(), anyhow::Error> {
        config.token = Some(MaskedString::from(value));
        Ok(())
    },

    Hwid => "hwid",
    get: |config: &ClientConfig| Ok(config.get_hwid()),
    set: |config: &mut ClientConfig, value: &str| -> Result<(), anyhow::Error> {
        config.hwid = Some(value.to_string());
        Ok(())
    },

    HeartbeatTimeout => "heartbeat_timeout",
    get: |config: &ClientConfig| Ok(config.heartbeat_timeout.to_string()),
    set: |config: &mut ClientConfig, value: &str| -> Result<(), anyhow::Error> {
        config.heartbeat_timeout = value.parse().context("Invalid heartbeat_timeout")?;
        Ok(())
    },

    OneCHome => "1c_home",
    get: |config: &ClientConfig| Ok(config.one_c_home.clone().unwrap_or_default()),
    set: |config: &mut ClientConfig, value: &str| -> Result<(), anyhow::Error> {
        config.one_c_home = if value.is_empty() { None } else { Some(value.to_string()) };
        Ok(())
    },

    OneCPlatform => "1c_platform",
    get: |config: &ClientConfig| Ok(config.one_c_platform.as_ref().map(|p| p.to_string()).unwrap_or_default()),
    set: |config: &mut ClientConfig, value: &str| -> Result<(), anyhow::Error> {
        config.one_c_platform = if value.is_empty() {
            None
        } else {
            Some(value.parse::<Platform>().context("Invalid platform")?)
        };
        Ok(())
    },

    OneCPublishDir => "1c_publish_dir",
    get: |config: &ClientConfig| Ok(config.one_c_publish_dir.clone().unwrap_or_default()),
    set: |config: &mut ClientConfig, value: &str| -> Result<(), anyhow::Error> {
        config.one_c_publish_dir = if value.is_empty() { None } else { Some(value.to_string()) };
        Ok(())
    },

    MinecraftServer => "minecraft_server",
    get: |config: &ClientConfig| Ok(config.minecraft_server.clone().unwrap_or_default()),
    set: |config: &mut ClientConfig, value: &str| -> Result<(), anyhow::Error> {
        config.minecraft_server = if value.is_empty() { None } else { Some(value.to_string()) };
        Ok(())
    },

    MinecraftJavaOpts => "minecraft_java_opts",
    get: |config: &ClientConfig| Ok(config.minecraft_java_opts.clone().unwrap_or_default()),
    set: |config: &mut ClientConfig, value: &str| -> Result<(), anyhow::Error> {
        config.minecraft_java_opts = if value.is_empty() { None } else { Some(value.to_string()) };
        Ok(())
    },

    MinimizeToTrayOnClose => "minimize_to_tray_on_close",
    get: |config: &ClientConfig| Ok(config.minimize_to_tray_on_close.map_or("false".to_string(), |v| v.to_string())),
    set: |config: &mut ClientConfig, value: &str| -> Result<(), anyhow::Error> {
        config.minimize_to_tray_on_close = Some(value.parse().context("Invalid boolean value")?);
        Ok(())
    },

    MinimizeToTrayOnStart => "minimize_to_tray_on_start",
    get: |config: &ClientConfig| Ok(config.minimize_to_tray_on_start.map_or("false".to_string(), |v| v.to_string())),
    set: |config: &mut ClientConfig, value: &str| -> Result<(), anyhow::Error> {
        config.minimize_to_tray_on_start = Some(value.parse().context("Invalid boolean value")?);
        Ok(())
    },

    UnsafeTls => "unsafe_tls",
    get: |config: &ClientConfig| Ok(config.transport.tls.as_ref().map_or("".to_string(), |tls| {
        tls.danger_ignore_certificate_verification.map_or("".to_string(), |v| v.to_string())
    })),
    set: |config: &mut ClientConfig, value: &str| -> Result<(), anyhow::Error> {
        let allow = value.parse().context("Invalid boolean value")?;
        if config.transport.tls.is_none() {
            config.transport.tls = Some(Default::default());
        }
        if let Some(tls) = config.transport.tls.as_mut() {
            tls.danger_ignore_certificate_verification = Some(allow);
        }
        Ok(())
    },
}

impl ClientConfig {
    pub fn get_option(&self, option: ConfigOption) -> Result<String> {
        option.get_value(self)
    }

    pub fn set_option(&mut self, option: ConfigOption, value: &str) -> Result<()> {
        option.set_value(self, value)?;
        self.save()
    }

    pub fn get_all_config_options(&self) -> Vec<(String, String)> {
        ConfigOption::all()
            .iter()
            .filter_map(|&option| {
                option
                    .get_value(self)
                    .ok()
                    .map(|value| (option.as_str().to_string(), value))
            })
            .collect()
    }
}