1mod field_data;
176mod resolver;
177
178use proc_macro::TokenStream;
179use quote::quote;
180use resolver::get_default_field_data;
181use syn::token::Comma;
182use syn::{
183 braced, parse::Parse, parse::ParseStream, parse_macro_input, punctuated::Punctuated, token,
184 Ident, LitStr, Result, Token,
185};
186
187#[allow(dead_code)]
188struct PropertyField {
189 name: Ident,
190 colon_token: Token![=],
191 path: LitStr,
192 force_optional: Option<Token![?]>,
197}
198
199#[allow(dead_code)]
200struct VimObjectMacro {
201 struct_token: Token![struct],
202 struct_name: Ident,
203 colon_token: Token![:],
204 object_type: Ident,
205 brace_token: token::Brace,
206 fields: Punctuated<PropertyField, Token![,]>,
207}
208
209impl Parse for PropertyField {
210 fn parse(input: ParseStream) -> Result<Self> {
211 let name: Ident = input.parse()?;
212 let colon_token: Token![=] = input.parse()?;
213 let path: LitStr = input.parse()?;
214 let force_optional: Option<Token![?]> = input.parse()?;
215 Ok(PropertyField {
216 name,
217 colon_token,
218 path,
219 force_optional,
220 })
221 }
222}
223
224impl Parse for VimObjectMacro {
225 fn parse(input: ParseStream) -> Result<Self> {
226 let content;
227 Ok(VimObjectMacro {
228 struct_token: input.parse()?,
229 struct_name: input.parse()?,
230 colon_token: input.parse()?,
231 object_type: input.parse()?,
232 brace_token: braced!(content in input),
233 fields: Punctuated::parse_terminated(&content)?,
234 })
235 }
236}
237
238struct FieldInfo<'a> {
239 property_field: &'a PropertyField,
240 field_data: resolver::FieldData,
241}
242
243#[proc_macro]
271pub fn vim_retrievable(input: TokenStream) -> TokenStream {
272 let VimObjectMacro {
273 struct_token: _,
274 struct_name,
275 colon_token: _,
276 object_type: managed_object_type,
277 brace_token: _,
278 fields,
279 } = parse_macro_input!(input as VimObjectMacro);
280
281 let (field_infos, errors) = resolve_fields(&managed_object_type, &fields);
282
283 let struct_tokens = generate_struct_decl(&struct_name, &field_infos);
284
285 let struct_impl_tokens =
286 generate_retrieve_struct_impl(&struct_name, &managed_object_type, &field_infos);
287
288 let try_from_object_content = generate_try_from_object_content(&struct_name, &field_infos);
289
290 let output = quote! {
291 #( #errors )*
292
293 #struct_tokens
294 #struct_impl_tokens
295
296 #try_from_object_content
297 };
298 output.into()
299}
300
301#[proc_macro]
334pub fn vim_updatable(input: TokenStream) -> TokenStream {
335 let VimObjectMacro {
336 struct_token: _,
337 struct_name,
338 colon_token: _,
339 object_type: managed_object_type,
340 brace_token: _,
341 fields,
342 } = parse_macro_input!(input as VimObjectMacro);
343
344 let (field_infos, errors) = resolve_fields(&managed_object_type, &fields);
345
346 let struct_tokens = generate_struct_decl(&struct_name, &field_infos);
347
348 let struct_impl_tokens =
349 generate_updateable_struct_impl(&struct_name, &managed_object_type, &field_infos);
350
351 let try_from_object_content = generate_try_from_object_update(&struct_name, &field_infos);
352
353 let output = quote! {
354 #( #errors )*
355
356 #struct_tokens
357 #struct_impl_tokens
358
359 #try_from_object_content
360 };
361 output.into()
362}
363
364fn resolve_fields<'a>(
365 managed_object_type: &Ident,
366 fields: &'a Punctuated<PropertyField, Comma>,
367) -> (Vec<FieldInfo<'a>>, Vec<proc_macro2::TokenStream>) {
368 let mut field_infos = Vec::new();
369 let mut errors: Vec<proc_macro2::TokenStream> = Vec::new();
370 for property_field in fields {
371 let path_str = property_field.path.value();
372 let res = resolver::resolve_path(&managed_object_type.to_string(), &path_str);
373 let mut field_data = match res {
374 Ok(field_type) => field_type,
375 Err(e) => {
376 let msg = format!("Error resolving path: {}", e);
377 errors.push(syn::Error::new(property_field.path.span(), msg).to_compile_error());
378 get_default_field_data()
379 }
380 };
381 if property_field.force_optional.is_some() && !field_data.is_optional {
387 field_data.is_optional = true;
388 field_data.data_type = format!("Option<{}>", field_data.data_type);
389 }
390 field_infos.push(FieldInfo {
391 property_field,
392 field_data,
393 });
394 }
395 (field_infos, errors)
396}
397
398fn generate_struct_decl(struct_name: &Ident, fields: &Vec<FieldInfo>) -> proc_macro2::TokenStream {
400 let mut field_declarations: Vec<proc_macro2::TokenStream> = Vec::with_capacity(fields.len());
401 let mut docs: Vec<&'static str> = Vec::with_capacity(fields.len());
402
403 for f in fields {
404 let field_name = &f.property_field.name;
405 let parsed_field_type: syn::Type = syn::parse_str(&f.field_data.data_type).unwrap();
406 let decl = quote! {
407 #field_name : #parsed_field_type
408 };
409 field_declarations.push(decl);
410 docs.push(f.field_data.doc.unwrap_or(""))
411 }
412
413 let struct_tokens = quote! {
414 #[derive(Debug)]
415 pub struct #struct_name {
416 #[doc = "Object identifier"]
417 pub id: vim_rs::types::structs::ManagedObjectReference,
418 #(#[doc = #docs]
419 pub #field_declarations,)*
420 }
421 };
422 struct_tokens
423}
424
425fn generate_retrieve_struct_impl(
426 struct_name: &Ident,
427 managed_object_type: &Ident,
428 fields: &Vec<FieldInfo>,
429) -> proc_macro2::TokenStream {
430 let prop_spec = prop_spec(managed_object_type, fields);
431 let id = id();
432 quote! {
433 impl vim_rs::core::pc_helpers::Queriable for #struct_name {
434 #prop_spec
435 }
436
437 impl #struct_name {
438 pub #id
439 }
440 }
441}
442
443fn generate_updateable_struct_impl(
444 struct_name: &Ident,
445 managed_object_type: &Ident,
446 fields: &Vec<FieldInfo>,
447) -> proc_macro2::TokenStream {
448 let prop_spec = prop_spec(managed_object_type, fields);
449 let id = id();
450 let apply_update = generate_apply_update(fields);
451 quote! {
452 impl vim_rs::core::pc_helpers::Queriable for #struct_name {
453 #prop_spec
454 }
455
456 impl vim_rs::core::pc_cache::Cacheable for #struct_name {
457 #id
458 #apply_update
459 }
460 }
461}
462
463fn prop_spec(managed_object_type: &Ident, fields: &Vec<FieldInfo>) -> proc_macro2::TokenStream {
464 let field_paths: Vec<&str> = fields
465 .iter()
466 .map(|f| f.field_data.vim_path.as_str())
467 .collect();
468 let prop_paths_quoted: Vec<proc_macro2::TokenStream> = field_paths
469 .iter()
470 .map(|path| quote! { #path.into() })
471 .collect();
472
473 quote! {
474 fn prop_spec() -> vim_rs::types::structs::PropertySpec {
475 vim_rs::types::structs::PropertySpec {
476 all: Some(false),
477 path_set: Some(vec![
478 #(#prop_paths_quoted),*
479 ]),
480 r#type: vim_rs::types::enums::MoTypesEnum::#managed_object_type.as_str().to_string(),
481 }
482 }
483 }
484}
485
486fn id() -> proc_macro2::TokenStream {
487 quote! {
488 fn id(&self) -> &vim_rs::types::structs::ManagedObjectReference {
489 &self.id
490 }
491 }
492}
493
494fn generate_try_from_object_content(
495 struct_name: &Ident,
496 fields: &Vec<FieldInfo>,
497) -> proc_macro2::TokenStream {
498 let mut field_declarations: Vec<proc_macro2::TokenStream> = Vec::with_capacity(fields.len());
499 let mut field_conversions: Vec<proc_macro2::TokenStream> = Vec::with_capacity(fields.len());
500 let mut field_assignments: Vec<proc_macro2::TokenStream> = Vec::with_capacity(fields.len());
501 let mut idx = 1;
502 for field in fields {
503 let field_alias: Ident = syn::parse_str(&format!("field{}", idx)).unwrap();
504 let field_name = &field.property_field.name;
505 field_declarations.push(quote! { let mut #field_alias = None; });
506 match field.field_data.processing_type {
507 resolver::FieldProcessingType::Enum(enum_field_name) => {
508 field_conversions.push(generate_enum_field_from_content(
509 field,
510 &field_alias,
511 &enum_field_name,
512 ));
513 }
514 resolver::FieldProcessingType::Struct => {
515 field_conversions.push(generate_struct_field_from_content(
516 field,
517 &field_alias,
518 &field.field_data.data_type,
519 ));
520 }
521 resolver::FieldProcessingType::Trait => {
522 field_conversions.push(generate_trait_field_from_content(
523 field,
524 &field_alias,
525 &field.field_data.data_type,
526 ));
527 }
528 }
529 if field.field_data.is_optional {
530 field_assignments.push(quote! { #field_name: #field_alias });
531 } else {
532 let field_name_str = field.field_data.vim_path.as_str();
533 field_assignments.push(quote! { #field_name: #field_alias.ok_or_else(|| vim_rs::core::error::Error::missing_required_field(#field_name_str.to_string()))? });
534 }
535 idx += 1;
536 }
537
538 quote! {
539 impl core::convert::TryFrom<vim_rs::types::structs::ObjectContent> for #struct_name {
540 type Error = vim_rs::core::error::Error;
541
542 fn try_from(row: vim_rs::types::structs::ObjectContent) -> vim_rs::core::error::Result<Self> {
543 let id = row.obj;
544 let Some(row) = row.prop_set else {
545 return Err(vim_rs::core::error::Error::no_data_found());
546 };
547
548 #(#field_declarations)*
549
550 for prop in row {
551 match prop.name.as_str() {
552 #(#field_conversions)*
553 name => {
554 return Err(vim_rs::core::error::Error::unexpected_property_path(name.to_string()));
555 }
556 }
557 }
558
559 Ok(#struct_name {
560 id,
561 #(#field_assignments),*
562 })
563 }
564 }
565 }
566}
567
568fn generate_try_from_object_update(
569 struct_name: &Ident,
570 fields: &Vec<FieldInfo>,
571) -> proc_macro2::TokenStream {
572 let mut field_declarations: Vec<proc_macro2::TokenStream> = Vec::with_capacity(fields.len());
573 let mut field_conversions: Vec<proc_macro2::TokenStream> = Vec::with_capacity(fields.len());
574 let mut field_assignments: Vec<proc_macro2::TokenStream> = Vec::with_capacity(fields.len());
575 let mut idx = 1;
576 for field in fields {
577 let field_alias: Ident = syn::parse_str(&format!("field{}", idx)).unwrap();
578 let field_name = &field.property_field.name;
579 field_declarations.push(quote! { let mut #field_alias = None; });
580 match field.field_data.processing_type {
581 resolver::FieldProcessingType::Enum(enum_field_name) => {
582 field_conversions.push(generate_enum_field_from_update(
583 field,
584 &field_alias,
585 &enum_field_name,
586 ));
587 }
588 resolver::FieldProcessingType::Struct => {
589 field_conversions.push(generate_struct_field_from_update(
590 field,
591 &field_alias,
592 &field.field_data.data_type,
593 ));
594 }
595 resolver::FieldProcessingType::Trait => {
596 field_conversions.push(generate_trait_field_from_update(
597 field,
598 &field_alias,
599 &field.field_data.data_type,
600 ));
601 }
602 }
603 if field.field_data.is_optional {
604 field_assignments.push(quote! { #field_name: #field_alias });
605 } else {
606 let field_name_str = field.field_data.vim_path.as_str();
607 field_assignments.push(quote! { #field_name: #field_alias.ok_or_else(|| vim_rs::core::error::Error::missing_required_field(#field_name_str.to_string()))? });
608 }
609 idx += 1;
610 }
611
612 quote! {
613 impl core::convert::TryFrom<vim_rs::types::structs::ObjectUpdate> for #struct_name {
614 type Error = vim_rs::core::error::Error;
615
616 fn try_from(row: vim_rs::types::structs::ObjectUpdate) -> vim_rs::core::error::Result<Self> {
617 let id = row.obj;
618 let Some(row) = row.change_set else {
619 return Err(vim_rs::core::error::Error::no_data_found());
620 };
621
622 #(#field_declarations)*
623
624 for prop in row {
625 if matches!(prop.op, vim_rs::types::enums::PropertyChangeOpEnum::Add | vim_rs::types::enums::PropertyChangeOpEnum::Remove | vim_rs::types::enums::PropertyChangeOpEnum::Other_(_)) {
626 continue;
634 }
635 match prop.name.as_str() {
636 #(#field_conversions)*
637 name => {
638 return Err(vim_rs::core::error::Error::unexpected_property_path(name.to_string()));
639 }
640 }
641 }
642
643 Ok(#struct_name {
644 id,
645 #(#field_assignments),*
646 })
647 }
648 }
649 }
650}
651
652fn generate_apply_update(fields: &Vec<FieldInfo>) -> proc_macro2::TokenStream {
653 let mut field_conversions: Vec<proc_macro2::TokenStream> = Vec::with_capacity(fields.len());
654 for field in fields {
655 match field.field_data.processing_type {
656 resolver::FieldProcessingType::Enum(enum_field_name) => {
657 field_conversions.push(generate_enum_field_apply(field, &enum_field_name));
658 }
659 resolver::FieldProcessingType::Struct => {
660 field_conversions.push(generate_struct_field_apply(
661 field,
662 &field.field_data.data_type,
663 ));
664 }
665 resolver::FieldProcessingType::Trait => {
666 field_conversions.push(generate_trait_field_apply(
667 field,
668 &field.field_data.data_type,
669 ));
670 }
671 }
672 }
673
674 quote! {
675 fn apply_update(&mut self, update: vim_rs::types::structs::ObjectUpdate) -> vim_rs::core::pc_helpers::Result<()> {
676 let Some(row) = update.change_set else {
677 return Ok(());
678 };
679
680 for prop in row {
681 if matches!(prop.op, vim_rs::types::enums::PropertyChangeOpEnum::Add | vim_rs::types::enums::PropertyChangeOpEnum::Remove | vim_rs::types::enums::PropertyChangeOpEnum::Other_(_)) {
682 continue;
690 }
691 match prop.name.as_str() {
692 #(#field_conversions)*
693 name => {
694 return Err(vim_rs::core::error::Error::unexpected_property_path(name.to_string()));
695 }
696 }
697 }
698 Ok(())
699 }
700 }
701}
702
703fn generate_enum_field_from_content(
713 field: &FieldInfo,
714 field_alias: &Ident,
715 enum_field_name: &str,
716) -> proc_macro2::TokenStream {
717 let path = &field.field_data.vim_path;
718 let enum_field = Ident::new(enum_field_name, field.property_field.path.span());
719 quote! {
720 #path => {
721 #field_alias = match prop.val {
722 vim_rs::types::vim_any::VimAny::Value(vim_rs::types::boxed_types::ValueElements::#enum_field(vd)) => Some(vd),
723 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))),
724 };
725 }
726 }
727}
728
729fn generate_enum_field_from_update(
730 field: &FieldInfo,
731 field_alias: &Ident,
732 enum_field_name: &str,
733) -> proc_macro2::TokenStream {
734 let path = &field.field_data.vim_path;
735 let enum_field = Ident::new(enum_field_name, field.property_field.path.span());
736 quote! {
737 #path => {
738 #field_alias = match prop.val {
739 Some(vim_rs::types::vim_any::VimAny::Value(vim_rs::types::boxed_types::ValueElements::#enum_field(vd))) => Some(vd),
740 None => continue,
741 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))),
742 };
743 }
744 }
745}
746
747fn generate_enum_field_apply(field: &FieldInfo, enum_field_name: &str) -> proc_macro2::TokenStream {
748 let path = &field.field_data.vim_path;
749 let enum_field = Ident::new(enum_field_name, field.property_field.path.span());
750 let field_name = &field.property_field.name;
751 let none_code;
752 let value_code;
753 if field.field_data.is_optional {
754 none_code = quote! { None };
755 value_code = quote! { Some(vd) };
756 } else {
757 none_code = quote! { return Err(vim_rs::core::error::Error::missing_required_field(#path.to_string())) };
758 value_code = quote! { vd };
759 };
760
761 quote! {
762 #path => {
763 self.#field_name = match prop.val {
764 Some(vim_rs::types::vim_any::VimAny::Value(vim_rs::types::boxed_types::ValueElements::#enum_field(vd))) => #value_code,
765 None => #none_code,
766 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))),
767 };
768 }
769 }
770}
771
772fn generate_struct_field_from_content(
786 field: &FieldInfo,
787 field_alias: &Ident,
788 struct_type: &str,
789) -> proc_macro2::TokenStream {
790 let path = &field.field_data.vim_path;
791
792 quote! {
793 #path => {
794 #field_alias = match prop.val {
795 vim_rs::types::vim_any::VimAny::Object(obj) => {
796 let name = obj.data_type().as_str();
797 match obj.as_any_box().downcast() {
798 Ok(val) => Some(*val),
799 Err(_) => return Err(vim_rs::core::error::Error::invalid_property_type( #path.to_string(), #struct_type.to_string(), name.to_string())),
800 }
801 },
802 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))),
803 };
804 }
805 }
806}
807
808fn generate_struct_field_from_update(
809 field: &FieldInfo,
810 field_alias: &Ident,
811 struct_type: &str,
812) -> proc_macro2::TokenStream {
813 let path = &field.field_data.vim_path;
814 quote! {
815 #path => {
816 #field_alias = match prop.val {
817 Some(vim_rs::types::vim_any::VimAny::Object(obj)) => {
818 let name = obj.data_type().as_str();
819 match obj.as_any_box().downcast() {
820 Ok(val) => Some(*val),
821 Err(_) => return Err(vim_rs::core::error::Error::invalid_property_type( #path.to_string(), #struct_type.to_string(), name.to_string())),
822 }
823 },
824 None => continue,
825 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))),
826 };
827 }
828 }
829}
830
831fn generate_struct_field_apply(field: &FieldInfo, struct_type: &str) -> proc_macro2::TokenStream {
832 let path = &field.field_data.vim_path;
833 let field_name = &field.property_field.name;
834 let none_code;
835 let value_code;
836 if field.field_data.is_optional {
837 none_code = quote! { None };
838 value_code = quote! { Some(*val) };
839 } else {
840 none_code = quote! { return Err(vim_rs::core::error::Error::missing_required_field(#path.to_string())) };
841 value_code = quote! { *val };
842 };
843 quote! {
844 #path => {
845 self.#field_name = match prop.val {
846 Some(vim_rs::types::vim_any::VimAny::Object(obj)) => {
847 let name = obj.data_type().as_str();
848 match obj.as_any_box().downcast() {
849 Ok(val) => #value_code,
850 Err(_) => return Err(vim_rs::core::error::Error::invalid_property_type( #path.to_string(), #struct_type.to_string(), name.to_string())),
851 }
852 },
853 None => #none_code,
854 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))),
855 };
856 }
857 }
858}
859
860fn generate_trait_field_from_content(
874 field: &FieldInfo,
875 field_alias: &Ident,
876 trait_type: &str,
877) -> proc_macro2::TokenStream {
878 let path = &field.field_data.vim_path;
879 quote! {
880 #path => {
881 #field_alias = match prop.val {
882 vim_rs::types::vim_any::VimAny::Object(obj) => {
883 let name = obj.data_type().as_str();
884 match vim_rs::types::convert::CastInto::into_box(obj) {
885 Ok(val) => Some(val),
886 Err(_) => return Err(vim_rs::core::error::Error::invalid_property_type( #path.to_string(), #trait_type.to_string(), name.to_string())),
887 }
888 },
889 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))),
890 };
891 }
892 }
893}
894
895fn generate_trait_field_from_update(
896 field: &FieldInfo,
897 field_alias: &Ident,
898 trait_type: &str,
899) -> proc_macro2::TokenStream {
900 let path = &field.field_data.vim_path;
901 quote! {
902 #path => {
903 #field_alias = match prop.val {
904 Some(vim_rs::types::vim_any::VimAny::Object(obj)) => {
905 let name = obj.data_type().as_str();
906 match vim_rs::types::convert::CastInto::into_box(obj) {
907 Ok(val) => Some(val),
908 Err(_) => return Err(vim_rs::core::error::Error::invalid_property_type( #path.to_string(), #trait_type.to_string(), name.to_string())),
909 }
910 },
911 None => continue,
912 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))),
913 };
914 }
915 }
916}
917
918fn generate_trait_field_apply(field: &FieldInfo, trait_type: &str) -> proc_macro2::TokenStream {
919 let path = &field.field_data.vim_path;
920 let field_name = &field.property_field.name;
921 let none_code;
922 let value_code;
923 if field.field_data.is_optional {
924 none_code = quote! { None };
925 value_code = quote! { Some(val) };
926 } else {
927 none_code = quote! { return Err(vim_rs::core::error::Error::missing_required_field(#path.to_string())) };
928 value_code = quote! { val };
929 };
930
931 quote! {
932 #path => {
933 self.#field_name = match prop.val {
934 Some(vim_rs::types::vim_any::VimAny::Object(obj)) => {
935 let name = obj.data_type().as_str();
936 match vim_rs::types::convert::CastInto::into_box(obj) {
937 Ok(val) => #value_code,
938 Err(_) => return Err(vim_rs::core::error::Error::invalid_property_type( #path.to_string(), #trait_type.to_string(), name.to_string())),
939 }
940 },
941 None => #none_code,
942 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))),
943 };
944 }
945 }
946}