1use std::ops::Range;
2
3use chrono::{DateTime, Duration, Utc};
4use relay_base_schema::events::EventType;
5use relay_common::time::UnixTimestamp;
6use relay_event_schema::processor::{
7 self, ProcessingAction, ProcessingResult, ProcessingState, Processor,
8};
9use relay_event_schema::protocol::{Event, Span, Timestamp, TraceContext};
10use relay_protocol::{Annotated, ErrorKind, Meta};
11
12use crate::{ClockDriftProcessor, TimestampProcessor};
13
14#[derive(Debug, Default)]
16pub struct EventValidationConfig {
17 pub received_at: Option<DateTime<Utc>>,
21
22 pub max_secs_in_past: Option<i64>,
26
27 pub max_secs_in_future: Option<i64>,
31
32 pub transaction_timestamp_range: Option<Range<UnixTimestamp>>,
37
38 pub is_validated: bool,
44}
45
46pub fn validate_event(
54 event: &mut Annotated<Event>,
55 config: &EventValidationConfig,
56) -> ProcessingResult {
57 if config.is_validated {
58 return Ok(());
59 }
60
61 let Annotated(Some(event), meta) = event else {
62 return Ok(());
63 };
64
65 normalize_timestamps(
68 event,
69 meta,
70 config.received_at,
71 config.max_secs_in_past,
72 config.max_secs_in_future,
73 );
74 TimestampProcessor.process_event(event, meta, ProcessingState::root())?;
75
76 if event.ty.value() == Some(&EventType::Transaction) {
77 validate_transaction_timestamps(event, config)?;
78 validate_trace_context(event)?;
79 end_all_spans(event);
82 validate_spans(event, None)?;
83 }
84
85 Ok(())
86}
87
88fn normalize_timestamps(
90 event: &mut Event,
91 meta: &mut Meta,
92 received_at: Option<DateTime<Utc>>,
93 max_secs_in_past: Option<i64>,
94 max_secs_in_future: Option<i64>,
95) {
96 let received_at = received_at.unwrap_or_else(Utc::now);
97
98 let mut sent_at = None;
99 let mut error_kind = ErrorKind::ClockDrift;
100
101 let _ = processor::apply(&mut event.timestamp, |timestamp, _meta| {
102 if let Some(secs) = max_secs_in_future {
103 if *timestamp > received_at + Duration::seconds(secs) {
104 error_kind = ErrorKind::FutureTimestamp;
105 sent_at = Some(*timestamp);
106 return Ok(());
107 }
108 }
109
110 if let Some(secs) = max_secs_in_past {
111 if *timestamp < received_at - Duration::seconds(secs) {
112 error_kind = ErrorKind::PastTimestamp;
113 sent_at = Some(*timestamp);
114 return Ok(());
115 }
116 }
117
118 Ok(())
119 });
120
121 let _ = ClockDriftProcessor::new(sent_at.map(|ts| ts.into_inner()), received_at)
122 .error_kind(error_kind)
123 .process_event(event, meta, ProcessingState::root());
124
125 event.received = Annotated::new(received_at.into());
127
128 if event.timestamp.value().is_none() {
129 event.timestamp.set_value(Some(received_at.into()));
130 }
131
132 let _ = processor::apply(&mut event.time_spent, |time_spent, _| {
133 validate_bounded_integer_field(*time_spent)
134 });
135}
136
137fn validate_transaction_timestamps(
139 transaction_event: &Event,
140 config: &EventValidationConfig,
141) -> ProcessingResult {
142 match (
143 transaction_event.start_timestamp.value(),
144 transaction_event.timestamp.value(),
145 ) {
146 (Some(start), Some(end)) => {
147 validate_timestamps(start, end, config.transaction_timestamp_range.as_ref())?;
148 Ok(())
149 }
150 (_, None) => Err(ProcessingAction::InvalidTransaction(
151 "timestamp hard-required for transaction events",
152 )),
153 (None, _) => Err(ProcessingAction::InvalidTransaction(
155 "start_timestamp hard-required for transaction events",
156 )),
157 }
158}
159
160fn validate_timestamps(
162 start: &Timestamp,
163 end: &Timestamp,
164 valid_range: Option<&Range<UnixTimestamp>>,
165) -> ProcessingResult {
166 if end < start {
167 return Err(ProcessingAction::InvalidTransaction(
168 "end timestamp is smaller than start timestamp",
169 ));
170 }
171
172 let Some(range) = valid_range else {
173 return Ok(());
174 };
175
176 let Some(timestamp) = UnixTimestamp::from_datetime(end.into_inner()) else {
177 return Err(ProcessingAction::InvalidTransaction(
178 "invalid unix timestamp",
179 ));
180 };
181 if !range.contains(×tamp) {
182 return Err(ProcessingAction::InvalidTransaction(
183 "timestamp is out of the valid range for metrics",
184 ));
185 }
186
187 Ok(())
188}
189
190fn validate_trace_context(transaction: &Event) -> ProcessingResult {
198 let Some(trace_context) = transaction.context::<TraceContext>() else {
199 return Err(ProcessingAction::InvalidTransaction(
200 "missing valid trace context",
201 ));
202 };
203
204 if trace_context.trace_id.value().is_none() {
205 return Err(ProcessingAction::InvalidTransaction(
206 "trace context is missing trace_id",
207 ));
208 }
209
210 if trace_context.span_id.value().is_none() {
211 return Err(ProcessingAction::InvalidTransaction(
212 "trace context is missing span_id",
213 ));
214 }
215
216 Ok(())
217}
218
219fn end_all_spans(event: &mut Event) {
221 let spans = event.spans.value_mut().get_or_insert_with(Vec::new);
222 for span in spans {
223 if let Some(span) = span.value_mut() {
224 if span.timestamp.value().is_none() {
225 span.timestamp.set_value(event.timestamp.value().cloned());
227 span.status =
228 Annotated::new(relay_base_schema::spans::SpanStatus::DeadlineExceeded);
229 }
230 }
231 }
232}
233
234fn validate_spans(
239 transaction: &Event,
240 timestamp_range: Option<&Range<UnixTimestamp>>,
241) -> ProcessingResult {
242 let Some(spans) = transaction.spans.value() else {
243 return Ok(());
244 };
245
246 for span in spans {
247 if let Some(span) = span.value() {
248 validate_span(span, timestamp_range)?;
249 } else {
250 return Err(ProcessingAction::InvalidTransaction(
251 "spans must be valid in transaction event",
252 ));
253 }
254 }
255
256 Ok(())
257}
258
259pub fn validate_span(
269 span: &Span,
270 timestamp_range: Option<&Range<UnixTimestamp>>,
271) -> ProcessingResult {
272 match (span.start_timestamp.value(), span.timestamp.value()) {
273 (Some(start), Some(end)) => {
274 validate_timestamps(start, end, timestamp_range)?;
275 }
276 (_, None) => {
277 return Err(ProcessingAction::InvalidTransaction(
279 "span is missing timestamp",
280 ));
281 }
282 (None, _) => {
283 return Err(ProcessingAction::InvalidTransaction(
285 "span is missing start_timestamp",
286 ));
287 }
288 }
289
290 if span.trace_id.value().is_none() {
291 return Err(ProcessingAction::InvalidTransaction(
292 "span is missing trace_id",
293 ));
294 }
295
296 if span.span_id.value().is_none() {
297 return Err(ProcessingAction::InvalidTransaction(
298 "span is missing span_id",
299 ));
300 }
301
302 Ok(())
303}
304
305fn validate_bounded_integer_field(value: u64) -> ProcessingResult {
307 if value < 2_147_483_647 {
308 Ok(())
309 } else {
310 Err(ProcessingAction::DeleteValueHard)
311 }
312}
313
314#[cfg(test)]
315mod tests {
316 use chrono::TimeZone;
317 use relay_base_schema::spans::SpanStatus;
318 use relay_event_schema::protocol::{Contexts, SpanId};
319 use relay_protocol::{Object, get_value};
320
321 use super::*;
322
323 fn new_test_event() -> Annotated<Event> {
324 let start = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap();
325 let end = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 10).unwrap();
326 Annotated::new(Event {
327 ty: Annotated::new(EventType::Transaction),
328 transaction: Annotated::new("/".to_owned()),
329 start_timestamp: Annotated::new(start.into()),
330 timestamp: Annotated::new(end.into()),
331 contexts: {
332 let mut contexts = Contexts::new();
333 contexts.add(TraceContext {
334 trace_id: Annotated::new("4c79f60c11214eb38604f4ae0781bfb2".parse().unwrap()),
335 span_id: Annotated::new(SpanId("fa90fdead5f74053".into())),
336 op: Annotated::new("http.server".to_owned()),
337 ..Default::default()
338 });
339 Annotated::new(contexts)
340 },
341 spans: Annotated::new(vec![Annotated::new(Span {
342 start_timestamp: Annotated::new(start.into()),
343 timestamp: Annotated::new(end.into()),
344 trace_id: Annotated::new("4c79f60c11214eb38604f4ae0781bfb2".parse().unwrap()),
345 span_id: Annotated::new(SpanId("fa90fdead5f74053".into())),
346 op: Annotated::new("db.statement".to_owned()),
347 ..Default::default()
348 })]),
349 ..Default::default()
350 })
351 }
352
353 #[test]
354 fn test_timestamp_added_if_missing() {
355 let mut event = Annotated::new(Event::default());
356 assert!(get_value!(event.timestamp).is_none());
357 assert!(validate_event(&mut event, &EventValidationConfig::default()).is_ok());
358 assert!(get_value!(event.timestamp).is_some());
359 }
360
361 #[test]
362 fn test_discards_when_timestamp_out_of_range() {
363 let mut event = new_test_event();
364
365 assert!(matches!(
366 validate_event(
367 &mut event,
368 &EventValidationConfig {
369 transaction_timestamp_range: Some(UnixTimestamp::now()..UnixTimestamp::now()),
370 is_validated: false,
371 ..Default::default()
372 }
373 ),
374 Err(ProcessingAction::InvalidTransaction(
375 "timestamp is out of the valid range for metrics"
376 ))
377 ));
378 }
379
380 #[test]
381 fn test_discards_when_missing_start_timestamp() {
382 let mut event = Annotated::new(Event {
383 ty: Annotated::new(EventType::Transaction),
384 timestamp: Annotated::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into()),
385 ..Default::default()
386 });
387
388 assert_eq!(
389 validate_event(&mut event, &EventValidationConfig::default()),
390 Err(ProcessingAction::InvalidTransaction(
391 "start_timestamp hard-required for transaction events"
392 ))
393 );
394 }
395
396 #[test]
397 fn test_discards_on_missing_contexts_map() {
398 let mut event = Annotated::new(Event {
399 ty: Annotated::new(EventType::Transaction),
400 timestamp: Annotated::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into()),
401 start_timestamp: Annotated::new(
402 Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(),
403 ),
404 ..Default::default()
405 });
406
407 assert_eq!(
408 validate_event(
409 &mut event,
410 &EventValidationConfig {
411 transaction_timestamp_range: None,
412 is_validated: false,
413 ..Default::default()
414 }
415 ),
416 Err(ProcessingAction::InvalidTransaction(
417 "missing valid trace context"
418 ))
419 );
420 }
421
422 #[test]
423 fn test_discards_on_missing_context() {
424 let mut event = Annotated::new(Event {
425 ty: Annotated::new(EventType::Transaction),
426 timestamp: Annotated::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into()),
427 start_timestamp: Annotated::new(
428 Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(),
429 ),
430 contexts: Annotated::new(Contexts::new()),
431 ..Default::default()
432 });
433
434 assert_eq!(
435 validate_event(
436 &mut event,
437 &EventValidationConfig {
438 transaction_timestamp_range: None,
439 is_validated: false,
440 ..Default::default()
441 }
442 ),
443 Err(ProcessingAction::InvalidTransaction(
444 "missing valid trace context"
445 ))
446 );
447 }
448
449 #[test]
450 fn test_discards_on_null_context() {
451 let mut event = Annotated::new(Event {
452 ty: Annotated::new(EventType::Transaction),
453 timestamp: Annotated::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into()),
454 start_timestamp: Annotated::new(
455 Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(),
456 ),
457 contexts: Annotated::new(Contexts({
458 let mut contexts = Object::new();
459 contexts.insert("trace".to_owned(), Annotated::empty());
460 contexts
461 })),
462 ..Default::default()
463 });
464
465 assert_eq!(
466 validate_event(&mut event, &EventValidationConfig::default()),
467 Err(ProcessingAction::InvalidTransaction(
468 "missing valid trace context"
469 ))
470 );
471 }
472
473 #[test]
474 fn test_discards_on_missing_trace_id_in_context() {
475 let mut event = Annotated::new(Event {
476 ty: Annotated::new(EventType::Transaction),
477 timestamp: Annotated::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into()),
478 start_timestamp: Annotated::new(
479 Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(),
480 ),
481 contexts: {
482 let mut contexts = Contexts::new();
483 contexts.add(TraceContext::default());
484 Annotated::new(contexts)
485 },
486 ..Default::default()
487 });
488
489 assert_eq!(
490 validate_event(&mut event, &EventValidationConfig::default()),
491 Err(ProcessingAction::InvalidTransaction(
492 "trace context is missing trace_id"
493 ))
494 );
495 }
496
497 #[test]
498 fn test_discards_on_missing_span_id_in_context() {
499 let mut event = Annotated::new(Event {
500 ty: Annotated::new(EventType::Transaction),
501 timestamp: Annotated::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into()),
502 start_timestamp: Annotated::new(
503 Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(),
504 ),
505 contexts: {
506 let mut contexts = Contexts::new();
507 contexts.add(TraceContext {
508 trace_id: Annotated::new("4c79f60c11214eb38604f4ae0781bfb2".parse().unwrap()),
509 ..Default::default()
510 });
511 Annotated::new(contexts)
512 },
513 ..Default::default()
514 });
515
516 assert_eq!(
517 validate_event(&mut event, &EventValidationConfig::default()),
518 Err(ProcessingAction::InvalidTransaction(
519 "trace context is missing span_id"
520 ))
521 );
522 }
523
524 #[test]
525 fn test_discards_transaction_event_with_nulled_out_span() {
526 let mut event = Annotated::new(Event {
527 ty: Annotated::new(EventType::Transaction),
528 timestamp: Annotated::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into()),
529 start_timestamp: Annotated::new(
530 Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(),
531 ),
532 contexts: {
533 let mut contexts = Contexts::new();
534 contexts.add(TraceContext {
535 trace_id: Annotated::new("4c79f60c11214eb38604f4ae0781bfb2".parse().unwrap()),
536 span_id: Annotated::new(SpanId("fa90fdead5f74053".into())),
537 op: Annotated::new("http.server".to_owned()),
538 ..Default::default()
539 });
540 Annotated::new(contexts)
541 },
542 spans: Annotated::new(vec![Annotated::empty()]),
543 ..Default::default()
544 });
545
546 assert_eq!(
547 validate_event(&mut event, &EventValidationConfig::default()),
548 Err(ProcessingAction::InvalidTransaction(
549 "spans must be valid in transaction event"
550 ))
551 );
552 }
553
554 #[test]
555 fn test_discards_transaction_event_with_span_with_missing_start_timestamp() {
556 let mut event = Annotated::new(Event {
557 ty: Annotated::new(EventType::Transaction),
558 timestamp: Annotated::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into()),
559 start_timestamp: Annotated::new(
560 Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(),
561 ),
562 contexts: {
563 let mut contexts = Contexts::new();
564 contexts.add(TraceContext {
565 trace_id: Annotated::new("4c79f60c11214eb38604f4ae0781bfb2".parse().unwrap()),
566 span_id: Annotated::new(SpanId("fa90fdead5f74053".into())),
567 op: Annotated::new("http.server".to_owned()),
568 ..Default::default()
569 });
570 Annotated::new(contexts)
571 },
572 spans: Annotated::new(vec![Annotated::new(Span {
573 timestamp: Annotated::new(
574 Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(),
575 ),
576 ..Default::default()
577 })]),
578 ..Default::default()
579 });
580
581 assert_eq!(
582 validate_event(&mut event, &EventValidationConfig::default()),
583 Err(ProcessingAction::InvalidTransaction(
584 "span is missing start_timestamp"
585 ))
586 );
587 }
588
589 #[test]
590 fn test_discards_transaction_event_with_span_with_missing_trace_id() {
591 let mut event = Annotated::new(Event {
592 ty: Annotated::new(EventType::Transaction),
593 timestamp: Annotated::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into()),
594 start_timestamp: Annotated::new(
595 Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(),
596 ),
597 contexts: {
598 let mut contexts = Contexts::new();
599 contexts.add(TraceContext {
600 trace_id: Annotated::new("4c79f60c11214eb38604f4ae0781bfb2".parse().unwrap()),
601 span_id: Annotated::new(SpanId("fa90fdead5f74053".into())),
602 op: Annotated::new("http.server".to_owned()),
603 ..Default::default()
604 });
605 Annotated::new(contexts)
606 },
607 spans: Annotated::new(vec![Annotated::new(Span {
608 timestamp: Annotated::new(
609 Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(),
610 ),
611 start_timestamp: Annotated::new(
612 Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(),
613 ),
614 ..Default::default()
615 })]),
616 ..Default::default()
617 });
618
619 assert_eq!(
620 validate_event(&mut event, &EventValidationConfig::default()),
621 Err(ProcessingAction::InvalidTransaction(
622 "span is missing trace_id"
623 ))
624 );
625 }
626
627 #[test]
628 fn test_discards_transaction_event_with_span_with_missing_span_id() {
629 let mut event = Annotated::new(Event {
630 ty: Annotated::new(EventType::Transaction),
631 timestamp: Annotated::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into()),
632 start_timestamp: Annotated::new(
633 Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(),
634 ),
635 contexts: {
636 let mut contexts = Contexts::new();
637 contexts.add(TraceContext {
638 trace_id: Annotated::new("4c79f60c11214eb38604f4ae0781bfb2".parse().unwrap()),
639 span_id: Annotated::new(SpanId("fa90fdead5f74053".into())),
640 op: Annotated::new("http.server".to_owned()),
641 ..Default::default()
642 });
643 Annotated::new(contexts)
644 },
645 spans: Annotated::new(vec![Annotated::new(Span {
646 timestamp: Annotated::new(
647 Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(),
648 ),
649 start_timestamp: Annotated::new(
650 Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(),
651 ),
652 trace_id: Annotated::new("4c79f60c11214eb38604f4ae0781bfb2".parse().unwrap()),
653 ..Default::default()
654 })]),
655 ..Default::default()
656 });
657
658 assert_eq!(
659 validate_event(&mut event, &EventValidationConfig::default()),
660 Err(ProcessingAction::InvalidTransaction(
661 "span is missing span_id"
662 ))
663 );
664 }
665
666 #[test]
667 fn test_reject_stale_transaction() {
668 let json = r#"{
669 "event_id": "52df9022835246eeb317dbd739ccd059",
670 "start_timestamp": -2,
671 "timestamp": -1
672}"#;
673 let mut transaction = Annotated::<Event>::from_json(json).unwrap();
674 let res = validate_event(&mut transaction, &EventValidationConfig::default());
675 assert_eq!(
676 res.unwrap_err().to_string(),
677 "invalid transaction event: timestamp is too stale"
678 );
679 }
680
681 #[test]
684 fn test_accept_recent_transactions_with_stale_timestamps() {
685 let config = EventValidationConfig {
686 received_at: Some(Utc::now()),
687 max_secs_in_past: Some(2),
688 max_secs_in_future: Some(1),
689 is_validated: false,
690 ..Default::default()
691 };
692
693 let json = r#"{
694 "event_id": "52df9022835246eeb317dbd739ccd059",
695 "transaction": "I have a stale timestamp, but I'm recent!",
696 "start_timestamp": -2,
697 "timestamp": -1
698}"#;
699 let mut event = Annotated::<Event>::from_json(json).unwrap();
700
701 assert!(validate_event(&mut event, &config).is_ok());
702 }
703
704 #[test]
710 fn test_reject_stale_transactions_after_timestamp_normalization() {
711 let now = Utc::now();
712 let config = EventValidationConfig {
713 received_at: Some(now),
714 max_secs_in_past: Some(2),
715 max_secs_in_future: Some(1),
716 is_validated: false,
717 ..Default::default()
718 };
719
720 let json = format!(
721 r#"{{
722 "event_id": "52df9022835246eeb317dbd739ccd059",
723 "transaction": "clockdrift is not enough to accept me :(",
724 "start_timestamp": -62135811111,
725 "timestamp": {}
726 }}"#,
727 now.timestamp()
728 );
729 let mut event = Annotated::<Event>::from_json(json.as_str()).unwrap();
730
731 assert_eq!(
732 validate_event(&mut event, &config).unwrap_err().to_string(),
733 "invalid transaction event: timestamp is too stale"
734 );
735 }
736
737 #[test]
739 fn test_accept_transactions_with_unfinished_spans() {
740 let json = r#"{
741 "event_id": "52df9022835246eeb317dbd739ccd059",
742 "type": "transaction",
743 "transaction": "I have a stale timestamp, but I'm recent!",
744 "start_timestamp": 1,
745 "timestamp": 2,
746 "contexts": {
747 "trace": {
748 "trace_id": "ff62a8b040f340bda5d830223def1d81",
749 "span_id": "bd429c44b67a3eb4"
750 }
751 },
752 "spans": [
753 {
754 "span_id": "bd429c44b67a3eb4",
755 "start_timestamp": 1,
756 "timestamp": null,
757 "trace_id": "ff62a8b040f340bda5d830223def1d81"
758 }
759 ]
760}"#;
761 let mut event = Annotated::<Event>::from_json(json).unwrap();
762
763 assert!(validate_event(&mut event, &EventValidationConfig::default()).is_ok());
764
765 let event = get_value!(event!);
766 let spans = &event.spans;
767 let span = get_value!(spans[0]!);
768
769 assert_eq!(span.timestamp, event.timestamp);
770 assert_eq!(span.status.value().unwrap(), &SpanStatus::DeadlineExceeded);
771 }
772
773 #[test]
775 fn test_finish_spans_with_normalized_transaction_end_timestamp() {
776 let json = r#"{
777 "event_id": "52df9022835246eeb317dbd739ccd059",
778 "type": "transaction",
779 "transaction": "I have a stale timestamp, but I'm recent!",
780 "start_timestamp": 946731000,
781 "timestamp": 946731555,
782 "contexts": {
783 "trace": {
784 "trace_id": "ff62a8b040f340bda5d830223def1d81",
785 "span_id": "bd429c44b67a3eb4"
786 }
787 },
788 "spans": [
789 {
790 "span_id": "bd429c44b67a3eb4",
791 "start_timestamp": 946731000,
792 "timestamp": null,
793 "trace_id": "ff62a8b040f340bda5d830223def1d81"
794 }
795 ]
796}"#;
797 let mut event = Annotated::<Event>::from_json(json).unwrap();
798
799 validate_event(
800 &mut event,
801 &EventValidationConfig {
802 received_at: Some(Utc::now()),
803 max_secs_in_past: Some(2),
804 max_secs_in_future: Some(1),
805 is_validated: false,
806 ..Default::default()
807 },
808 )
809 .unwrap();
810 validate_event(&mut event, &EventValidationConfig::default()).unwrap();
811
812 let event = get_value!(event!);
813 let spans = &event.spans;
814 let span = get_value!(spans[0]!);
815
816 assert_eq!(span.timestamp.value(), event.timestamp.value());
817 }
818
819 #[test]
820 fn test_skip_transaction_validation_on_renormalization() {
821 let json = r#"{
822 "event_id": "52df9022835246eeb317dbd739ccd059",
823 "type": "transaction",
824 "transaction": "I'm invalid because I don't have any timestamps!"
825}"#;
826 let mut event = Annotated::<Event>::from_json(json).unwrap();
827
828 assert!(validate_event(&mut event, &EventValidationConfig::default()).is_err());
829 assert!(
830 validate_event(
831 &mut event,
832 &EventValidationConfig {
833 is_validated: true,
834 ..Default::default()
835 }
836 )
837 .is_ok()
838 );
839 }
840
841 #[test]
842 fn test_skip_event_timestamp_validation_on_renormalization() {
843 let json = r#"{
844 "event_id": "52df9022835246eeb317dbd739ccd059",
845 "transaction": "completely outdated transaction",
846 "start_timestamp": -2,
847 "timestamp": -1
848}"#;
849 let mut event = Annotated::<Event>::from_json(json).unwrap();
850
851 assert!(validate_event(&mut event, &EventValidationConfig::default()).is_err());
852 assert!(
853 validate_event(
854 &mut event,
855 &EventValidationConfig {
856 is_validated: true,
857 ..Default::default()
858 }
859 )
860 .is_ok()
861 );
862 }
863}