1use proc_macro::TokenStream;
21use quote::quote;
22use syn::{Data, DeriveInput, Fields, Lit, parse_macro_input};
23
24#[proc_macro_derive(Crous, attributes(crous))]
30pub fn derive_crous(input: TokenStream) -> TokenStream {
31 let input = parse_macro_input!(input as DeriveInput);
32 let name = &input.ident;
33 let name_str = name.to_string();
34
35 let fields = match &input.data {
36 Data::Struct(data) => match &data.fields {
37 Fields::Named(fields) => &fields.named,
38 _ => panic!("Crous derive only supports structs with named fields"),
39 },
40 _ => panic!("Crous derive only supports structs"),
41 };
42
43 let mut field_infos = Vec::new();
45 for field in fields {
46 let field_name = field.ident.as_ref().unwrap();
47 let field_name_str = field_name.to_string();
48 let mut field_id: Option<u64> = None;
49
50 for attr in &field.attrs {
51 if attr.path().is_ident("crous") {
52 attr.parse_nested_meta(|meta| {
53 if meta.path.is_ident("id") {
54 let value = meta.value()?;
55 let lit: Lit = value.parse()?;
56 if let Lit::Int(lit_int) = lit {
57 field_id = Some(lit_int.base10_parse().unwrap());
58 }
59 }
60 Ok(())
61 })
62 .ok();
63 }
64 }
65
66 let id = field_id.unwrap_or_else(|| {
67 let hash = xxhash_rust::xxh64::xxh64(field_name_str.as_bytes(), 0);
70 hash & 0xFFFF });
72
73 field_infos.push((field_name.clone(), field_name_str, id));
74 }
75
76 let schema_str = format!(
78 "{}:{}",
79 name_str,
80 field_infos
81 .iter()
82 .map(|(_, n, id)| format!("{n}={id}"))
83 .collect::<Vec<_>>()
84 .join(",")
85 );
86 let fingerprint = xxhash_rust::xxh64::xxh64(schema_str.as_bytes(), 0);
87
88 let encode_fields: Vec<_> = field_infos
90 .iter()
91 .map(|(fname, fname_str, _id)| {
92 quote! {
93 (
94 #fname_str.to_string(),
95 crous_core::Crous::to_crous_value(&self.#fname)
96 )
97 }
98 })
99 .collect();
100
101 let decode_fields_init: Vec<_> = field_infos
103 .iter()
104 .map(|(fname, _fname_str, _id)| {
105 quote! {
106 let mut #fname = None;
107 }
108 })
109 .collect();
110
111 let decode_fields_match: Vec<_> = field_infos
112 .iter()
113 .map(|(fname, fname_str, _id)| {
114 quote! {
115 #fname_str => {
116 #fname = Some(crous_core::Crous::from_crous_value(v)?);
117 }
118 }
119 })
120 .collect();
121
122 let decode_fields_unwrap: Vec<_> = field_infos
123 .iter()
124 .map(|(fname, fname_str, _id)| {
125 quote! {
126 #fname: #fname.ok_or_else(|| crous_core::CrousError::SchemaMismatch(
127 format!("missing field '{}' in {}", #fname_str, #name_str)
128 ))?
129 }
130 })
131 .collect();
132
133 let expanded = quote! {
134 impl crous_core::Crous for #name {
135 fn to_crous_value(&self) -> crous_core::Value {
136 crous_core::Value::Object(vec![
137 #(#encode_fields),*
138 ])
139 }
140
141 fn from_crous_value(value: &crous_core::Value) -> crous_core::Result<Self> {
142 match value {
143 crous_core::Value::Object(entries) => {
144 #(#decode_fields_init)*
145
146 for (k, v) in entries {
147 match k.as_str() {
148 #(#decode_fields_match)*
149 _ => {} }
151 }
152
153 Ok(Self {
154 #(#decode_fields_unwrap),*
155 })
156 }
157 _ => Err(crous_core::CrousError::SchemaMismatch(
158 format!("expected object for {}", #name_str)
159 )),
160 }
161 }
162
163 fn schema_fingerprint() -> u64 {
164 #fingerprint
165 }
166
167 fn type_name() -> &'static str {
168 #name_str
169 }
170 }
171 };
172
173 TokenStream::from(expanded)
174}
175
176#[proc_macro_derive(CrousSchema, attributes(crous))]
181pub fn derive_crous_schema(input: TokenStream) -> TokenStream {
182 let input = parse_macro_input!(input as DeriveInput);
183 let name = &input.ident;
184 let name_str = name.to_string();
185
186 let fields = match &input.data {
187 Data::Struct(data) => match &data.fields {
188 Fields::Named(fields) => &fields.named,
189 _ => panic!("CrousSchema only supports structs with named fields"),
190 },
191 _ => panic!("CrousSchema only supports structs"),
192 };
193
194 let mut field_entries = Vec::new();
195 for field in fields {
196 let fname = field.ident.as_ref().unwrap().to_string();
197 let mut fid: u64 = 0;
198
199 for attr in &field.attrs {
200 if attr.path().is_ident("crous") {
201 attr.parse_nested_meta(|meta| {
202 if meta.path.is_ident("id") {
203 let value = meta.value()?;
204 let lit: Lit = value.parse()?;
205 if let Lit::Int(lit_int) = lit {
206 fid = lit_int.base10_parse().unwrap();
207 }
208 }
209 Ok(())
210 })
211 .ok();
212 }
213 }
214
215 field_entries.push(quote! {
216 (#fname, #fid)
217 });
218 }
219
220 let expanded = quote! {
221 impl #name {
222 pub fn schema_info() -> &'static [(&'static str, u64)] {
224 &[ #(#field_entries),* ]
225 }
226
227 pub fn schema_type_name() -> &'static str {
229 #name_str
230 }
231 }
232 };
233
234 TokenStream::from(expanded)
235}