Initial commit

Change-Id: I2b916ff0acd2a88aeef709cf4f900503e823d44d
diff --git a/schema/Cargo.toml b/schema/Cargo.toml
new file mode 100644
index 0000000..a7f44a6
--- /dev/null
+++ b/schema/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "ovsdb-schema"
+version = "0.0.1"
+edition = "2021"
+description = "Rust types and serialization for the Open vSwitch Database Management Protocol (OVSDB)"
+license = "Apache-2.0"
+keywords = ["ovsdb", "ovs", "openvswitch", "database", "serialization"]
+categories = ["database", "network-programming", "api-bindings"]
+repository = "https://review.vexxhost.dev/plugins/gitiles/ovsdb"
+
+[dependencies]
+serde = { version = "1.0.218", features = ["derive"] }
+serde_json = "1.0.140"
+uuid = { version = "1.15.1", features = ["serde"] }
+
+[dev-dependencies]
+ovsdb-derive = { path = "../derive" }
diff --git a/schema/README.md b/schema/README.md
new file mode 100644
index 0000000..59a218c
--- /dev/null
+++ b/schema/README.md
@@ -0,0 +1,103 @@
+# ovsdb-schema
+
+A Rust implementation of the OVSDB protocol serialization and deserialization types.
+
+## Overview
+
+This crate provides the core primitives and traits needed to work with the Open vSwitch Database Management Protocol (OVSDB) as defined in [RFC7047](https://datatracker.ietf.org/doc/html/rfc7047). It includes:
+
+- Type definitions for OVSDB data structures
+- Serialization/deserialization between Rust types and OVSDB JSON format
+- Traits to make your own types compatible with OVSDB
+
+This crate is designed to be used alongside `ovsdb-derive` for a complete OVSDB client implementation.
+
+## Features
+
+- `OvsdbAtom` and `OvsdbValue` types representing OVSDB's basic data types
+- `OvsdbSerializable` trait for converting between Rust types and OVSDB values
+- Implementations for common Rust types like `String`, `i64`, `bool`, etc.
+- Support for collections like `Vec<T>` and `HashMap<K, V>`
+- Helper functions for UUID handling
+- Full support for OVSDB's type system: atoms, sets, and maps
+
+## Usage
+
+### Basic Usage
+
+```rust
+use ovsdb_schema::{OvsdbSerializable, OvsdbSerializableExt};
+use std::collections::HashMap;
+use uuid::Uuid;
+
+// Use the trait directly
+let my_string = "hello".to_string();
+let ovsdb_value = my_string.to_ovsdb();
+let json_value = my_string.to_ovsdb_json().unwrap();
+
+// Extract UUIDs from JSON values
+let uuid_str = "550e8400-e29b-41d4-a716-446655440000";
+let uuid = Uuid::parse_str(uuid_str).unwrap();
+let json_value = serde_json::json!(["uuid", uuid_str]);
+let extracted_uuid = ovsdb_schema::extract_uuid(&json_value).unwrap();
+assert_eq!(uuid, extracted_uuid);
+```
+
+### With `ovsdb-derive`
+
+This crate is designed to work with the companion `ovsdb-derive` crate:
+
+```rust
+use ovsdb_derive::ovsdb_object;
+use std::collections::HashMap;
+
+#[ovsdb_object]
+pub struct NbGlobal {
+    pub name: Option<String>,
+    pub nb_cfg: Option<i64>,
+    pub external_ids: Option<HashMap<String, String>>,
+}
+
+// The macro adds _uuid and _version fields and implements
+// OvsdbSerializable automatically
+```
+
+## Type Conversion
+
+| Rust Type | OVSDB Type |
+|-----------|------------|
+| `String` | string |
+| `i64` | integer |
+| `f64` | real |
+| `bool` | boolean |
+| `Uuid` | uuid |
+| `Vec<T>` | set |
+| `HashMap<K, V>` | map |
+| `Option<T>` | value or empty set |
+
+## Custom Types
+
+Implement `OvsdbSerializable` for your custom types:
+
+```rust
+use ovsdb_schema::{OvsdbSerializable, OvsdbValue, OvsdbAtom};
+
+struct MyType(String);
+
+impl OvsdbSerializable for MyType {
+    fn to_ovsdb(&self) -> OvsdbValue {
+        OvsdbValue::Atom(OvsdbAtom::String(self.0.clone()))
+    }
+
+    fn from_ovsdb(value: &OvsdbValue) -> Option<Self> {
+        match value {
+            OvsdbValue::Atom(OvsdbAtom::String(s)) => Some(MyType(s.clone())),
+            _ => None,
+        }
+    }
+}
+```
+
+## License
+
+This project is licensed under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0).
diff --git a/schema/src/lib.rs b/schema/src/lib.rs
new file mode 100644
index 0000000..e246b8a
--- /dev/null
+++ b/schema/src/lib.rs
@@ -0,0 +1,353 @@
+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,
+    }
+}
diff --git a/schema/tests/integration.rs b/schema/tests/integration.rs
new file mode 100644
index 0000000..aa80e18
--- /dev/null
+++ b/schema/tests/integration.rs
@@ -0,0 +1,274 @@
+use ovsdb_derive::ovsdb_object;
+use serde_json::Value;
+use std::collections::HashMap;
+use uuid::Uuid;
+
+#[ovsdb_object]
+#[derive(Debug, PartialEq)]
+pub struct NbGlobal {
+    pub name: Option<String>,
+    pub nb_cfg: Option<i64>,
+    pub nb_cfg_timestamp: Option<i64>,
+    pub sb_cfg: Option<i64>,
+    pub sb_cfg_timestamp: Option<i64>,
+    pub hv_cfg: Option<i64>,
+    pub hv_cfg_timestamp: Option<i64>,
+    pub external_ids: Option<HashMap<String, String>>,
+    pub connections: Option<Vec<Uuid>>,
+    pub ssl: Option<Vec<Uuid>>,
+    pub options: Option<HashMap<String, String>>,
+    pub ipsec: Option<bool>,
+
+    // Required fields
+    pub _uuid: Option<Uuid>,
+    pub _version: Option<Uuid>,
+}
+
+#[test]
+fn test_nb_global_deserialization() {
+    // The provided JSON sample
+    let json_str = r#"{
+        "connections": ["uuid", "601c7161-97df-42ae-b377-3baf21830d8f"],
+        "external_ids": ["map", [["test", "bara"]]],
+        "hv_cfg": 0,
+        "hv_cfg_timestamp": 0,
+        "ipsec": false,
+        "name": "global",
+        "nb_cfg": 0,
+        "nb_cfg_timestamp": 0,
+        "options": ["map", [["name", "global"], ["northd-backoff-interval-ms", "300"], ["northd_probe_interval", "5000"]]],
+        "sb_cfg": 0,
+        "sb_cfg_timestamp": 0,
+        "ssl": ["set", []]
+    }"#;
+
+    let json_value: Value = serde_json::from_str(json_str).unwrap();
+
+    // Parse the JSON to our NbGlobal struct
+    let nb_global =
+        NbGlobal::from_map(&serde_json::from_value(json_value.clone()).unwrap()).unwrap();
+
+    // Test individual fields
+    assert_eq!(nb_global.name, Some("global".to_string()));
+    assert_eq!(nb_global.ipsec, Some(false));
+    assert_eq!(nb_global.hv_cfg, Some(0));
+    assert_eq!(nb_global.hv_cfg_timestamp, Some(0));
+    assert_eq!(nb_global.nb_cfg, Some(0));
+    assert_eq!(nb_global.nb_cfg_timestamp, Some(0));
+    assert_eq!(nb_global.sb_cfg, Some(0));
+    assert_eq!(nb_global.sb_cfg_timestamp, Some(0));
+
+    // Test UUID field
+    let connection_uuid = Uuid::parse_str("601c7161-97df-42ae-b377-3baf21830d8f").unwrap();
+    assert_eq!(nb_global.connections, Some(vec![connection_uuid]));
+
+    // Test empty set
+    assert_eq!(nb_global.ssl, Some(vec![]));
+
+    // Test maps
+    let expected_external_ids = {
+        let mut map = HashMap::new();
+        map.insert("test".to_string(), "bara".to_string());
+        map
+    };
+    assert_eq!(nb_global.external_ids, Some(expected_external_ids));
+
+    let expected_options = {
+        let mut map = HashMap::new();
+        map.insert("name".to_string(), "global".to_string());
+        map.insert("northd-backoff-interval-ms".to_string(), "300".to_string());
+        map.insert("northd_probe_interval".to_string(), "5000".to_string());
+        map
+    };
+    assert_eq!(nb_global.options, Some(expected_options));
+}
+
+#[test]
+fn test_nb_global_serialization() {
+    // Create an NbGlobal object with the same values as the JSON sample
+    let mut nb_global = NbGlobal::new();
+
+    // Set scalar values
+    nb_global.name = Some("global".to_string());
+    nb_global.ipsec = Some(false);
+    nb_global.hv_cfg = Some(0);
+    nb_global.hv_cfg_timestamp = Some(0);
+    nb_global.nb_cfg = Some(0);
+    nb_global.nb_cfg_timestamp = Some(0);
+    nb_global.sb_cfg = Some(0);
+    nb_global.sb_cfg_timestamp = Some(0);
+
+    // Set UUID connection
+    let connection_uuid = Uuid::parse_str("601c7161-97df-42ae-b377-3baf21830d8f").unwrap();
+    nb_global.connections = Some(vec![connection_uuid]);
+
+    // Set empty SSL set
+    nb_global.ssl = Some(vec![]);
+
+    // Set maps
+    let mut external_ids = HashMap::new();
+    external_ids.insert("test".to_string(), "bara".to_string());
+    nb_global.external_ids = Some(external_ids);
+
+    let mut options = HashMap::new();
+    options.insert("name".to_string(), "global".to_string());
+    options.insert("northd-backoff-interval-ms".to_string(), "300".to_string());
+    options.insert("northd_probe_interval".to_string(), "5000".to_string());
+    nb_global.options = Some(options);
+
+    // Serialize to JSON
+    let serialized = nb_global.to_map();
+
+    // Verify each field
+    assert_eq!(serialized.get("name").unwrap().as_str().unwrap(), "global");
+    assert!(!serialized.get("ipsec").unwrap().as_bool().unwrap());
+    assert_eq!(serialized.get("hv_cfg").unwrap().as_i64().unwrap(), 0);
+
+    // Test UUID serialization
+    let connections_json = serialized.get("connections").unwrap();
+    assert!(connections_json.is_array());
+    let connections_array = connections_json.as_array().unwrap();
+    assert_eq!(connections_array[0].as_str().unwrap(), "uuid");
+    assert_eq!(
+        connections_array[1].as_str().unwrap(),
+        "601c7161-97df-42ae-b377-3baf21830d8f"
+    );
+
+    // Test empty set serialization
+    let ssl_json = serialized.get("ssl").unwrap();
+    assert!(ssl_json.is_array());
+    assert_eq!(ssl_json.as_array().unwrap().len(), 0);
+
+    // Test map serialization
+    let external_ids_json = serialized.get("external_ids").unwrap();
+    assert!(external_ids_json.is_array());
+    assert_eq!(
+        external_ids_json.as_array().unwrap()[0].as_str().unwrap(),
+        "map"
+    );
+
+    let options_json = serialized.get("options").unwrap();
+    assert!(options_json.is_array());
+    assert_eq!(options_json.as_array().unwrap()[0].as_str().unwrap(), "map");
+}
+
+#[test]
+fn test_round_trip() {
+    // JSON string representing an NB_Global object
+    let json_str = r#"{
+        "connections": ["uuid", "601c7161-97df-42ae-b377-3baf21830d8f"],
+        "external_ids": ["map", [["test", "bara"]]],
+        "hv_cfg": 0,
+        "hv_cfg_timestamp": 0,
+        "ipsec": false,
+        "name": "global",
+        "nb_cfg": 0,
+        "nb_cfg_timestamp": 0,
+        "options": ["map", [["name", "global"], ["northd-backoff-interval-ms", "300"], ["northd_probe_interval", "5000"]]],
+        "sb_cfg": 0,
+        "sb_cfg_timestamp": 0,
+        "ssl": ["set", []]
+    }"#;
+
+    // Deserialize from JSON string to NbGlobal object
+    let json_value: Value = serde_json::from_str(json_str).unwrap();
+    let nb_global = NbGlobal::from_map(&serde_json::from_value(json_value).unwrap()).unwrap();
+
+    // Serialize back to JSON
+    let serialized = serde_json::to_value(nb_global.to_map()).unwrap();
+
+    // Deserialize again
+    let nb_global2 = NbGlobal::from_map(&serde_json::from_value(serialized).unwrap()).unwrap();
+
+    // The two objects should be equal
+    assert_eq!(nb_global, nb_global2);
+}
+
+#[test]
+fn test_handle_single_element_set() {
+    // JSON with a single UUID in connections (no ["set", ...] wrapper)
+    let json_str = r#"{
+        "connections": ["uuid", "601c7161-97df-42ae-b377-3baf21830d8f"],
+        "name": "global"
+    }"#;
+
+    let json_value: Value = serde_json::from_str(json_str).unwrap();
+    let nb_global = NbGlobal::from_map(&serde_json::from_value(json_value).unwrap()).unwrap();
+
+    // Should be parsed as a Vec with one element
+    let connection_uuid = Uuid::parse_str("601c7161-97df-42ae-b377-3baf21830d8f").unwrap();
+    assert_eq!(nb_global.connections, Some(vec![connection_uuid]));
+}
+
+#[test]
+fn test_handle_multiple_element_set() {
+    // JSON with multiple UUIDs in connections using ["set", [...]] wrapper
+    let json_str = r#"{
+        "connections": ["set", [
+            ["uuid", "601c7161-97df-42ae-b377-3baf21830d8f"],
+            ["uuid", "701c7161-97df-42ae-b377-3baf21830d8f"]
+        ]],
+        "name": "global"
+    }"#;
+
+    let json_value: Value = serde_json::from_str(json_str).unwrap();
+    let nb_global = NbGlobal::from_map(&serde_json::from_value(json_value).unwrap()).unwrap();
+
+    // Should be parsed as a Vec with two elements
+    let uuid1 = Uuid::parse_str("601c7161-97df-42ae-b377-3baf21830d8f").unwrap();
+    let uuid2 = Uuid::parse_str("701c7161-97df-42ae-b377-3baf21830d8f").unwrap();
+    assert_eq!(nb_global.connections, Some(vec![uuid1, uuid2]));
+}
+
+#[test]
+fn test_empty_set() {
+    // JSON with empty set
+    let json_str = r#"{
+        "ssl": ["set", []],
+        "name": "global"
+    }"#;
+
+    let json_value: Value = serde_json::from_str(json_str).unwrap();
+    let nb_global = NbGlobal::from_map(&serde_json::from_value(json_value).unwrap()).unwrap();
+
+    // Should be parsed as an empty Vec
+    assert_eq!(nb_global.ssl, Some(vec![]));
+}
+
+#[test]
+fn test_serialization_single_element_set() {
+    let mut nb_global = NbGlobal::new();
+
+    // Set single UUID connection
+    let connection_uuid = Uuid::parse_str("601c7161-97df-42ae-b377-3baf21830d8f").unwrap();
+    nb_global.connections = Some(vec![connection_uuid]);
+
+    // Serialize to JSON
+    let serialized = nb_global.to_map();
+    let connections_json = serialized.get("connections").unwrap();
+
+    // Should be serialized as ["uuid", "..."] (not wrapped in ["set", [...]])
+    assert!(connections_json.is_array());
+    let connections_array = connections_json.as_array().unwrap();
+    assert_eq!(connections_array.len(), 2);
+    assert_eq!(connections_array[0].as_str().unwrap(), "uuid");
+}
+
+#[test]
+fn test_serialization_multiple_element_set() {
+    let mut nb_global = NbGlobal::new();
+
+    // Set multiple UUID connections
+    let uuid1 = Uuid::parse_str("601c7161-97df-42ae-b377-3baf21830d8f").unwrap();
+    let uuid2 = Uuid::parse_str("701c7161-97df-42ae-b377-3baf21830d8f").unwrap();
+    nb_global.connections = Some(vec![uuid1, uuid2]);
+
+    // Serialize to JSON
+    let serialized = nb_global.to_map();
+    let connections_json = serialized.get("connections").unwrap();
+
+    // Should be serialized as ["set", [...]]
+    assert!(connections_json.is_array());
+    let connections_array = connections_json.as_array().unwrap();
+    assert_eq!(connections_array[0].as_str().unwrap(), "set");
+}