irpc_schema_derive/
lib.rs1extern crate proc_macro;
2
3use proc_macro::TokenStream;
4use quote::quote;
5use syn::{parse_macro_input, Data, DeriveInput, Fields, ItemEnum, Lit, Meta};
6
7#[proc_macro_attribute]
9pub fn schema(attr: TokenStream, item: TokenStream) -> TokenStream {
10 let input = parse_macro_input!(item as DeriveInput);
11 let name = &input.ident;
12
13 let attr_meta = parse_macro_input!(attr as Meta);
15 let (schema_type, explicit_name) = match attr_meta {
16 Meta::Path(path) => {
17 let schema_type = path.get_ident().unwrap().to_string();
18 (schema_type, None)
19 }
20 Meta::NameValue(name_value) => {
21 let schema_type = name_value.path.get_ident().unwrap().to_string();
22 let explicit_name = match name_value.lit {
23 Lit::Str(lit_str) => lit_str.value(),
24 _ => panic!("Expected string literal for schema name"),
25 };
26 (schema_type, Some(explicit_name))
27 }
28 _ => panic!("Unsupported attribute format"),
29 };
30
31 let schema_impl = match schema_type.as_str() {
32 "Atom" => generate_atom_schema(&name, explicit_name.as_ref().map(|s| s.as_str())),
33 "Structural" => generate_structural_schema(&input.data),
34 "Nominal" => generate_nominal_schema(&name, &input.data, explicit_name.as_ref().map(|s| s.as_str())),
35 _ => panic!("Unsupported schema type"),
36 };
37
38 let expanded = quote! {
39 #input
40
41 impl ::irpc_schema::HasSchema for #name {
42 fn schema() -> ::irpc_schema::Schema {
43 #schema_impl
44 }
45 }
46 };
47
48 TokenStream::from(expanded)
49}
50
51fn generate_atom_schema(name: &syn::Ident, explicit_name: Option<&str>) -> proc_macro2::TokenStream {
53 let type_name = match explicit_name {
54 Some(name) => name.to_string(),
55 None => name.to_string(),
56 };
57 quote! {
58 ::irpc_schema::Schema::Atom(#type_name.to_string())
59 }
60}
61
62fn generate_structural_schema(data: &syn::Data) -> proc_macro2::TokenStream {
64 match data {
65 Data::Struct(data_struct) => match &data_struct.fields {
66 Fields::Named(fields) => {
67 let types: Vec<proc_macro2::TokenStream> = fields
68 .named
69 .iter()
70 .map(|f| {
71 let ty = &f.ty;
72 quote! {
73 <#ty as ::irpc_schema::HasSchema>::schema()
74 }
75 })
76 .collect();
77 if types.is_empty() {
78 quote! {
79 ::irpc_schema::Schema::Unit
80 }
81 } else {
82 quote! {
83 ::irpc_schema::Schema::Product(vec![#(#types),*])
84 }
85 }
86 }
87 Fields::Unnamed(fields) => {
88 let types: Vec<proc_macro2::TokenStream> = fields
89 .unnamed
90 .iter()
91 .map(|f| {
92 let ty = &f.ty;
93 quote! {
94 <#ty as ::irpc_schema::HasSchema>::schema()
95 }
96 })
97 .collect();
98 if types.is_empty() {
99 quote! {
100 ::irpc_schema::Schema::Unit
101 }
102 } else {
103 quote! {
104 ::irpc_schema::Schema::Product(vec![#(#types),*])
105 }
106 }
107 }
108 Fields::Unit => quote! {
109 ::irpc_schema::Schema::Unit
110 },
111 },
112 Data::Enum(data_enum) => {
113 let variant_schemas: Vec<proc_macro2::TokenStream> = data_enum
114 .variants
115 .iter()
116 .map(|v| {
117 let variant_fields = match &v.fields {
118 Fields::Named(fields) => fields
119 .named
120 .iter()
121 .map(|f| {
122 let ty = &f.ty;
123 quote! {
124 <#ty as ::irpc_schema::HasSchema>::schema()
125 }
126 })
127 .collect(),
128 Fields::Unnamed(fields) => fields
129 .unnamed
130 .iter()
131 .map(|f| {
132 let ty = &f.ty;
133 quote! {
134 <#ty as ::irpc_schema::HasSchema>::schema()
135 }
136 })
137 .collect(),
138 Fields::Unit => vec![],
139 };
140 if variant_fields.is_empty() {
141 quote! {
142 ::irpc_schema::Schema::Unit
143 }
144 } else {
145 quote! {
146 ::irpc_schema::Schema::Product(vec![#(#variant_fields),*])
147 }
148 }
149 })
150 .collect();
151 if variant_schemas.is_empty() {
152 return quote! {
153 ::irpc_schema::Schema::Bottom
154 };
155 }
156 quote! {
157 ::irpc_schema::Schema::Sum(vec![#(#variant_schemas),*])
158 }
159 }
160 _ => panic!("Unsupported type for Structural schema"),
161 }
162}
163
164fn generate_nominal_schema(name: &syn::Ident, data: &syn::Data, explicit_name: Option<&str>) -> proc_macro2::TokenStream {
166 let name_text = explicit_name.unwrap_or(&name.to_string()).to_string();
167 match data {
168 Data::Struct(data_struct) => match &data_struct.fields {
169 Fields::Named(fields) => {
170 let field_schemas: Vec<proc_macro2::TokenStream> = fields
171 .named
172 .iter()
173 .map(|f| {
174 let field_name = f.ident.as_ref().unwrap().to_string();
175 let field_type = &f.ty;
176 quote! {
177 ::irpc_schema::Named(#field_name.to_string(), <#field_type as ::irpc_schema::HasSchema>::schema())
178 }
179 })
180 .collect();
181 let schema = if field_schemas.is_empty() {
182 quote! { ::irpc_schema::Schema::Unit }
183 } else {
184 quote! { ::irpc_schema::Schema::Struct(vec![#(#field_schemas),*]) }
185 };
186 quote! {
187 ::irpc_schema::Schema::Named(
188 Box::new(::irpc_schema::Named(#name_text.to_string(), #schema))
189 )
190 }
191 }
192 Fields::Unnamed(fields) => {
193 let field_schemas: Vec<proc_macro2::TokenStream> = fields
194 .unnamed
195 .iter()
196 .enumerate()
197 .map(|(_i, f)| {
198 let field_type = &f.ty;
199 quote! {
200 <#field_type as ::irpc_schema::HasSchema>::schema()
201 }
202 })
203 .collect();
204 let schema = if field_schemas.is_empty() {
205 quote! { ::irpc_schema::Schema::Unit }
206 } else {
207 quote! { ::irpc_schema::Schema::Product(vec![#(#field_schemas),*]) }
208 };
209 quote! {
210 ::irpc_schema::Schema::Named(
211 Box::new(::irpc_schema::Named(#name_text.to_string(), #schema))
212 )
213 }
214 }
215 Fields::Unit => quote! {
216 ::irpc_schema::Schema::Named(
217 Box::new(::irpc_schema::Named(#name_text.to_string(), ::irpc_schema::Schema::Unit))
218 )
219 },
220 },
221 Data::Enum(data_enum) => {
222 let variants: Vec<proc_macro2::TokenStream> = data_enum
223 .variants
224 .iter()
225 .map(|v| {
226 let variant_name = &v.ident;
227 let variant_name_text = variant_name.to_string();
228 match &v.fields {
229 Fields::Named(fields) => {
230 let named = fields
231 .named
232 .iter()
233 .map(|f| {
234 let field_type = &f.ty;
235 let field_name = f.ident.as_ref().unwrap().to_string();
236 quote! {
237 ::irpc_schema::Named(#field_name.to_string(),<#field_type as ::irpc_schema::HasSchema>::schema())
238 }
239 })
240 .collect::<Vec<_>>();
241 let schema_type = if named.is_empty() {
242 quote! { ::irpc_schema::Schema::Unit }
243 } else if named.len() == 1 {
244 quote! { ::irpc_schema::Schema::Struct(vec![#(#named),*]) }
245 } else {
246 quote! { ::irpc_schema::Schema::Enum(vec![#(#named),*]) }
247 };
248 quote! {
249 ::irpc_schema::Named(
250 #variant_name_text.to_string(),
251 #schema_type
252 )
253 }
254 }
255 Fields::Unnamed(fields) => {
256 let unnamed = fields
257 .unnamed
258 .iter()
259 .map(|f| {
260 let field_type = &f.ty;
261 quote! {
262 <#field_type as ::irpc_schema::HasSchema>::schema()
263 }
264 })
265 .collect::<Vec<_>>();
266 let schema_type = if unnamed.is_empty() {
267 quote! { ::irpc_schema::Schema::Unit }
268 } else if unnamed.len() == 1 {
269 quote! { ::irpc_schema::Schema::Product(vec![#(#unnamed),*]) }
270 } else {
271 quote! { ::irpc_schema::Schema::Sum(vec![#(#unnamed),*]) }
272 };
273 quote! {
274 ::irpc_schema::Named(
275 #variant_name_text.to_string(),
276 #schema_type
277 )
278 }
279 }
280 Fields::Unit => {
281 quote! {
282 ::irpc_schema::Named(
283 #variant_name_text.to_string(),
284 ::irpc_schema::Schema::Unit
285 )
286 }
287 }
288 }
289 })
290 .collect::<Vec<_>>();
291
292 let schema = if variants.is_empty() {
293 quote! { ::irpc_schema::Schema::Bottom }
294 } else if variants.len() == 1 {
295 quote! { ::irpc_schema::Schema::Struct(vec![#(#variants),*]) }
296 } else {
297 quote! { ::irpc_schema::Schema::Enum(vec![#(#variants),*]) }
298 };
299 quote! {
300 ::irpc_schema::Schema::Named(
301 Box::new(::irpc_schema::Named(#name_text.to_string(), #schema))
302 )
303 }
304 }
305 _ => panic!("Unsupported type for Nominal schema"),
306 }
307}
308
309#[proc_macro_attribute]
315pub fn serialize_stable(_attr: TokenStream, item: TokenStream) -> TokenStream {
316 let input = parse_macro_input!(item as ItemEnum);
318
319 let original_enum = input.clone();
321
322 let enum_name = &input.ident;
324
325 let hashes_struct_name =
327 syn::Ident::new(&format!("{}SchemaHashes", enum_name), enum_name.span());
328 let static_name = syn::Ident::new(&format!("__{}_SCHEMA_HASHES", enum_name), enum_name.span());
329
330 let variants = &input.variants;
332
333 for variant in variants {
335 match &variant.fields {
336 Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
337 }
339 _ => panic!("HashDiscriminator only supports variants with a single unnamed field"),
340 }
341 }
342
343 let mut variant_names = Vec::new();
345 let mut field_types = Vec::new();
346
347 for variant in variants {
348 let variant_name = &variant.ident;
349 variant_names.push(variant_name);
350
351 let field_type = match &variant.fields {
352 Fields::Unnamed(fields) => &fields.unnamed.first().unwrap().ty,
353 _ => unreachable!(), };
355
356 field_types.push(field_type);
357 }
358
359 let hash_fields = variant_names.iter().map(|variant_name| {
361 quote! { pub #variant_name: [u8; 32] }
362 });
363
364 let hash_inits =
366 variant_names
367 .iter()
368 .zip(field_types.iter())
369 .map(|(variant_name, field_type)| {
370 quote! {
371 #variant_name: *<#field_type as ::irpc_schema::HasSchema>::schema().stable_hash().as_bytes()
372 }
373 });
374
375 let serialize_arms = variant_names.iter().map(|variant_name| {
377 quote! {
378 #enum_name::#variant_name(payload) => {
379 let hash = hashes.#variant_name;
380
381 let mut tup = serializer.serialize_tuple(2)?;
382 tup.serialize_element(&hash)?;
383 tup.serialize_element(payload)?;
384 tup.end()
385 }
386 }
387 });
388
389 let deserialize_branches =
391 variant_names
392 .iter()
393 .zip(field_types.iter())
394 .map(|(variant_name, field_type)| {
395 quote! {
396 if hash_bytes == hashes.#variant_name {
397 let payload = seq.next_element::<#field_type>()?.ok_or_else(||
398 serde::de::Error::custom("missing payload"))?;
399 return Ok(#enum_name::#variant_name(payload));
400 }
401 }
402 });
403
404 let generated_impls = quote! {
406 #original_enum
408
409 struct #hashes_struct_name {
411 #(#hash_fields),*
412 }
413
414 use std::sync::OnceLock;
416 static #static_name: OnceLock<#hashes_struct_name> = OnceLock::new();
417
418 impl #hashes_struct_name {
419 fn new() -> Self {
421 Self {
422 #(#hash_inits),*
423 }
424 }
425
426 fn get() -> &'static Self {
428 #static_name.get_or_init(|| Self::new())
429 }
430 }
431
432 impl serde::Serialize for #enum_name {
434 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
435 where
436 S: serde::Serializer,
437 {
438 use serde::ser::SerializeTuple;
439 let hashes = #hashes_struct_name::get();
440
441 match self {
442 #(#serialize_arms),*
443 }
444 }
445 }
446
447 impl<'de> serde::Deserialize<'de> for #enum_name {
449 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
450 where
451 D: serde::Deserializer<'de>,
452 {
453 struct Visitor;
455
456 impl<'de> serde::de::Visitor<'de> for Visitor {
457 type Value = #enum_name;
458
459 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
460 formatter.write_str("a tuple with a hash discriminator and payload")
461 }
462
463 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
464 where
465 A: serde::de::SeqAccess<'de>,
466 {
467 let hash_bytes = seq.next_element::<[u8; 32]>()?.ok_or_else(||
469 serde::de::Error::custom("missing hash"))?;
470
471 let hashes = #hashes_struct_name::get();
473
474 #(#deserialize_branches)*
476
477 Err(serde::de::Error::custom("unknown discriminator"))
479 }
480 }
481
482 deserializer.deserialize_tuple(2, Visitor)
484 }
485 }
486 };
487
488 TokenStream::from(generated_impls)
490}