Initial commit

Change-Id: I2b916ff0acd2a88aeef709cf4f900503e823d44d
diff --git a/derive/Cargo.toml b/derive/Cargo.toml
new file mode 100644
index 0000000..99082fd
--- /dev/null
+++ b/derive/Cargo.toml
@@ -0,0 +1,23 @@
+[package]
+name = "ovsdb-derive"
+version = "0.0.1"
+edition = "2021"
+description = "Derive macro for OVSDB table structs"
+license = "Apache-2.0"
+keywords = ["ovsdb", "derive", "macro"]
+categories = ["database"]
+repository = "https://review.vexxhost.dev/plugins/gitiles/ovsdb"
+
+[lib]
+proc-macro = true
+
+[dependencies]
+syn = { version = "2.0", features = ["full", "extra-traits"] }
+quote = "1.0"
+proc-macro2 = "1.0"
+ovsdb-schema = { version = "0.0.1", path = "../schema" }
+
+[dev-dependencies]
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
+uuid = { version = "1.0", features = ["v4"] }
diff --git a/derive/README.md b/derive/README.md
new file mode 100644
index 0000000..9684136
--- /dev/null
+++ b/derive/README.md
@@ -0,0 +1,64 @@
+# OVSDB Derive
+
+A procedural macro crate for Rust to generate code for OVSDB table structs.
+
+## Overview
+
+This crate provides two approaches for working with OVSDB tables:
+
+- `#[ovsdb_object]` attribute macro: Automatically adds `_uuid` and `_version` fields to your struct
+- `#[derive(OVSDB)]` derive macro: requires manual fields but offers more control
+
+## Usage
+
+You can either use the attribute macro or the derive macro to generate code for your OVSDB table structs. For more details
+on how to use the library, check out the examples in the `examples` directory.
+
+### Attribute Macro (Recommended)
+
+```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>>,
+    // No need to add _uuid and _version fields
+}
+```
+
+### Derive Macro (Alternative)
+
+```rust
+use ovsdb_derive::OVSDB;
+use std::collections::HashMap;
+use uuid::Uuid;
+
+#[derive(Debug, Clone, PartialEq, OVSDB)]
+pub struct NbGlobal {
+    pub name: Option<String>,
+    pub nb_cfg: Option<i64>,
+    pub external_ids: Option<HashMap<String, String>>,
+
+    // Required fields with the derive approach
+    pub _uuid: Option<Uuid>,
+    pub _version: Option<Uuid>,
+}
+```
+
+## Generated Code
+
+Both macros generate the following implementations:
+
+- `new()` method that creates a new instance with default values
+- `to_map()` method that converts the struct to a HashMap for OVSDB serialization
+- `from_map()` method that creates a struct from a HashMap received from OVSDB
+- `Default` trait implementation
+- `serde::Serialize` trait implementation
+- `serde::Deserialize` trait implementation
+
+## License
+
+This project is licensed under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0).
diff --git a/derive/examples/attribute.rs b/derive/examples/attribute.rs
new file mode 100644
index 0000000..9fb29df
--- /dev/null
+++ b/derive/examples/attribute.rs
@@ -0,0 +1,47 @@
+use ovsdb_derive::ovsdb_object;
+use std::collections::HashMap;
+use uuid::Uuid;
+
+#[ovsdb_object]
+#[derive(Debug, Clone, PartialEq)]
+pub struct NbGlobal {
+    pub name: String,
+    pub nb_cfg: i64,
+    pub nb_cfg_timestamp: i64,
+    pub sb_cfg: i64,
+    pub sb_cfg_timestamp: i64,
+    pub hv_cfg: i64,
+    pub hv_cfg_timestamp: i64,
+    pub external_ids: HashMap<String, String>,
+    pub connections: Vec<Uuid>,
+    pub ssl: Vec<Uuid>,
+    pub options: HashMap<String, String>,
+    pub ipsec: bool,
+}
+
+fn main() {
+    // Create a new NbGlobal instance
+    let mut nb_global = NbGlobal::new();
+
+    // Set some values
+    nb_global.name = "global".to_string();
+    nb_global.nb_cfg = 0;
+    nb_global
+        .external_ids
+        .insert("test".to_string(), "value".to_string());
+
+    // Convert to a HashMap for OVSDB serialization
+    let map = nb_global.to_map();
+    println!("{:?}", map);
+
+    // Convert to JSON for sending to OVSDB
+    let json = serde_json::to_string(&map).unwrap();
+    println!("{}", json);
+
+    // Simulate receiving JSON from OVSDB
+    let received_map: HashMap<String, serde_json::Value> = serde_json::from_str(&json).unwrap();
+
+    // Convert back to NbGlobal
+    let parsed_nb_global = NbGlobal::from_map(&received_map).unwrap();
+    println!("{:?}", parsed_nb_global);
+}
diff --git a/derive/examples/derive.rs b/derive/examples/derive.rs
new file mode 100644
index 0000000..08233d0
--- /dev/null
+++ b/derive/examples/derive.rs
@@ -0,0 +1,50 @@
+use ovsdb_derive::OVSDB;
+use std::collections::HashMap;
+use uuid::Uuid;
+
+#[derive(Debug, Clone, PartialEq, OVSDB)]
+pub struct NbGlobal {
+    pub name: String,
+    pub nb_cfg: i64,
+    pub nb_cfg_timestamp: i64,
+    pub sb_cfg: i64,
+    pub sb_cfg_timestamp: i64,
+    pub hv_cfg: i64,
+    pub hv_cfg_timestamp: i64,
+    pub external_ids: HashMap<String, String>,
+    pub connections: Vec<Uuid>,
+    pub ssl: Vec<Uuid>,
+    pub options: HashMap<String, String>,
+    pub ipsec: bool,
+
+    // Required fields
+    pub _uuid: Option<Uuid>,
+    pub _version: Option<Uuid>,
+}
+
+fn main() {
+    // Create a new NbGlobal instance
+    let mut nb_global = NbGlobal::new();
+
+    // Set some values
+    nb_global.name = "global".to_string();
+    nb_global.nb_cfg = 0;
+    nb_global
+        .external_ids
+        .insert("test".to_string(), "value".to_string());
+
+    // Convert to a HashMap for OVSDB serialization
+    let map = nb_global.to_map();
+    println!("{:?}", map);
+
+    // Convert to JSON for sending to OVSDB
+    let json = serde_json::to_string(&map).unwrap();
+    println!("{}", json);
+
+    // Simulate receiving JSON from OVSDB
+    let received_map: HashMap<String, serde_json::Value> = serde_json::from_str(&json).unwrap();
+
+    // Convert back to NbGlobal
+    let parsed_nb_global = NbGlobal::from_map(&received_map).unwrap();
+    println!("{:?}", parsed_nb_global);
+}
diff --git a/derive/src/lib.rs b/derive/src/lib.rs
new file mode 100644
index 0000000..149b437
--- /dev/null
+++ b/derive/src/lib.rs
@@ -0,0 +1,319 @@
+extern crate proc_macro;
+
+use proc_macro::TokenStream;
+use quote::quote;
+use syn::{parse_macro_input, parse_quote, Data, DeriveInput, Fields};
+
+/// Attribute macro for OVSDB table structs
+///
+/// This macro automatically adds `_uuid` and `_version` fields to your struct
+/// and generates the necessary implementations for it to work with OVSDB.
+///
+/// # Example
+///
+/// ```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>>,
+/// }
+/// ```
+#[proc_macro_attribute]
+pub fn ovsdb_object(_attr: TokenStream, item: TokenStream) -> TokenStream {
+    // Parse the struct definition
+    let mut input = parse_macro_input!(item as DeriveInput);
+
+    // Add _uuid and _version fields if they don't exist
+    if let Data::Struct(ref mut data_struct) = input.data {
+        if let Fields::Named(ref mut fields) = data_struct.fields {
+            // Check if _uuid and _version already exist
+            let has_uuid = fields
+                .named
+                .iter()
+                .any(|f| f.ident.as_ref().is_some_and(|i| i == "_uuid"));
+            let has_version = fields
+                .named
+                .iter()
+                .any(|f| f.ident.as_ref().is_some_and(|i| i == "_version"));
+
+            // Add fields if they don't exist
+            if !has_uuid {
+                // Add _uuid field
+                fields.named.push(parse_quote! {
+                    pub _uuid: Option<uuid::Uuid>
+                });
+            }
+            if !has_version {
+                // Add _version field
+                fields.named.push(parse_quote! {
+                    pub _version: Option<uuid::Uuid>
+                });
+            }
+        }
+    }
+
+    // Get the name of the struct
+    let struct_name = &input.ident;
+
+    // Extract field names and types, excluding _uuid and _version
+    let mut field_names = Vec::new();
+    let mut field_types = Vec::new();
+
+    if let Data::Struct(ref data_struct) = input.data {
+        if let Fields::Named(ref fields) = data_struct.fields {
+            for field in &fields.named {
+                if let Some(ident) = &field.ident {
+                    if ident == "_uuid" || ident == "_version" {
+                        continue;
+                    }
+                    field_names.push(ident);
+                    field_types.push(&field.ty);
+                }
+            }
+        }
+    }
+
+    // Generate implementations
+    let implementation = quote! {
+        // Re-export the input struct with the added fields
+        #input
+
+        // Automatically import necessary items from ovsdb-schema
+        use ::ovsdb_schema::{extract_uuid, OvsdbSerializableExt};
+
+        impl #struct_name {
+            /// Create a new instance with default values
+            pub fn new() -> Self {
+                Self {
+                    #(
+                        #field_names: Default::default(),
+                    )*
+                    _uuid: None,
+                    _version: None,
+                }
+            }
+
+            /// Convert to a HashMap for OVSDB serialization
+            pub fn to_map(&self) -> std::collections::HashMap<String, serde_json::Value> {
+                let mut map = std::collections::HashMap::new();
+
+                #(
+                    // Skip None values
+                    let field_value = &self.#field_names;
+                    if let Some(value) = field_value.to_ovsdb_json() {
+                        map.insert(stringify!(#field_names).to_string(), value);
+                    }
+                )*
+
+                map
+            }
+
+            /// Create from a HashMap received from OVSDB
+            pub fn from_map(map: &std::collections::HashMap<String, serde_json::Value>) -> Result<Self, String> {
+                let mut result = Self::new();
+
+                // Extract UUID if present
+                if let Some(uuid_val) = map.get("_uuid") {
+                    if let Some(uuid) = extract_uuid(uuid_val) {
+                        result._uuid = Some(uuid);
+                    }
+                }
+
+                // Extract version if present
+                if let Some(version_val) = map.get("_version") {
+                    if let Some(version) = extract_uuid(version_val) {
+                        result._version = Some(version);
+                    }
+                }
+
+                // Extract other fields
+                #(
+                    if let Some(value) = map.get(stringify!(#field_names)) {
+                        result.#field_names = <#field_types>::from_ovsdb_json(value)
+                            .ok_or_else(|| format!("Failed to parse field {}", stringify!(#field_names)))?;
+                    }
+                )*
+
+                Ok(result)
+            }
+        }
+
+        impl Default for #struct_name {
+            fn default() -> Self {
+                Self::new()
+            }
+        }
+
+        impl serde::Serialize for #struct_name {
+            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+            where
+                S: serde::Serializer
+            {
+                self.to_map().serialize(serializer)
+            }
+        }
+
+        impl<'de> serde::Deserialize<'de> for #struct_name {
+            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+            where
+                D: serde::Deserializer<'de>
+            {
+                let map = std::collections::HashMap::<String, serde_json::Value>::deserialize(deserializer)?;
+                Self::from_map(&map).map_err(serde::de::Error::custom)
+            }
+        }
+    };
+
+    // Return the modified struct and implementations
+    TokenStream::from(implementation)
+}
+
+/// Derive macro for OVSDB table structs (requires manual _uuid and _version fields)
+///
+/// This macro generates the necessary implementations for a struct to work with OVSDB.
+/// The struct must have `_uuid` and `_version` fields of type `Option<uuid::Uuid>`.
+///
+/// # Example
+///
+/// ```rust
+/// use ovsdb_derive::OVSDB;
+/// use std::collections::HashMap;
+/// use uuid::Uuid;
+///
+/// #[derive(Debug, Clone, PartialEq, OVSDB)]
+/// pub struct NbGlobal {
+///     pub name: Option<String>,
+///     pub nb_cfg: Option<i64>,
+///     pub external_ids: Option<HashMap<String, String>>,
+///     
+///     // Required fields
+///     pub _uuid: Option<Uuid>,
+///     pub _version: Option<Uuid>,
+/// }
+/// ```
+#[proc_macro_derive(OVSDB)]
+pub fn ovsdb_derive(input: TokenStream) -> TokenStream {
+    // Parse the input tokens into a syntax tree
+    let input = parse_macro_input!(input as DeriveInput);
+
+    // Get the name of the struct
+    let struct_name = &input.ident;
+
+    // Check if the input is a struct
+    let fields = match &input.data {
+        Data::Struct(data_struct) => match &data_struct.fields {
+            Fields::Named(fields_named) => &fields_named.named,
+            _ => panic!("OVSDB can only be derived for structs with named fields"),
+        },
+        _ => panic!("OVSDB can only be derived for structs"),
+    };
+
+    // Extract field names and types, excluding _uuid and _version
+    let mut field_names = Vec::new();
+    let mut field_types = Vec::new();
+
+    for field in fields {
+        if let Some(ident) = &field.ident {
+            if ident == "_uuid" || ident == "_version" {
+                continue;
+            }
+            field_names.push(ident);
+            field_types.push(&field.ty);
+        }
+    }
+
+    // Generate code for the implementation
+    let expanded = quote! {
+        // Automatically import necessary items from ovsdb-schema
+        use ::ovsdb_schema::{extract_uuid, OvsdbSerializableExt};
+
+        impl #struct_name {
+            /// Create a new instance with default values
+            pub fn new() -> Self {
+                Self {
+                    #(
+                        #field_names: Default::default(),
+                    )*
+                    _uuid: None,
+                    _version: None,
+                }
+            }
+
+            /// Convert to a HashMap for OVSDB serialization
+            pub fn to_map(&self) -> std::collections::HashMap<String, serde_json::Value> {
+                let mut map = std::collections::HashMap::new();
+
+                #(
+                    // Skip None values
+                    let field_value = &self.#field_names;
+                    if let Some(value) = field_value.to_ovsdb_json() {
+                        map.insert(stringify!(#field_names).to_string(), value);
+                    }
+                )*
+
+                map
+            }
+
+            /// Create from a HashMap received from OVSDB
+            pub fn from_map(map: &std::collections::HashMap<String, serde_json::Value>) -> Result<Self, String> {
+                let mut result = Self::new();
+
+                // Extract UUID if present
+                if let Some(uuid_val) = map.get("_uuid") {
+                    if let Some(uuid) = extract_uuid(uuid_val) {
+                        result._uuid = Some(uuid);
+                    }
+                }
+
+                // Extract version if present
+                if let Some(version_val) = map.get("_version") {
+                    if let Some(version) = extract_uuid(version_val) {
+                        result._version = Some(version);
+                    }
+                }
+
+                // Extract other fields
+                #(
+                    if let Some(value) = map.get(stringify!(#field_names)) {
+                        result.#field_names = <#field_types>::from_ovsdb_json(value)
+                            .ok_or_else(|| format!("Failed to parse field {}", stringify!(#field_names)))?;
+                    }
+                )*
+
+                Ok(result)
+            }
+        }
+
+        impl Default for #struct_name {
+            fn default() -> Self {
+                Self::new()
+            }
+        }
+
+        impl serde::Serialize for #struct_name {
+            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+            where
+                S: serde::Serializer
+            {
+                self.to_map().serialize(serializer)
+            }
+        }
+
+        impl<'de> serde::Deserialize<'de> for #struct_name {
+            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+            where
+                D: serde::Deserializer<'de>
+            {
+                let map = std::collections::HashMap::<String, serde_json::Value>::deserialize(deserializer)?;
+                Self::from_map(&map).map_err(serde::de::Error::custom)
+            }
+        }
+    };
+
+    // Return the generated code
+    TokenStream::from(expanded)
+}