1use darling::{FromDeriveInput, FromMeta, FromVariant};
2use proc_macro2::TokenStream;
3use quote::quote;
4use syn::{Ident, Path, Type};
5
6#[derive(Debug, FromDeriveInput)]
8#[darling(attributes(envelope))]
9pub(crate) struct EnvelopeArgs {
10 pub ident: Ident,
12
13 pub generics: syn::Generics,
15
16 #[darling(default)]
19 pub tx_type_name: Option<Ident>,
20
21 #[darling(default)]
24 pub alloy_consensus: Option<Path>,
25
26 #[darling(default)]
28 pub serde_cfg: Option<syn::Meta>,
29
30 #[darling(default)]
32 pub arbitrary_cfg: Option<syn::Meta>,
33
34 #[darling(default)]
37 pub typed: Option<Ident>,
38
39 pub data: darling::ast::Data<EnvelopeVariant, ()>,
41}
42
43#[derive(Debug, FromVariant)]
45#[darling(attributes(envelope), forward_attrs(serde, doc))]
46pub(crate) struct EnvelopeVariant {
47 pub ident: Ident,
49
50 pub fields: darling::ast::Fields<syn::Type>,
52
53 #[darling(flatten)]
55 pub kind: VariantKind,
56
57 #[darling(default)]
59 pub typed: Option<Ident>,
60
61 pub attrs: Vec<syn::Attribute>,
63}
64
65#[derive(Debug, Clone, FromMeta)]
67pub(crate) enum VariantKind {
68 #[darling(rename = "ty")]
70 Typed(u8),
71 #[darling(word, rename = "flatten")]
73 Flattened,
74}
75
76impl VariantKind {
77 pub(crate) fn serde_tag_and_aliases(&self) -> (String, Vec<String>) {
79 let Self::Typed(ty) = self else { return Default::default() };
80
81 let tx_type_hex = format!("{ty:x}");
82
83 let mut aliases = vec![];
84 if tx_type_hex.len() == 1 {
86 aliases.push(format!("0x0{}", tx_type_hex));
87 }
88
89 if tx_type_hex != tx_type_hex.to_uppercase() {
91 aliases.push(format!("0x{}", tx_type_hex.to_uppercase()));
92 }
93
94 (format!("0x{tx_type_hex}"), aliases)
95 }
96}
97
98#[derive(Debug, Clone)]
100pub(crate) struct ProcessedVariant {
101 pub name: Ident,
103 pub ty: Type,
105 pub kind: VariantKind,
107 pub serde_attrs: Option<TokenStream>,
109 pub doc_attrs: Vec<syn::Attribute>,
111 pub typed: Option<Ident>,
113}
114
115impl ProcessedVariant {
116 pub(crate) const fn is_legacy(&self) -> bool {
118 matches!(self.kind, VariantKind::Typed(0))
119 }
120
121 pub(crate) fn inner_type(&self) -> TokenStream {
123 if let Some(custom) = &self.typed {
125 return quote! { #custom };
126 }
127
128 let ty = &self.ty;
129
130 if let syn::Type::Path(type_path) = ty {
132 if let Some(segment) = type_path.path.segments.last() {
133 if segment.ident == "Signed" || segment.ident == "Sealed" {
134 if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
135 if let Some(syn::GenericArgument::Type(inner_ty)) = args.args.first() {
136 return quote! { #inner_ty };
137 }
138 }
139 }
140 }
141 }
142 quote! { #ty }
144 }
145}
146
147pub(crate) struct GroupedVariants {
149 pub all: Vec<ProcessedVariant>,
151 pub typed: Vec<ProcessedVariant>,
153 pub flattened: Vec<ProcessedVariant>,
155}
156
157impl GroupedVariants {
158 pub(crate) fn from_args(args: EnvelopeArgs) -> darling::Result<Self> {
160 let variants = match args.data {
162 darling::ast::Data::Enum(variants) => variants,
163 _ => {
164 return Err(darling::Error::custom(
165 "`TransactionEnvelope` can only be derived for enums",
166 )
167 .with_span(&args.ident));
168 }
169 };
170
171 let mut processed = Vec::new();
172 for variant in variants {
173 let EnvelopeVariant { ident, fields, kind, attrs, typed } = variant;
174
175 let mut serde_attrs = None;
176 let mut doc_attrs = Vec::new();
177
178 for attr in attrs {
179 if attr.path().is_ident("serde") {
180 if let syn::Meta::List(list) = attr.meta {
181 serde_attrs = Some(list.tokens);
182 }
183 } else if attr.path().is_ident("doc") {
184 doc_attrs.push(attr);
185 }
186 }
187
188 let ty = match &fields.style {
190 darling::ast::Style::Tuple if fields.len() == 1 => fields.fields[0].clone(),
191 darling::ast::Style::Tuple => {
192 return Err(darling::Error::custom(format!(
193 "expected exactly one field, found {}",
194 fields.len()
195 ))
196 .with_span(&ident))
197 }
198 _ => {
199 return Err(darling::Error::custom(
200 "TransactionEnvelope variants must have a single unnamed field",
201 )
202 .with_span(&ident))
203 }
204 };
205
206 processed.push(ProcessedVariant {
207 name: ident,
208 ty,
209 kind,
210 serde_attrs,
211 doc_attrs,
212 typed,
213 });
214 }
215
216 let typed =
217 processed.iter().filter(|v| matches!(v.kind, VariantKind::Typed(_))).cloned().collect();
218
219 let flattened = processed
220 .iter()
221 .filter(|v| matches!(v.kind, VariantKind::Flattened))
222 .cloned()
223 .collect();
224
225 Ok(Self { all: processed, typed, flattened })
226 }
227
228 pub(crate) fn legacy_variant(&self) -> Option<&ProcessedVariant> {
230 self.all.iter().find(|v| v.is_legacy())
231 }
232
233 pub(crate) fn variant_names(&self) -> Vec<&syn::Ident> {
235 self.all.iter().map(|v| &v.name).collect()
236 }
237
238 pub(crate) fn variant_types(&self) -> Vec<&syn::Type> {
240 self.all.iter().map(|v| &v.ty).collect()
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247
248 #[test]
249 fn serde_tag() {
250 assert_eq!(
251 VariantKind::Typed(126).serde_tag_and_aliases(),
252 ("0x7e".to_string(), vec!["0x7E".to_string()])
253 );
254 assert_eq!(
255 VariantKind::Typed(1).serde_tag_and_aliases(),
256 ("0x1".to_string(), vec!["0x01".to_string()])
257 );
258 assert_eq!(
259 VariantKind::Typed(10).serde_tag_and_aliases(),
260 ("0xa".to_string(), vec!["0x0a".to_string(), "0xA".to_string()])
261 );
262 }
263}