Skip to main content

gproxy_protocol_macros/
lib.rs

1//! Builders for protocol structs with required arguments and optional setters.
2
3use proc_macro::TokenStream;
4use quote::{format_ident, quote};
5use syn::{Data, DeriveInput, Fields, GenericArgument, PathArguments, Type, parse_macro_input};
6
7mod declared_fields;
8
9/// Rebuilds the same wire type using declared fields, recursively dropping
10/// extension bags without reading, cloning, or serializing them. Mark a bag
11/// with `#[declared(extension)]`; `#[serde(flatten)] rest` is also recognized.
12/// Formal JSON fields are preserved. This derive does not change serde behavior.
13/// An alternate protocol crate path can be supplied with `#[declared(crate = "path")]`.
14/// `#[declared(bound = "T: path::DeclaredFields")]` replaces inferred formal-field
15/// bounds, for example for mutually recursive generic types. Existing where
16/// clauses and extension-field Default bounds are retained.
17#[proc_macro_derive(DeclaredFields, attributes(declared))]
18pub fn declared_fields(input: TokenStream) -> TokenStream {
19    declared_fields::expand(parse_macro_input!(input as DeriveInput))
20        .unwrap_or_else(syn::Error::into_compile_error)
21        .into()
22}
23
24/// Generates `Type::builder(required_fields...)` and a `TypeBuilder`.
25///
26/// Required fields are supplied in declaration order. `Option<T>` fields start
27/// as `None` and have setters accepting `T`; `rest` starts empty and has a
28/// setter accepting its declared type. `build()` returns the completed value.
29/// Mark a nullable but required field with `#[wire(required)]` to require an
30/// explicit `Option<T>` argument instead of defaulting it in the builder.
31#[proc_macro_derive(WireBuilder, attributes(wire))]
32pub fn wire_builder(input: TokenStream) -> TokenStream {
33    let input = parse_macro_input!(input as DeriveInput);
34    expand(input)
35        .unwrap_or_else(syn::Error::into_compile_error)
36        .into()
37}
38
39fn expand(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
40    let Data::Struct(data) = &input.data else {
41        return Err(syn::Error::new_spanned(
42            &input,
43            "WireBuilder requires a struct",
44        ));
45    };
46    let Fields::Named(fields) = &data.fields else {
47        return Err(syn::Error::new_spanned(
48            &input,
49            "WireBuilder requires named fields",
50        ));
51    };
52    let name = &input.ident;
53    let builder = format_ident!("{}Builder", name);
54    let visibility = &input.vis;
55    let generics = &input.generics;
56    let (impl_generics, type_generics, where_clause) = generics.split_for_impl();
57    let mut arguments = Vec::new();
58    let mut initializers = Vec::new();
59    let mut setters = Vec::new();
60
61    for field in &fields.named {
62        let ident = field.ident.as_ref().expect("named field");
63        let ty = &field.ty;
64        let mut required = false;
65        for attr in field
66            .attrs
67            .iter()
68            .filter(|attr| attr.path().is_ident("wire"))
69        {
70            attr.parse_nested_meta(|meta| {
71                if meta.path.is_ident("required") {
72                    required = true;
73                    Ok(())
74                } else {
75                    Err(meta.error("expected `required`"))
76                }
77            })?;
78        }
79        if required {
80            arguments.push(quote!(#ident: #ty));
81            initializers.push(quote!(#ident));
82            continue;
83        }
84        if let Some(inner) = option_inner(ty) {
85            initializers.push(quote!(#ident: ::core::option::Option::None));
86            setters.push(quote! {
87                #[must_use]
88                pub fn #ident(mut self, value: impl ::core::convert::Into<#inner>) -> Self {
89                    self.0.#ident = ::core::option::Option::Some(value.into());
90                    self
91                }
92            });
93        } else if ident == "rest" {
94            initializers.push(quote!(#ident: ::core::default::Default::default()));
95            setters.push(quote! {
96                #[must_use]
97                pub fn #ident(mut self, value: #ty) -> Self {
98                    self.0.#ident = value;
99                    self
100                }
101            });
102        } else {
103            arguments.push(quote!(#ident: #ty));
104            initializers.push(quote!(#ident));
105        }
106    }
107
108    Ok(quote! {
109        impl #impl_generics #name #type_generics #where_clause {
110            /// Start a builder by supplying the required fields in declaration order.
111            #[allow(clippy::too_many_arguments)]
112            pub fn builder(#(#arguments),*) -> #builder #type_generics {
113                #builder(Self { #(#initializers),* })
114            }
115        }
116
117        #[doc = concat!("Builder for [`", stringify!(#name), "`].")]
118        #[must_use]
119        #visibility struct #builder #generics (#name #type_generics) #where_clause;
120
121        impl #impl_generics #builder #type_generics #where_clause {
122            #(#setters)*
123
124            /// Return the completed wire value.
125            pub fn build(self) -> #name #type_generics {
126                self.0
127            }
128        }
129    })
130}
131
132fn option_inner(ty: &Type) -> Option<&Type> {
133    let Type::Path(path) = ty else { return None };
134    let segment = path.path.segments.last()?;
135    if segment.ident != "Option" {
136        return None;
137    }
138    let PathArguments::AngleBracketed(arguments) = &segment.arguments else {
139        return None;
140    };
141    match arguments.args.first()? {
142        GenericArgument::Type(inner) => Some(inner),
143        _ => None,
144    }
145}