relay_event_derive/
lib.rs

1//! Derive for visitor traits on the Event schema.
2//!
3//! This derive provides the `ProcessValue` trait. It must be used within the `relay-event-schema`
4//! crate.
5
6#![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 the `ProcessValue` trait.
26    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            // Process variant twice s.t. both processor functions are called.
39            //
40            // E.g.:
41            // - For `Value::String`, call `process_string` as well as `process_value`.
42            // - For `LenientString`, call `process_lenient_string` (not a thing.. yet) as well as
43            // `process_string`.
44
45            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                // This is a copy of `funcs::process_value`, due to ownership issues. In particular
58                // we want to pass the same meta twice.
59                //
60                // NOTE: Handling for ProcessingAction is slightly different (early-return). This
61                // should be fine though.
62                let action = __processor.before_process(
63                    Some(&*#ident),
64                    __meta,
65                    &__state
66                )?;
67
68                ::relay_event_schema::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            // `process_child_values` has to be a noop because otherwise we recurse into the
91            // subtree twice due to the weird `process_value` impl
92
93            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: ::relay_event_schema::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                        ::relay_event_schema::processor::ValueType::for_field(#ident),
128                    )
129                }
130            } else {
131                quote! {
132                    __state.enter_borrowed(
133                        #field_name,
134                        Some(::std::borrow::Cow::Borrowed(&#field_attrs_name)),
135                        ::relay_event_schema::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                    ::relay_event_schema::processor::ProcessValue::process_child_values(
148                        #ident,
149                        __processor,
150                       &#enter_state
151                    )?;
152                })
153                .to_tokens(&mut body);
154            } else {
155                (quote! {
156                    ::relay_event_schema::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                // enumset produces a deprecation warning because it thinks we use its internals
175                // directly, but we do actually use the macro
176                #[allow(deprecated)]
177                {
178                    enumset::enum_set!( #(::relay_event_schema::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!(::relay_event_schema::processor::ProcessValue::value_type(#ident))
185        } else {
186            quote!(::relay_event_schema::processor::EnumSet::empty())
187        }
188    });
189
190    Ok(s.gen_impl(quote! {
191        #[automatically_derived]
192        gen impl ::relay_event_schema::processor::ProcessValue for @Self {
193            fn value_type(&self) -> ::relay_event_schema::processor::EnumSet<::relay_event_schema::processor::ValueType> {
194                match *self {
195                    #value_type_arms
196                }
197            }
198
199            fn process_value<P>(
200                &mut self,
201                __meta: &mut relay_protocol::Meta,
202                __processor: &mut P,
203                __state: &::relay_event_schema::processor::ProcessingState<'_>,
204            ) -> ::relay_event_schema::processor::ProcessingResult
205            where
206                P: ::relay_event_schema::processor::Processor,
207            {
208                #process_func_call_tokens;
209                match *self {
210                    #process_value_arms
211                }
212
213                Ok(())
214            }
215
216            #[inline]
217            fn process_child_values<P>(
218                &mut self,
219                __processor: &mut P,
220                __state: &::relay_event_schema::processor::ProcessingState<'_>
221            ) -> ::relay_event_schema::processor::ProcessingResult
222            where
223                P: ::relay_event_schema::processor::Processor,
224            {
225                match *self {
226                    #process_child_values_arms
227                }
228
229                Ok(())
230            }
231        }
232    }))
233}
234
235#[derive(Default)]
236struct TypeAttrs {
237    process_func: Option<String>,
238    value_type: Vec<String>,
239    /// The default trim value for the container.
240    ///
241    /// If `trim` is specified on the container all fields of the container,
242    /// will default to this value for `trim`.
243    trim: Option<bool>,
244    /// The default pii value for the container.
245    ///
246    /// If `pii` is specified on the container, all fields of the container
247    /// will default to this value for `pii`.
248    pii: Option<Pii>,
249}
250
251impl TypeAttrs {
252    fn process_func_call_tokens(&self) -> TokenStream {
253        if let Some(ref func_name) = self.process_func {
254            let func_name = Ident::new(func_name, Span::call_site());
255            quote! {
256                __processor.#func_name(self, __meta, __state)?;
257            }
258        } else {
259            quote! {
260                self.process_child_values(__processor, __state)?;
261            }
262        }
263    }
264}
265
266fn parse_type_attributes(s: &synstructure::Structure<'_>) -> syn::Result<TypeAttrs> {
267    let mut rv = TypeAttrs::default();
268
269    for attr in &s.ast().attrs {
270        if !attr.path().is_ident("metastructure") {
271            continue;
272        }
273
274        attr.parse_nested_meta(|meta| {
275            let ident = meta.path.require_ident()?;
276
277            if ident == "process_func" {
278                let s = meta.value()?.parse::<LitStr>()?;
279                rv.process_func = Some(s.value());
280            } else if ident == "value_type" {
281                let s = meta.value()?.parse::<LitStr>()?;
282                rv.value_type.push(s.value());
283            } else if ident == "trim" {
284                let s = meta.value()?.parse::<LitBool>()?;
285                rv.trim = Some(s.value());
286            } else if ident == "pii" {
287                rv.pii = Some(meta.value()?.parse()?);
288            } else {
289                // Ignore other attributes used by `relay-protocol-derive`.
290                if !meta.input.peek(syn::Token![,]) {
291                    let _ = meta.value()?.parse::<Lit>()?;
292                }
293            }
294
295            Ok(())
296        })?;
297    }
298
299    Ok(rv)
300}
301
302#[derive(Clone, Debug)]
303enum Pii {
304    True,
305    False,
306    Maybe,
307    Dynamic(ExprPath),
308}
309
310impl syn::parse::Parse for Pii {
311    fn parse(input: ParseStream) -> syn::Result<Self> {
312        let head = input.fork();
313        let value = input.parse::<LitStr>()?;
314        let pii = match value.value().as_str() {
315            "true" => Self::True,
316            "false" => Self::False,
317            "maybe" => Self::Maybe,
318            _ => Self::Dynamic(value.parse().map_err(|_| {
319                head.error("Expected one of `true`, `false`, `maybe`, or a function name")
320            })?),
321        };
322        Ok(pii)
323    }
324}
325
326impl Pii {
327    fn as_tokens(&self) -> TokenStream {
328        match self {
329            Self::True => quote!(::relay_event_schema::processor::PiiMode::Static(
330                ::relay_event_schema::processor::Pii::True
331            )),
332            Self::False => quote!(::relay_event_schema::processor::PiiMode::Static(
333                ::relay_event_schema::processor::Pii::False
334            )),
335            Self::Maybe => quote!(::relay_event_schema::processor::PiiMode::Static(
336                ::relay_event_schema::processor::Pii::Maybe
337            )),
338            Self::Dynamic(fun) => quote!(::relay_event_schema::processor::PiiMode::Dynamic(#fun)),
339        }
340    }
341}
342
343#[derive(Clone, Debug)]
344enum Size {
345    Static(usize),
346    Dynamic(ExprPath),
347}
348
349impl syn::parse::Parse for Size {
350    fn parse(input: ParseStream) -> syn::Result<Self> {
351        if input.peek(LitInt) {
352            let lit = input.parse::<LitInt>()?;
353            return Ok(Self::Static(lit.base10_parse()?));
354        }
355
356        let head = input.fork();
357        let path = input
358            .parse::<LitStr>()?
359            .parse()
360            .map_err(|_| head.error("Expected a function name"))?;
361        Ok(Self::Dynamic(path))
362    }
363}
364
365impl Size {
366    fn as_tokens(&self) -> TokenStream {
367        match self {
368            Self::Static(n) => quote!(::relay_event_schema::processor::SizeMode::Static(Some(#n))),
369            Self::Dynamic(fun) => quote!(::relay_event_schema::processor::SizeMode::Dynamic(#fun)),
370        }
371    }
372}
373
374#[derive(Default)]
375struct FieldAttrs {
376    additional_properties: bool,
377    omit_from_schema: bool,
378    field_name: String,
379    flatten: bool,
380    required: Option<bool>,
381    nonempty: Option<bool>,
382    trim_whitespace: Option<bool>,
383    pii: Option<Pii>,
384    retain: bool,
385    characters: Option<TokenStream>,
386    max_chars: Option<Size>,
387    max_chars_allowance: Option<TokenStream>,
388    max_depth: Option<TokenStream>,
389    max_bytes: Option<Size>,
390    bytes_size: Option<Size>,
391    trim: Option<bool>,
392}
393
394impl FieldAttrs {
395    fn as_tokens(
396        &self,
397        type_attrs: &TypeAttrs,
398        inherit_from_field_attrs: Option<TokenStream>,
399    ) -> TokenStream {
400        let field_name = &self.field_name;
401
402        if self.required.is_none() && self.nonempty.is_some() {
403            panic!(
404                "`required` has to be explicitly set to \"true\" or \"false\" if `nonempty` is used."
405            );
406        }
407        let required = if let Some(required) = self.required {
408            quote!(#required)
409        } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
410            quote!(#parent_attrs.required)
411        } else {
412            quote!(false)
413        };
414
415        let nonempty = if let Some(nonempty) = self.nonempty {
416            quote!(#nonempty)
417        } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
418            quote!(#parent_attrs.nonempty)
419        } else {
420            quote!(false)
421        };
422
423        let trim_whitespace = if let Some(trim_whitespace) = self.trim_whitespace {
424            quote!(#trim_whitespace)
425        } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
426            quote!(#parent_attrs.trim_whitespace)
427        } else {
428            quote!(false)
429        };
430
431        let pii = if let Some(pii) = self.pii.as_ref().or(type_attrs.pii.as_ref()) {
432            pii.as_tokens()
433        } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
434            quote!(#parent_attrs.pii)
435        } else {
436            quote!(::relay_event_schema::processor::PiiMode::Static(
437                ::relay_event_schema::processor::Pii::False
438            ))
439        };
440
441        let trim = if let Some(trim) = self.trim.or(type_attrs.trim) {
442            quote!(#trim)
443        } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
444            quote!(#parent_attrs.trim)
445        } else {
446            quote!(true)
447        };
448
449        let retain = self.retain;
450
451        let max_chars = if let Some(ref max_chars) = self.max_chars {
452            max_chars.as_tokens()
453        } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
454            quote!(#parent_attrs.max_chars)
455        } else {
456            quote!(::relay_event_schema::processor::SizeMode::Static(None))
457        };
458
459        let max_chars_allowance = if let Some(ref max_chars_allowance) = self.max_chars_allowance {
460            quote!(#max_chars_allowance)
461        } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
462            quote!(#parent_attrs.max_chars_allowance)
463        } else {
464            quote!(0)
465        };
466
467        let max_depth = if let Some(ref max_depth) = self.max_depth {
468            quote!(Some(#max_depth))
469        } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
470            quote!(#parent_attrs.max_depth)
471        } else {
472            quote!(None)
473        };
474
475        let max_bytes = if let Some(ref max_bytes) = self.max_bytes {
476            max_bytes.as_tokens()
477        } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
478            quote!(#parent_attrs.max_bytes)
479        } else {
480            quote!(::relay_event_schema::processor::SizeMode::Static(None))
481        };
482
483        let bytes_size = if let Some(ref bytes_size) = self.bytes_size {
484            bytes_size.as_tokens()
485        } else {
486            quote!(::relay_event_schema::processor::SizeMode::Static(None))
487        };
488
489        let characters = if let Some(ref characters) = self.characters {
490            quote!(Some(#characters))
491        } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
492            quote!(#parent_attrs.characters)
493        } else {
494            quote!(None)
495        };
496
497        quote!({
498            ::relay_event_schema::processor::FieldAttrs {
499                name: Some(#field_name),
500                required: #required,
501                nonempty: #nonempty,
502                trim_whitespace: #trim_whitespace,
503                max_chars: #max_chars,
504                max_chars_allowance: #max_chars_allowance,
505                characters: #characters,
506                max_depth: #max_depth,
507                max_bytes: #max_bytes,
508                bytes_size: #bytes_size,
509                pii: #pii,
510                retain: #retain,
511                trim: #trim,
512            }
513        })
514    }
515}
516
517fn parse_field_attributes(
518    index: usize,
519    bi_ast: &syn::Field,
520    is_tuple_struct: &mut bool,
521) -> syn::Result<FieldAttrs> {
522    if bi_ast.ident.is_none() {
523        *is_tuple_struct = true;
524    } else if *is_tuple_struct {
525        panic!("invalid tuple struct");
526    }
527
528    let mut rv = FieldAttrs {
529        field_name: bi_ast
530            .ident
531            .as_ref()
532            .map(ToString::to_string)
533            .unwrap_or_else(|| index.to_string()),
534        ..Default::default()
535    };
536
537    for attr in &bi_ast.attrs {
538        if !attr.path().is_ident("metastructure") {
539            continue;
540        }
541
542        attr.parse_nested_meta(|meta| {
543            let ident = meta.path.require_ident()?;
544
545            if ident == "additional_properties" {
546                rv.additional_properties = true;
547            } else if ident == "omit_from_schema" {
548                rv.omit_from_schema = true;
549            } else if ident == "field" {
550                let s = meta.value()?.parse::<LitStr>()?;
551                rv.field_name = s.value();
552            } else if ident == "flatten" {
553                rv.flatten = true;
554            } else if ident == "required" {
555                let s = meta.value()?.parse::<LitBool>()?;
556                rv.required = Some(s.value());
557            } else if ident == "nonempty" {
558                let s = meta.value()?.parse::<LitBool>()?;
559                rv.nonempty = Some(s.value());
560            } else if ident == "trim_whitespace" {
561                let s = meta.value()?.parse::<LitBool>()?;
562                rv.trim_whitespace = Some(s.value());
563            } else if ident == "allow_chars" || ident == "deny_chars" {
564                if rv.characters.is_some() {
565                    return Err(meta.error("allow_chars and deny_chars are mutually exclusive"));
566                }
567                let s = meta.value()?.parse::<LitStr>()?;
568                rv.characters = Some(parse_character_set(ident, &s.value()));
569            } else if ident == "max_chars" {
570                rv.max_chars = Some(meta.value()?.parse()?);
571            } else if ident == "max_chars_allowance" {
572                let s = meta.value()?.parse::<LitInt>()?;
573                rv.max_chars_allowance = Some(quote!(#s));
574            } else if ident == "max_depth" {
575                let s = meta.value()?.parse::<LitInt>()?;
576                rv.max_depth = Some(quote!(#s));
577            } else if ident == "max_bytes" {
578                rv.max_bytes = Some(meta.value()?.parse()?);
579            } else if ident == "bytes_size" {
580                rv.bytes_size = Some(meta.value()?.parse()?);
581            } else if ident == "pii" {
582                rv.pii = Some(meta.value()?.parse()?);
583            } else if ident == "retain" {
584                let s = meta.value()?.parse::<LitBool>()?;
585                rv.retain = s.value();
586            } else if ident == "trim" {
587                let s = meta.value()?.parse::<LitBool>()?;
588                rv.trim = Some(s.value());
589            } else if ident == "legacy_alias" || ident == "skip_serialization" {
590                let _ = meta.value()?.parse::<Lit>()?;
591            } else {
592                return Err(meta.error("Unknown argument"));
593            }
594
595            Ok(())
596        })?;
597    }
598
599    Ok(rv)
600}
601
602fn is_newtype(variant: &synstructure::VariantInfo) -> bool {
603    variant.bindings().len() == 1 && variant.bindings()[0].ast().ident.is_none()
604}
605
606fn parse_character_set(ident: &Ident, value: &str) -> TokenStream {
607    #[derive(Clone, Copy)]
608    enum State {
609        Blank,
610        OpenRange(char),
611        MidRange(char),
612    }
613
614    let mut state = State::Blank;
615    let mut ranges = Vec::new();
616
617    for c in value.chars() {
618        match (state, c) {
619            (State::Blank, a) => state = State::OpenRange(a),
620            (State::OpenRange(a), '-') => state = State::MidRange(a),
621            (State::OpenRange(a), c) => {
622                state = State::OpenRange(c);
623                ranges.push(quote!(#a..=#a));
624            }
625            (State::MidRange(a), b) => {
626                ranges.push(quote!(#a..=#b));
627                state = State::Blank;
628            }
629        }
630    }
631
632    match state {
633        State::OpenRange(a) => ranges.push(quote!(#a..=#a)),
634        State::MidRange(a) => {
635            ranges.push(quote!(#a..=#a));
636            ranges.push(quote!('-'..='-'));
637        }
638        State::Blank => {}
639    }
640
641    let is_negative = ident == "deny_chars";
642
643    quote! {
644        ::relay_event_schema::processor::CharacterSet {
645            char_is_valid: |c: char| -> bool {
646                match c {
647                    #((#ranges) => !#is_negative,)*
648                    _ => #is_negative,
649                }
650            },
651            ranges: &[ #(#ranges,)* ],
652            is_negative: #is_negative,
653        }
654    }
655}