tryparse_derive/
lib.rs

1//! Derive macros for tryparse
2//!
3//! This crate provides the `LlmDeserialize` derive macro for automatically
4//! generating fuzzy deserialization logic from Rust types.
5
6mod attributes;
7mod enum_gen;
8mod struct_gen;
9mod union_gen;
10
11use proc_macro::TokenStream;
12use quote::quote;
13use syn::{parse_macro_input, Data, DeriveInput};
14
15use attributes::has_union_attribute;
16use enum_gen::generate_enum_deserialize;
17use struct_gen::generate_struct_deserialize;
18use union_gen::generate_union_deserialize;
19
20/// Derives the `LlmDeserialize` trait for structs and enums.
21///
22/// This macro generates a custom deserialization implementation using BAML's
23/// algorithms for fuzzy field matching and type coercion.
24///
25/// # Features
26///
27/// - **Fuzzy field matching**: Handles different naming conventions (userName ↔ user_name)
28/// - **Fuzzy enum matching**: Case-insensitive, substring, and edit-distance matching for variants
29/// - **Union types**: Score-based variant selection with `#[llm(union)]`
30/// - **Optional fields**: Automatic handling of `Option<T>` fields
31/// - **Transformation tracking**: Records all coercions applied during parsing
32///
33/// # Example
34///
35/// ```ignore
36/// use tryparse::deserializer::LlmDeserialize;
37///
38/// #[derive(LlmDeserialize)]
39/// struct User {
40///     name: String,
41///     age: u32,
42///     email: Option<String>, // Optional field
43/// }
44///
45/// // Handles messy input like:
46/// // {"userName": "Alice", "age": "30"}  // camelCase + string number
47/// ```
48///
49/// # Union Types
50///
51/// ```ignore
52/// #[derive(LlmDeserialize)]
53/// #[llm(union)]
54/// enum Value {
55///     Number(i64),
56///     Text(String),
57/// }
58///
59/// // Automatically picks the best matching variant
60/// ```
61#[proc_macro_derive(LlmDeserialize, attributes(llm))]
62pub fn derive_llm_deserialize(input: TokenStream) -> TokenStream {
63    let input = parse_macro_input!(input as DeriveInput);
64
65    let name = &input.ident;
66    let generics = &input.generics;
67    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
68
69    match &input.data {
70        Data::Struct(data_struct) => {
71            let deserialize_impl = generate_struct_deserialize(name, data_struct);
72
73            let expanded = quote! {
74                impl #impl_generics ::tryparse::deserializer::LlmDeserialize for #name #ty_generics #where_clause {
75                    #deserialize_impl
76                }
77            };
78
79            TokenStream::from(expanded)
80        }
81        Data::Enum(data_enum) => {
82            // Check if this is a union enum (has #[llm(union)] attribute)
83            let is_union = has_union_attribute(&input.attrs);
84
85            let deserialize_impl = if is_union {
86                generate_union_deserialize(name, data_enum, &input.attrs)
87            } else {
88                generate_enum_deserialize(name, data_enum, &input.attrs)
89            };
90
91            let expanded = quote! {
92                impl #impl_generics ::tryparse::deserializer::LlmDeserialize for #name #ty_generics #where_clause {
93                    #deserialize_impl
94                }
95            };
96
97            TokenStream::from(expanded)
98        }
99        Data::Union(_) => {
100            syn::Error::new_spanned(input, "LlmDeserialize cannot be derived for unions")
101                .to_compile_error()
102                .into()
103        }
104    }
105}