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::parse::ParseStream;
16use syn::{ExprPath, 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 variant_attrs = parse_variant_attributes(variant.ast().attrs)?;
48 let mut field_attrs = parse_field_attributes(0, bi.ast(), &mut true)?;
49 field_attrs.retain |= variant_attrs.retain;
52 let field_attrs_tokens = field_attrs.as_tokens(&type_attrs, Some(quote!(parent_attrs)));
53
54 let process_value = if variant_attrs.fallback_variant {
55 quote! {
56 __processor.process_other(#ident, &__state)?;
57 }
58 } else {
59 quote! {
60 ::relay_event_schema::processor::ProcessValue::process_value(
61 #ident,
62 __meta,
63 __processor,
64 &__state
65 )?;
66
67 }
68 };
69 Ok(quote! {
70 let parent_attrs = __state.attrs();
71 let attrs = #field_attrs_tokens;
72 let __state = &__state.enter_nothing(
73 Some(::std::borrow::Cow::Owned(attrs))
74 );
75
76 let action = __processor.before_process(
82 Some(&*#ident),
83 __meta,
84 &__state
85 )?;
86
87 #process_value
88
89 __processor.after_process(
90 Some(#ident),
91 __meta,
92 &__state
93 )?;
94 })
95 } else {
96 Ok(quote!())
97 }
98 })?;
99
100 let process_child_values_arms = s.try_each_variant(|variant| {
101 let mut is_tuple_struct = false;
102
103 if is_newtype(variant) {
104 return Ok(quote!());
108 }
109
110 let mut body = TokenStream::new();
111 for (index, bi) in variant.bindings().iter().enumerate() {
112 let field_attrs = parse_field_attributes(index, bi.ast(), &mut is_tuple_struct)?;
113 let ident = &bi.binding;
114 let field_attrs_name = Ident::new(&format!("FIELD_ATTRS_{index}"), Span::call_site());
115 let field_name = field_attrs.field_name.clone();
116
117 let field_attrs_tokens = field_attrs.as_tokens(&type_attrs, None);
118
119 (quote! {
120 static #field_attrs_name: ::relay_event_schema::processor::FieldAttrs = #field_attrs_tokens;
121 })
122 .to_tokens(&mut body);
123
124 let enter_state = if field_attrs.additional_properties {
125 if is_tuple_struct {
126 panic!("additional_properties not allowed in tuple struct");
127 }
128
129 quote! {
130 __state.enter_nothing(Some(::std::borrow::Cow::Borrowed(&#field_attrs_name)))
131 }
132 } else if field_attrs.flatten {
133 quote! {
134 __state.enter_nothing(Some(::std::borrow::Cow::Borrowed(&#field_attrs_name)))
135 }
136 } else if is_tuple_struct {
137 quote! {
138 __state.enter_index(
139 #index,
140 Some(::std::borrow::Cow::Borrowed(&#field_attrs_name)),
141 ::relay_event_schema::processor::ValueType::for_field(#ident),
142 )
143 }
144 } else {
145 quote! {
146 __state.enter_borrowed(
147 #field_name,
148 Some(::std::borrow::Cow::Borrowed(&#field_attrs_name)),
149 ::relay_event_schema::processor::ValueType::for_field(#ident),
150 )
151 }
152 };
153
154 if field_attrs.additional_properties {
155 (quote! {
156 __processor.process_other(#ident, &#enter_state)?;
157 })
158 .to_tokens(&mut body);
159 } else if field_attrs.flatten {
160 (quote! {
161 ::relay_event_schema::processor::ProcessValue::process_child_values(
162 #ident,
163 __processor,
164 &#enter_state
165 )?;
166 })
167 .to_tokens(&mut body);
168 } else {
169 (quote! {
170 ::relay_event_schema::processor::process_value(#ident, __processor, &#enter_state)?;
171 })
172 .to_tokens(&mut body);
173 }
174 }
175
176 Ok(quote!({ #body }))
177 })?;
178
179 let _ = s.bind_with(|_bi| synstructure::BindStyle::Ref);
180
181 let value_type_arms = s.each_variant(|variant| {
182 if !type_attrs.value_type.is_empty() {
183 let value_names = type_attrs
184 .value_type
185 .iter()
186 .map(|value_name| Ident::new(value_name, Span::call_site()));
187 quote! {
188 #[allow(deprecated)]
191 {
192 enumset::enum_set!( #(::relay_event_schema::processor::ValueType::#value_names)|* )
193 }
194 }
195 } else if is_newtype(variant) {
196 let bi = &variant.bindings()[0];
197 let ident = &bi.binding;
198 quote!(::relay_event_schema::processor::ProcessValue::value_type(#ident))
199 } else {
200 quote!(::relay_event_schema::processor::EnumSet::empty())
201 }
202 });
203
204 Ok(s.gen_impl(quote! {
205 #[automatically_derived]
206 gen impl ::relay_event_schema::processor::ProcessValue for @Self {
207 fn value_type(&self) -> ::relay_event_schema::processor::EnumSet<::relay_event_schema::processor::ValueType> {
208 match *self {
209 #value_type_arms
210 }
211 }
212
213 fn process_value<P>(
214 &mut self,
215 __meta: &mut relay_protocol::Meta,
216 __processor: &mut P,
217 __state: &::relay_event_schema::processor::ProcessingState<'_>,
218 ) -> ::relay_event_schema::processor::ProcessingResult
219 where
220 P: ::relay_event_schema::processor::Processor,
221 {
222 #process_func_call_tokens;
223 match *self {
224 #process_value_arms
225 }
226
227 Ok(())
228 }
229
230 #[inline]
231 fn process_child_values<P>(
232 &mut self,
233 __processor: &mut P,
234 __state: &::relay_event_schema::processor::ProcessingState<'_>
235 ) -> ::relay_event_schema::processor::ProcessingResult
236 where
237 P: ::relay_event_schema::processor::Processor,
238 {
239 match *self {
240 #process_child_values_arms
241 }
242
243 Ok(())
244 }
245 }
246 }))
247}
248
249#[derive(Default)]
250struct TypeAttrs {
251 process_func: Option<String>,
252 value_type: Vec<String>,
253 trim: Option<bool>,
258 pii: Option<Pii>,
263}
264
265impl TypeAttrs {
266 fn process_func_call_tokens(&self) -> TokenStream {
267 if let Some(ref func_name) = self.process_func {
268 let func_name = Ident::new(func_name, Span::call_site());
269 quote! {
270 __processor.#func_name(self, __meta, __state)?;
271 }
272 } else {
273 quote! {
274 self.process_child_values(__processor, __state)?;
275 }
276 }
277 }
278}
279
280fn parse_type_attributes(s: &synstructure::Structure<'_>) -> syn::Result<TypeAttrs> {
281 let mut rv = TypeAttrs::default();
282
283 for attr in &s.ast().attrs {
284 if !attr.path().is_ident("metastructure") {
285 continue;
286 }
287
288 attr.parse_nested_meta(|meta| {
289 let ident = meta.path.require_ident()?;
290
291 if ident == "process_func" {
292 let s = meta.value()?.parse::<LitStr>()?;
293 rv.process_func = Some(s.value());
294 } else if ident == "value_type" {
295 let s = meta.value()?.parse::<LitStr>()?;
296 rv.value_type.push(s.value());
297 } else if ident == "trim" {
298 let s = meta.value()?.parse::<LitBool>()?;
299 rv.trim = Some(s.value());
300 } else if ident == "pii" {
301 rv.pii = Some(meta.value()?.parse()?);
302 } else {
303 if !meta.input.peek(syn::Token![,]) {
305 let _ = meta.value()?.parse::<Lit>()?;
306 }
307 }
308
309 Ok(())
310 })?;
311 }
312
313 Ok(rv)
314}
315
316#[derive(Clone, Debug)]
317enum Pii {
318 True,
319 False,
320 Maybe,
321 Dynamic(ExprPath),
322}
323
324impl syn::parse::Parse for Pii {
325 fn parse(input: ParseStream) -> syn::Result<Self> {
326 let head = input.fork();
327 let value = input.parse::<LitStr>()?;
328 let pii = match value.value().as_str() {
329 "true" => Self::True,
330 "false" => Self::False,
331 "maybe" => Self::Maybe,
332 _ => Self::Dynamic(value.parse().map_err(|_| {
333 head.error("Expected one of `true`, `false`, `maybe`, or a function name")
334 })?),
335 };
336 Ok(pii)
337 }
338}
339
340impl Pii {
341 fn as_tokens(&self) -> TokenStream {
342 match self {
343 Self::True => quote!(::relay_event_schema::processor::PiiMode::Static(
344 ::relay_event_schema::processor::Pii::True
345 )),
346 Self::False => quote!(::relay_event_schema::processor::PiiMode::Static(
347 ::relay_event_schema::processor::Pii::False
348 )),
349 Self::Maybe => quote!(::relay_event_schema::processor::PiiMode::Static(
350 ::relay_event_schema::processor::Pii::Maybe
351 )),
352 Self::Dynamic(fun) => quote!(::relay_event_schema::processor::PiiMode::Dynamic(#fun)),
353 }
354 }
355}
356
357#[derive(Clone, Debug)]
358enum Size {
359 Static(usize),
360 Dynamic(ExprPath),
361}
362
363impl syn::parse::Parse for Size {
364 fn parse(input: ParseStream) -> syn::Result<Self> {
365 if input.peek(LitInt) {
366 let lit = input.parse::<LitInt>()?;
367 return Ok(Self::Static(lit.base10_parse()?));
368 }
369
370 let head = input.fork();
371 let path = input
372 .parse::<LitStr>()?
373 .parse()
374 .map_err(|_| head.error("Expected a function name"))?;
375 Ok(Self::Dynamic(path))
376 }
377}
378
379impl Size {
380 fn as_tokens(&self) -> TokenStream {
381 match self {
382 Self::Static(n) => quote!(::relay_event_schema::processor::SizeMode::Static(Some(#n))),
383 Self::Dynamic(fun) => quote!(::relay_event_schema::processor::SizeMode::Dynamic(#fun)),
384 }
385 }
386}
387
388#[derive(Default, Debug)]
389struct VariantAttrs {
390 fallback_variant: bool,
391 retain: bool,
392}
393
394fn parse_variant_attributes(attrs: &[syn::Attribute]) -> syn::Result<VariantAttrs> {
395 let mut rv = VariantAttrs::default();
396 for attr in attrs {
397 if !attr.path().is_ident("metastructure") {
398 continue;
399 }
400
401 attr.parse_nested_meta(|meta| {
402 let ident = meta.path.require_ident()?;
403
404 if ident == "fallback_variant" {
405 rv.fallback_variant = true;
406 } else if ident == "retain" {
407 let s = meta.value()?.parse::<LitBool>()?;
408 rv.retain = s.value();
409 } else {
410 if let Ok(v) = meta.value() {
412 let _ = v.parse::<Lit>()?;
413 }
414 }
415
416 Ok(())
417 })?;
418 }
419
420 Ok(rv)
421}
422
423#[derive(Default, Debug)]
424struct FieldAttrs {
425 additional_properties: bool,
426 omit_from_schema: bool,
427 field_name: String,
428 flatten: bool,
429 required: Option<bool>,
430 nonempty: Option<bool>,
431 trim_whitespace: Option<bool>,
432 pii: Option<Pii>,
433 retain: bool,
434 characters: Option<TokenStream>,
435 max_chars: Option<Size>,
436 max_chars_allowance: Option<TokenStream>,
437 max_depth: Option<TokenStream>,
438 max_bytes: Option<Size>,
439 bytes_size: Option<Size>,
440 trim: Option<bool>,
441}
442
443impl FieldAttrs {
444 fn as_tokens(
445 &self,
446 type_attrs: &TypeAttrs,
447 inherit_from_field_attrs: Option<TokenStream>,
448 ) -> TokenStream {
449 let field_name = &self.field_name;
450
451 if self.required.is_none() && self.nonempty.is_some() {
452 panic!(
453 "`required` has to be explicitly set to \"true\" or \"false\" if `nonempty` is used."
454 );
455 }
456 let required = if let Some(required) = self.required {
457 quote!(#required)
458 } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
459 quote!(#parent_attrs.required)
460 } else {
461 quote!(false)
462 };
463
464 let nonempty = if let Some(nonempty) = self.nonempty {
465 quote!(#nonempty)
466 } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
467 quote!(#parent_attrs.nonempty)
468 } else {
469 quote!(false)
470 };
471
472 let trim_whitespace = if let Some(trim_whitespace) = self.trim_whitespace {
473 quote!(#trim_whitespace)
474 } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
475 quote!(#parent_attrs.trim_whitespace)
476 } else {
477 quote!(false)
478 };
479
480 let pii = if let Some(pii) = self.pii.as_ref().or(type_attrs.pii.as_ref()) {
481 pii.as_tokens()
482 } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
483 quote!(#parent_attrs.pii)
484 } else {
485 quote!(::relay_event_schema::processor::PiiMode::Static(
486 ::relay_event_schema::processor::Pii::False
487 ))
488 };
489
490 let trim = if let Some(trim) = self.trim.or(type_attrs.trim) {
491 quote!(#trim)
492 } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
493 quote!(#parent_attrs.trim)
494 } else {
495 quote!(true)
496 };
497
498 let retain = self.retain;
499
500 let max_chars = if let Some(ref max_chars) = self.max_chars {
501 max_chars.as_tokens()
502 } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
503 quote!(#parent_attrs.max_chars)
504 } else {
505 quote!(::relay_event_schema::processor::SizeMode::Static(None))
506 };
507
508 let max_chars_allowance = if let Some(ref max_chars_allowance) = self.max_chars_allowance {
509 quote!(#max_chars_allowance)
510 } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
511 quote!(#parent_attrs.max_chars_allowance)
512 } else {
513 quote!(0)
514 };
515
516 let max_depth = if let Some(ref max_depth) = self.max_depth {
517 quote!(Some(#max_depth))
518 } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
519 quote!(#parent_attrs.max_depth)
520 } else {
521 quote!(None)
522 };
523
524 let max_bytes = if let Some(ref max_bytes) = self.max_bytes {
525 max_bytes.as_tokens()
526 } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
527 quote!(#parent_attrs.max_bytes)
528 } else {
529 quote!(::relay_event_schema::processor::SizeMode::Static(None))
530 };
531
532 let bytes_size = if let Some(ref bytes_size) = self.bytes_size {
533 bytes_size.as_tokens()
534 } else {
535 quote!(::relay_event_schema::processor::SizeMode::Static(None))
536 };
537
538 let characters = if let Some(ref characters) = self.characters {
539 quote!(Some(#characters))
540 } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
541 quote!(#parent_attrs.characters)
542 } else {
543 quote!(None)
544 };
545
546 quote!({
547 ::relay_event_schema::processor::FieldAttrs {
548 name: Some(#field_name),
549 required: #required,
550 nonempty: #nonempty,
551 trim_whitespace: #trim_whitespace,
552 max_chars: #max_chars,
553 max_chars_allowance: #max_chars_allowance,
554 characters: #characters,
555 max_depth: #max_depth,
556 max_bytes: #max_bytes,
557 bytes_size: #bytes_size,
558 pii: #pii,
559 retain: #retain,
560 trim: #trim,
561 }
562 })
563 }
564}
565
566fn parse_field_attributes(
567 index: usize,
568 bi_ast: &syn::Field,
569 is_tuple_struct: &mut bool,
570) -> syn::Result<FieldAttrs> {
571 if bi_ast.ident.is_none() {
572 *is_tuple_struct = true;
573 } else if *is_tuple_struct {
574 panic!("invalid tuple struct");
575 }
576
577 let mut rv = FieldAttrs {
578 field_name: bi_ast
579 .ident
580 .as_ref()
581 .map(ToString::to_string)
582 .unwrap_or_else(|| index.to_string()),
583 ..Default::default()
584 };
585
586 for attr in &bi_ast.attrs {
587 if !attr.path().is_ident("metastructure") {
588 continue;
589 }
590
591 attr.parse_nested_meta(|meta| {
592 let ident = meta.path.require_ident()?;
593
594 if ident == "additional_properties" {
595 rv.additional_properties = true;
596 } else if ident == "omit_from_schema" {
597 rv.omit_from_schema = true;
598 } else if ident == "field" {
599 let s = meta.value()?.parse::<LitStr>()?;
600 rv.field_name = s.value();
601 } else if ident == "flatten" {
602 rv.flatten = true;
603 } else if ident == "required" {
604 let s = meta.value()?.parse::<LitBool>()?;
605 rv.required = Some(s.value());
606 } else if ident == "nonempty" {
607 let s = meta.value()?.parse::<LitBool>()?;
608 rv.nonempty = Some(s.value());
609 } else if ident == "trim_whitespace" {
610 let s = meta.value()?.parse::<LitBool>()?;
611 rv.trim_whitespace = Some(s.value());
612 } else if ident == "allow_chars" || ident == "deny_chars" {
613 if rv.characters.is_some() {
614 return Err(meta.error("allow_chars and deny_chars are mutually exclusive"));
615 }
616 let s = meta.value()?.parse::<LitStr>()?;
617 rv.characters = Some(parse_character_set(ident, &s.value()));
618 } else if ident == "max_chars" {
619 rv.max_chars = Some(meta.value()?.parse()?);
620 } else if ident == "max_chars_allowance" {
621 let s = meta.value()?.parse::<LitInt>()?;
622 rv.max_chars_allowance = Some(quote!(#s));
623 } else if ident == "max_depth" {
624 let s = meta.value()?.parse::<LitInt>()?;
625 rv.max_depth = Some(quote!(#s));
626 } else if ident == "max_bytes" {
627 rv.max_bytes = Some(meta.value()?.parse()?);
628 } else if ident == "bytes_size" {
629 rv.bytes_size = Some(meta.value()?.parse()?);
630 } else if ident == "pii" {
631 rv.pii = Some(meta.value()?.parse()?);
632 } else if ident == "retain" {
633 let s = meta.value()?.parse::<LitBool>()?;
634 rv.retain = s.value();
635 } else if ident == "trim" {
636 let s = meta.value()?.parse::<LitBool>()?;
637 rv.trim = Some(s.value());
638 } else if ident == "legacy_alias" || ident == "skip_serialization" {
639 let _ = meta.value()?.parse::<Lit>()?;
640 } else {
641 return Err(meta.error("Unknown argument"));
642 }
643
644 Ok(())
645 })?;
646 }
647
648 Ok(rv)
649}
650
651fn is_newtype(variant: &synstructure::VariantInfo) -> bool {
652 variant.bindings().len() == 1 && variant.bindings()[0].ast().ident.is_none()
653}
654
655fn parse_character_set(ident: &Ident, value: &str) -> TokenStream {
656 #[derive(Clone, Copy)]
657 enum State {
658 Blank,
659 OpenRange(char),
660 MidRange(char),
661 }
662
663 let mut state = State::Blank;
664 let mut ranges = Vec::new();
665
666 for c in value.chars() {
667 match (state, c) {
668 (State::Blank, a) => state = State::OpenRange(a),
669 (State::OpenRange(a), '-') => state = State::MidRange(a),
670 (State::OpenRange(a), c) => {
671 state = State::OpenRange(c);
672 ranges.push(quote!(#a..=#a));
673 }
674 (State::MidRange(a), b) => {
675 ranges.push(quote!(#a..=#b));
676 state = State::Blank;
677 }
678 }
679 }
680
681 match state {
682 State::OpenRange(a) => ranges.push(quote!(#a..=#a)),
683 State::MidRange(a) => {
684 ranges.push(quote!(#a..=#a));
685 ranges.push(quote!('-'..='-'));
686 }
687 State::Blank => {}
688 }
689
690 let is_negative = ident == "deny_chars";
691
692 quote! {
693 ::relay_event_schema::processor::CharacterSet {
694 char_is_valid: |c: char| -> bool {
695 match c {
696 #((#ranges) => !#is_negative,)*
697 _ => #is_negative,
698 }
699 },
700 ranges: &[ #(#ranges,)* ],
701 is_negative: #is_negative,
702 }
703 }
704}