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