ovsdb-schema 0.0.1

Rust types and serialization for the Open vSwitch Database Management Protocol (OVSDB)
Documentation
use serde::{Serialize, Serializer};
use std::collections::HashMap;
use uuid::Uuid;

/// Primitive OVSDB Atom types
#[derive(Debug, Clone, PartialEq)]
pub enum OvsdbAtom {
    String(String),
    Integer(i64),
    Real(f64),
    Boolean(bool),
    Uuid(Uuid),
    NamedUuid(String),
}

/// OVSDB Value types (atom, set, or map)
#[derive(Debug, Clone, PartialEq)]
pub enum OvsdbValue {
    Atom(OvsdbAtom),
    Set(Vec<OvsdbAtom>),
    Map(Vec<(OvsdbAtom, OvsdbAtom)>),
}

/// Trait for converting between Rust types and OVSDB Values
pub trait OvsdbSerializable: Sized {
    fn to_ovsdb(&self) -> OvsdbValue;
    fn from_ovsdb(value: &OvsdbValue) -> Option<Self>;
}

impl<T: OvsdbSerializable> OvsdbSerializable for Option<T> {
    fn to_ovsdb(&self) -> OvsdbValue {
        match self {
            Some(val) => val.to_ovsdb(),
            None => OvsdbValue::Set(vec![]), // Empty set for None
        }
    }

    fn from_ovsdb(value: &OvsdbValue) -> Option<Self> {
        T::from_ovsdb(value).map(Some)
    }
}

impl OvsdbSerializable for String {
    fn to_ovsdb(&self) -> OvsdbValue {
        OvsdbValue::Atom(OvsdbAtom::String(self.clone()))
    }

    fn from_ovsdb(value: &OvsdbValue) -> Option<Self> {
        match value {
            OvsdbValue::Atom(OvsdbAtom::String(s)) => Some(s.clone()),
            _ => None,
        }
    }
}

impl OvsdbSerializable for i64 {
    fn to_ovsdb(&self) -> OvsdbValue {
        OvsdbValue::Atom(OvsdbAtom::Integer(*self))
    }

    fn from_ovsdb(value: &OvsdbValue) -> Option<Self> {
        match value {
            OvsdbValue::Atom(OvsdbAtom::Integer(i)) => Some(*i),
            _ => None,
        }
    }
}

impl OvsdbSerializable for f64 {
    fn to_ovsdb(&self) -> OvsdbValue {
        OvsdbValue::Atom(OvsdbAtom::Real(*self))
    }

    fn from_ovsdb(value: &OvsdbValue) -> Option<Self> {
        match value {
            OvsdbValue::Atom(OvsdbAtom::Real(r)) => Some(*r),
            _ => None,
        }
    }
}

impl OvsdbSerializable for bool {
    fn to_ovsdb(&self) -> OvsdbValue {
        OvsdbValue::Atom(OvsdbAtom::Boolean(*self))
    }

    fn from_ovsdb(value: &OvsdbValue) -> Option<Self> {
        match value {
            OvsdbValue::Atom(OvsdbAtom::Boolean(b)) => Some(*b),
            _ => None,
        }
    }
}

impl OvsdbSerializable for Uuid {
    fn to_ovsdb(&self) -> OvsdbValue {
        OvsdbValue::Atom(OvsdbAtom::Uuid(*self))
    }

    fn from_ovsdb(value: &OvsdbValue) -> Option<Self> {
        match value {
            OvsdbValue::Atom(OvsdbAtom::Uuid(uuid)) => Some(*uuid),
            _ => None,
        }
    }
}

impl<T: OvsdbSerializable> OvsdbSerializable for Vec<T> {
    fn to_ovsdb(&self) -> OvsdbValue {
        if self.is_empty() {
            return OvsdbValue::Set(vec![]);
        }

        // Try to convert each item to an OvsdbAtom
        let mut atoms = Vec::with_capacity(self.len());
        for item in self {
            match item.to_ovsdb() {
                OvsdbValue::Atom(atom) => atoms.push(atom),
                _ => return OvsdbValue::Set(vec![]), // Invalid conversion, return empty set
            }
        }

        OvsdbValue::Set(atoms)
    }

