Skip to main content

packr_derive/
lib.rs

1//! Derive macros for pack-abi Value conversion.
2//!
3//! This crate provides `#[derive(GraphValue)]` which generates implementations
4//! of `From<T> for Value` and `TryFrom<Value> for T`.
5//!
6//! # Example
7//!
8//! ```ignore
9//! use packr_abi::{GraphValue, Value};
10//!
11//! #[derive(GraphValue)]
12//! struct Point {
13//!     x: i64,
14//!     y: i64,
15//! }
16//!
17//! let point = Point { x: 10, y: 20 };
18//! let value: Value = point.into();
19//! let back: Point = value.try_into().unwrap();
20//! ```
21//!
22//! # Crate Path
23//!
24//! By default, the macro expects `packr_abi` to be in scope. For `no_std` guests
25//! using `packr_guest`, specify the crate path:
26//!
27//! ```ignore
28//! use packr_guest::GraphValue;
29//!
30//! #[derive(GraphValue)]
31//! #[graph(crate = "packr_guest::composite_abi")]
32//! struct MyState {
33//!     count: i32,
34//! }
35//! ```
36
37use proc_macro::TokenStream;
38use quote::{format_ident, quote};
39use syn::{parse_macro_input, Attribute, Data, DeriveInput, Fields, Meta};
40
41/// Extract the crate path from `#[graph(crate = "...")]` attribute.
42/// Defaults to `packr_abi` if not specified.
43fn get_crate_path(attrs: &[Attribute]) -> proc_macro2::TokenStream {
44    for attr in attrs {
45        if attr.path().is_ident("graph") {
46            if let Meta::List(list) = &attr.meta {
47                let tokens = list.tokens.to_string();
48                // Parse crate = "..."
49                if let Some(rest) = tokens.strip_prefix("crate") {
50                    let rest = rest.trim();
51                    if let Some(rest) = rest.strip_prefix('=') {
52                        let rest = rest.trim();
53                        if rest.starts_with('"') && rest.ends_with('"') {
54                            let path_str = &rest[1..rest.len() - 1];
55                            // Convert string path to token stream
56                            let path: syn::Path =
57                                syn::parse_str(path_str).expect("Invalid crate path");
58                            return quote! { #path };
59                        }
60                    }
61                }
62            }
63        }
64    }
65    // Default to packr_abi
66    quote! { packr_abi }
67}
68
69/// Derive macro for converting between Rust types and `Value`.
70///
71/// # Structs
72///
73/// Structs are converted to `Value::Record` with field names as keys.
74///
75/// ```ignore
76/// #[derive(GraphValue)]
77/// struct Person {
78///     name: String,
79///     age: i64,
80/// }
81/// ```
82///
83/// # Enums
84///
85/// Enums are converted to `Value::Variant` with the variant index as tag.
86///
87/// ```ignore
88/// #[derive(GraphValue)]
89/// enum Shape {
90///     Circle(f64),           // tag 0, payload = radius
91///     Rectangle(f64, f64),   // tag 1, payload = tuple(width, height)
92///     Point,                 // tag 2, no payload
93/// }
94/// ```
95///
96/// # Attributes
97///
98/// - `#[graph(crate = "path")]` - Specify the crate path (default: `packr_abi`)
99/// - `#[graph(rename = "name")]` - Use a different name for field/variant
100/// - `#[graph(tag = N)]` - Use explicit tag number for variant
101#[proc_macro_derive(GraphValue, attributes(graph))]
102pub fn derive_graph_value(input: TokenStream) -> TokenStream {
103    let input = parse_macro_input!(input as DeriveInput);
104    let crate_path = get_crate_path(&input.attrs);
105
106    let expanded = match &input.data {
107        Data::Struct(data) => derive_struct(&input, data, &crate_path),
108        Data::Enum(data) => derive_enum(&input, data, &crate_path),
109        Data::Union(_) => {
110            return syn::Error::new_spanned(&input, "GraphValue cannot be derived for unions")
111                .to_compile_error()
112                .into();
113        }
114    };
115
116    expanded.into()
117}
118
119fn derive_struct(
120    input: &DeriveInput,
121    data: &syn::DataStruct,
122    krate: &proc_macro2::TokenStream,
123) -> proc_macro2::TokenStream {
124    let name = &input.ident;
125    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
126
127    match &data.fields {
128        Fields::Named(fields) => {
129            // Generate TryFrom<Value> for T
130            let field_from_value: Vec<_> = fields.named.iter().map(|f| {
131                let field_name = f.ident.as_ref().unwrap();
132                let field_name_str = get_rename(&f.attrs).unwrap_or_else(|| field_name.to_string());
133                let field_type = &f.ty;
134                quote! {
135                    #field_name: {
136                        let field_value = fields.iter()
137                            .find(|(name, _)| name == #field_name_str)
138                            .map(|(_, v)| v.clone())
139                            .ok_or_else(|| #krate::ConversionError::MissingField(
140                                #krate::__private::String::from(#field_name_str)
141                            ))?;
142                        <#field_type as #krate::__private::TryFrom<#krate::Value>>::try_from(field_value)
143                            .map_err(|e| #krate::ConversionError::FieldError(
144                                #krate::__private::String::from(#field_name_str),
145                                #krate::__private::Box::new(e)
146                            ))?
147                    }
148                }
149            }).collect();
150
151            let field_count = fields.named.len();
152
153            // Generate field accessors for From impl
154            let field_accessors: Vec<_> = fields
155                .named
156                .iter()
157                .map(|f| {
158                    let field_name = f.ident.as_ref().unwrap();
159                    let field_name_str =
160                        get_rename(&f.attrs).unwrap_or_else(|| field_name.to_string());
161                    quote! {
162                        (
163                            #krate::__private::String::from(#field_name_str),
164                            #krate::Value::from(value.#field_name)
165                        )
166                    }
167                })
168                .collect();
169
170            let type_name_str = name.to_string();
171
172            quote! {
173                impl #impl_generics #krate::__private::From<#name #ty_generics> for #krate::Value #where_clause {
174                    fn from(value: #name #ty_generics) -> #krate::Value {
175                        #krate::Value::Record {
176                            type_name: #krate::__private::String::from(#type_name_str),
177                            fields: #krate::__private::vec![
178                                #(#field_accessors),*
179                            ],
180                        }
181                    }
182                }
183
184                impl #impl_generics #krate::__private::TryFrom<#krate::Value> for #name #ty_generics #where_clause {
185                    type Error = #krate::ConversionError;
186
187                    fn try_from(value: #krate::Value) -> #krate::__private::Result<Self, Self::Error> {
188                        match value {
189                            #krate::Value::Record { fields, .. } => {
190                                if fields.len() != #field_count {
191                                    return #krate::__private::Err(#krate::ConversionError::WrongFieldCount {
192                                        expected: #field_count,
193                                        got: fields.len(),
194                                    });
195                                }
196                                #krate::__private::Ok(Self {
197                                    #(#field_from_value),*
198                                })
199                            }
200                            other => #krate::__private::Err(#krate::ConversionError::ExpectedRecord(
201                                #krate::__private::format!("{:?}", other)
202                            )),
203                        }
204                    }
205                }
206            }
207        }
208        Fields::Unnamed(fields) => {
209            // Tuple struct -> Value::Tuple
210            let field_indices: Vec<_> = (0..fields.unnamed.len()).map(syn::Index::from).collect();
211
212            let field_from_value: Vec<_> = fields.unnamed.iter().enumerate().map(|(i, f)| {
213                let field_type = &f.ty;
214                quote! {
215                    <#field_type as #krate::__private::TryFrom<#krate::Value>>::try_from(
216                        fields.get(#i).cloned().ok_or_else(|| #krate::ConversionError::MissingIndex(#i))?
217                    ).map_err(|e| #krate::ConversionError::IndexError(#i, #krate::__private::Box::new(e)))?
218                }
219            }).collect();
220
221            let field_count = fields.unnamed.len();
222
223            quote! {
224                impl #impl_generics #krate::__private::From<#name #ty_generics> for #krate::Value #where_clause {
225                    fn from(value: #name #ty_generics) -> #krate::Value {
226                        #krate::Value::Tuple(#krate::__private::vec![
227                            #(#krate::Value::from(value.#field_indices)),*
228                        ])
229                    }
230                }
231
232                impl #impl_generics #krate::__private::TryFrom<#krate::Value> for #name #ty_generics #where_clause {
233                    type Error = #krate::ConversionError;
234
235                    fn try_from(value: #krate::Value) -> #krate::__private::Result<Self, Self::Error> {
236                        match value {
237                            #krate::Value::Tuple(fields) => {
238                                if fields.len() != #field_count {
239                                    return #krate::__private::Err(#krate::ConversionError::WrongFieldCount {
240                                        expected: #field_count,
241                                        got: fields.len(),
242                                    });
243                                }
244                                #krate::__private::Ok(Self(
245                                    #(#field_from_value),*
246                                ))
247                            }
248                            other => #krate::__private::Err(#krate::ConversionError::ExpectedTuple(
249                                #krate::__private::format!("{:?}", other)
250                            )),
251                        }
252                    }
253                }
254            }
255        }
256        Fields::Unit => {
257            // Unit struct -> Value::Tuple([])
258            quote! {
259                impl #impl_generics #krate::__private::From<#name #ty_generics> for #krate::Value #where_clause {
260                    fn from(_: #name #ty_generics) -> #krate::Value {
261                        #krate::Value::Tuple(#krate::__private::vec![])
262                    }
263                }
264
265                impl #impl_generics #krate::__private::TryFrom<#krate::Value> for #name #ty_generics #where_clause {
266                    type Error = #krate::ConversionError;
267
268                    fn try_from(value: #krate::Value) -> #krate::__private::Result<Self, Self::Error> {
269                        match value {
270                            #krate::Value::Tuple(fields) if fields.is_empty() => {
271                                #krate::__private::Ok(Self)
272                            }
273                            #krate::Value::Tuple(fields) => {
274                                #krate::__private::Err(#krate::ConversionError::WrongFieldCount {
275                                    expected: 0,
276                                    got: fields.len(),
277                                })
278                            }
279                            other => #krate::__private::Err(#krate::ConversionError::ExpectedTuple(
280                                #krate::__private::format!("{:?}", other)
281                            )),
282                        }
283                    }
284                }
285            }
286        }
287    }
288}
289
290fn derive_enum(
291    input: &DeriveInput,
292    data: &syn::DataEnum,
293    krate: &proc_macro2::TokenStream,
294) -> proc_macro2::TokenStream {
295    let name = &input.ident;
296    let type_name_str = name.to_string();
297    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
298
299    // Generate match arms for From<T> for Value
300    let to_value_arms: Vec<_> = data
301        .variants
302        .iter()
303        .enumerate()
304        .map(|(default_tag, variant)| {
305            let variant_name = &variant.ident;
306            let case_name_str = variant_name.to_string();
307            let tag = get_tag(&variant.attrs).unwrap_or(default_tag);
308
309            match &variant.fields {
310                Fields::Named(fields) => {
311                    let field_names: Vec<_> = fields
312                        .named
313                        .iter()
314                        .map(|f| f.ident.as_ref().unwrap())
315                        .collect();
316                    // For named fields, we wrap in a Record as the single payload element
317                    let field_to_value: Vec<_> = fields
318                        .named
319                        .iter()
320                        .map(|f| {
321                            let field_name = f.ident.as_ref().unwrap();
322                            let field_name_str =
323                                get_rename(&f.attrs).unwrap_or_else(|| field_name.to_string());
324                            quote! {
325                                (
326                                    #krate::__private::String::from(#field_name_str),
327                                    #krate::Value::from(#field_name)
328                                )
329                            }
330                        })
331                        .collect();
332
333                    quote! {
334                        #name::#variant_name { #(#field_names),* } => {
335                            #krate::Value::Variant {
336                                type_name: #krate::__private::String::from(#type_name_str),
337                                case_name: #krate::__private::String::from(#case_name_str),
338                                tag: #tag,
339                                payload: #krate::__private::vec![
340                                    #krate::Value::Record {
341                                        type_name: #krate::__private::String::from(#case_name_str),
342                                        fields: #krate::__private::vec![#(#field_to_value),*],
343                                    }
344                                ],
345                            }
346                        }
347                    }
348                }
349                Fields::Unnamed(fields) => {
350                    let field_names: Vec<_> = (0..fields.unnamed.len())
351                        .map(|i| format_ident!("f{}", i))
352                        .collect();
353
354                    // Payload is a vec of all the field values
355                    quote! {
356                        #name::#variant_name(#(#field_names),*) => {
357                            #krate::Value::Variant {
358                                type_name: #krate::__private::String::from(#type_name_str),
359                                case_name: #krate::__private::String::from(#case_name_str),
360                                tag: #tag,
361                                payload: #krate::__private::vec![
362                                    #(#krate::Value::from(#field_names)),*
363                                ],
364                            }
365                        }
366                    }
367                }
368                Fields::Unit => {
369                    quote! {
370                        #name::#variant_name => {
371                            #krate::Value::Variant {
372                                type_name: #krate::__private::String::from(#type_name_str),
373                                case_name: #krate::__private::String::from(#case_name_str),
374                                tag: #tag,
375                                payload: #krate::__private::vec![],
376                            }
377                        }
378                    }
379                }
380            }
381        })
382        .collect();
383
384    // Generate match arms for TryFrom<Value> for T
385    let from_value_arms: Vec<_> = data.variants.iter().enumerate().map(|(default_tag, variant)| {
386        let variant_name = &variant.ident;
387        let tag = get_tag(&variant.attrs).unwrap_or(default_tag);
388
389        match &variant.fields {
390            Fields::Named(fields) => {
391                let field_from_value: Vec<_> = fields.named.iter().map(|f| {
392                    let field_name = f.ident.as_ref().unwrap();
393                    let field_name_str = get_rename(&f.attrs).unwrap_or_else(|| field_name.to_string());
394                    let field_type = &f.ty;
395                    quote! {
396                        #field_name: {
397                            let field_value = record_fields.iter()
398                                .find(|(name, _)| name == #field_name_str)
399                                .map(|(_, v)| v.clone())
400                                .ok_or_else(|| #krate::ConversionError::MissingField(
401                                    #krate::__private::String::from(#field_name_str)
402                                ))?;
403                            <#field_type as #krate::__private::TryFrom<#krate::Value>>::try_from(field_value)
404                                .map_err(|e| #krate::ConversionError::FieldError(
405                                    #krate::__private::String::from(#field_name_str),
406                                    #krate::__private::Box::new(e)
407                                ))?
408                        }
409                    }
410                }).collect();
411
412                quote! {
413                    #tag => {
414                        // For named fields, payload should contain a single Record
415                        if payload.len() != 1 {
416                            return #krate::__private::Err(#krate::ConversionError::WrongFieldCount {
417                                expected: 1,
418                                got: payload.len(),
419                            });
420                        }
421                        match &payload[0] {
422                            #krate::Value::Record { fields: record_fields, .. } => {
423                                #krate::__private::Ok(#name::#variant_name {
424                                    #(#field_from_value),*
425                                })
426                            }
427                            other => #krate::__private::Err(#krate::ConversionError::ExpectedRecord(
428                                #krate::__private::format!("{:?}", other)
429                            )),
430                        }
431                    }
432                }
433            }
434            Fields::Unnamed(fields) => {
435                let field_count = fields.unnamed.len();
436                let field_conversions: Vec<_> = fields.unnamed.iter().enumerate().map(|(i, f)| {
437                    let field_type = &f.ty;
438                    quote! {
439                        <#field_type as #krate::__private::TryFrom<#krate::Value>>::try_from(
440                            payload.get(#i).cloned().ok_or_else(|| #krate::ConversionError::MissingIndex(#i))?
441                        ).map_err(|e| #krate::ConversionError::IndexError(#i, #krate::__private::Box::new(e)))?
442                    }
443                }).collect();
444
445                quote! {
446                    #tag => {
447                        if payload.len() != #field_count {
448                            return #krate::__private::Err(#krate::ConversionError::WrongFieldCount {
449                                expected: #field_count,
450                                got: payload.len(),
451                            });
452                        }
453                        #krate::__private::Ok(#name::#variant_name(
454                            #(#field_conversions),*
455                        ))
456                    }
457                }
458            }
459            Fields::Unit => {
460                quote! {
461                    #tag => {
462                        if !payload.is_empty() {
463                            return #krate::__private::Err(#krate::ConversionError::UnexpectedPayload);
464                        }
465                        #krate::__private::Ok(#name::#variant_name)
466                    }
467                }
468            }
469        }
470    }).collect();
471
472    let variant_count = data.variants.len();
473
474    quote! {
475        impl #impl_generics #krate::__private::From<#name #ty_generics> for #krate::Value #where_clause {
476            fn from(value: #name #ty_generics) -> #krate::Value {
477                match value {
478                    #(#to_value_arms),*
479                }
480            }
481        }
482
483        impl #impl_generics #krate::__private::TryFrom<#krate::Value> for #name #ty_generics #where_clause {
484            type Error = #krate::ConversionError;
485
486            fn try_from(value: #krate::Value) -> #krate::__private::Result<Self, Self::Error> {
487                match value {
488                    #krate::Value::Variant { tag, payload, .. } => {
489                        match tag {
490                            #(#from_value_arms),*
491                            other => #krate::__private::Err(#krate::ConversionError::UnknownTag {
492                                tag: other,
493                                max: #variant_count,
494                            }),
495                        }
496                    }
497                    other => #krate::__private::Err(#krate::ConversionError::ExpectedVariant(
498                        #krate::__private::format!("{:?}", other)
499                    )),
500                }
501            }
502        }
503    }
504}
505
506/// Extract `#[graph(rename = "...")]` attribute
507fn get_rename(attrs: &[Attribute]) -> Option<String> {
508    for attr in attrs {
509        if attr.path().is_ident("graph") {
510            if let Meta::List(list) = &attr.meta {
511                let tokens = list.tokens.to_string();
512                // Parse rename = "..."
513                if let Some(rest) = tokens.strip_prefix("rename") {
514                    let rest = rest.trim();
515                    if let Some(rest) = rest.strip_prefix('=') {
516                        let rest = rest.trim();
517                        if rest.starts_with('"') && rest.ends_with('"') {
518                            return Some(rest[1..rest.len() - 1].to_string());
519                        }
520                    }
521                }
522            }
523        }
524    }
525    None
526}
527
528/// Extract `#[graph(tag = N)]` attribute
529fn get_tag(attrs: &[Attribute]) -> Option<usize> {
530    for attr in attrs {
531        if attr.path().is_ident("graph") {
532            if let Meta::List(list) = &attr.meta {
533                let tokens = list.tokens.to_string();
534                // Parse tag = N
535                if let Some(rest) = tokens.strip_prefix("tag") {
536                    let rest = rest.trim();
537                    if let Some(rest) = rest.strip_prefix('=') {
538                        let rest = rest.trim();
539                        return rest.parse().ok();
540                    }
541                }
542            }
543        }
544    }
545    None
546}