1#![warn(missing_docs)]
7#![doc(
8 html_logo_url = "https://raw.githubusercontent.com/getsentry/relay/master/artwork/relay-icon.png",
9 html_favicon_url = "https://raw.githubusercontent.com/getsentry/relay/master/artwork/relay-icon.png"
10)]
11#![recursion_limit = "256"]
12
13use proc_macro2::{Span, TokenStream};
14use quote::{ToTokens, quote};
15use syn::meta::ParseNestedMeta;
16use syn::{Ident, Lit, LitBool, LitInt, LitStr};
17use synstructure::decl_derive;
18
19mod utils;
20
21use utils::SynstructureExt as _;
22
23decl_derive!(
24 [ProcessValue, attributes(metastructure)] =>
25 derive_process_value
27);
28
29fn derive_process_value(mut s: synstructure::Structure<'_>) -> syn::Result<TokenStream> {
30 let _ = s.bind_with(|_bi| synstructure::BindStyle::RefMut);
31 let _ = s.add_bounds(synstructure::AddBounds::Generics);
32
33 let type_attrs = parse_type_attributes(&s)?;
34 let process_func_call_tokens = type_attrs.process_func_call_tokens();
35
36 let process_value_arms = s.try_each_variant(|variant| {
37 if is_newtype(variant) {
38 let bi = &variant.bindings()[0];
46 let ident = &bi.binding;
47 let field_attrs = parse_field_attributes(0, bi.ast(), &mut true)?;
48 let field_attrs_tokens = field_attrs.as_tokens(&type_attrs, Some(quote!(parent_attrs)));
49
50 Ok(quote! {
51 let parent_attrs = __state.attrs();
52 let attrs = #field_attrs_tokens;
53 let __state = &__state.enter_nothing(
54 Some(::std::borrow::Cow::Owned(attrs))
55 );
56
57 let action = __processor.before_process(
63 Some(&*#ident),
64 __meta,
65 &__state
66 )?;
67
68 crate::processor::ProcessValue::process_value(
69 #ident,
70 __meta,
71 __processor,
72 &__state
73 )?;
74
75 __processor.after_process(
76 Some(#ident),
77 __meta,
78 &__state
79 )?;
80 })
81 } else {
82 Ok(quote!())
83 }
84 })?;
85
86 let process_child_values_arms = s.try_each_variant(|variant| {
87 let mut is_tuple_struct = false;
88
89 if is_newtype(variant) {
90 return Ok(quote!());
94 }
95
96 let mut body = TokenStream::new();
97 for (index, bi) in variant.bindings().iter().enumerate() {
98 let field_attrs = parse_field_attributes(index, bi.ast(), &mut is_tuple_struct)?;
99 let ident = &bi.binding;
100 let field_attrs_name = Ident::new(&format!("FIELD_ATTRS_{index}"), Span::call_site());
101 let field_name = field_attrs.field_name.clone();
102
103 let field_attrs_tokens = field_attrs.as_tokens(&type_attrs, None);
104
105 (quote! {
106 static #field_attrs_name: crate::processor::FieldAttrs = #field_attrs_tokens;
107 })
108 .to_tokens(&mut body);
109
110 let enter_state = if field_attrs.additional_properties {
111 if is_tuple_struct {
112 panic!("additional_properties not allowed in tuple struct");
113 }
114
115 quote! {
116 __state.enter_nothing(Some(::std::borrow::Cow::Borrowed(&#field_attrs_name)))
117 }
118 } else if field_attrs.flatten {
119 quote! {
120 __state.enter_nothing(Some(::std::borrow::Cow::Borrowed(&#field_attrs_name)))
121 }
122 } else if is_tuple_struct {
123 quote! {
124 __state.enter_index(
125 #index,
126 Some(::std::borrow::Cow::Borrowed(&#field_attrs_name)),
127 crate::processor::ValueType::for_field(#ident),
128 )
129 }
130 } else {
131 quote! {
132 __state.enter_static(
133 #field_name,
134 Some(::std::borrow::Cow::Borrowed(&#field_attrs_name)),
135 crate::processor::ValueType::for_field(#ident),
136 )
137 }
138 };
139
140 if field_attrs.additional_properties {
141 (quote! {
142 __processor.process_other(#ident, &#enter_state)?;
143 })
144 .to_tokens(&mut body);
145 } else if field_attrs.flatten {
146 (quote! {
147 crate::processor::ProcessValue::process_child_values(
148 #ident,
149 __processor,
150 &#enter_state
151 )?;
152 })
153 .to_tokens(&mut body);
154 } else {
155 (quote! {
156 crate::processor::process_value(#ident, __processor, &#enter_state)?;
157 })
158 .to_tokens(&mut body);
159 }
160 }
161
162 Ok(quote!({ #body }))
163 })?;
164
165 let _ = s.bind_with(|_bi| synstructure::BindStyle::Ref);
166
167 let value_type_arms = s.each_variant(|variant| {
168 if !type_attrs.value_type.is_empty() {
169 let value_names = type_attrs
170 .value_type
171 .iter()
172 .map(|value_name| Ident::new(value_name, Span::call_site()));
173 quote! {
174 #[allow(deprecated)]
177 {
178 enumset::enum_set!( #(crate::processor::ValueType::#value_names)|* )
179 }
180 }
181 } else if is_newtype(variant) {
182 let bi = &variant.bindings()[0];
183 let ident = &bi.binding;
184 quote!(crate::processor::ProcessValue::value_type(#ident))
185 } else {
186 quote!(enumset::EnumSet::empty())
187 }
188 });
189
190 Ok(s.gen_impl(quote! {
191 #[automatically_derived]
192 #[expect(non_local_definitions, reason = "crate needs to be migrated to syn")]
193 gen impl crate::processor::ProcessValue for @Self {
194 fn value_type(&self) -> enumset::EnumSet<crate::processor::ValueType> {
195 match *self {
196 #value_type_arms
197 }
198 }
199
200 fn process_value<P>(
201 &mut self,
202 __meta: &mut relay_protocol::Meta,
203 __processor: &mut P,
204 __state: &crate::processor::ProcessingState<'_>,
205 ) -> crate::processor::ProcessingResult
206 where
207 P: crate::processor::Processor,
208 {
209 #process_func_call_tokens;
210 match *self {
211 #process_value_arms
212 }
213
214 Ok(())
215 }
216
217 #[inline]
218 fn process_child_values<P>(
219 &mut self,
220 __processor: &mut P,
221 __state: &crate::processor::ProcessingState<'_>
222 ) -> crate::processor::ProcessingResult
223 where
224 P: crate::processor::Processor,
225 {
226 match *self {
227 #process_child_values_arms
228 }
229
230 Ok(())
231 }
232 }
233 }))
234}
235
236#[derive(Default)]
237struct TypeAttrs {
238 process_func: Option<String>,
239 value_type: Vec<String>,
240 trim: Option<bool>,
245 pii: Option<Pii>,
250}
251
252impl TypeAttrs {
253 fn process_func_call_tokens(&self) -> TokenStream {
254 if let Some(ref func_name) = self.process_func {
255 let func_name = Ident::new(func_name, Span::call_site());
256 quote! {
257 __processor.#func_name(self, __meta, __state)?;
258 }
259 } else {
260 quote! {
261 self.process_child_values(__processor, __state)?;
262 }
263 }
264 }
265}
266
267fn parse_type_attributes(s: &synstructure::Structure<'_>) -> syn::Result<TypeAttrs> {
268 let mut rv = TypeAttrs::default();
269
270 for attr in &s.ast().attrs {
271 if !attr.path().is_ident("metastructure") {
272 continue;
273 }
274
275 attr.parse_nested_meta(|meta| {
276 let ident = meta.path.require_ident()?;
277
278 if ident == "process_func" {
279 let s = meta.value()?.parse::<LitStr>()?;
280 rv.process_func = Some(s.value());
281 } else if ident == "value_type" {
282 let s = meta.value()?.parse::<LitStr>()?;
283 rv.value_type.push(s.value());
284 } else if ident == "trim" {
285 let s = meta.value()?.parse::<LitBool>()?;
286 rv.trim = Some(s.value());
287 } else if ident == "pii" {
288 let s = meta.value()?.parse::<LitStr>()?;
289 rv.pii = parse_pii_value(s, &meta)?;
290 } else {
291 if !meta.input.peek(syn::Token![,]) {
293 let _ = meta.value()?.parse::<Lit>()?;
294 }
295 }
296
297 Ok(())
298 })?;
299 }
300
301 Ok(rv)
302}
303
304#[derive(Copy, Clone, Debug)]
305enum Pii {
306 True,
307 False,
308 Maybe,
309}
310
311impl Pii {
312 fn as_tokens(self) -> TokenStream {
313 match self {
314 Pii::True => quote!(crate::processor::Pii::True),
315 Pii::False => quote!(crate::processor::Pii::False),
316 Pii::Maybe => quote!(crate::processor::Pii::Maybe),
317 }
318 }
319}
320
321#[derive(Default)]
322struct FieldAttrs {
323 additional_properties: bool,
324 omit_from_schema: bool,
325 field_name: String,
326 flatten: bool,
327 required: Option<bool>,
328 nonempty: Option<bool>,
329 trim_whitespace: Option<bool>,
330 pii: Option<Pii>,
331 retain: bool,
332 characters: Option<TokenStream>,
333 max_chars: Option<TokenStream>,
334 max_chars_allowance: Option<TokenStream>,
335 max_depth: Option<TokenStream>,
336 max_bytes: Option<TokenStream>,
337 trim: Option<bool>,
338}
339
340impl FieldAttrs {
341 fn as_tokens(
342 &self,
343 type_attrs: &TypeAttrs,
344 inherit_from_field_attrs: Option<TokenStream>,
345 ) -> TokenStream {
346 let field_name = &self.field_name;
347
348 if self.required.is_none() && self.nonempty.is_some() {
349 panic!(
350 "`required` has to be explicitly set to \"true\" or \"false\" if `nonempty` is used."
351 );
352 }
353 let required = if let Some(required) = self.required {
354 quote!(#required)
355 } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
356 quote!(#parent_attrs.required)
357 } else {
358 quote!(false)
359 };
360
361 let nonempty = if let Some(nonempty) = self.nonempty {
362 quote!(#nonempty)
363 } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
364 quote!(#parent_attrs.nonempty)
365 } else {
366 quote!(false)
367 };
368
369 let trim_whitespace = if let Some(trim_whitespace) = self.trim_whitespace {
370 quote!(#trim_whitespace)
371 } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
372 quote!(#parent_attrs.trim_whitespace)
373 } else {
374 quote!(false)
375 };
376
377 let pii = if let Some(pii) = self.pii.or(type_attrs.pii) {
378 pii.as_tokens()
379 } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
380 quote!(#parent_attrs.pii)
381 } else {
382 quote!(crate::processor::Pii::False)
383 };
384
385 let trim = if let Some(trim) = self.trim.or(type_attrs.trim) {
386 quote!(#trim)
387 } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
388 quote!(#parent_attrs.trim)
389 } else {
390 quote!(true)
391 };
392
393 let retain = self.retain;
394
395 let max_chars = if let Some(ref max_chars) = self.max_chars {
396 quote!(Some(#max_chars))
397 } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
398 quote!(#parent_attrs.max_chars)
399 } else {
400 quote!(None)
401 };
402
403 let max_chars_allowance = if let Some(ref max_chars_allowance) = self.max_chars_allowance {
404 quote!(#max_chars_allowance)
405 } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
406 quote!(#parent_attrs.max_chars_allowance)
407 } else {
408 quote!(0)
409 };
410
411 let max_depth = if let Some(ref max_depth) = self.max_depth {
412 quote!(Some(#max_depth))
413 } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
414 quote!(#parent_attrs.max_depth)
415 } else {
416 quote!(None)
417 };
418
419 let max_bytes = if let Some(ref max_bytes) = self.max_bytes {
420 quote!(Some(#max_bytes))
421 } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
422 quote!(#parent_attrs.max_bytes)
423 } else {
424 quote!(None)
425 };
426
427 let characters = if let Some(ref characters) = self.characters {
428 quote!(Some(#characters))
429 } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
430 quote!(#parent_attrs.characters)
431 } else {
432 quote!(None)
433 };
434
435 quote!({
436 crate::processor::FieldAttrs {
437 name: Some(#field_name),
438 required: #required,
439 nonempty: #nonempty,
440 trim_whitespace: #trim_whitespace,
441 max_chars: #max_chars,
442 max_chars_allowance: #max_chars_allowance,
443 characters: #characters,
444 max_depth: #max_depth,
445 max_bytes: #max_bytes,
446 pii: #pii,
447 retain: #retain,
448 trim: #trim,
449 }
450 })
451 }
452}
453
454fn parse_field_attributes(
455 index: usize,
456 bi_ast: &syn::Field,
457 is_tuple_struct: &mut bool,
458) -> syn::Result<FieldAttrs> {
459 if bi_ast.ident.is_none() {
460 *is_tuple_struct = true;
461 } else if *is_tuple_struct {
462 panic!("invalid tuple struct");
463 }
464
465 let mut rv = FieldAttrs {
466 field_name: bi_ast
467 .ident
468 .as_ref()
469 .map(ToString::to_string)
470 .unwrap_or_else(|| index.to_string()),
471 ..Default::default()
472 };
473
474 for attr in &bi_ast.attrs {
475 if !attr.path().is_ident("metastructure") {
476 continue;
477 }
478
479 attr.parse_nested_meta(|meta| {
480 let ident = meta.path.require_ident()?;
481
482 if ident == "additional_properties" {
483 rv.additional_properties = true;
484 } else if ident == "omit_from_schema" {
485 rv.omit_from_schema = true;
486 } else if ident == "field" {
487 let s = meta.value()?.parse::<LitStr>()?;
488 rv.field_name = s.value();
489 } else if ident == "flatten" {
490 rv.flatten = true;
491 } else if ident == "required" {
492 let s = meta.value()?.parse::<LitBool>()?;
493 rv.required = Some(s.value());
494 } else if ident == "nonempty" {
495 let s = meta.value()?.parse::<LitBool>()?;
496 rv.nonempty = Some(s.value());
497 } else if ident == "trim_whitespace" {
498 let s = meta.value()?.parse::<LitBool>()?;
499 rv.trim_whitespace = Some(s.value());
500 } else if ident == "allow_chars" || ident == "deny_chars" {
501 if rv.characters.is_some() {
502 return Err(meta.error("allow_chars and deny_chars are mutually exclusive"));
503 }
504 let s = meta.value()?.parse::<LitStr>()?;
505 rv.characters = Some(parse_character_set(ident, &s.value()));
506 } else if ident == "max_chars" {
507 let s = meta.value()?.parse::<LitInt>()?;
508 rv.max_chars = Some(quote!(#s));
509 } else if ident == "max_chars_allowance" {
510 let s = meta.value()?.parse::<LitInt>()?;
511 rv.max_chars_allowance = Some(quote!(#s));
512 } else if ident == "max_depth" {
513 let s = meta.value()?.parse::<LitInt>()?;
514 rv.max_depth = Some(quote!(#s));
515 } else if ident == "max_bytes" {
516 let s = meta.value()?.parse::<LitInt>()?;
517 rv.max_bytes = Some(quote!(#s));
518 } else if ident == "pii" {
519 let s = meta.value()?.parse::<LitStr>()?;
520 rv.pii = parse_pii_value(s, &meta)?;
521 } else if ident == "retain" {
522 let s = meta.value()?.parse::<LitBool>()?;
523 rv.retain = s.value();
524 } else if ident == "trim" {
525 let s = meta.value()?.parse::<LitBool>()?;
526 rv.trim = Some(s.value());
527 } else if ident == "legacy_alias" || ident == "skip_serialization" {
528 let _ = meta.value()?.parse::<Lit>()?;
529 } else {
530 return Err(meta.error("Unknown argument"));
531 }
532
533 Ok(())
534 })?;
535 }
536
537 Ok(rv)
538}
539
540fn is_newtype(variant: &synstructure::VariantInfo) -> bool {
541 variant.bindings().len() == 1 && variant.bindings()[0].ast().ident.is_none()
542}
543
544fn parse_character_set(ident: &Ident, value: &str) -> TokenStream {
545 #[derive(Clone, Copy)]
546 enum State {
547 Blank,
548 OpenRange(char),
549 MidRange(char),
550 }
551
552 let mut state = State::Blank;
553 let mut ranges = Vec::new();
554
555 for c in value.chars() {
556 match (state, c) {
557 (State::Blank, a) => state = State::OpenRange(a),
558 (State::OpenRange(a), '-') => state = State::MidRange(a),
559 (State::OpenRange(a), c) => {
560 state = State::OpenRange(c);
561 ranges.push(quote!(#a..=#a));
562 }
563 (State::MidRange(a), b) => {
564 ranges.push(quote!(#a..=#b));
565 state = State::Blank;
566 }
567 }
568 }
569
570 match state {
571 State::OpenRange(a) => ranges.push(quote!(#a..=#a)),
572 State::MidRange(a) => {
573 ranges.push(quote!(#a..=#a));
574 ranges.push(quote!('-'..='-'));
575 }
576 State::Blank => {}
577 }
578
579 let is_negative = ident == "deny_chars";
580
581 quote! {
582 crate::processor::CharacterSet {
583 char_is_valid: |c: char| -> bool {
584 match c {
585 #((#ranges) => !#is_negative,)*
586 _ => #is_negative,
587 }
588 },
589 ranges: &[ #(#ranges,)* ],
590 is_negative: #is_negative,
591 }
592 }
593}
594
595fn parse_pii_value(value: LitStr, meta: &ParseNestedMeta) -> syn::Result<Option<Pii>> {
596 Ok(Some(match value.value().as_str() {
597 "true" => Pii::True,
598 "false" => Pii::False,
599 "maybe" => Pii::Maybe,
600 _ => return Err(meta.error("Expected one of `true`, `false`, `maybe`")),
601 }))
602}