    fn from_ovsdb(value: &OvsdbValue) -> Option<Self> {
        match value {
            OvsdbValue::Set(atoms) => {
                let mut result = Vec::with_capacity(atoms.len());
                for atom in atoms {
                    if let Some(item) = T::from_ovsdb(&OvsdbValue::Atom(atom.clone())) {
                        result.push(item);
                    } else {
                        return None;
                    }
                }
                Some(result)
            }
            // Handle single atom as a one-element set
            OvsdbValue::Atom(atom) => {
                T::from_ovsdb(&OvsdbValue::Atom(atom.clone())).map(|item| vec![item])
            }
            _ => None,
        }
    }
}

impl<K: OvsdbSerializable + ToString + Eq + std::hash::Hash, V: OvsdbSerializable> OvsdbSerializable
    for HashMap<K, V>
{
    fn to_ovsdb(&self) -> OvsdbValue {
        let mut pairs = Vec::with_capacity(self.len());

        for (key, value) in self {
            if let OvsdbValue::Atom(key_atom) = key.to_ovsdb() {
                if let OvsdbValue::Atom(value_atom) = value.to_ovsdb() {
                    pairs.push((key_atom, value_atom));
                    continue;
                }
            }
            return OvsdbValue::Map(vec![]);
        }

        OvsdbValue::Map(pairs)
    }

    fn from_ovsdb(value: &OvsdbValue) -> Option<Self> {
        match value {
            OvsdbValue::Map(map) => {
                let mut result = HashMap::with_capacity(map.len());

                for (key, val) in map {
                    if let Some(key_converted) = K::from_ovsdb(&OvsdbValue::Atom(key.clone())) {
                        if let Some(val_converted) = V::from_ovsdb(&OvsdbValue::Atom(val.clone())) {
                            result.insert(key_converted, val_converted);
                        } else {
                            return None;
                        }
                    } else {
                        return None;
                    }
                }

                Some(result)
            }
            _ => None,
        }
    }
}

/// Custom serde serialization format for OvsdbValue
/// Implements the specific JSON format required by OVSDB
impl Serialize for OvsdbValue {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        match self {
            OvsdbValue::Atom(atom) => atom.serialize(serializer),
            OvsdbValue::Set(set) => {
                if set.is_empty() {
                    let empty: Vec<String> = vec![];
                    empty.serialize(serializer)
                } else if set.len() == 1 {
                    set[0].serialize(serializer)
                } else {
                    let wrapper = ("set", set);
                    wrapper.serialize(serializer)
                }
            }
            OvsdbValue::Map(map) => {
                let pairs: Vec<[&OvsdbAtom; 2]> = map.iter().map(|(k, v)| [k, v]).collect();
                let wrapper = ("map", pairs);
                wrapper.serialize(serializer)
            }
        }
    }
}

impl Serialize for OvsdbAtom {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        match self {
            OvsdbAtom::String(s) => s.serialize(serializer),
            OvsdbAtom::Integer(i) => i.serialize(serializer),
            OvsdbAtom::Real(r) => r.serialize(serializer),
            OvsdbAtom::Boolean(b) => b.serialize(serializer),
            OvsdbAtom::Uuid(uuid) => {
                let wrapper = ("uuid", uuid.to_string());
                wrapper.serialize(serializer)
            }
            OvsdbAtom::NamedUuid(name) => {
                let wrapper = ("named-uuid", name);
                wrapper.serialize(serializer)
            }
        }
    }
}

/// Extension trait for OvsdbSerializable to handle JSON conversion
pub trait OvsdbSerializableExt: OvsdbSerializable {
    fn to_ovsdb_json(&self) -> Option<serde_json::Value> {
        serde_json::to_value(self.to_ovsdb()).ok()
    }

