zc2 0.0.25

P2P compute broker with credit-based billing, WAL, and broker mesh support
//! The `Connector` seam and backend auto-selection.

use crate::vpn::profile::WgProfile;
use crate::vpn::{Backend, ConnectionInfo, NetError};

/// Forced backend preference from the CLI (`--native` / `--docker`).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Preference {
    Auto,
    Native,
    Docker,
}

/// A tunnel backend. Each impl is independently testable.
pub trait Connector {
    fn available(&self) -> bool;
    fn connect(&self, profile: &WgProfile) -> Result<ConnectionInfo, NetError>;
    fn status(&self) -> Result<Option<ConnectionInfo>, NetError>;
    fn disconnect(&self) -> Result<(), NetError>;
}

/// Pure selection logic: pick a backend given a preference and what's available.
pub(crate) fn choose(
    pref: Preference,
    native_ok: bool,
    docker_ok: bool,
) -> Result<Backend, NetError> {
    match pref {
        Preference::Native => {
            if native_ok {
                Ok(Backend::Native)
            } else {
                Err(NetError::NoBackend)
            }
        }
        Preference::Docker => {
            if docker_ok {
                Ok(Backend::Docker)
            } else {
                Err(NetError::NoBackend)
            }
        }
        Preference::Auto => {
            if native_ok {
                Ok(Backend::Native)
            } else if docker_ok {
                Ok(Backend::Docker)
            } else {
                Err(NetError::NoBackend)
            }
        }
    }
}

/// Build the concrete connector to use, honoring `pref` and real availability.
pub fn select_connector(pref: Preference) -> Result<Box<dyn Connector>, NetError> {
    let native = crate::vpn::native::NativeConnector;
    let docker = crate::vpn::docker::DockerConnector;
    let backend = choose(pref, native.available(), docker.available())?;
    Ok(match backend {
        Backend::Native => Box::new(native),
        Backend::Docker => Box::new(docker),
    })
}

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

    #[test]
    fn auto_prefers_native_then_docker() {
        assert_eq!(
            choose(Preference::Auto, true, true).unwrap(),
            Backend::Native
        );
        assert_eq!(
            choose(Preference::Auto, false, true).unwrap(),
            Backend::Docker
        );
        assert!(matches!(
            choose(Preference::Auto, false, false),
            Err(NetError::NoBackend)
        ));
    }

    #[test]
    fn forced_backends_require_availability() {
        assert_eq!(
            choose(Preference::Native, true, false).unwrap(),
            Backend::Native
        );
        assert!(matches!(
            choose(Preference::Native, false, true),
            Err(NetError::NoBackend)
        ));
        assert_eq!(
            choose(Preference::Docker, false, true).unwrap(),
            Backend::Docker
        );
        assert!(matches!(
            choose(Preference::Docker, true, false),
            Err(NetError::NoBackend)
        ));
    }
}