1extern crate proc_macro;
2
3use proc_macro::TokenStream;
4use quote::quote;
5use syn::{parse_macro_input, parse_quote, Data, DeriveInput, Fields};
6
7#[proc_macro_attribute]
26pub fn ovsdb_object(_attr: TokenStream, item: TokenStream) -> TokenStream {
27 let mut input = parse_macro_input!(item as DeriveInput);
29
30 if let Data::Struct(ref mut data_struct) = input.data {
32 if let Fields::Named(ref mut fields) = data_struct.fields {
33 let has_uuid = fields
35 .named
36 .iter()
37 .any(|f| f.ident.as_ref().is_some_and(|i| i == "_uuid"));
38 let has_version = fields
39 .named
40 .iter()
41 .any(|f| f.ident.as_ref().is_some_and(|i| i == "_version"));
42
43 if !has_uuid {
45 fields.named.push(parse_quote! {
47 pub _uuid: Option<uuid::Uuid>
48 });
49 }
50 if !has_version {
51 fields.named.push(parse_quote! {
53 pub _version: Option<uuid::Uuid>
54 });
55 }
56 }
57 }
58
59 let struct_name = &input.ident;
61
62 let mut field_names = Vec::new();
64 let mut field_types = Vec::new();
65
66 if let Data::Struct(ref data_struct) = input.data {
67 if let Fields::Named(ref fields) = data_struct.fields {
68 for field in &fields.named {
69 if let Some(ident) = &field.ident {
70 if ident == "_uuid" || ident == "_version" {
71 continue;
72 }
73 field_names.push(ident);
74 field_types.push(&field.ty);
75 }
76 }
77 }
78 }
79
80 let implementation = quote! {
82 #input
84
85 use ::ovsdb_schema::{extract_uuid, OvsdbSerializableExt};
87
88 impl #struct_name {
89 pub fn new() -> Self {
91 Self {
92 #(
93 #field_names: Default::default(),
94 )*
95 _uuid: None,
96 _version: None,
97 }
98 }
99
100 pub fn to_map(&self) -> std::collections::HashMap<String, serde_json::Value> {
102 let mut map = std::collections::HashMap::new();
103
104 #(
105 let field_value = &self.#field_names;
107 if let Some(value) = field_value.to_ovsdb_json() {
108 map.insert(stringify!(#field_names).to_string(), value);
109 }
110 )*
111
112 map
113 }
114
115 pub fn from_map(map: &std::collections::HashMap<String, serde_json::Value>) -> Result<Self, String> {
117 let mut result = Self::new();
118
119 if let Some(uuid_val) = map.get("_uuid") {
121 if let Some(uuid) = extract_uuid(uuid_val) {
122 result._uuid = Some(uuid);
123 }
124 }
125
126 if let Some(version_val) = map.get("_version") {
128 if let Some(version) = extract_uuid(version_val) {
129 result._version = Some(version);
130 }
131 }
132
133 #(
135 if let Some(value) = map.get(stringify!(#field_names)) {
136 result.#field_names = <#field_types>::from_ovsdb_json(value)
137 .ok_or_else(|| format!("Failed to parse field {}", stringify!(#field_names)))?;
138 }
139 )*
140
141 Ok(result)
142 }
143 }
144
145 impl Default for #struct_name {
146 fn default() -> Self {
147 Self::new()
148 }
149 }
150
151 impl serde::Serialize for #struct_name {
152 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
153 where
154 S: serde::Serializer
155 {
156 self.to_map().serialize(serializer)
157 }
158 }
159
160 impl<'de> serde::Deserialize<'de> for #struct_name {
161 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
162 where
163 D: serde::Deserializer<'de>
164 {
165 let map = std::collections::HashMap::<String, serde_json::Value>::deserialize(deserializer)?;
166 Self::from_map(&map).map_err(serde::de::Error::custom)
167 }
168 }
169 };
170
171 TokenStream::from(implementation)
173}
174
175#[proc_macro_derive(OVSDB)]
199pub fn ovsdb_derive(input: TokenStream) -> TokenStream {
200 let input = parse_macro_input!(input as DeriveInput);
202
203 let struct_name = &input.ident;
205
206 let fields = match &input.data {
208 Data::Struct(data_struct) => match &data_struct.fields {
209 Fields::Named(fields_named) => &fields_named.named,
210 _ => panic!("OVSDB can only be derived for structs with named fields"),
211 },
212 _ => panic!("OVSDB can only be derived for structs"),
213 };
214
215 let mut field_names = Vec::new();
217 let mut field_types = Vec::new();
218
219 for field in fields {
220 if let Some(ident) = &field.ident {
221 if ident == "_uuid" || ident == "_version" {
222 continue;
223 }
224 field_names.push(ident);
225 field_types.push(&field.ty);
226 }
227 }
228
229 let expanded = quote! {
231 use ::ovsdb_schema::{extract_uuid, OvsdbSerializableExt};
233
234 impl #struct_name {
235 pub fn new() -> Self {
237 Self {
238 #(
239 #field_names: Default::default(),
240 )*
241 _uuid: None,
242 _version: None,
243 }
244 }
245
246 pub fn to_map(&self) -> std::collections::HashMap<String, serde_json::Value> {
248 let mut map = std::collections::HashMap::new();
249
250 #(
251 let field_value = &self.#field_names;
253 if let Some(value) = field_value.to_ovsdb_json() {
254 map.insert(stringify!(#field_names).to_string(), value);
255 }
256 )*
257
258 map
259 }
260
261 pub fn from_map(map: &std::collections::HashMap<String, serde_json::Value>) -> Result<Self, String> {
263 let mut result = Self::new();
264
265 if let Some(uuid_val) = map.get("_uuid") {
267 if let Some(uuid) = extract_uuid(uuid_val) {
268 result._uuid = Some(uuid);
269 }
270 }
271
272 if let Some(version_val) = map.get("_version") {
274 if let Some(version) = extract_uuid(version_val) {
275 result._version = Some(version);
276 }
277 }
278
279 #(
281 if let Some(value) = map.get(stringify!(#field_names)) {
282 result.#field_names = <#field_types>::from_ovsdb_json(value)
283 .ok_or_else(|| format!("Failed to parse field {}", stringify!(#field_names)))?;
284 }
285 )*
286
287 Ok(result)
288 }
289 }
290
291 impl Default for #struct_name {
292 fn default() -> Self {
293 Self::new()
294 }
295 }
296
297 impl serde::Serialize for #struct_name {
298 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
299 where
300 S: serde::Serializer
301 {
302 self.to_map().serialize(serializer)
303 }
304 }
305
306 impl<'de> serde::Deserialize<'de> for #struct_name {
307 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
308 where
309 D: serde::Deserializer<'de>
310 {
311 let map = std::collections::HashMap::<String, serde_json::Value>::deserialize(deserializer)?;
312 Self::from_map(&map).map_err(serde::de::Error::custom)
313 }
314 }
315 };
316
317 TokenStream::from(expanded)
319}