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, Copy, Debug)]
358enum Required {
359 True,
360 False,
361 ValueOrMeta,
362}
363
364impl Required {
365 fn as_tokens(&self) -> TokenStream {
366 match self {
367 Self::True => quote!(::relay_event_schema::processor::Required::Value),
368 Self::False => quote!(::relay_event_schema::processor::Required::False),
369 Self::ValueOrMeta => quote!(::relay_event_schema::processor::Required::ValueOrMeta),
370 }
371 }
372}
373
374impl syn::parse::Parse for Required {
375 fn parse(input: ParseStream) -> syn::Result<Self> {
376 let head = input.fork();
377 if let Ok(bool) = input.parse::<LitBool>() {
378 return match bool.value() {
379 true => Ok(Self::True),
380 false => Ok(Self::False),
381 };
382 }
383
384 let input = head.fork();
385 let value = input.parse::<LitStr>()?;
386
387 match value.value().as_str() {
388 "value_or_meta" => Ok(Self::ValueOrMeta),
389 _ => Err(head.error(r#"Expected one of `true`, `false`, or `"value_or_meta"`"#)),
390 }
391 }
392}
393
394#[derive(Clone, Debug)]
395enum Size {
396 Static(usize),
397 Dynamic(ExprPath),
398}
399
400impl syn::parse::Parse for Size {
401 fn parse(input: ParseStream) -> syn::Result<Self> {
402 if input.peek(LitInt) {
403 let lit = input.parse::<LitInt>()?;
404 return Ok(Self::Static(lit.base10_parse()?));
405 }
406
407 let head = input.fork();
408 let path = input
409 .parse::<LitStr>()?
410 .parse()
411 .map_err(|_| head.error("Expected a function name"))?;
412 Ok(Self::Dynamic(path))
413 }
414}
415
416impl Size {
417 fn as_tokens(&self) -> TokenStream {
418 match self {
419 Self::Static(n) => quote!(::relay_event_schema::processor::SizeMode::Static(Some(#n))),
420 Self::Dynamic(fun) => quote!(::relay_event_schema::processor::SizeMode::Dynamic(#fun)),
421 }
422 }
423}
424
425#[derive(Default, Debug)]
426struct VariantAttrs {
427 fallback_variant: bool,
428 retain: bool,
429}
430
431fn parse_variant_attributes(attrs: &[syn::Attribute]) -> syn::Result<VariantAttrs> {
432 let mut rv = VariantAttrs::default();
433 for attr in attrs {
434 if !attr.path().is_ident("metastructure") {
435 continue;
436 }
437
438 attr.parse_nested_meta(|meta| {
439 let ident = meta.path.require_ident()?;
440
441 if ident == "fallback_variant" {
442 rv.fallback_variant = true;
443 } else if ident == "retain" {
444 let s = meta.value()?.parse::<LitBool>()?;
445 rv.retain = s.value();
446 } else {
447 if let Ok(v) = meta.value() {
449 let _ = v.parse::<Lit>()?;
450 }
451 }
452
453 Ok(())
454 })?;
455 }
456
457 Ok(rv)
458}
459
460#[derive(Default, Debug)]
461struct FieldAttrs {
462 additional_properties: bool,
463 omit_from_schema: bool,
464 field_name: String,
465 flatten: bool,
466 required: Option<Required>,
467 nonempty: Option<bool>,
468 trim_whitespace: Option<bool>,
469 pii: Option<Pii>,
470 retain: bool,
471 characters: Option<TokenStream>,
472 max_chars: Option<Size>,
473 max_chars_allowance: Option<TokenStream>,
474 max_depth: Option<TokenStream>,
475 max_bytes: Option<Size>,
476 bytes_size: Option<Size>,
477 trim: Option<bool>,
478}
479
480impl FieldAttrs {
481 fn as_tokens(
482 &self,
483 type_attrs: &TypeAttrs,
484 inherit_from_field_attrs: Option<TokenStream>,
485 ) -> TokenStream {
486 let field_name = &self.field_name;
487
488 if self.required.is_none() && self.nonempty.is_some() {
489 panic!(
490 "`required` has to be explicitly set to \"true\" or \"false\" if `nonempty` is used."
491 );
492 }
493 let required = if let Some(required) = self.required {
494 required.as_tokens()
495 } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
496 quote!(#parent_attrs.required)
497 } else {
498 quote!(::relay_event_schema::processor::Required::False)
499 };
500
501 let nonempty = if let Some(nonempty) = self.nonempty {
502 quote!(#nonempty)
503 } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
504 quote!(#parent_attrs.nonempty)
505 } else {
506 quote!(false)
507 };
508
509 let trim_whitespace = if let Some(trim_whitespace) = self.trim_whitespace {
510 quote!(#trim_whitespace)
511 } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
512 quote!(#parent_attrs.trim_whitespace)
513 } else {
514 quote!(false)
515 };
516
517 let pii = if let Some(pii) = self.pii.as_ref().or(type_attrs.pii.as_ref()) {
518 pii.as_tokens()
519 } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
520 quote!(#parent_attrs.pii)
521 } else {
522 quote!(::relay_event_schema::processor::PiiMode::Static(
523 ::relay_event_schema::processor::Pii::False
524 ))
525 };
526
527 let trim = if let Some(trim) = self.trim.or(type_attrs.trim) {
528 quote!(#trim)
529 } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
530 quote!(#parent_attrs.trim)
531 } else {
532 quote!(true)
533 };
534
535 let retain = self.retain;
536
537 let max_chars = if let Some(ref max_chars) = self.max_chars {
538 max_chars.as_tokens()
539 } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
540 quote!(#parent_attrs.max_chars)
541 } else {
542 quote!(::relay_event_schema::processor::SizeMode::Static(None))
543 };
544
545 let max_chars_allowance = if let Some(ref max_chars_allowance) = self.max_chars_allowance {
546 quote!(#max_chars_allowance)
547 } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
548 quote!(#parent_attrs.max_chars_allowance)
549 } else {
550 quote!(0)
551 };
552
553 let max_depth = if let Some(ref max_depth) = self.max_depth {
554 quote!(Some(#max_depth))
555 } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
556 quote!(#parent_attrs.max_depth)
557 } else {
558 quote!(None)
559 };
560
561 let max_bytes = if let Some(ref max_bytes) = self.max_bytes {
562 max_bytes.as_tokens()
563 } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
564 quote!(#parent_attrs.max_bytes)
565 } else {
566 quote!(::relay_event_schema::processor::SizeMode::Static(None))
567 };
568
569 let bytes_size = if let Some(ref bytes_size) = self.bytes_size {
570 bytes_size.as_tokens()
571 } else {
572 quote!(::relay_event_schema::processor::SizeMode::Static(None))
573 };
574
575 let characters = if let Some(ref characters) = self.characters {
576 quote!(Some(#characters))
577 } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
578 quote!(#parent_attrs.characters)
579 } else {
580 quote!(None)
581 };
582
583 quote!({
584 ::relay_event_schema::processor::FieldAttrs {
585 name: Some(#field_name),
586 required: #required,
587 nonempty: #nonempty,
588 trim_whitespace: #trim_whitespace,
589 max_chars: #max_chars,
590 max_chars_allowance: #max_chars_allowance,
591 characters: #characters,
592 max_depth: #max_depth,
593 max_bytes: #max_bytes,
594 bytes_size: #bytes_size,
595 pii: #pii,
596 retain: #retain,
597 trim: #trim,
598 }
599 })
600 }
601}
602
603fn parse_field_attributes(
604 index: usize,
605 bi_ast: &syn::Field,
606 is_tuple_struct: &mut bool,
607) -> syn::Result<FieldAttrs> {
608 if bi_ast.ident.is_none() {
609 *is_tuple_struct = true;
610 } else if *is_tuple_struct {
611 panic!("invalid tuple struct");
612 }
613
614 let mut rv = FieldAttrs {
615 field_name: bi_ast
616 .ident
617 .as_ref()
618 .map(ToString::to_string)
619 .unwrap_or_else(|| index.to_string()),
620 ..Default::default()
621 };
622
623 for attr in &bi_ast.attrs {
624 if !attr.path().is_ident("metastructure") {
625 continue;
626 }
627
628 attr.parse_nested_meta(|meta| {
629 let ident = meta.path.require_ident()?;
630
631 if ident == "additional_properties" {
632 rv.additional_properties = true;
633 } else if ident == "omit_from_schema" {
634 rv.omit_from_schema = true;
635 } else if ident == "field" {
636 let s = meta.value()?.parse::<LitStr>()?;
637 rv.field_name = s.value();
638 } else if ident == "flatten" {
639 rv.flatten = true;
640 } else if ident == "required" {
641 rv.required = Some(meta.value()?.parse::<Required>()?);
642 } else if ident == "nonempty" {
643 let s = meta.value()?.parse::<LitBool>()?;
644 rv.nonempty = Some(s.value());
645 } else if ident == "trim_whitespace" {
646 let s = meta.value()?.parse::<LitBool>()?;
647 rv.trim_whitespace = Some(s.value());
648 } else if ident == "allow_chars" || ident == "deny_chars" {
649 if rv.characters.is_some() {
650 return Err(meta.error("allow_chars and deny_chars are mutually exclusive"));
651 }
652 let s = meta.value()?.parse::<LitStr>()?;
653 rv.characters = Some(parse_character_set(ident, &s.value()));
654 } else if ident == "max_chars" {
655 rv.max_chars = Some(meta.value()?.parse()?);
656 } else if ident == "max_chars_allowance" {
657 let s = meta.value()?.parse::<LitInt>()?;
658 rv.max_chars_allowance = Some(quote!(#s));
659 } else if ident == "max_depth" {
660 let s = meta.value()?.parse::<LitInt>()?;
661 rv.max_depth = Some(quote!(#s));
662 } else if ident == "max_bytes" {
663 rv.max_bytes = Some(meta.value()?.parse()?);
664 } else if ident == "bytes_size" {
665 rv.bytes_size = Some(meta.value()?.parse()?);
666 } else if ident == "pii" {
667 rv.pii = Some(meta.value()?.parse()?);
668 } else if ident == "retain" {
669 let s = meta.value()?.parse::<LitBool>()?;
670 rv.retain = s.value();
671 } else if ident == "trim" {
672 let s = meta.value()?.parse::<LitBool>()?;
673 rv.trim = Some(s.value());
674 } else if ident == "legacy_alias" || ident == "skip_serialization" {
675 let _ = meta.value()?.parse::<Lit>()?;
676 } else {
677 return Err(meta.error("Unknown argument"));
678 }
679
680 Ok(())
681 })?;
682 }
683
684 Ok(rv)
685}
686
687fn is_newtype(variant: &synstructure::VariantInfo) -> bool {
688 variant.bindings().len() == 1 && variant.bindings()[0].ast().ident.is_none()
689}
690
691fn parse_character_set(ident: &Ident, value: &str) -> TokenStream {
692 #[derive(Clone, Copy)]
693 enum State {
694 Blank,
695 OpenRange(char),
696 MidRange(char),
697 }
698
699 let mut state = State::Blank;
700 let mut ranges = Vec::new();
701
702 for c in value.chars() {
703 match (state, c) {
704 (State::Blank, a) => state = State::OpenRange(a),
705 (State::OpenRange(a), '-') => state = State::MidRange(a),
706 (State::OpenRange(a), c) => {
707 state = State::OpenRange(c);
708 ranges.push(quote!(#a..=#a));
709 }
710 (State::MidRange(a), b) => {
711 ranges.push(quote!(#a..=#b));
712 state = State::Blank;
713 }
714 }
715 }
716
717 match state {
718 State::OpenRange(a) => ranges.push(quote!(#a..=#a)),
719 State::MidRange(a) => {
720 ranges.push(quote!(#a..=#a));
721 ranges.push(quote!('-'..='-'));
722 }
723 State::Blank => {}
724 }
725
726 let is_negative = ident == "deny_chars";
727
728 quote! {
729 ::relay_event_schema::processor::CharacterSet {
730 char_is_valid: |c: char| -> bool {
731 match c {
732 #((#ranges) => !#is_negative,)*
733 _ => #is_negative,
734 }
735 },
736 ranges: &[ #(#ranges,)* ],
737 is_negative: #is_negative,
738 }
739 }
740}