a3s-runtime 0.2.0

Provider-neutral execution contract and client for A3S runtimes
Documentation
use serde::{Deserialize, Serialize};
use std::collections::BTreeSet;

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum NetworkMode {
    None,
    Outbound,
    Service,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TransportProtocol {
    Tcp,
    Udp,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RuntimePort {
    pub name: String,
    pub container_port: u16,
    pub protocol: TransportProtocol,
}

impl RuntimePort {
    pub(crate) fn validate(&self) -> Result<(), String> {
        super::validate_name("port name", &self.name)?;
        if self.container_port == 0 {
            return Err("container_port must be positive".into());
        }
        Ok(())
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RuntimeNetworkSpec {
    pub mode: NetworkMode,
    pub ports: Vec<RuntimePort>,
}

impl RuntimeNetworkSpec {
    pub(crate) fn validate(&self) -> Result<(), String> {
        if self.ports.len() > 64 {
            return Err("Runtime unit declares more than 64 ports".into());
        }
        if self.mode != NetworkMode::Service && !self.ports.is_empty() {
            return Err("declared ports require service network mode".into());
        }
        let mut names = BTreeSet::new();
        let mut sockets = BTreeSet::new();
        for port in &self.ports {
            port.validate()?;
            if !names.insert(&port.name) {
                return Err(format!("duplicate Runtime port name {:?}", port.name));
            }
            if !sockets.insert((port.container_port, port.protocol)) {
                return Err(format!(
                    "duplicate Runtime port socket {}/{}",
                    port.container_port,
                    match port.protocol {
                        TransportProtocol::Tcp => "tcp",
                        TransportProtocol::Udp => "udp",
                    }
                ));
            }
        }
        Ok(())
    }

    pub(crate) fn has_port(&self, name: &str) -> bool {
        self.ports.iter().any(|port| port.name == name)
    }
}