fswtch 0.1.8

Rust bindings and helpers for writing FreeSWITCH modules
Documentation
use std::sync::{LazyLock, Mutex};

static CONFIG: LazyLock<Mutex<Config>> = LazyLock::new(|| Mutex::new(Config::default()));

fswtch::module_exports! {
    module = mod_config_xml,
    load = switch_module_load,
}

#[derive(Debug, Clone)]
struct Config {
    enabled: bool,
    greeting: String,
    max_sessions: u32,
}

impl Default for Config {
    fn default() -> Self {
        Self {
            enabled: true,
            greeting: "hello from XML config".to_owned(),
            max_sessions: 8,
        }
    }
}

fswtch::api_callback! {
    fn show_api(_cmd, _session, stream) {
        fswtch::log_info("mod_config_xml", "rust_config_xml_show invoked");
        let config = CONFIG
            .lock()
            .unwrap_or_else(|poisoned| poisoned.into_inner());
        stream.write(
            &format!(
                "enabled={} greeting={} max_sessions={}\n",
                config.enabled, config.greeting, config.max_sessions
            ),
        )
    }
}

fswtch::api_callback! {
    fn reload_api(_cmd, _session, stream) {
        fswtch::log_info("mod_config_xml", "rust_config_xml_reload invoked");
        match load_config() {
            Ok(config) => {
                *CONFIG
                    .lock()
                    .unwrap_or_else(|poisoned| poisoned.into_inner()) = config;
                stream.write("config reloaded\n")
            }
            Err(error) => {
                stream.write(&format!("config reload failed: {error}\n"))
            }
        }
    }
}

fswtch::module_load! {
    fn switch_module_load(module) for "mod_config_xml" {
        fswtch::log_info("mod_config_xml", "loading module");
        if let Ok(config) = load_config() {
            *CONFIG
                .lock()
                .unwrap_or_else(|poisoned| poisoned.into_inner()) = config;
        }
        module
            .api(
                "rust_config_xml_show",
                "prints settings loaded from fswtch_examples.conf",
                "rust_config_xml_show",
                show_api,
            )
            .and_then(|module| {
                module.api(
                    "rust_config_xml_reload",
                    "reloads settings from fswtch_examples.conf",
                    "rust_config_xml_reload",
                    reload_api,
                )
            })
    }
}

fn load_config() -> Result<Config, &'static str> {
    fswtch::log_info("mod_config_xml", "loading fswtch_examples.conf");
    let mut config = Config::default();
    let xml =
        fswtch::XmlConfig::open("fswtch_examples.conf").ok_or("fswtch_examples.conf not found")?;

    if let Some(settings) = xml.settings().and_then(|node| node.child("settings")) {
        parse_settings(settings, &mut config);
    }

    Ok(config)
}

fn parse_settings(settings: fswtch::XmlNode, config: &mut Config) {
    let mut param = settings.child("param");
    while let Some(node) = param {
        let Some(name) = node.attr("name") else {
            param = node.next();
            continue;
        };
        let Some(value) = node.attr("value") else {
            param = node.next();
            continue;
        };

        match name.as_str() {
            "enabled" => config.enabled = matches!(value.as_str(), "true" | "yes" | "1"),
            "greeting" => config.greeting = value,
            "max-sessions" => {
                if let Ok(parsed) = value.parse() {
                    config.max_sessions = parsed;
                }
            }
            _ => {}
        }

        param = node.next();
    }
}