    fn from_ovsdb_json(json: &serde_json::Value) -> Option<Self> {
        // Convert JSON to OvsdbValue
        let value = json_to_ovsdb_value(json)?;
        Self::from_ovsdb(&value)
    }
}

// Implement the extension trait for all types that implement OvsdbSerializable
impl<T: OvsdbSerializable> OvsdbSerializableExt for T {}

/// Helper function to extract a UUID from a JSON value
pub fn extract_uuid(value: &serde_json::Value) -> Option<Uuid> {
    if let serde_json::Value::Array(arr) = value {
        if arr.len() == 2 && arr[0] == "uuid" {
            if let serde_json::Value::String(uuid_str) = &arr[1] {
                return Uuid::parse_str(uuid_str).ok();
            }
        }
    }
    None
}

/// Convert a JSON value to an OvsdbValue
fn json_to_ovsdb_value(json: &serde_json::Value) -> Option<OvsdbValue> {
    match json {
        serde_json::Value::String(s) => Some(OvsdbValue::Atom(OvsdbAtom::String(s.clone()))),
        serde_json::Value::Number(n) => {
            if let Some(i) = n.as_i64() {
                Some(OvsdbValue::Atom(OvsdbAtom::Integer(i)))
            } else {
                n.as_f64().map(|f| OvsdbValue::Atom(OvsdbAtom::Real(f)))
            }
        }
        serde_json::Value::Bool(b) => Some(OvsdbValue::Atom(OvsdbAtom::Boolean(*b))),
        serde_json::Value::Array(arr) => {
            if arr.len() == 2 {
                if let serde_json::Value::String(tag) = &arr[0] {
                    match tag.as_str() {
                        "uuid" => {
                            if let serde_json::Value::String(uuid_str) = &arr[1] {
                                if let Ok(uuid) = Uuid::parse_str(uuid_str) {
                                    return Some(OvsdbValue::Atom(OvsdbAtom::Uuid(uuid)));
                                }
                            }
                        }
                        "named-uuid" => {
                            if let serde_json::Value::String(name) = &arr[1] {
                                return Some(OvsdbValue::Atom(OvsdbAtom::NamedUuid(name.clone())));
                            }
                        }
                        "set" => {
                            if let serde_json::Value::Array(elements) = &arr[1] {
                                let mut atoms = Vec::with_capacity(elements.len());
                                for elem in elements {
                                    if let Some(OvsdbValue::Atom(atom)) = json_to_ovsdb_value(elem)
                                    {
                                        atoms.push(atom);
                                    } else {
                                        return None;
                                    }
                                }
                                return Some(OvsdbValue::Set(atoms));
                            }
                        }
                        "map" => {
                            if let serde_json::Value::Array(pairs) = &arr[1] {
                                let mut map_pairs = Vec::with_capacity(pairs.len());
                                for pair in pairs {
                                    if let serde_json::Value::Array(kv) = pair {
                                        if kv.len() == 2 {
                                            if let (
                                                Some(OvsdbValue::Atom(key)),
                                                Some(OvsdbValue::Atom(value)),
                                            ) = (
                                                json_to_ovsdb_value(&kv[0]),
                                                json_to_ovsdb_value(&kv[1]),
                                            ) {
                                                map_pairs.push((key, value));
                                                continue;
                                            }
                                        }
                                    }
                                    return None;
                                }
                                return Some(OvsdbValue::Map(map_pairs));
                            }
                        }
                        _ => {}
                    }
                }
            }

            // Empty array means empty set
            if arr.is_empty() {
                return Some(OvsdbValue::Set(vec![]));
            }

            None
        }
        serde_json::Value::Null => {
            // Null is represented as an empty set
            Some(OvsdbValue::Set(vec![]))
        }
        _ => None,
    }
}