zod_derive/
lib.rs

1#![deny(unsafe_code)]
2
3#[cfg(feature = "rpc")]
4mod rpc;
5
6#[cfg(feature = "rpc")]
7use quote::quote;
8
9mod args;
10mod docs;
11mod impl_enum;
12mod impl_inventory;
13mod impl_namespace;
14mod impl_struct;
15
16use darling::{ast::Data, FromDeriveInput};
17use docs::RustDocs;
18use proc_macro::TokenStream;
19use proc_macro_error::proc_macro_error;
20use quote::{format_ident, quote_spanned};
21use serde_derive_internals::Derive;
22use syn::{Ident, Path};
23
24use proc_macro2::Span;
25
26fn get_crate_name() -> String {
27    let found_crate = proc_macro_crate::crate_name("zod").unwrap_or_else(|err| {
28        proc_macro_error::abort_call_site!("Error: {}", err);
29    });
30
31    match found_crate {
32        proc_macro_crate::FoundCrate::Itself => String::from("zod"),
33        proc_macro_crate::FoundCrate::Name(name) => name,
34    }
35}
36
37pub(crate) fn get_zod() -> Path {
38    let name = get_crate_name();
39    let ident = Ident::new(&name, Span::call_site());
40    syn::parse_quote!(::#ident)
41}
42
43pub(crate) fn get_zod_spanned(span: Span) -> Path {
44    let name = get_crate_name();
45    let ident = Ident::new(&name, Span::call_site());
46    syn::parse_quote_spanned!(span => ::#ident)
47}
48
49pub(crate) fn get_private() -> Path {
50    let zod = get_zod();
51    syn::parse_quote!(#zod::__private)
52}
53
54pub(crate) fn get_private_spanned(span: Span) -> Path {
55    let zod = get_zod_spanned(span);
56    syn::parse_quote_spanned!(span => #zod::__private)
57}
58
59#[proc_macro_error]
60#[proc_macro_derive(Zod, attributes(zod))]
61pub fn zod(input: TokenStream) -> TokenStream {
62    let parsed = match syn::parse(input) {
63        Ok(parsed) => parsed,
64        Err(err) => {
65            return err.into_compile_error().into();
66        }
67    };
68
69    let cx = serde_derive_internals::Ctxt::new();
70
71    let container =
72        serde_derive_internals::ast::Container::from_ast(&cx, &parsed, Derive::Deserialize)
73            .unwrap();
74
75    cx.check().unwrap();
76
77    let docs = match RustDocs::from_attrs(&parsed.attrs) {
78        Ok(docs) => docs,
79        Err(err) => {
80            return err.into_compile_error().into();
81        }
82    };
83
84    let input = match args::Input::from_derive_input(&parsed) {
85        Ok(input) => input,
86        Err(err) => {
87            return err.write_errors().into();
88        }
89    };
90
91    let expanded = match input.data.clone() {
92        Data::Enum(e) => impl_enum::expand(input, &e, container, docs),
93        Data::Struct(e) => impl_struct::expand(input, e, container, docs),
94    };
95    expanded.into()
96}
97
98#[proc_macro_error]
99#[proc_macro_derive(Namespace, attributes(namespace))]
100pub fn derive_namespace(input: TokenStream) -> TokenStream {
101    let parsed = match syn::parse(input) {
102        Ok(parsed) => parsed,
103        Err(err) => {
104            return err.into_compile_error().into();
105        }
106    };
107    let input = match args::NamespaceInput::from_derive_input(&parsed) {
108        Ok(input) => input,
109        Err(err) => {
110            return err.write_errors().into();
111        }
112    };
113
114    let docs = match RustDocs::from_attrs(&parsed.attrs) {
115        Ok(docs) => docs,
116        Err(err) => {
117            return err.into_compile_error().into();
118        }
119    };
120
121    impl_namespace::expand(input, docs).into()
122}
123
124#[cfg(feature = "rpc")]
125#[proc_macro_error]
126#[proc_macro_derive(Backend, attributes(rpc))]
127pub fn backend(input: TokenStream) -> TokenStream {
128    let parsed = match syn::parse(input) {
129        Ok(parsed) => parsed,
130        Err(err) => {
131            return err.into_compile_error().into();
132        }
133    };
134
135    let input = match rpc::args::BackendInput::from_derive_input(&parsed) {
136        Ok(input) => input,
137        Err(err) => {
138            return err.write_errors().into();
139        }
140    };
141
142    let expanded = match input.data.clone() {
143        Data::Enum(_) => unreachable!(),
144        Data::Struct(e) => rpc::backend_impl::expand(input, e),
145    };
146    expanded.into()
147}
148
149#[cfg(feature = "rpc")]
150#[proc_macro_error]
151#[proc_macro_attribute]
152pub fn namespace(_attr: TokenStream, input: TokenStream) -> TokenStream {
153    let orig = proc_macro2::TokenStream::from(input.clone());
154
155    let ast = syn::parse_macro_input!(input as syn::ItemImpl);
156    let extra = rpc::impl_rpc::expand(rpc::args::RpcInput::from_ast(ast));
157
158    let output = quote! {
159        #orig
160
161        #extra
162    };
163
164    output.into()
165}
166
167fn format_ident_for_registration(p: &syn::Path) -> syn::Path {
168    let mut segments = p.segments.clone();
169    let last = segments.last_mut().unwrap();
170    last.ident = format_ident!("__ZodRegister__{}", last.ident);
171
172    syn::Path {
173        leading_colon: p.leading_colon,
174        segments,
175    }
176}
177
178/// Prevent duplicate interfaces
179fn expand_type_registration(ident: &Ident, ns_path: &Path) -> proc_macro2::TokenStream {
180    let register_path = format_ident_for_registration(ns_path);
181
182    quote_spanned! {ident.span() =>
183        impl #register_path {
184            #[allow(dead_code)]
185            #[allow(non_upper_case_globals)]
186            const #ident: () = ();
187        }
188    }
189}