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::meta::ParseNestedMeta;
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 crate::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: crate::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 crate::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 crate::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 crate::processor::ProcessValue::process_child_values(
148 #ident,
149 __processor,
150 &#enter_state
151 )?;
152 })
153 .to_tokens(&mut body);
154 } else {
155 (quote! {
156 crate::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!( #(crate::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!(crate::processor::ProcessValue::value_type(#ident))
185 } else {
186 quote!(enumset::EnumSet::empty())
187 }
188 });
189
190 Ok(s.gen_impl(quote! {
191 #[automatically_derived]
192 gen impl crate::processor::ProcessValue for @Self {
193 fn value_type(&self) -> enumset::EnumSet<crate::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: &crate::processor::ProcessingState<'_>,
204 ) -> crate::processor::ProcessingResult
205 where
206 P: crate::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: &crate::processor::ProcessingState<'_>
221 ) -> crate::processor::ProcessingResult
222 where
223 P: crate::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 let s = meta.value()?.parse::<LitStr>()?;
288 rv.pii = parse_pii_value(s, &meta)?;
289 } else {
290 if !meta.input.peek(syn::Token![,]) {
292 let _ = meta.value()?.parse::<Lit>()?;
293 }
294 }
295
296 Ok(())
297 })?;
298 }
299
300 Ok(rv)
301}
302
303#[derive(Clone, Debug)]
304enum Pii {
305 True,
306 False,
307 Maybe,
308 Dynamic(ExprPath),
309}
310
311impl Pii {
312 fn as_tokens(&self) -> TokenStream {
313 match self {
314 Pii::True => quote!(crate::processor::PiiMode::Static(
315 crate::processor::Pii::True
316 )),
317 Pii::False => quote!(crate::processor::PiiMode::Static(
318 crate::processor::Pii::False
319 )),
320 Pii::Maybe => quote!(crate::processor::PiiMode::Static(
321 crate::processor::Pii::Maybe
322 )),
323 Pii::Dynamic(fun) => quote!(crate::processor::PiiMode::Dynamic(#fun)),
324 }
325 }
326}
327
328#[derive(Default)]
329struct FieldAttrs {
330 additional_properties: bool,
331 omit_from_schema: bool,
332 field_name: String,
333 flatten: bool,
334 required: Option<bool>,
335 nonempty: Option<bool>,
336 trim_whitespace: Option<bool>,
337 pii: Option<Pii>,
338 retain: bool,
339 characters: Option<TokenStream>,
340 max_chars: Option<TokenStream>,
341 max_chars_allowance: Option<TokenStream>,
342 max_depth: Option<TokenStream>,
343 max_bytes: Option<TokenStream>,
344 trim: Option<bool>,
345}
346
347impl FieldAttrs {
348 fn as_tokens(
349 &self,
350 type_attrs: &TypeAttrs,
351 inherit_from_field_attrs: Option<TokenStream>,
352 ) -> TokenStream {
353 let field_name = &self.field_name;
354
355 if self.required.is_none() && self.nonempty.is_some() {
356 panic!(
357 "`required` has to be explicitly set to \"true\" or \"false\" if `nonempty` is used."
358 );
359 }
360 let required = if let Some(required) = self.required {
361 quote!(#required)
362 } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
363 quote!(#parent_attrs.required)
364 } else {
365 quote!(false)
366 };
367
368 let nonempty = if let Some(nonempty) = self.nonempty {
369 quote!(#nonempty)
370 } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
371 quote!(#parent_attrs.nonempty)
372 } else {
373 quote!(false)
374 };
375
376 let trim_whitespace = if let Some(trim_whitespace) = self.trim_whitespace {
377 quote!(#trim_whitespace)
378 } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
379 quote!(#parent_attrs.trim_whitespace)
380 } else {
381 quote!(false)
382 };
383
384 let pii = if let Some(pii) = self.pii.as_ref().or(type_attrs.pii.as_ref()) {
385 pii.as_tokens()
386 } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
387 quote!(#parent_attrs.pii)
388 } else {
389 quote!(crate::processor::PiiMode::Static(
390 crate::processor::Pii::False
391 ))
392 };
393
394 let trim = if let Some(trim) = self.trim.or(type_attrs.trim) {
395 quote!(#trim)
396 } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
397 quote!(#parent_attrs.trim)
398 } else {
399 quote!(true)
400 };
401
402 let retain = self.retain;
403
404 let max_chars = if let Some(ref max_chars) = self.max_chars {
405 quote!(Some(#max_chars))
406 } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
407 quote!(#parent_attrs.max_chars)
408 } else {
409 quote!(None)
410 };
411
412 let max_chars_allowance = if let Some(ref max_chars_allowance) = self.max_chars_allowance {
413 quote!(#max_chars_allowance)
414 } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
415 quote!(#parent_attrs.max_chars_allowance)
416 } else {
417 quote!(0)
418 };
419
420 let max_depth = if let Some(ref max_depth) = self.max_depth {
421 quote!(Some(#max_depth))
422 } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
423 quote!(#parent_attrs.max_depth)
424 } else {
425 quote!(None)
426 };
427
428 let max_bytes = if let Some(ref max_bytes) = self.max_bytes {
429 quote!(Some(#max_bytes))
430 } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
431 quote!(#parent_attrs.max_bytes)
432 } else {
433 quote!(None)
434 };
435
436 let characters = if let Some(ref characters) = self.characters {
437 quote!(Some(#characters))
438 } else if let Some(ref parent_attrs) = inherit_from_field_attrs {
439 quote!(#parent_attrs.characters)
440 } else {
441 quote!(None)
442 };
443
444 quote!({
445 crate::processor::FieldAttrs {
446 name: Some(#field_name),
447 required: #required,
448 nonempty: #nonempty,
449 trim_whitespace: #trim_whitespace,
450 max_chars: #max_chars,
451 max_chars_allowance: #max_chars_allowance,
452 characters: #characters,
453 max_depth: #max_depth,
454 max_bytes: #max_bytes,
455 pii: #pii,
456 retain: #retain,
457 trim: #trim,
458 }
459 })
460 }
461}
462
463fn parse_field_attributes(
464 index: usize,
465 bi_ast: &syn::Field,
466 is_tuple_struct: &mut bool,
467) -> syn::Result<FieldAttrs> {
468 if bi_ast.ident.is_none() {
469 *is_tuple_struct = true;
470 } else if *is_tuple_struct {
471 panic!("invalid tuple struct");
472 }
473
474 let mut rv = FieldAttrs {
475 field_name: bi_ast
476 .ident
477 .as_ref()
478 .map(ToString::to_string)
479 .unwrap_or_else(|| index.to_string()),
480 ..Default::default()
481 };
482
483 for attr in &bi_ast.attrs {
484 if !attr.path().is_ident("metastructure") {
485 continue;
486 }
487
488 attr.parse_nested_meta(|meta| {
489 let ident = meta.path.require_ident()?;
490
491 if ident == "additional_properties" {
492 rv.additional_properties = true;
493 } else if ident == "omit_from_schema" {
494 rv.omit_from_schema = true;
495 } else if ident == "field" {
496 let s = meta.value()?.parse::<LitStr>()?;
497 rv.field_name = s.value();
498 } else if ident == "flatten" {
499 rv.flatten = true;
500 } else if ident == "required" {
501 let s = meta.value()?.parse::<LitBool>()?;
502 rv.required = Some(s.value());
503 } else if ident == "nonempty" {
504 let s = meta.value()?.parse::<LitBool>()?;
505 rv.nonempty = Some(s.value());
506 } else if ident == "trim_whitespace" {
507 let s = meta.value()?.parse::<LitBool>()?;
508 rv.trim_whitespace = Some(s.value());
509 } else if ident == "allow_chars" || ident == "deny_chars" {
510 if rv.characters.is_some() {
511 return Err(meta.error("allow_chars and deny_chars are mutually exclusive"));
512 }
513 let s = meta.value()?.parse::<LitStr>()?;
514 rv.characters = Some(parse_character_set(ident, &s.value()));
515 } else if ident == "max_chars" {
516 let s = meta.value()?.parse::<LitInt>()?;
517 rv.max_chars = Some(quote!(#s));
518 } else if ident == "max_chars_allowance" {
519 let s = meta.value()?.parse::<LitInt>()?;
520 rv.max_chars_allowance = Some(quote!(#s));
521 } else if ident == "max_depth" {
522 let s = meta.value()?.parse::<LitInt>()?;
523 rv.max_depth = Some(quote!(#s));
524 } else if ident == "max_bytes" {
525 let s = meta.value()?.parse::<LitInt>()?;
526 rv.max_bytes = Some(quote!(#s));
527 } else if ident == "pii" {
528 let s = meta.value()?.parse::<LitStr>()?;
529 rv.pii = parse_pii_value(s, &meta)?;
530 } else if ident == "retain" {
531 let s = meta.value()?.parse::<LitBool>()?;
532 rv.retain = s.value();
533 } else if ident == "trim" {
534 let s = meta.value()?.parse::<LitBool>()?;
535 rv.trim = Some(s.value());
536 } else if ident == "legacy_alias" || ident == "skip_serialization" {
537 let _ = meta.value()?.parse::<Lit>()?;
538 } else {
539 return Err(meta.error("Unknown argument"));
540 }
541
542 Ok(())
543 })?;
544 }
545
546 Ok(rv)
547}
548
549fn is_newtype(variant: &synstructure::VariantInfo) -> bool {
550 variant.bindings().len() == 1 && variant.bindings()[0].ast().ident.is_none()
551}
552
553fn parse_character_set(ident: &Ident, value: &str) -> TokenStream {
554 #[derive(Clone, Copy)]
555 enum State {
556 Blank,
557 OpenRange(char),
558 MidRange(char),
559 }
560
561 let mut state = State::Blank;
562 let mut ranges = Vec::new();
563
564 for c in value.chars() {
565 match (state, c) {
566 (State::Blank, a) => state = State::OpenRange(a),
567 (State::OpenRange(a), '-') => state = State::MidRange(a),
568 (State::OpenRange(a), c) => {
569 state = State::OpenRange(c);
570 ranges.push(quote!(#a..=#a));
571 }
572 (State::MidRange(a), b) => {
573 ranges.push(quote!(#a..=#b));
574 state = State::Blank;
575 }
576 }
577 }
578
579 match state {
580 State::OpenRange(a) => ranges.push(quote!(#a..=#a)),
581 State::MidRange(a) => {
582 ranges.push(quote!(#a..=#a));
583 ranges.push(quote!('-'..='-'));
584 }
585 State::Blank => {}
586 }
587
588 let is_negative = ident == "deny_chars";
589
590 quote! {
591 crate::processor::CharacterSet {
592 char_is_valid: |c: char| -> bool {
593 match c {
594 #((#ranges) => !#is_negative,)*
595 _ => #is_negative,
596 }
597 },
598 ranges: &[ #(#ranges,)* ],
599 is_negative: #is_negative,
600 }
601 }
602}
603
604fn parse_pii_value(value: LitStr, meta: &ParseNestedMeta) -> syn::Result<Option<Pii>> {
605 Ok(Some(match value.value().as_str() {
606 "true" => Pii::True,
607 "false" => Pii::False,
608 "maybe" => Pii::Maybe,
609 _ => Pii::Dynamic(value.parse().map_err(|_| {
610 meta.error("Expected one of `true`, `false`, `maybe`, or a function name")
611 })?),
612 }))
613}