blob: e246b8a5635613adf0a7690d09b017352660d468 [file] [log] [blame] [edit]
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,
}
}