1mod resolver;
147mod field_data;
148
149use proc_macro::TokenStream;
150use quote::{quote};
151use syn::{parse_macro_input, Token, braced, punctuated::Punctuated, parse::Parse, parse::ParseStream, Result, Ident, LitStr, token};
152use syn::token::Comma;
153use resolver::get_default_field_data;
154
155#[allow(dead_code)]
156struct PropertyField {
157 name: Ident,
158 colon_token: Token![=],
159 path: LitStr,
160}
161
162#[allow(dead_code)]
163struct VimObjectMacro {
164 struct_token: Token![struct],
165 struct_name: Ident,
166 colon_token: Token![:],
167 object_type: Ident,
168 brace_token: token::Brace,
169 fields: Punctuated<PropertyField, Token![,]>,
170}
171
172impl Parse for PropertyField {
173 fn parse(input: ParseStream) -> Result<Self> {
174 Ok(PropertyField {
175 name: input.parse()?,
176 colon_token: input.parse()?,
177 path: input.parse()?,
178 })
179 }
180}
181
182impl Parse for VimObjectMacro {
183 fn parse(input: ParseStream) -> Result<Self> {
184 let content;
185 Ok(VimObjectMacro {
186 struct_token: input.parse()?,
187 struct_name: input.parse()?,
188 colon_token: input.parse()?,
189 object_type: input.parse()?,
190 brace_token: braced!(content in input),
191 fields: Punctuated::parse_terminated(&content)?,
192 })
193 }
194}
195
196struct FieldInfo<'a> {
197 property_field: &'a PropertyField,
198 field_data: resolver::FieldData,
199}
200
201
202
203#[proc_macro]
228pub fn vim_retrievable(input: TokenStream) -> TokenStream {
229 let VimObjectMacro { struct_token: _, struct_name, colon_token: _, object_type: managed_object_type, brace_token: _, fields } =
230 parse_macro_input!(input as VimObjectMacro);
231
232 let (field_infos, errors) = resolve_fields(&managed_object_type, &fields);
233
234 let struct_tokens = generate_struct_decl(&struct_name, &field_infos);
235
236 let struct_impl_tokens = generate_retrieve_struct_impl(&struct_name, &managed_object_type, &field_infos);
237
238 let try_from_object_content = generate_try_from_object_content(&struct_name, &field_infos);
239
240 let output = quote! {
241 #( #errors )*
242
243 #struct_tokens
244 #struct_impl_tokens
245
246 #try_from_object_content
247 };
248 output.into()
249}
250
251#[proc_macro]
281pub fn vim_updatable(input: TokenStream) -> TokenStream {
282 let VimObjectMacro { struct_token: _, struct_name, colon_token: _, object_type: managed_object_type, brace_token: _, fields } =
283 parse_macro_input!(input as VimObjectMacro);
284
285 let (field_infos, errors) = resolve_fields(&managed_object_type, &fields);
286
287 let struct_tokens = generate_struct_decl(&struct_name, &field_infos);
288
289 let struct_impl_tokens = generate_updateable_struct_impl(&struct_name, &managed_object_type, &field_infos);
290
291 let try_from_object_content = generate_try_from_object_update(&struct_name, &field_infos);
292
293 let output = quote! {
294 #( #errors )*
295
296 #struct_tokens
297 #struct_impl_tokens
298
299 #try_from_object_content
300 };
301 output.into()
302}
303
304fn resolve_fields<'a>(managed_object_type: &Ident, fields: &'a Punctuated<PropertyField, Comma>) -> (Vec<FieldInfo<'a>>, Vec<proc_macro2::TokenStream>) {
305 let mut field_infos = Vec::new();
306 let mut errors: Vec<proc_macro2::TokenStream> = Vec::new();
307 for property_field in fields {
308 let path_str = property_field.path.value();
309 let res = resolver::resolve_path(&managed_object_type.to_string(), &path_str);
310 let field_data = match res {
311 Ok(field_type) => field_type,
312 Err(e) => {
313 let msg = format!("Error resolving path: {}", e);
314 errors.push(syn::Error::new(property_field.path.span(), msg).to_compile_error());
315 get_default_field_data()
316 }
317 };
318 field_infos.push(FieldInfo { property_field, field_data });
319 };
320 (field_infos, errors)
321}
322
323
324fn generate_struct_decl(struct_name: &Ident, fields: &Vec<FieldInfo>) -> proc_macro2::TokenStream {
326 let mut field_declarations: Vec<proc_macro2::TokenStream> = Vec::with_capacity(fields.len());
327 let mut docs: Vec<&'static str> = Vec::with_capacity(fields.len());
328
329 for f in fields {
330 let field_name = &f.property_field.name;
331 let parsed_field_type: syn::Type = syn::parse_str(&f.field_data.data_type).unwrap();
332 let decl = quote! {
333 #field_name : #parsed_field_type
334 };
335 field_declarations.push(decl);
336 docs.push(f.field_data.doc.unwrap_or(""))
337 }
338
339 let struct_tokens = quote! {
340 #[derive(Debug)]
341 pub struct #struct_name {
342 #[doc = "Object identifier"]
343 pub id: vim_rs::types::structs::ManagedObjectReference,
344 #(#[doc = #docs]
345 pub #field_declarations,)*
346 }
347 };
348 struct_tokens
349}
350
351fn generate_retrieve_struct_impl(struct_name: &Ident, managed_object_type: &Ident, fields: &Vec<FieldInfo>) -> proc_macro2::TokenStream {
352 let prop_spec = prop_spec(managed_object_type, fields);
353 let id = id();
354 quote! {
355 impl vim_rs::core::pc_helpers::Queriable for #struct_name {
356 #prop_spec
357 }
358
359 impl #struct_name {
360 pub #id
361 }
362 }
363}
364
365fn generate_updateable_struct_impl(struct_name: &Ident, managed_object_type: &Ident, fields: &Vec<FieldInfo>) -> proc_macro2::TokenStream {
366 let prop_spec = prop_spec(managed_object_type, fields);
367 let id = id();
368 let apply_update = generate_apply_update(fields);
369 quote! {
370 impl vim_rs::core::pc_helpers::Queriable for #struct_name {
371 #prop_spec
372 }
373
374 impl vim_rs::core::pc_cache::Cacheable for #struct_name {
375 #id
376 #apply_update
377 }
378 }
379}
380
381fn prop_spec(managed_object_type: &Ident, fields: &Vec<FieldInfo>) -> proc_macro2::TokenStream {
382 let field_paths: Vec<&str> = fields.iter().map(|f| f.field_data.vim_path.as_str()).collect();
383 let prop_paths_quoted: Vec<proc_macro2::TokenStream> = field_paths
384 .iter()
385 .map(|path| quote! { #path.into() })
386 .collect();
387
388 quote! {
389 fn prop_spec() -> vim_rs::types::structs::PropertySpec {
390 vim_rs::types::structs::PropertySpec {
391 all: Some(false),
392 path_set: Some(vec![
393 #(#prop_paths_quoted),*
394 ]),
395 r#type: Into::<&str>::into(vim_rs::types::enums::MoTypesEnum::#managed_object_type).to_string(),
396 }
397 }
398 }
399}
400
401fn id() -> proc_macro2::TokenStream {
402 quote! {
403 fn id(&self) -> &vim_rs::types::structs::ManagedObjectReference {
404 &self.id
405 }
406 }
407}
408
409fn generate_try_from_object_content(struct_name: &Ident, fields: &Vec<FieldInfo>) -> proc_macro2::TokenStream {
410
411 let mut field_declarations: Vec<proc_macro2::TokenStream> = Vec::with_capacity(fields.len());
412 let mut field_conversions: Vec<proc_macro2::TokenStream> = Vec::with_capacity(fields.len());
413 let mut field_assignments: Vec<proc_macro2::TokenStream> = Vec::with_capacity(fields.len());
414 let mut idx = 1;
415 for field in fields {
416 let field_alias: Ident = syn::parse_str(&format!("field{}", idx)).unwrap();
417 let field_name = &field.property_field.name;
418 field_declarations.push(quote! { let mut #field_alias = None; });
419 match field.field_data.processing_type {
420 resolver::FieldProcessingType::Enum(enum_field_name) => {
421 field_conversions.push(generate_enum_field_from_content(field, &field_alias, &enum_field_name));
422 },
423 resolver::FieldProcessingType::Struct => {
424 field_conversions.push(generate_struct_field_from_content(field, &field_alias, &field.field_data.data_type));
425 },
426 resolver::FieldProcessingType::Trait => {
427 field_conversions.push(generate_trait_field_from_content(field, &field_alias, &field.field_data.data_type));
428 },
429 }
430 if field.field_data.is_optional {
431 field_assignments.push(quote! { #field_name: #field_alias });
432 } else {
433 let field_name_str = field.field_data.vim_path.as_str();
434 field_assignments.push(quote! { #field_name: #field_alias.ok_or_else(|| vim_rs::core::error::Error::missing_required_field(#field_name_str.to_string()))? });
435 }
436 idx += 1;
437 }
438
439 quote! {
440 impl core::convert::TryFrom<vim_rs::types::structs::ObjectContent> for #struct_name {
441 type Error = vim_rs::core::error::Error;
442
443 fn try_from(row: vim_rs::types::structs::ObjectContent) -> vim_rs::core::error::Result<Self> {
444 let id = row.obj;
445 let Some(row) = row.prop_set else {
446 return Err(vim_rs::core::error::Error::no_data_found());
447 };
448
449 #(#field_declarations)*
450
451 for prop in row {
452 match prop.name.as_str() {
453 #(#field_conversions)*
454 name => {
455 return Err(vim_rs::core::error::Error::unexpected_property_path(name.to_string()));
456 }
457 }
458 }
459
460 Ok(#struct_name {
461 id,
462 #(#field_assignments),*
463 })
464 }
465 }
466 }
467}
468
469fn generate_try_from_object_update(struct_name: &Ident, fields: &Vec<FieldInfo>) -> proc_macro2::TokenStream {
470
471 let mut field_declarations: Vec<proc_macro2::TokenStream> = Vec::with_capacity(fields.len());
472 let mut field_conversions: Vec<proc_macro2::TokenStream> = Vec::with_capacity(fields.len());
473 let mut field_assignments: Vec<proc_macro2::TokenStream> = Vec::with_capacity(fields.len());
474 let mut idx = 1;
475 for field in fields {
476 let field_alias: Ident = syn::parse_str(&format!("field{}", idx)).unwrap();
477 let field_name = &field.property_field.name;
478 field_declarations.push(quote! { let mut #field_alias = None; });
479 match field.field_data.processing_type {
480 resolver::FieldProcessingType::Enum(enum_field_name) => {
481 field_conversions.push(generate_enum_field_from_update(field, &field_alias, &enum_field_name));
482 },
483 resolver::FieldProcessingType::Struct => {
484 field_conversions.push(generate_struct_field_from_update(field, &field_alias, &field.field_data.data_type));
485 },
486 resolver::FieldProcessingType::Trait => {
487 field_conversions.push(generate_trait_field_from_update(field, &field_alias, &field.field_data.data_type));
488 },
489 }
490 if field.field_data.is_optional {
491 field_assignments.push(quote! { #field_name: #field_alias });
492 } else {
493 let field_name_str = field.field_data.vim_path.as_str();
494 field_assignments.push(quote! { #field_name: #field_alias.ok_or_else(|| vim_rs::core::error::Error::missing_required_field(#field_name_str.to_string()))? });
495 }
496 idx += 1;
497 }
498
499 quote! {
500 impl core::convert::TryFrom<vim_rs::types::structs::ObjectUpdate> for #struct_name {
501 type Error = vim_rs::core::error::Error;
502
503 fn try_from(row: vim_rs::types::structs::ObjectUpdate) -> vim_rs::core::error::Result<Self> {
504 let id = row.obj;
505 let Some(row) = row.change_set else {
506 return Err(vim_rs::core::error::Error::no_data_found());
507 };
508
509 #(#field_declarations)*
510
511 for prop in row {
512 if matches!(prop.op, vim_rs::types::enums::PropertyChangeOpEnum::Add | vim_rs::types::enums::PropertyChangeOpEnum::Remove | vim_rs::types::enums::PropertyChangeOpEnum::Other_(_)) {
513 continue;
521 }
522 match prop.name.as_str() {
523 #(#field_conversions)*
524 name => {
525 return Err(vim_rs::core::error::Error::unexpected_property_path(name.to_string()));
526 }
527 }
528 }
529
530 Ok(#struct_name {
531 id,
532 #(#field_assignments),*
533 })
534 }
535 }
536 }
537}
538
539
540fn generate_apply_update(fields: &Vec<FieldInfo>) -> proc_macro2::TokenStream {
541
542 let mut field_conversions: Vec<proc_macro2::TokenStream> = Vec::with_capacity(fields.len());
543 for field in fields {
544
545 match field.field_data.processing_type {
546 resolver::FieldProcessingType::Enum(enum_field_name) => {
547 field_conversions.push(generate_enum_field_apply(field, &enum_field_name));
548 },
549 resolver::FieldProcessingType::Struct => {
550 field_conversions.push(generate_struct_field_apply(field, &field.field_data.data_type));
551 },
552 resolver::FieldProcessingType::Trait => {
553 field_conversions.push(generate_trait_field_apply(field, &field.field_data.data_type));
554 },
555 }
556 }
557
558 quote! {
559 fn apply_update(&mut self, update: vim_rs::types::structs::ObjectUpdate) -> vim_rs::core::pc_helpers::Result<()> {
560 let Some(row) = update.change_set else {
561 return Ok(());
562 };
563
564 for prop in row {
565 if matches!(prop.op, vim_rs::types::enums::PropertyChangeOpEnum::Add | vim_rs::types::enums::PropertyChangeOpEnum::Remove | vim_rs::types::enums::PropertyChangeOpEnum::Other_(_)) {
566 continue;
574 }
575 match prop.name.as_str() {
576 #(#field_conversions)*
577 name => {
578 return Err(vim_rs::core::error::Error::unexpected_property_path(name.to_string()));
579 }
580 }
581 }
582 Ok(())
583 }
584 }
585}
586
587fn generate_enum_field_from_content(field: &FieldInfo, field_alias: &Ident, enum_field_name: &str) -> proc_macro2::TokenStream {
597 let path = &field.field_data.vim_path;
598 let enum_field = Ident::new(enum_field_name, field.property_field.path.span());
599 quote! {
600 #path => {
601 #field_alias = match prop.val {
602 vim_rs::types::vim_any::VimAny::Value(vim_rs::types::boxed_types::ValueElements::#enum_field(vd)) => Some(vd),
603 ref val => return Err(vim_rs::core::error::Error::invalid_property_type(#path.to_string(), #enum_field_name.to_string(), vim_rs::core::pc_helpers::type_name(val))),
604 };
605 }
606 }
607}
608
609fn generate_enum_field_from_update(field: &FieldInfo, field_alias: &Ident, enum_field_name: &str) -> proc_macro2::TokenStream {
610 let path = &field.field_data.vim_path;
611 let enum_field = Ident::new(enum_field_name, field.property_field.path.span());
612 quote! {
613 #path => {
614 #field_alias = match prop.val {
615 Some(vim_rs::types::vim_any::VimAny::Value(vim_rs::types::boxed_types::ValueElements::#enum_field(vd))) => Some(vd),
616 None => continue,
617 Some(ref val) => return Err(vim_rs::core::error::Error::invalid_property_type(#path.to_string(), #enum_field_name.to_string(), vim_rs::core::pc_helpers::type_name(val))),
618 };
619 }
620 }
621}
622
623fn generate_enum_field_apply(field: &FieldInfo, enum_field_name: &str) -> proc_macro2::TokenStream {
624 let path = &field.field_data.vim_path;
625 let enum_field = Ident::new(enum_field_name, field.property_field.path.span());
626 let field_name = &field.property_field.name;
627 let none_code;
628 let value_code;
629 if field.field_data.is_optional {
630 none_code = quote! { None };
631 value_code = quote! { Some(vd) };
632 } else {
633 none_code = quote! { return Err(vim_rs::core::error::Error::missing_required_field(#path.to_string())) };
634 value_code = quote! { vd };
635 };
636
637 quote! {
638 #path => {
639 self.#field_name = match prop.val {
640 Some(vim_rs::types::vim_any::VimAny::Value(vim_rs::types::boxed_types::ValueElements::#enum_field(vd))) => #value_code,
641 None => #none_code,
642 Some(ref val) => return Err(vim_rs::core::error::Error::invalid_property_type(#path.to_string(), #enum_field_name.to_string(), vim_rs::core::pc_helpers::type_name(val))),
643 };
644 }
645 }
646}
647
648
649fn generate_struct_field_from_content(field: &FieldInfo, field_alias: &Ident, struct_type: &str) -> proc_macro2::TokenStream {
663 let path = &field.field_data.vim_path;
664
665 quote! {
666 #path => {
667 #field_alias = match prop.val {
668 vim_rs::types::vim_any::VimAny::Object(obj) => {
669 let name: &'static str = obj.data_type().into();
670 match obj.as_any_box().downcast() {
671 Ok(val) => Some(*val),
672 Err(_) => return Err(vim_rs::core::error::Error::invalid_property_type( #path.to_string(), #struct_type.to_string(), name.to_string())),
673 }
674 },
675 ref val => return Err(vim_rs::core::error::Error::invalid_property_type( #path.to_string(), #struct_type.to_string(), vim_rs::core::pc_helpers::type_name(val))),
676 };
677 }
678 }
679}
680
681fn generate_struct_field_from_update(field: &FieldInfo, field_alias: &Ident, struct_type: &str) -> proc_macro2::TokenStream {
682 let path = &field.field_data.vim_path;
683 quote! {
684 #path => {
685 #field_alias = match prop.val {
686 Some(vim_rs::types::vim_any::VimAny::Object(obj)) => {
687 let name: &'static str = obj.data_type().into();
688 match obj.as_any_box().downcast() {
689 Ok(val) => Some(*val),
690 Err(_) => return Err(vim_rs::core::error::Error::invalid_property_type( #path.to_string(), #struct_type.to_string(), name.to_string())),
691 }
692 },
693 None => continue,
694 Some(ref val) => return Err(vim_rs::core::error::Error::invalid_property_type( #path.to_string(), #struct_type.to_string(), vim_rs::core::pc_helpers::type_name(val))),
695 };
696 }
697 }
698}
699
700fn generate_struct_field_apply(field: &FieldInfo, struct_type: &str) -> proc_macro2::TokenStream {
701 let path = &field.field_data.vim_path;
702 let field_name = &field.property_field.name;
703 let none_code;
704 let value_code;
705 if field.field_data.is_optional {
706 none_code = quote! { None };
707 value_code = quote! { Some(*val) };
708 } else {
709 none_code = quote! { return Err(vim_rs::core::error::Error::missing_required_field(#path.to_string())) };
710 value_code = quote! { *val };
711 };
712 quote! {
713 #path => {
714 self.#field_name = match prop.val {
715 Some(vim_rs::types::vim_any::VimAny::Object(obj)) => {
716 let name: &'static str = obj.data_type().into();
717 match obj.as_any_box().downcast() {
718 Ok(val) => #value_code,
719 Err(_) => return Err(vim_rs::core::error::Error::invalid_property_type( #path.to_string(), #struct_type.to_string(), name.to_string())),
720 }
721 },
722 None => #none_code,
723 Some(ref val) => return Err(vim_rs::core::error::Error::invalid_property_type( #path.to_string(), #struct_type.to_string(), vim_rs::core::pc_helpers::type_name(val))),
724 };
725 }
726 }
727}
728
729fn generate_trait_field_from_content(field: &FieldInfo, field_alias: &Ident, trait_type: &str) -> proc_macro2::TokenStream {
743 let path = &field.field_data.vim_path;
744 quote! {
745 #path => {
746 #field_alias = match prop.val {
747 vim_rs::types::vim_any::VimAny::Object(obj) => {
748 let name: &'static str = obj.data_type().into();
749 match vim_rs::types::convert::CastInto::into_box(obj) {
750 Ok(val) => Some(val),
751 Err(_) => return Err(vim_rs::core::error::Error::invalid_property_type( #path.to_string(), #trait_type.to_string(), name.to_string())),
752 }
753 },
754 ref val => return Err(vim_rs::core::error::Error::invalid_property_type( #path.to_string(), #trait_type.to_string(), vim_rs::core::pc_helpers::type_name(val))),
755 };
756 }
757 }
758}
759
760fn generate_trait_field_from_update(field: &FieldInfo, field_alias: &Ident, trait_type: &str) -> proc_macro2::TokenStream {
761 let path = &field.field_data.vim_path;
762 quote! {
763 #path => {
764 #field_alias = match prop.val {
765 Some(vim_rs::types::vim_any::VimAny::Object(obj)) => {
766 let name: &'static str = obj.data_type().into();
767 match vim_rs::types::convert::CastInto::into_box(obj) {
768 Ok(val) => Some(val),
769 Err(_) => return Err(vim_rs::core::error::Error::invalid_property_type( #path.to_string(), #trait_type.to_string(), name.to_string())),
770 }
771 },
772 None => continue,
773 Some(ref val) => return Err(vim_rs::core::error::Error::invalid_property_type( #path.to_string(), #trait_type.to_string(), vim_rs::core::pc_helpers::type_name(val))),
774 };
775 }
776 }
777}
778
779fn generate_trait_field_apply(field: &FieldInfo, trait_type: &str) -> proc_macro2::TokenStream {
780 let path = &field.field_data.vim_path;
781 let field_name = &field.property_field.name;
782 let none_code;
783 let value_code;
784 if field.field_data.is_optional {
785 none_code = quote! { None };
786 value_code = quote! { Some(val) };
787 } else {
788 none_code = quote! { return Err(vim_rs::core::error::Error::missing_required_field(#path.to_string())) };
789 value_code = quote! { val };
790 };
791
792 quote! {
793 #path => {
794 self.#field_name = match prop.val {
795 Some(vim_rs::types::vim_any::VimAny::Object(obj)) => {
796 let name: &'static str = obj.data_type().into();
797 match vim_rs::types::convert::CastInto::into_box(obj) {
798 Ok(val) => #value_code,
799 Err(_) => return Err(vim_rs::core::error::Error::invalid_property_type( #path.to_string(), #trait_type.to_string(), name.to_string())),
800 }
801 },
802 None => #none_code,
803 Some(ref val) => return Err(vim_rs::core::error::Error::invalid_property_type( #path.to_string(), #trait_type.to_string(), vim_rs::core::pc_helpers::type_name(val))),
804 };
805 }
806 }
807}
808