prns-config 0.3.4

Config discovery, parsing, and reconciliation for the Personal Reticulum daemon
Documentation
use super::keys::interface as interface_key;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InterfaceKind {
    Auto,
    TcpClient,
    TcpServer,
    Udp,
    Serial,
    Kiss,
    Ax25Kiss,
    Rnode,
    RnodeMulti,
    Pipe,
    Backbone,
    BackboneClient,
    I2p,
    Weave,
    PrnsUsbAuto,
    PrnsBluetoothAuto,
    PrnsWebSocketClient,
    PrnsWebSocketServer,
}

impl InterfaceKind {
    pub const CANONICAL_NAMES: &[&str] = &[
        "AutoInterface",
        "TCPClientInterface",
        "TCPServerInterface",
        "UDPInterface",
        "SerialInterface",
        "KISSInterface",
        "AX25KISSInterface",
        "RNodeInterface",
        "RNodeMultiInterface",
        "PipeInterface",
        "BackboneInterface",
        "BackboneClientInterface",
        "I2PInterface",
        "WeaveInterface",
        "PrnsUsbAuto",
        "PrnsBluetoothAuto",
        "PrnsWebSocketClient",
        "PrnsWebSocketServer",
    ];

    pub fn parse(value: &str) -> Option<Self> {
        match value {
            "AutoInterface" => Some(Self::Auto),
            "TCPClientInterface" => Some(Self::TcpClient),
            "TCPServerInterface" => Some(Self::TcpServer),
            "UDPInterface" => Some(Self::Udp),
            "SerialInterface" => Some(Self::Serial),
            "KISSInterface" => Some(Self::Kiss),
            "AX25KISSInterface" => Some(Self::Ax25Kiss),
            "RNodeInterface" => Some(Self::Rnode),
            "RNodeMultiInterface" => Some(Self::RnodeMulti),
            "PipeInterface" => Some(Self::Pipe),
            "BackboneInterface" => Some(Self::Backbone),
            "BackboneClientInterface" => Some(Self::BackboneClient),
            "I2PInterface" => Some(Self::I2p),
            "WeaveInterface" => Some(Self::Weave),
            _ => Self::parse_prns(value),
        }
    }

