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 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 ::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 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 #[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 trim: Option<bool>,
244 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 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}