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