    pub const fn canonical_name(self) -> &'static str {
        match self {
            Self::Auto => "AutoInterface",
            Self::TcpClient => "TCPClientInterface",
            Self::TcpServer => "TCPServerInterface",
            Self::Udp => "UDPInterface",
            Self::Serial => "SerialInterface",
            Self::Kiss => "KISSInterface",
            Self::Ax25Kiss => "AX25KISSInterface",
            Self::Rnode => "RNodeInterface",
            Self::RnodeMulti => "RNodeMultiInterface",
            Self::Pipe => "PipeInterface",
            Self::Backbone => "BackboneInterface",
            Self::BackboneClient => "BackboneClientInterface",
            Self::I2p => "I2PInterface",
            Self::Weave => "WeaveInterface",
            Self::PrnsUsbAuto => "PrnsUsbAuto",
            Self::PrnsBluetoothAuto => "PrnsBluetoothAuto",
            Self::PrnsWebSocketClient => "PrnsWebSocketClient",
            Self::PrnsWebSocketServer => "PrnsWebSocketServer",
        }
    }

    fn parse_prns(value: &str) -> Option<Self> {
        if ["PrnsUsbAuto", "PrnsUsbAutoInterface"]
            .iter()
            .any(|candidate| value.eq_ignore_ascii_case(candidate))
        {
            return Some(Self::PrnsUsbAuto);
        }
        if [
            "PrnsBluetoothAuto",
            "PrnsBluetoothAutoInterface",
            "PrnsBleAuto",
            "PrnsBleAutoInterface",
        ]
        .iter()
        .any(|candidate| value.eq_ignore_ascii_case(candidate))
        {
            return Some(Self::PrnsBluetoothAuto);
        }
        if ["PrnsWebSocketClient", "PrnsWebSocketClientInterface"]
            .iter()
            .any(|candidate| value.eq_ignore_ascii_case(candidate))
        {
            return Some(Self::PrnsWebSocketClient);
        }
        if ["PrnsWebSocketServer", "PrnsWebSocketServerInterface"]
            .iter()
            .any(|candidate| value.eq_ignore_ascii_case(candidate))
        {
            return Some(Self::PrnsWebSocketServer);
        }
        None
    }

    pub const fn cli_name(self) -> &'static str {
        match self {
            Self::Auto => "auto-wifi",
            Self::TcpClient => "tcp-client",
            Self::TcpServer => "tcp-server",
            Self::Udp => "udp",
            Self::Serial => "serial",
            Self::Kiss => "kiss",
            Self::Ax25Kiss => "ax25-kiss",
            Self::Rnode => "rnode",
            Self::RnodeMulti => "rnode-multi",
            Self::Pipe => "pipe",
            Self::Backbone => "backbone-server",
            Self::BackboneClient => "backbone-client",
            Self::I2p => "i2p",
            Self::Weave => "weave",
            Self::PrnsUsbAuto => "usb-auto",
            Self::PrnsBluetoothAuto => "bluetooth-auto",
            Self::PrnsWebSocketClient => "websocket-client",
            Self::PrnsWebSocketServer => "websocket-server",
        }
    }

    pub fn parse_cli(value: &str) -> Option<Self> {
        let normalized = value.trim().to_ascii_lowercase();
        match normalized.as_str() {
            "auto" | "auto-wifi" | "wifi-auto" => Some(Self::Auto),
            "tcp-client" => Some(Self::TcpClient),
            "tcp-server" => Some(Self::TcpServer),
            "udp" => Some(Self::Udp),
            "serial" => Some(Self::Serial),
            "kiss" => Some(Self::Kiss),
            "ax25-kiss" | "ax25" => Some(Self::Ax25Kiss),
            "rnode" => Some(Self::Rnode),
            "rnode-multi" => Some(Self::RnodeMulti),
            "pipe" => Some(Self::Pipe),
            "backbone" | "backbone-server" => Some(Self::Backbone),
            "backbone-client" => Some(Self::BackboneClient),
            "i2p" => Some(Self::I2p),
            "weave" => Some(Self::Weave),
            "usb" | "usb-auto" => Some(Self::PrnsUsbAuto),
            "bluetooth" | "bluetooth-auto" | "ble" | "ble-auto" => Some(Self::PrnsBluetoothAuto),
            "websocket-client" | "ws-client" => Some(Self::PrnsWebSocketClient),
            "websocket-server" | "ws-server" => Some(Self::PrnsWebSocketServer),
            _ => Self::parse(value),
        }
    }

    pub fn accepts_setting(self, key: &str) -> bool {
        if interface_key::COMMON.contains(&key) {
            return true;
        }
        let medium = match self {
            Self::Auto => interface_key::AUTO,
            Self::TcpClient => interface_key::TCP_CLIENT,
            Self::TcpServer => interface_key::TCP_SERVER,
            Self::Udp => interface_key::UDP,
            Self::Serial => interface_key::SERIAL,
            Self::Kiss => interface_key::KISS,
            Self::Ax25Kiss => interface_key::AX25_KISS,
            Self::Rnode => interface_key::RNODE,
            Self::RnodeMulti => interface_key::RNODE_MULTI,
            Self::Pipe => interface_key::PIPE,
            Self::Backbone | Self::BackboneClient => interface_key::BACKBONE,
            Self::I2p => interface_key::I2P,
            Self::Weave => interface_key::WEAVE,
            Self::PrnsUsbAuto | Self::PrnsBluetoothAuto => &[],
            Self::PrnsWebSocketClient => interface_key::PRNS_WEBSOCKET_CLIENT,
            Self::PrnsWebSocketServer => interface_key::PRNS_WEBSOCKET_SERVER,
        };
        medium.contains(&key)
    }
}

pub(super) use InterfaceKind as InterfaceType;

#[cfg(test)]
mod tests {
    use super::InterfaceKind;

    #[test]
    fn prns_names_normalize_without_relaxing_stock_names() {
        for alias in [
            "PrnsUsbAuto",
            "prnsusbauto",
            "PRNSUSBAUTOINTERFACE",
            "PrnsBluetoothAuto",
            "prnsbleauto",
            "PRNSBLEAUTOINTERFACE",
            "prnswebsocketclient",
            "PRNSWEBSOCKETCLIENTINTERFACE",
            "prnswebsocketserver",
            "PRNSWEBSOCKETSERVERINTERFACE",
        ] {
            assert!(InterfaceKind::parse(alias).is_some(), "{alias}");
        }
        assert_eq!(InterfaceKind::parse("autointerface"), None);
    }
}