vmnet_derive/
lib.rs

1use darling::FromVariant;
2use proc_macro::{self, TokenStream};
3use proc_macro2::Ident;
4use quote::{format_ident, quote};
5use syn::spanned::Spanned;
6use syn::Fields::Unnamed;
7use syn::{parse_macro_input, DeriveInput, Error};
8
9#[derive(FromVariant)]
10#[darling(attributes(vmnet))]
11struct Opts {
12    ffi: Option<String>,
13}
14
15#[proc_macro_derive(Vmnet, attributes(vmnet))]
16pub fn derive(input: TokenStream) -> TokenStream {
17    let input: DeriveInput = parse_macro_input!(input);
18
19    let input_enum = match input.data {
20        syn::Data::Enum(input_enum) => input_enum,
21        _ => {
22            return Error::new(input.span(), "only enumerations are supported")
23                .to_compile_error()
24                .into()
25        }
26    };
27
28    // Normally would equate to "Parameter"
29    let input_enum_name = input.ident.clone();
30    // Normally would equate to "ParameterKind"
31    let kind_enum_name = format_ident!("{}Kind", input.ident);
32
33    let mut kinds: Vec<Ident> = Vec::new();
34    let mut kind_enum_to_vmnet_ffi_key_arms = Vec::new();
35    let mut kind_enum_to_input_enum_arms = Vec::new();
36    let mut input_enum_to_xpc_data_arms = Vec::new();
37    let mut input_enum_to_kind_enum_arms = Vec::new();
38
39    for input_variant in input_enum.variants {
40        let unnamed = match input_variant.fields {
41            Unnamed(ref unnamed) => &unnamed.unnamed,
42            _ => {
43                return Error::new(input_variant.span(), "only unnamed fields are supported")
44                    .to_compile_error()
45                    .into()
46            }
47        };
48
49        if unnamed.len() > 1 {
50            return Error::new(
51                input_variant.span(),
52                "there should be exactly one unnamed field",
53            )
54            .to_compile_error()
55            .into();
56        }
57
58        let field = unnamed.first().unwrap();
59
60        let typ = match &field.ty {
61            syn::Type::Path(syn::TypePath { path, .. }) if path.is_ident("String") => {
62                format_ident!("String")
63            }
64            syn::Type::Path(syn::TypePath { path, .. }) if path.is_ident("u64") => {
65                format_ident!("Uint64")
66            }
67            syn::Type::Path(syn::TypePath { path, .. }) if path.is_ident("bool") => {
68                format_ident!("Bool")
69            }
70            syn::Type::Path(syn::TypePath { path, .. }) if path.is_ident("Uuid") => {
71                format_ident!("Uuid")
72            }
73            _ => {
74                return Error::new(
75                    input_variant.span(),
76                    "unsupported unnamed field type (expected String, u64, bool or Uuid)",
77                )
78                .to_compile_error()
79                .into();
80            }
81        };
82
83        let opts: Opts = Opts::from_variant(&input_variant).unwrap();
84
85        let vmnet_ffi_key = match opts.ffi {
86            Some(ffi) => format_ident!("{}", ffi),
87            None => {
88                return Error::new(
89                    input_variant.span(),
90                    "missing #[vmnet(ffi = \"...\" attribute",
91                )
92                .to_compile_error()
93                .into()
94            }
95        };
96
97        let variant_name = format_ident!("{}", input_variant.ident);
98        kinds.push(variant_name.clone());
99
100        input_enum_to_kind_enum_arms.push(quote! {
101            #input_enum_name::#variant_name(_) => { #kind_enum_name::#variant_name }
102        });
103
104        input_enum_to_xpc_data_arms.push(quote! {
105            #input_enum_name::#variant_name(val) => { XpcData::from(val) }
106        });
107
108        kind_enum_to_vmnet_ffi_key_arms.push(quote! {
109            #kind_enum_name::#variant_name => #vmnet_ffi_key
110        });
111
112        kind_enum_to_input_enum_arms.push(quote! {
113            (#kind_enum_name::#variant_name, XpcData::#typ(val)) => { Some(#input_enum_name::#variant_name(val)) }
114        });
115    }
116
117    let output = quote! {
118        /// Parameter key (kind) useful for retrieving a specific parameter from the [`Parameters`](Parameters) dictionary.
119        #[derive(Debug, Hash, Eq, PartialEq, Sequence)]
120        pub enum #kind_enum_name {
121            #(#kinds),*
122        }
123
124        impl From<&#input_enum_name> for #kind_enum_name {
125            fn from(val: &#input_enum_name) -> Self {
126                match val {
127                    #(#input_enum_to_kind_enum_arms),*
128                }
129            }
130        }
131
132        impl From<#input_enum_name> for XpcData {
133            fn from(val: #input_enum_name) -> Self {
134                match val {
135                    #(#input_enum_to_xpc_data_arms),*
136                }
137            }
138        }
139
140        impl #kind_enum_name {
141            pub fn vmnet_ffi_key(&self) -> *const c_char {
142                unsafe {
143                    match self {
144                        #(#kind_enum_to_vmnet_ffi_key_arms),*
145                    }
146                }
147            }
148
149            pub fn vmnet_key(&self) -> String {
150                unsafe { CStr::from_ptr(self.vmnet_ffi_key()).to_string_lossy().to_string() }
151            }
152
153            fn parse(&self, value: XpcData) -> Option<#input_enum_name> {
154                match (self, value) {
155                    #(#kind_enum_to_input_enum_arms)*
156                    _ => { None }
157                }
158            }
159        }
160    };
161
162    output.into()
163}