1use std::fmt::{self, Write};
2use std::future::Future;
3use std::marker::PhantomData;
4
5use relay_profiling::ProfileType;
6use relay_quotas::{
7 DataCategory, ItemScoping, QuotaScope, RateLimit, RateLimitScope, RateLimits, ReasonCode,
8 Scoping,
9};
10use smallvec::SmallVec;
11
12use crate::envelope::{AttachmentParentType, AttachmentType, Envelope, Item, ItemType};
13use crate::integrations::Integration;
14use crate::managed::{Managed, ManagedEnvelope};
15use crate::services::outcome::Outcome;
16
17pub const RATE_LIMITS_HEADER: &str = "X-Sentry-Rate-Limits";
19
20pub fn format_rate_limits(rate_limits: &RateLimits) -> String {
22 let mut header = String::new();
23
24 for rate_limit in rate_limits {
25 if !header.is_empty() {
26 header.push_str(", ");
27 }
28
29 write!(header, "{}:", rate_limit.retry_after.remaining_seconds()).ok();
30
31 for (index, category) in rate_limit.categories.iter().enumerate() {
32 if index > 0 {
33 header.push(';');
34 }
35 write!(header, "{category}").ok();
36 }
37
38 write!(header, ":{}", rate_limit.scope.name()).ok();
39
40 if let Some(ref reason_code) = rate_limit.reason_code {
41 write!(header, ":{reason_code}").ok();
42 } else if !rate_limit.namespaces.is_empty() {
43 write!(header, ":").ok(); }
45
46 for (index, namespace) in rate_limit.namespaces.iter().enumerate() {
47 header.push(if index == 0 { ':' } else { ';' });
48 write!(header, "{namespace}").ok();
49 }
50 }
51
52 header
53}
54
55pub fn parse_rate_limits(scoping: &Scoping, string: &str) -> RateLimits {
57 let mut rate_limits = RateLimits::new();
58
59 for limit in string.split(',') {
60 let limit = limit.trim();
61 if limit.is_empty() {
62 continue;
63 }
64
65 let mut components = limit.split(':');
66
67 let retry_after = match components.next().and_then(|s| s.parse().ok()) {
68 Some(retry_after) => retry_after,
69 None => continue,
70 };
71
72 let categories = components
73 .next()
74 .unwrap_or("")
75 .split(';')
76 .filter(|category| !category.is_empty())
77 .map(DataCategory::from_name)
78 .collect();
79
80 let quota_scope = QuotaScope::from_name(components.next().unwrap_or(""));
81 let scope = RateLimitScope::for_quota(*scoping, quota_scope);
82
83 let reason_code = components
84 .next()
85 .filter(|s| !s.is_empty())
86 .map(ReasonCode::new);
87
88 let namespace = components
89 .next()
90 .unwrap_or("")
91 .split(';')
92 .filter(|s| !s.is_empty())
93 .filter_map(|s| s.parse().ok())
94 .collect();
95
96 rate_limits.add(RateLimit {
97 categories,
98 scope,
99 reason_code,
100 retry_after,
101 namespaces: namespace,
102 });
103 }
104
105 rate_limits
106}
107
108fn infer_event_category(item: &Item) -> Option<DataCategory> {
116 match item.ty() {
117 ItemType::Event => Some(DataCategory::Error),
118 ItemType::Transaction => Some(DataCategory::Transaction),
119 ItemType::Security | ItemType::RawSecurity => Some(DataCategory::Security),
120 ItemType::UnrealReport => Some(DataCategory::Error),
121 ItemType::UserReportV2 => Some(DataCategory::UserReportV2),
122 ItemType::Attachment if item.creates_event() => Some(DataCategory::Error),
123 ItemType::Attachment => None,
124 ItemType::Session => None,
125 ItemType::Sessions => None,
126 ItemType::Statsd => None,
127 ItemType::MetricBuckets => None,
128 ItemType::FormData => None,
129 ItemType::UserReport => None,
130 ItemType::Profile => None,
131 ItemType::ReplayEvent => None,
132 ItemType::ReplayRecording => None,
133 ItemType::ReplayVideo => None,
134 ItemType::ClientReport => None,
135 ItemType::CheckIn => None,
136 ItemType::Nel => None,
137 ItemType::Log => None,
138 ItemType::TraceMetric => None,
139 ItemType::Span => None,
140 ItemType::ProfileChunk => None,
141 ItemType::Integration => None,
142 ItemType::Unknown(_) => None,
143 }
144}
145
146#[derive(Clone, Copy, Debug, Default)]
150pub struct AttachmentQuantity {
151 pub count: usize,
153 pub bytes: usize,
155}
156
157impl AttachmentQuantity {
158 pub fn is_empty(&self) -> bool {
159 self.count == 0 && self.bytes == 0
160 }
161}
162
163#[derive(Clone, Copy, Debug, Default)]
167pub struct AttachmentQuantities {
168 pub event: AttachmentQuantity,
172 pub trace: AttachmentQuantity,
174 pub span: AttachmentQuantity,
176}
177
178impl AttachmentQuantities {
179 pub fn count(&self) -> usize {
181 let AttachmentQuantities { event, trace, span } = self;
182 event.count + trace.count + span.count
183 }
184
185 pub fn bytes(&self) -> usize {
187 let AttachmentQuantities { event, trace, span } = self;
188 event.bytes + trace.bytes + span.bytes
189 }
190}
191
192#[derive(Clone, Copy, Debug, Default)]
194pub struct ProfileQuantities {
195 pub backend: usize,
197 pub ui: usize,
199 pub total: usize,
202}
203
204#[non_exhaustive]
209#[derive(Clone, Copy, Debug, Default)]
210pub struct EnvelopeSummary {
211 pub event_category: Option<DataCategory>,
213
214 pub attachment_quantities: AttachmentQuantities,
216
217 pub session_quantity: usize,
219
220 pub profile_quantity: ProfileQuantities,
222
223 pub replay_quantity: usize,
225
226 pub user_report_quantity: usize,
228
229 pub monitor_quantity: usize,
231
232 pub log_item_quantity: usize,
234
235 pub log_byte_quantity: usize,
237
238 pub secondary_transaction_quantity: usize,
247
248 pub secondary_span_quantity: usize,
250
251 pub span_quantity: usize,
253
254 pub has_plain_attachments: bool,
256
257 pub payload_size: usize,
259
260 pub profile_chunk_quantity: usize,
262 pub profile_chunk_ui_quantity: usize,
264
265 pub trace_metric_quantity: usize,
267
268 pub trace_metric_byte_quantity: usize,
270}
271
272impl EnvelopeSummary {
273 pub fn empty() -> Self {
275 Self::default()
276 }
277
278 pub fn compute(envelope: &Envelope) -> Self {
280 Self::compute_items(envelope.items())
281 }
282
283 pub fn compute_items<'a>(items: impl IntoIterator<Item = &'a Item>) -> Self {
284 let mut summary = Self::empty();
285
286 for item in items {
287 if item.creates_event() {
288 summary.infer_category(item);
289 } else if item.ty() == &ItemType::Attachment {
290 summary.has_plain_attachments = true;
292 }
293
294 if item.rate_limited() {
297 continue;
298 }
299
300 if let Some(source_quantities) = item.source_quantities() {
301 summary.secondary_transaction_quantity += source_quantities.transactions;
302 summary.secondary_span_quantity += source_quantities.spans;
303 }
304
305 summary.payload_size += item.len();
306
307 summary.add_quantities(item);
308
309 if item.ty() == &ItemType::UserReport {
312 summary.user_report_quantity += 1;
313 }
314 }
315
316 summary
317 }
318
319 fn add_quantities(&mut self, item: &Item) {
320 if item.attachment_type() == Some(AttachmentType::NintendoSwitchDyingMessage) {
327 return;
328 }
329
330 for (category, quantity) in item.quantities() {
331 let target_quantity = match category {
332 DataCategory::Attachment => match item.attachment_parent_type() {
333 AttachmentParentType::Span => &mut self.attachment_quantities.span.bytes,
334 AttachmentParentType::Trace => &mut self.attachment_quantities.trace.bytes,
335 AttachmentParentType::Event => &mut self.attachment_quantities.event.bytes,
336 },
337 DataCategory::AttachmentItem => match item.attachment_parent_type() {
338 AttachmentParentType::Span => &mut self.attachment_quantities.span.count,
339 AttachmentParentType::Trace => &mut self.attachment_quantities.trace.count,
340 AttachmentParentType::Event => &mut self.attachment_quantities.event.count,
341 },
342 DataCategory::Session => &mut self.session_quantity,
343 DataCategory::Profile => &mut self.profile_quantity.total,
344 DataCategory::ProfileBackend => &mut self.profile_quantity.backend,
345 DataCategory::ProfileUi => &mut self.profile_quantity.ui,
346 DataCategory::Replay => &mut self.replay_quantity,
347 DataCategory::DoNotUseReplayVideo => &mut self.replay_quantity,
348 DataCategory::Monitor => &mut self.monitor_quantity,
349 DataCategory::Span => &mut self.span_quantity,
350 DataCategory::TraceMetric => &mut self.trace_metric_quantity,
351 DataCategory::TraceMetricByte => &mut self.trace_metric_byte_quantity,
352 DataCategory::LogItem => &mut self.log_item_quantity,
353 DataCategory::LogByte => &mut self.log_byte_quantity,
354 DataCategory::ProfileChunk => &mut self.profile_chunk_quantity,
355 DataCategory::ProfileChunkUi => &mut self.profile_chunk_ui_quantity,
356 _ => continue,
358 };
359 *target_quantity += quantity;
360 }
361 }
362
363 fn infer_category(&mut self, item: &Item) {
368 if matches!(self.event_category, None | Some(DataCategory::Default))
369 && let Some(category) = infer_event_category(item)
370 {
371 self.event_category = Some(category);
372 }
373 }
374
375 pub fn has_span_dependent_items(&self) -> bool {
380 !self.attachment_quantities.span.is_empty()
381 }
382}
383
384#[derive(Debug, Default, PartialEq)]
386#[cfg_attr(test, derive(Clone))]
387pub struct CategoryLimit {
388 category: Option<DataCategory>,
390 extra_outcome_categories: SmallVec<[DataCategory; 1]>,
392 quantity: usize,
396 reason_code: Option<ReasonCode>,
400}
401
402impl CategoryLimit {
403 fn new(category: DataCategory, quantity: usize, rate_limit: Option<&RateLimit>) -> Self {
407 match rate_limit {
408 Some(limit) => Self {
409 category: Some(category),
410 quantity,
411 extra_outcome_categories: Default::default(),
412 reason_code: limit.reason_code.clone(),
413 },
414 None => Self::default(),
415 }
416 }
417
418 pub fn add_outcome_category(mut self, category: DataCategory) -> Self {
420 self.extra_outcome_categories.push(category);
421 self
422 }
423
424 pub fn clone_for(&self, category: DataCategory, quantity: usize) -> CategoryLimit {
426 if !self.is_active() {
427 return Self::default();
428 }
429
430 Self {
431 category: Some(category),
432 extra_outcome_categories: Default::default(),
433 quantity,
434 reason_code: self.reason_code.clone(),
435 }
436 }
437
438 pub fn is_active(&self) -> bool {
442 self.category.is_some()
443 }
444
445 fn outcomes(self) -> impl Iterator<Item = (Outcome, DataCategory, usize)> {
446 let Self {
447 category,
448 extra_outcome_categories,
449 quantity,
450 reason_code,
451 } = self;
452
453 if category.is_none() || quantity == 0 {
454 return either::Either::Left(std::iter::empty());
455 }
456
457 let outcomes = std::iter::chain(category, extra_outcome_categories).map(move |category| {
458 (
459 Outcome::RateLimited(reason_code.clone()),
460 category,
461 quantity,
462 )
463 });
464
465 either::Either::Right(outcomes)
466 }
467}
468
469#[derive(Default, Debug)]
471#[cfg_attr(test, derive(Clone))]
472pub struct AttachmentLimits {
473 pub bytes: CategoryLimit,
475 pub count: CategoryLimit,
477}
478
479impl AttachmentLimits {
480 fn is_active(&self) -> bool {
481 self.bytes.is_active() || self.count.is_active()
482 }
483}
484
485#[derive(Default, Debug)]
489#[cfg_attr(test, derive(Clone))]
490pub struct AttachmentsLimits {
491 pub event: AttachmentLimits,
493 pub trace: AttachmentLimits,
495 pub span: AttachmentLimits,
497}
498
499#[derive(Default, Debug)]
501#[cfg_attr(test, derive(Clone))]
502pub struct Enforcement {
503 pub event: CategoryLimit,
505 pub event_indexed: CategoryLimit,
507 pub attachments_limits: AttachmentsLimits,
509 pub sessions: CategoryLimit,
511 pub profiles: CategoryLimit,
516 pub profiles_backend: CategoryLimit,
518 pub profiles_ui: CategoryLimit,
520 pub profiles_indexed: CategoryLimit,
522 pub replays: CategoryLimit,
524 pub check_ins: CategoryLimit,
526 pub log_items: CategoryLimit,
528 pub log_bytes: CategoryLimit,
530 pub spans: CategoryLimit,
532 pub spans_indexed: CategoryLimit,
534 pub user_reports: CategoryLimit,
536 pub profile_chunks: CategoryLimit,
538 pub profile_chunks_ui: CategoryLimit,
540 pub trace_metrics: CategoryLimit,
542 pub trace_metrics_bytes: CategoryLimit,
544}
545
546impl Enforcement {
547 pub fn active_event(&self) -> Option<&CategoryLimit> {
551 if self.event.is_active() {
552 Some(&self.event)
553 } else if self.event_indexed.is_active() {
554 Some(&self.event_indexed)
555 } else {
556 None
557 }
558 }
559
560 pub fn is_event_active(&self) -> bool {
562 self.active_event().is_some()
563 }
564
565 fn get_outcomes(self) -> impl Iterator<Item = (Outcome, DataCategory, usize)> {
567 let Self {
568 event,
569 event_indexed,
570 attachments_limits:
571 AttachmentsLimits {
572 event:
573 AttachmentLimits {
574 bytes: event_attachment_bytes,
575 count: event_attachment_item,
576 },
577 trace:
578 AttachmentLimits {
579 bytes: trace_attachment_bytes,
580 count: trace_attachment_item,
581 },
582 span:
583 AttachmentLimits {
584 bytes: span_attachment_bytes,
585 count: span_attachment_item,
586 },
587 },
588 sessions: _, profiles,
590 profiles_backend,
591 profiles_ui,
592 profiles_indexed,
593 replays,
594 check_ins,
595 log_items,
596 log_bytes,
597 spans,
598 spans_indexed,
599 user_reports,
600 profile_chunks,
601 profile_chunks_ui,
602 trace_metrics,
603 trace_metrics_bytes,
604 } = self;
605
606 let limits = [
607 event,
608 event_indexed,
609 event_attachment_bytes,
610 event_attachment_item,
611 trace_attachment_bytes,
612 trace_attachment_item,
613 span_attachment_bytes,
614 span_attachment_item,
615 profiles,
616 profiles_backend,
617 profiles_ui,
618 profiles_indexed,
619 replays,
620 check_ins,
621 log_items,
622 log_bytes,
623 spans,
624 spans_indexed,
625 user_reports,
626 profile_chunks,
627 profile_chunks_ui,
628 trace_metrics,
629 trace_metrics_bytes,
630 ];
631
632 limits.into_iter().flat_map(|limit| limit.outcomes())
633 }
634
635 pub fn apply_with_outcomes(self, envelope: &mut ManagedEnvelope) {
671 envelope
672 .envelope_mut()
673 .retain_items(|item| self.retain_item(item));
674 self.track_outcomes(envelope);
675 }
676
677 pub fn apply_to_managed(self, envelope: &mut Managed<Box<Envelope>>) {
683 envelope.modify(|envelope, records| {
684 envelope.retain_items(|item| self.retain_item(item));
685
686 records.lenient(DataCategory::Session);
688 records.lenient(DataCategory::UserReportV2);
696
697 for (outcome, category, quantity) in self.get_outcomes() {
698 records.reject_err(outcome, (category, quantity))
699 }
700 });
701 }
702
703 fn retain_item(&self, item: &mut Item) -> bool {
705 if self.event.is_active() && item.requires_event() {
707 return false;
708 }
709
710 match item.ty() {
714 ItemType::Attachment => {
715 match item.attachment_parent_type() {
716 AttachmentParentType::Span => !self.attachments_limits.span.is_active(),
717 AttachmentParentType::Trace => !self.attachments_limits.trace.is_active(),
718 AttachmentParentType::Event => {
719 if !self.attachments_limits.event.is_active() {
720 return true;
721 }
722 if item.creates_event() {
723 item.set_rate_limited(true);
724 true
725 } else {
726 false
727 }
728 }
729 }
730 }
731 ItemType::Session => !self.sessions.is_active(),
732 ItemType::Profile => {
733 if self.profiles_indexed.is_active() {
734 false
735 } else if let Some(platform) = item.profile_type() {
736 match platform {
737 ProfileType::Backend => !self.profiles_backend.is_active(),
738 ProfileType::Ui => !self.profiles_ui.is_active(),
739 }
740 } else {
741 true
742 }
743 }
744 ItemType::ReplayEvent => !self.replays.is_active(),
745 ItemType::ReplayVideo => !self.replays.is_active(),
746 ItemType::ReplayRecording => !self.replays.is_active(),
747 ItemType::UserReport => !self.user_reports.is_active(),
748 ItemType::CheckIn => !self.check_ins.is_active(),
749 ItemType::Log => {
750 !(self.log_items.is_active() || self.log_bytes.is_active())
751 }
752 ItemType::Span => !self.spans_indexed.is_active(),
753 ItemType::ProfileChunk => match item.profile_type() {
754 Some(ProfileType::Backend) => !self.profile_chunks.is_active(),
755 Some(ProfileType::Ui) => !self.profile_chunks_ui.is_active(),
756 None => true,
757 },
758 ItemType::TraceMetric => !(self.trace_metrics.is_active() || self.trace_metrics_bytes.is_active()),
759 ItemType::Integration => match item.integration() {
760 Some(Integration::Logs(_)) => !(self.log_items.is_active() || self.log_bytes.is_active()),
761 Some(Integration::Spans(_)) => !self.spans_indexed.is_active(),
762 None => true,
763 },
764 ItemType::Event
765 | ItemType::Transaction
766 | ItemType::Security
767 | ItemType::FormData
768 | ItemType::RawSecurity
769 | ItemType::Nel
770 | ItemType::UnrealReport
771 | ItemType::Sessions
772 | ItemType::Statsd
773 | ItemType::MetricBuckets
774 | ItemType::ClientReport
775 | ItemType::UserReportV2 | ItemType::Unknown(_) => true,
777 }
778 }
779
780 fn track_outcomes(self, envelope: &mut ManagedEnvelope) {
784 for (outcome, category, quantity) in self.get_outcomes() {
785 envelope.track_outcome(outcome, category, quantity)
786 }
787 }
788}
789
790#[derive(Debug, Copy, Clone)]
792pub enum CheckLimits {
793 NonIndexed,
800 All,
802}
803
804struct Check<F, E, R> {
805 limits: CheckLimits,
806 check: F,
807 _1: PhantomData<E>,
808 _2: PhantomData<R>,
809}
810
811impl<F, E, R> Check<F, E, R>
812where
813 F: FnMut(ItemScoping, usize) -> R,
814 R: Future<Output = Result<RateLimits, E>>,
815{
816 async fn apply(&mut self, scoping: ItemScoping, quantity: usize) -> Result<RateLimits, E> {
817 if matches!(self.limits, CheckLimits::NonIndexed) && scoping.category.is_indexed() {
818 return Ok(RateLimits::default());
819 }
820
821 (self.check)(scoping, quantity).await
822 }
823}
824
825pub struct EnvelopeLimiter<F, E, R> {
837 check: Check<F, E, R>,
838 event_category: Option<DataCategory>,
839}
840
841impl<'a, F, E, R> EnvelopeLimiter<F, E, R>
842where
843 F: FnMut(ItemScoping, usize) -> R,
844 R: Future<Output = Result<RateLimits, E>>,
845{
846 pub fn new(limits: CheckLimits, check: F) -> Self {
848 Self {
849 check: Check {
850 check,
851 limits,
852 _1: PhantomData,
853 _2: PhantomData,
854 },
855 event_category: None,
856 }
857 }
858
859 pub fn assume_event(&mut self, category: DataCategory) {
865 self.event_category = Some(category);
866 }
867
868 pub async fn compute(
879 mut self,
880 envelope: &Envelope,
881 scoping: &'a Scoping,
882 ) -> Result<(Enforcement, RateLimits), E> {
883 let mut summary = EnvelopeSummary::compute(envelope);
884 summary.event_category = self.event_category.or(summary.event_category);
885
886 let (enforcement, rate_limits) = self.execute(&summary, scoping).await?;
887 Ok((enforcement, rate_limits))
888 }
889
890 async fn execute(
891 &mut self,
892 summary: &EnvelopeSummary,
893 scoping: &'a Scoping,
894 ) -> Result<(Enforcement, RateLimits), E> {
895 let mut rate_limits = RateLimits::new();
896 let mut enforcement = Enforcement::default();
897
898 if let Some(category) = summary.event_category {
900 let mut event_limits = self.check.apply(scoping.item(category), 1).await?;
902 enforcement.event = CategoryLimit::new(category, 1, event_limits.longest());
903
904 if let Some(index_category) = category.index_category() {
905 if event_limits.is_empty() {
908 event_limits.merge(self.check.apply(scoping.item(index_category), 1).await?);
909 }
910
911 enforcement.event_indexed =
912 CategoryLimit::new(index_category, 1, event_limits.longest());
913 };
914
915 rate_limits.merge(event_limits);
916 }
917
918 if enforcement.is_event_active() {
920 enforcement.spans = enforcement
921 .event
922 .clone_for(DataCategory::Span, summary.span_quantity);
923
924 enforcement.spans_indexed = enforcement
925 .event_indexed
926 .clone_for(DataCategory::SpanIndexed, summary.span_quantity);
927 } else if summary.span_quantity > 0 || summary.has_span_dependent_items() {
928 let mut span_limits = self
929 .check
930 .apply(scoping.item(DataCategory::Span), summary.span_quantity)
931 .await?;
932 enforcement.spans = CategoryLimit::new(
933 DataCategory::Span,
934 summary.span_quantity,
935 span_limits.longest(),
936 );
937
938 if span_limits.is_empty() {
939 span_limits.merge(
940 self.check
941 .apply(
942 scoping.item(DataCategory::SpanIndexed),
943 summary.span_quantity,
944 )
945 .await?,
946 );
947 }
948
949 enforcement.spans_indexed = CategoryLimit::new(
950 DataCategory::SpanIndexed,
951 summary.span_quantity,
952 span_limits.longest(),
953 );
954
955 rate_limits.merge(span_limits);
956 }
957
958 if enforcement.spans_indexed.is_active() {
960 enforcement.attachments_limits.span.bytes = enforcement.spans_indexed.clone_for(
961 DataCategory::Attachment,
962 summary.attachment_quantities.span.bytes,
963 );
964 enforcement.attachments_limits.span.count = enforcement.spans_indexed.clone_for(
965 DataCategory::AttachmentItem,
966 summary.attachment_quantities.span.count,
967 );
968 } else if !summary.attachment_quantities.span.is_empty() {
969 enforcement.attachments_limits.span = self
974 .check_attachment_limits(scoping, &summary.attachment_quantities.span)
975 .await?;
976 }
977
978 if let Some(limit) = enforcement.active_event() {
980 let limit1 = limit.clone_for(
981 DataCategory::Attachment,
982 summary.attachment_quantities.event.bytes,
983 );
984 let limit2 = limit.clone_for(
985 DataCategory::AttachmentItem,
986 summary.attachment_quantities.event.count,
987 );
988
989 enforcement.attachments_limits.event.bytes = limit1;
990 enforcement.attachments_limits.event.count = limit2;
991 } else {
992 let mut attachment_limits = RateLimits::new();
993 if summary.attachment_quantities.event.bytes > 0 {
994 let item_scoping = scoping.item(DataCategory::Attachment);
995
996 let attachment_byte_limits = self
997 .check
998 .apply(item_scoping, summary.attachment_quantities.event.bytes)
999 .await?;
1000
1001 enforcement.attachments_limits.event.bytes = CategoryLimit::new(
1002 DataCategory::Attachment,
1003 summary.attachment_quantities.event.bytes,
1004 attachment_byte_limits.longest(),
1005 );
1006 enforcement.attachments_limits.event.count =
1007 enforcement.attachments_limits.event.bytes.clone_for(
1008 DataCategory::AttachmentItem,
1009 summary.attachment_quantities.event.count,
1010 );
1011 attachment_limits.merge(attachment_byte_limits);
1012 }
1013 if !attachment_limits.is_limited() && summary.attachment_quantities.event.count > 0 {
1014 let item_scoping = scoping.item(DataCategory::AttachmentItem);
1015
1016 let attachment_item_limits = self
1017 .check
1018 .apply(item_scoping, summary.attachment_quantities.event.count)
1019 .await?;
1020
1021 enforcement.attachments_limits.event.count = CategoryLimit::new(
1022 DataCategory::AttachmentItem,
1023 summary.attachment_quantities.event.count,
1024 attachment_item_limits.longest(),
1025 );
1026 enforcement.attachments_limits.event.bytes =
1027 enforcement.attachments_limits.event.count.clone_for(
1028 DataCategory::Attachment,
1029 summary.attachment_quantities.event.bytes,
1030 );
1031 attachment_limits.merge(attachment_item_limits);
1032 }
1033
1034 if summary.has_plain_attachments {
1038 rate_limits.merge(attachment_limits);
1039 }
1040 }
1041
1042 if !summary.attachment_quantities.trace.is_empty() {
1044 enforcement.attachments_limits.trace = self
1045 .check_attachment_limits(scoping, &summary.attachment_quantities.trace)
1046 .await?;
1047 }
1048
1049 if summary.session_quantity > 0 {
1051 let item_scoping = scoping.item(DataCategory::Session);
1052 let session_limits = self
1053 .check
1054 .apply(item_scoping, summary.session_quantity)
1055 .await?;
1056 enforcement.sessions = CategoryLimit::new(
1057 DataCategory::Session,
1058 summary.session_quantity,
1059 session_limits.longest(),
1060 );
1061 rate_limits.merge(session_limits);
1062 }
1063
1064 let mut trace_metric_limits = RateLimits::new();
1066 if summary.trace_metric_quantity > 0 {
1067 let item_scoping = scoping.item(DataCategory::TraceMetric);
1068 trace_metric_limits = self
1069 .check
1070 .apply(item_scoping, summary.trace_metric_quantity)
1071 .await?;
1072 enforcement.trace_metrics = CategoryLimit::new(
1073 DataCategory::TraceMetric,
1074 summary.trace_metric_quantity,
1075 trace_metric_limits.longest(),
1076 );
1077 enforcement.trace_metrics_bytes = CategoryLimit::new(
1078 DataCategory::TraceMetricByte,
1079 summary.trace_metric_byte_quantity,
1080 trace_metric_limits.longest(),
1081 );
1082 }
1083 if !trace_metric_limits.is_limited() && summary.trace_metric_byte_quantity > 0 {
1084 let item_scoping = scoping.item(DataCategory::TraceMetricByte);
1085 trace_metric_limits = self
1086 .check
1087 .apply(item_scoping, summary.trace_metric_byte_quantity)
1088 .await?;
1089 enforcement.trace_metrics = CategoryLimit::new(
1090 DataCategory::TraceMetric,
1091 summary.trace_metric_quantity,
1092 trace_metric_limits.longest(),
1093 );
1094 enforcement.trace_metrics_bytes = CategoryLimit::new(
1095 DataCategory::TraceMetricByte,
1096 summary.trace_metric_byte_quantity,
1097 trace_metric_limits.longest(),
1098 );
1099 }
1100 rate_limits.merge(trace_metric_limits);
1101
1102 let mut log_limits = RateLimits::new();
1104 if summary.log_item_quantity > 0 {
1105 let item_scoping = scoping.item(DataCategory::LogItem);
1106 log_limits = self
1107 .check
1108 .apply(item_scoping, summary.log_item_quantity)
1109 .await?;
1110 enforcement.log_bytes = CategoryLimit::new(
1111 DataCategory::LogByte,
1112 summary.log_byte_quantity,
1113 log_limits.longest(),
1114 );
1115 enforcement.log_items = CategoryLimit::new(
1116 DataCategory::LogItem,
1117 summary.log_item_quantity,
1118 log_limits.longest(),
1119 );
1120 }
1121 if !log_limits.is_limited() && summary.log_byte_quantity > 0 {
1122 let item_scoping = scoping.item(DataCategory::LogByte);
1123 log_limits = self
1124 .check
1125 .apply(item_scoping, summary.log_byte_quantity)
1126 .await?;
1127 enforcement.log_bytes = CategoryLimit::new(
1128 DataCategory::LogByte,
1129 summary.log_byte_quantity,
1130 log_limits.longest(),
1131 );
1132 enforcement.log_items = CategoryLimit::new(
1133 DataCategory::LogItem,
1134 summary.log_item_quantity,
1135 log_limits.longest(),
1136 );
1137 }
1138 rate_limits.merge(log_limits);
1139
1140 if enforcement.is_event_active() {
1142 enforcement.profiles = enforcement
1143 .event
1144 .clone_for(DataCategory::Profile, summary.profile_quantity.total);
1145 enforcement.profiles_indexed = enforcement
1146 .event_indexed
1147 .clone_for(DataCategory::ProfileIndexed, summary.profile_quantity.total);
1148
1149 enforcement.profiles_backend = enforcement.event.clone_for(
1150 DataCategory::ProfileBackend,
1151 summary.profile_quantity.backend,
1152 );
1153 enforcement.profiles_ui = enforcement
1154 .event
1155 .clone_for(DataCategory::ProfileUi, summary.profile_quantity.ui);
1156 } else if summary.profile_quantity.total > 0 {
1157 let mut profile_limits = self
1158 .check
1159 .apply(
1160 scoping.item(DataCategory::Profile),
1161 summary.profile_quantity.total,
1162 )
1163 .await?;
1164
1165 if profile_limits.is_empty() && summary.event_category.is_none() {
1168 profile_limits = self
1169 .check
1170 .apply(scoping.item(DataCategory::Transaction), 0)
1171 .await?;
1172 }
1173
1174 enforcement.profiles = CategoryLimit::new(
1175 DataCategory::Profile,
1176 summary.profile_quantity.total,
1177 profile_limits.longest(),
1178 );
1179
1180 if enforcement.profiles.quantity == 0 {
1181 if summary.profile_quantity.backend > 0 {
1182 let limit = self
1183 .check
1184 .apply(
1185 scoping.item(DataCategory::ProfileBackend),
1186 summary.profile_quantity.backend,
1187 )
1188 .await?;
1189
1190 enforcement.profiles_backend = CategoryLimit::new(
1191 DataCategory::ProfileBackend,
1192 summary.profile_quantity.backend,
1193 limit.longest(),
1194 )
1195 .add_outcome_category(DataCategory::Profile);
1196
1197 profile_limits.merge(limit);
1198 }
1199 if summary.profile_quantity.ui > 0 {
1200 let limit = self
1201 .check
1202 .apply(
1203 scoping.item(DataCategory::ProfileUi),
1204 summary.profile_quantity.ui,
1205 )
1206 .await?;
1207
1208 enforcement.profiles_ui = CategoryLimit::new(
1209 DataCategory::ProfileUi,
1210 summary.profile_quantity.ui,
1211 limit.longest(),
1212 )
1213 .add_outcome_category(DataCategory::Profile);
1214
1215 profile_limits.merge(limit);
1216 }
1217 } else {
1218 enforcement.profiles_backend = CategoryLimit::new(
1219 DataCategory::ProfileBackend,
1220 summary.profile_quantity.backend,
1221 profile_limits.longest(),
1222 );
1223 enforcement.profiles_ui = CategoryLimit::new(
1224 DataCategory::ProfileUi,
1225 summary.profile_quantity.ui,
1226 profile_limits.longest(),
1227 );
1228 }
1229
1230 if enforcement.profiles.quantity > 0 {
1231 enforcement.profiles_indexed = enforcement
1232 .profiles
1233 .clone_for(DataCategory::ProfileIndexed, summary.profile_quantity.total);
1234 } else {
1235 let limit = self
1236 .check
1237 .apply(
1238 scoping.item(DataCategory::ProfileIndexed),
1239 summary.profile_quantity.total,
1240 )
1241 .await?;
1242
1243 if !limit.is_empty() {
1244 enforcement.profiles_indexed = CategoryLimit::new(
1245 DataCategory::ProfileIndexed,
1246 summary.profile_quantity.total,
1247 limit.longest(),
1248 );
1249
1250 profile_limits.merge(limit);
1251 } else {
1252 enforcement.profiles_backend = enforcement
1253 .profiles_backend
1254 .add_outcome_category(DataCategory::ProfileIndexed);
1255 enforcement.profiles_ui = enforcement
1256 .profiles_ui
1257 .add_outcome_category(DataCategory::ProfileIndexed);
1258 }
1259 }
1260
1261 rate_limits.merge(profile_limits);
1262 }
1263
1264 if summary.replay_quantity > 0 {
1266 let item_scoping = scoping.item(DataCategory::Replay);
1267 let replay_limits = self
1268 .check
1269 .apply(item_scoping, summary.replay_quantity)
1270 .await?;
1271 enforcement.replays = CategoryLimit::new(
1272 DataCategory::Replay,
1273 summary.replay_quantity,
1274 replay_limits.longest(),
1275 );
1276 rate_limits.merge(replay_limits);
1277 }
1278
1279 if summary.user_report_quantity > 0 {
1281 let item_scoping = scoping.item(DataCategory::UserReportV2);
1282 let user_report_v2_limits = self
1283 .check
1284 .apply(item_scoping, summary.user_report_quantity)
1285 .await?;
1286 enforcement.user_reports = CategoryLimit::new(
1287 DataCategory::UserReportV2,
1288 summary.user_report_quantity,
1289 user_report_v2_limits.longest(),
1290 );
1291 rate_limits.merge(user_report_v2_limits);
1292 }
1293
1294 if summary.monitor_quantity > 0 {
1296 let item_scoping = scoping.item(DataCategory::Monitor);
1297 let checkin_limits = self
1298 .check
1299 .apply(item_scoping, summary.monitor_quantity)
1300 .await?;
1301 enforcement.check_ins = CategoryLimit::new(
1302 DataCategory::Monitor,
1303 summary.monitor_quantity,
1304 checkin_limits.longest(),
1305 );
1306 rate_limits.merge(checkin_limits);
1307 }
1308
1309 if summary.profile_chunk_quantity > 0 {
1311 let item_scoping = scoping.item(DataCategory::ProfileChunk);
1312 let limits = self
1313 .check
1314 .apply(item_scoping, summary.profile_chunk_quantity)
1315 .await?;
1316 enforcement.profile_chunks = CategoryLimit::new(
1317 DataCategory::ProfileChunk,
1318 summary.profile_chunk_quantity,
1319 limits.longest(),
1320 );
1321 rate_limits.merge(limits);
1322 }
1323
1324 if summary.profile_chunk_ui_quantity > 0 {
1325 let item_scoping = scoping.item(DataCategory::ProfileChunkUi);
1326 let limits = self
1327 .check
1328 .apply(item_scoping, summary.profile_chunk_ui_quantity)
1329 .await?;
1330 enforcement.profile_chunks_ui = CategoryLimit::new(
1331 DataCategory::ProfileChunkUi,
1332 summary.profile_chunk_ui_quantity,
1333 limits.longest(),
1334 );
1335 rate_limits.merge(limits);
1336 }
1337
1338 Ok((enforcement, rate_limits))
1339 }
1340
1341 async fn check_attachment_limits(
1342 &mut self,
1343 scoping: &Scoping,
1344 quantities: &AttachmentQuantity,
1345 ) -> Result<AttachmentLimits, E> {
1346 let mut attachment_limits = self
1347 .check
1348 .apply(scoping.item(DataCategory::Attachment), quantities.bytes)
1349 .await?;
1350
1351 if !attachment_limits.is_limited() && quantities.count > 0 {
1354 attachment_limits.merge(
1355 self.check
1356 .apply(scoping.item(DataCategory::AttachmentItem), quantities.count)
1357 .await?,
1358 );
1359 }
1360
1361 Ok(AttachmentLimits {
1362 bytes: CategoryLimit::new(
1363 DataCategory::Attachment,
1364 quantities.bytes,
1365 attachment_limits.longest(),
1366 ),
1367 count: CategoryLimit::new(
1368 DataCategory::AttachmentItem,
1369 quantities.count,
1370 attachment_limits.longest(),
1371 ),
1372 })
1373 }
1374}
1375
1376impl<F, E, R> fmt::Debug for EnvelopeLimiter<F, E, R> {
1377 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1378 f.debug_struct("EnvelopeLimiter")
1379 .field("event_category", &self.event_category)
1380 .finish()
1381 }
1382}
1383
1384#[cfg(test)]
1385mod tests {
1386
1387 use std::collections::{BTreeMap, BTreeSet};
1388 use std::sync::Arc;
1389
1390 use relay_base_schema::organization::OrganizationId;
1391 use relay_base_schema::project::{ProjectId, ProjectKey};
1392 use relay_metrics::MetricNamespace;
1393 use relay_quotas::RetryAfter;
1394 use relay_system::Addr;
1395 use smallvec::smallvec;
1396 use tokio::sync::Mutex;
1397
1398 use super::*;
1399 use crate::envelope::ParentId;
1400 use crate::{
1401 envelope::{AttachmentType, ContentType, SourceQuantities},
1402 extractors::RequestMeta,
1403 };
1404
1405 struct RateLimitTestCase {
1406 name: &'static str,
1407 denied_categories: &'static [DataCategory],
1408 expect_attachment_limit_active: bool,
1409 expected_limiter_calls: &'static [(DataCategory, usize)],
1410 expected_outcomes: &'static [(DataCategory, usize)],
1411 }
1412
1413 #[tokio::test]
1414 async fn test_format_rate_limits() {
1415 let mut rate_limits = RateLimits::new();
1416
1417 rate_limits.add(RateLimit {
1419 categories: Default::default(),
1420 scope: RateLimitScope::Organization(OrganizationId::new(42)),
1421 reason_code: Some(ReasonCode::new("my_limit")),
1422 retry_after: RetryAfter::from_secs(42),
1423 namespaces: smallvec![],
1424 });
1425
1426 rate_limits.add(RateLimit {
1428 categories: [DataCategory::Transaction, DataCategory::Security].into(),
1429 scope: RateLimitScope::Project(ProjectId::new(21)),
1430 reason_code: None,
1431 retry_after: RetryAfter::from_secs(4711),
1432 namespaces: smallvec![],
1433 });
1434
1435 let formatted = format_rate_limits(&rate_limits);
1436 let expected = "42::organization:my_limit, 4711:transaction;security:project";
1437 assert_eq!(formatted, expected);
1438 }
1439
1440 #[tokio::test]
1441 async fn test_format_rate_limits_namespace() {
1442 let mut rate_limits = RateLimits::new();
1443
1444 rate_limits.add(RateLimit {
1446 categories: [DataCategory::MetricBucket].into(),
1447 scope: RateLimitScope::Organization(OrganizationId::new(42)),
1448 reason_code: Some(ReasonCode::new("my_limit")),
1449 retry_after: RetryAfter::from_secs(42),
1450 namespaces: smallvec![MetricNamespace::Custom, MetricNamespace::Spans],
1451 });
1452
1453 rate_limits.add(RateLimit {
1455 categories: [DataCategory::MetricBucket].into(),
1456 scope: RateLimitScope::Organization(OrganizationId::new(42)),
1457 reason_code: None,
1458 retry_after: RetryAfter::from_secs(42),
1459 namespaces: smallvec![MetricNamespace::Spans],
1460 });
1461
1462 let formatted = format_rate_limits(&rate_limits);
1463 let expected = "42:metric_bucket:organization:my_limit:custom;spans, 42:metric_bucket:organization::spans";
1464 assert_eq!(formatted, expected);
1465 }
1466
1467 #[tokio::test]
1468 async fn test_parse_invalid_rate_limits() {
1469 let scoping = Scoping {
1470 organization_id: OrganizationId::new(42),
1471 project_id: ProjectId::new(21),
1472 project_key: ProjectKey::parse("a94ae32be2584e0bbd7a4cbb95971fee").unwrap(),
1473 key_id: Some(17),
1474 };
1475
1476 assert!(parse_rate_limits(&scoping, "").is_ok());
1477 assert!(parse_rate_limits(&scoping, "invalid").is_ok());
1478 assert!(parse_rate_limits(&scoping, ",,,").is_ok());
1479 }
1480
1481 #[tokio::test]
1482 async fn test_parse_rate_limits() {
1483 let scoping = Scoping {
1484 organization_id: OrganizationId::new(42),
1485 project_id: ProjectId::new(21),
1486 project_key: ProjectKey::parse("a94ae32be2584e0bbd7a4cbb95971fee").unwrap(),
1487 key_id: Some(17),
1488 };
1489
1490 let formatted =
1492 "42::organization:my_limit, invalid, 4711:foobar;transaction;security:project";
1493 let rate_limits: Vec<RateLimit> =
1494 parse_rate_limits(&scoping, formatted).into_iter().collect();
1495
1496 assert_eq!(
1497 rate_limits,
1498 vec![
1499 RateLimit {
1500 categories: Default::default(),
1501 scope: RateLimitScope::Organization(OrganizationId::new(42)),
1502 reason_code: Some(ReasonCode::new("my_limit")),
1503 retry_after: rate_limits[0].retry_after,
1504 namespaces: smallvec![],
1505 },
1506 RateLimit {
1507 categories: [
1508 DataCategory::Unknown,
1509 DataCategory::Transaction,
1510 DataCategory::Security,
1511 ]
1512 .into(),
1513 scope: RateLimitScope::Project(ProjectId::new(21)),
1514 reason_code: None,
1515 retry_after: rate_limits[1].retry_after,
1516 namespaces: smallvec![],
1517 }
1518 ]
1519 );
1520
1521 assert_eq!(42, rate_limits[0].retry_after.remaining_seconds());
1522 assert_eq!(4711, rate_limits[1].retry_after.remaining_seconds());
1523 }
1524
1525 #[tokio::test]
1526 async fn test_parse_rate_limits_namespace() {
1527 let scoping = Scoping {
1528 organization_id: OrganizationId::new(42),
1529 project_id: ProjectId::new(21),
1530 project_key: ProjectKey::parse("a94ae32be2584e0bbd7a4cbb95971fee").unwrap(),
1531 key_id: Some(17),
1532 };
1533
1534 let formatted = "42:metric_bucket:organization::custom;spans";
1535 let rate_limits: Vec<RateLimit> =
1536 parse_rate_limits(&scoping, formatted).into_iter().collect();
1537
1538 assert_eq!(
1539 rate_limits,
1540 vec![RateLimit {
1541 categories: [DataCategory::MetricBucket].into(),
1542 scope: RateLimitScope::Organization(OrganizationId::new(42)),
1543 reason_code: None,
1544 retry_after: rate_limits[0].retry_after,
1545 namespaces: smallvec![MetricNamespace::Custom, MetricNamespace::Spans],
1546 }]
1547 );
1548 }
1549
1550 #[tokio::test]
1551 async fn test_parse_rate_limits_empty_namespace() {
1552 let scoping = Scoping {
1553 organization_id: OrganizationId::new(42),
1554 project_id: ProjectId::new(21),
1555 project_key: ProjectKey::parse("a94ae32be2584e0bbd7a4cbb95971fee").unwrap(),
1556 key_id: Some(17),
1557 };
1558
1559 let formatted = "42:metric_bucket:organization:some_reason:";
1561 let rate_limits: Vec<RateLimit> =
1562 parse_rate_limits(&scoping, formatted).into_iter().collect();
1563
1564 assert_eq!(
1565 rate_limits,
1566 vec![RateLimit {
1567 categories: [DataCategory::MetricBucket].into(),
1568 scope: RateLimitScope::Organization(OrganizationId::new(42)),
1569 reason_code: Some(ReasonCode::new("some_reason")),
1570 retry_after: rate_limits[0].retry_after,
1571 namespaces: smallvec![],
1572 }]
1573 );
1574 }
1575
1576 #[tokio::test]
1577 async fn test_parse_rate_limits_only_unknown() {
1578 let scoping = Scoping {
1579 organization_id: OrganizationId::new(42),
1580 project_id: ProjectId::new(21),
1581 project_key: ProjectKey::parse("a94ae32be2584e0bbd7a4cbb95971fee").unwrap(),
1582 key_id: Some(17),
1583 };
1584
1585 let formatted = "42:foo;bar:organization";
1586 let rate_limits: Vec<RateLimit> =
1587 parse_rate_limits(&scoping, formatted).into_iter().collect();
1588
1589 assert_eq!(
1590 rate_limits,
1591 vec![RateLimit {
1592 categories: [DataCategory::Unknown, DataCategory::Unknown].into(),
1593 scope: RateLimitScope::Organization(OrganizationId::new(42)),
1594 reason_code: None,
1595 retry_after: rate_limits[0].retry_after,
1596 namespaces: smallvec![],
1597 },]
1598 );
1599 }
1600
1601 macro_rules! envelope {
1602 ($( $item_type:ident $( :: $attachment_type:ident )? ),*) => {{
1603 let bytes = "{\"dsn\":\"https://e12d836b15bb49d7bbf99e64295d995b:@sentry.io/42\"}";
1604 #[allow(unused_mut)]
1605 let mut envelope = Envelope::parse_bytes(bytes.into()).unwrap();
1606 $(
1607 let mut item = Item::new(ItemType::$item_type);
1608 item.set_payload(ContentType::OctetStream, "0123456789");
1609 $( item.set_attachment_type(AttachmentType::$attachment_type); )?
1610 envelope.add_item(item);
1611 )*
1612
1613 let (outcome_aggregator, _) = Addr::custom();
1614
1615 ManagedEnvelope::new(
1616 envelope,
1617 outcome_aggregator,
1618 )
1619 }}
1620 }
1621
1622 fn set_extracted(envelope: &mut Envelope, ty: ItemType) {
1623 envelope
1624 .get_item_by_mut(|item| *item.ty() == ty)
1625 .unwrap()
1626 .set_metrics_extracted(true);
1627 }
1628
1629 fn rate_limit(category: DataCategory) -> RateLimit {
1630 RateLimit {
1631 categories: [category].into(),
1632 scope: RateLimitScope::Organization(OrganizationId::new(42)),
1633 reason_code: None,
1634 retry_after: RetryAfter::from_secs(60),
1635 namespaces: smallvec![],
1636 }
1637 }
1638
1639 fn trace_attachment_item(bytes: usize, parent_id: Option<ParentId>) -> Item {
1640 let mut item = Item::new(ItemType::Attachment);
1641 item.set_payload(ContentType::TraceAttachment, "0".repeat(bytes));
1642 item.set_parent_id(parent_id);
1643 item
1644 }
1645
1646 #[derive(Debug, Default)]
1647 struct MockLimiter {
1648 denied: Vec<DataCategory>,
1649 called: BTreeMap<DataCategory, usize>,
1650 checked: BTreeSet<DataCategory>,
1651 }
1652
1653 impl MockLimiter {
1654 pub fn deny(mut self, category: DataCategory) -> Self {
1655 self.denied.push(category);
1656 self
1657 }
1658
1659 pub fn check(&mut self, scoping: ItemScoping, quantity: usize) -> Result<RateLimits, ()> {
1660 let cat = scoping.category;
1661 let previous = self.called.insert(cat, quantity);
1662 assert!(previous.is_none(), "rate limiter invoked twice for {cat}");
1663
1664 let mut limits = RateLimits::new();
1665 if self.denied.contains(&cat) {
1666 limits.add(rate_limit(cat));
1667 }
1668 Ok(limits)
1669 }
1670
1671 #[track_caller]
1672 pub fn assert_call(&mut self, category: DataCategory, expected: usize) {
1673 self.checked.insert(category);
1674
1675 let quantity = self.called.get(&category).copied();
1676 assert_eq!(
1677 quantity,
1678 Some(expected),
1679 "Expected quantity `{expected}` for data category `{category}`, got {quantity:?}."
1680 );
1681 }
1682 }
1683
1684 impl Drop for MockLimiter {
1685 fn drop(&mut self) {
1686 if std::thread::panicking() {
1687 return;
1688 }
1689
1690 for checked in &self.checked {
1691 self.called.remove(checked);
1692 }
1693
1694 if self.called.is_empty() {
1695 return;
1696 }
1697
1698 let not_asserted = self
1699 .called
1700 .iter()
1701 .map(|(k, v)| format!("- {k}: {v}"))
1702 .collect::<Vec<_>>()
1703 .join("\n");
1704
1705 panic!("Following calls to the limiter were not asserted:\n{not_asserted}");
1706 }
1707 }
1708
1709 async fn enforce_and_apply(
1710 mock: Arc<Mutex<MockLimiter>>,
1711 envelope: &mut ManagedEnvelope,
1712 #[allow(unused_variables)] assume_event: Option<DataCategory>,
1713 ) -> (Enforcement, RateLimits) {
1714 let scoping = envelope.scoping();
1715
1716 #[allow(unused_mut)]
1717 let mut limiter = EnvelopeLimiter::new(CheckLimits::All, move |s, q| {
1718 let mock = mock.clone();
1719 async move {
1720 let mut mock = mock.lock().await;
1721 mock.check(s, q)
1722 }
1723 });
1724 #[cfg(feature = "processing")]
1725 if let Some(assume_event) = assume_event {
1726 limiter.assume_event(assume_event);
1727 }
1728
1729 let (enforcement, limits) = limiter
1730 .compute(envelope.envelope_mut(), &scoping)
1731 .await
1732 .unwrap();
1733
1734 enforcement.clone().apply_with_outcomes(envelope);
1737
1738 (enforcement, limits)
1739 }
1740
1741 fn mock_limiter(categories: &[DataCategory]) -> Arc<Mutex<MockLimiter>> {
1742 let mut mock = MockLimiter::default();
1743 for &category in categories {
1744 mock = mock.deny(category);
1745 }
1746
1747 Arc::new(Mutex::new(mock))
1748 }
1749
1750 #[tokio::test]
1751 async fn test_enforce_pass_empty() {
1752 let mut envelope = envelope![];
1753
1754 let mock = mock_limiter(&[]);
1755 let (_, limits) = enforce_and_apply(mock, &mut envelope, None).await;
1756
1757 assert!(!limits.is_limited());
1758 assert!(envelope.envelope().is_empty());
1759 }
1760
1761 #[tokio::test]
1762 async fn test_enforce_limit_error_event() {
1763 let mut envelope = envelope![Event];
1764
1765 let mock = mock_limiter(&[DataCategory::Error]);
1766 let (_, limits) = enforce_and_apply(mock.clone(), &mut envelope, None).await;
1767
1768 assert!(limits.is_limited());
1769 assert!(envelope.envelope().is_empty());
1770 mock.lock().await.assert_call(DataCategory::Error, 1);
1771 }
1772
1773 #[tokio::test]
1774 async fn test_enforce_limit_error_with_attachments() {
1775 let mut envelope = envelope![Event, Attachment];
1776
1777 let mock = mock_limiter(&[DataCategory::Error]);
1778 let (_, limits) = enforce_and_apply(mock.clone(), &mut envelope, None).await;
1779
1780 assert!(limits.is_limited());
1781 assert!(envelope.envelope().is_empty());
1782 mock.lock().await.assert_call(DataCategory::Error, 1);
1783 }
1784
1785 #[tokio::test]
1786 async fn test_enforce_limit_minidump() {
1787 let mut envelope = envelope![Attachment::Minidump];
1788
1789 let mock = mock_limiter(&[DataCategory::Error]);
1790 let (_, limits) = enforce_and_apply(mock.clone(), &mut envelope, None).await;
1791
1792 assert!(limits.is_limited());
1793 assert!(envelope.envelope().is_empty());
1794 mock.lock().await.assert_call(DataCategory::Error, 1);
1795 }
1796
1797 #[tokio::test]
1798 async fn test_enforce_limit_attachments() {
1799 let mut envelope = envelope![Attachment::Minidump, Attachment];
1800
1801 let mock = mock_limiter(&[DataCategory::Attachment]);
1802 let (_, limits) = enforce_and_apply(mock.clone(), &mut envelope, None).await;
1803
1804 assert!(limits.is_limited());
1806 assert_eq!(envelope.envelope().len(), 1);
1807 mock.lock().await.assert_call(DataCategory::Error, 1);
1808 mock.lock().await.assert_call(DataCategory::Attachment, 20);
1809 }
1810
1811 #[tokio::test]
1813 async fn test_enforce_limit_profiles() {
1814 let mut envelope = envelope![Profile, Profile];
1815
1816 let mock = mock_limiter(&[DataCategory::Profile]);
1817 let (enforcement, limits) = enforce_and_apply(mock.clone(), &mut envelope, None).await;
1818
1819 assert!(limits.is_limited());
1820 assert_eq!(envelope.envelope().len(), 0);
1821 mock.lock().await.assert_call(DataCategory::Profile, 2);
1822
1823 assert_eq!(
1824 get_outcomes(enforcement),
1825 vec![
1826 (DataCategory::Profile, 2),
1827 (DataCategory::ProfileIndexed, 2)
1828 ]
1829 );
1830 }
1831
1832 #[tokio::test]
1834 async fn test_enforce_limit_profile_chunks_no_profile_type() {
1835 let mut envelope = envelope![ProfileChunk, ProfileChunk];
1838
1839 let mock = mock_limiter(&[DataCategory::ProfileChunk]);
1840 let (enforcement, limits) = enforce_and_apply(mock.clone(), &mut envelope, None).await;
1841 assert!(!limits.is_limited());
1842 assert_eq!(get_outcomes(enforcement), vec![]);
1843
1844 let mock = mock_limiter(&[DataCategory::ProfileChunkUi]);
1845 let (enforcement, limits) = enforce_and_apply(mock.clone(), &mut envelope, None).await;
1846 assert!(!limits.is_limited());
1847 assert_eq!(get_outcomes(enforcement), vec![]);
1848
1849 assert_eq!(envelope.envelope().len(), 2);
1850 }
1851
1852 #[tokio::test]
1853 async fn test_enforce_limit_profile_chunks_ui() {
1854 let mut envelope = envelope![];
1855
1856 let mut item = Item::new(ItemType::ProfileChunk);
1857 item.set_platform("python".to_owned());
1858 envelope.envelope_mut().add_item(item);
1859 let mut item = Item::new(ItemType::ProfileChunk);
1860 item.set_platform("javascript".to_owned());
1861 envelope.envelope_mut().add_item(item);
1862
1863 let mock = mock_limiter(&[DataCategory::ProfileChunkUi]);
1864 let (enforcement, limits) = enforce_and_apply(mock.clone(), &mut envelope, None).await;
1865
1866 assert!(limits.is_limited());
1867 assert_eq!(envelope.envelope().len(), 1);
1868 mock.lock()
1869 .await
1870 .assert_call(DataCategory::ProfileChunkUi, 1);
1871 mock.lock().await.assert_call(DataCategory::ProfileChunk, 1);
1872
1873 assert_eq!(
1874 get_outcomes(enforcement),
1875 vec![(DataCategory::ProfileChunkUi, 1)]
1876 );
1877 }
1878
1879 #[tokio::test]
1880 async fn test_enforce_limit_profile_chunks_backend() {
1881 let mut envelope = envelope![];
1882
1883 let mut item = Item::new(ItemType::ProfileChunk);
1884 item.set_platform("python".to_owned());
1885 envelope.envelope_mut().add_item(item);
1886 let mut item = Item::new(ItemType::ProfileChunk);
1887 item.set_platform("javascript".to_owned());
1888 envelope.envelope_mut().add_item(item);
1889
1890 let mock = mock_limiter(&[DataCategory::ProfileChunk]);
1891 let (enforcement, limits) = enforce_and_apply(mock.clone(), &mut envelope, None).await;
1892
1893 assert!(limits.is_limited());
1894 assert_eq!(envelope.envelope().len(), 1);
1895 mock.lock()
1896 .await
1897 .assert_call(DataCategory::ProfileChunkUi, 1);
1898 mock.lock().await.assert_call(DataCategory::ProfileChunk, 1);
1899
1900 assert_eq!(
1901 get_outcomes(enforcement),
1902 vec![(DataCategory::ProfileChunk, 1)]
1903 );
1904 }
1905
1906 #[tokio::test]
1908 async fn test_enforce_limit_replays() {
1909 let mut envelope = envelope![ReplayEvent, ReplayRecording, ReplayVideo];
1910
1911 let mock = mock_limiter(&[DataCategory::Replay]);
1912 let (enforcement, limits) = enforce_and_apply(mock.clone(), &mut envelope, None).await;
1913
1914 assert!(limits.is_limited());
1915 assert_eq!(envelope.envelope().len(), 0);
1916 mock.lock().await.assert_call(DataCategory::Replay, 3);
1917
1918 assert_eq!(get_outcomes(enforcement), vec![(DataCategory::Replay, 3),]);
1919 }
1920
1921 #[tokio::test]
1923 async fn test_enforce_limit_monitor_checkins() {
1924 let mut envelope = envelope![CheckIn];
1925
1926 let mock = mock_limiter(&[DataCategory::Monitor]);
1927 let (enforcement, limits) = enforce_and_apply(mock.clone(), &mut envelope, None).await;
1928
1929 assert!(limits.is_limited());
1930 assert_eq!(envelope.envelope().len(), 0);
1931 mock.lock().await.assert_call(DataCategory::Monitor, 1);
1932
1933 assert_eq!(get_outcomes(enforcement), vec![(DataCategory::Monitor, 1)])
1934 }
1935
1936 #[tokio::test]
1937 async fn test_enforce_pass_minidump() {
1938 let mut envelope = envelope![Attachment::Minidump];
1939
1940 let mock = mock_limiter(&[DataCategory::Attachment]);
1941 let (_, limits) = enforce_and_apply(mock.clone(), &mut envelope, None).await;
1942
1943 assert!(!limits.is_limited());
1945 assert_eq!(envelope.envelope().len(), 1);
1946 mock.lock().await.assert_call(DataCategory::Error, 1);
1947 mock.lock().await.assert_call(DataCategory::Attachment, 10);
1948 }
1949
1950 #[tokio::test]
1951 async fn test_enforce_skip_rate_limited() {
1952 let mut envelope = envelope![];
1953
1954 let mut item = Item::new(ItemType::Attachment);
1955 item.set_payload(ContentType::OctetStream, "0123456789");
1956 item.set_rate_limited(true);
1957 envelope.envelope_mut().add_item(item);
1958
1959 let mock = mock_limiter(&[DataCategory::Error]);
1960 let (_, limits) = enforce_and_apply(mock, &mut envelope, None).await;
1961
1962 assert!(!limits.is_limited()); assert_eq!(envelope.envelope().len(), 1); }
1965
1966 #[tokio::test]
1967 async fn test_enforce_pass_sessions() {
1968 let mut envelope = envelope![Session, Session, Session];
1969
1970 let mock = mock_limiter(&[DataCategory::Error]);
1971 let (_, limits) = enforce_and_apply(mock.clone(), &mut envelope, None).await;
1972
1973 assert!(!limits.is_limited());
1975 assert_eq!(envelope.envelope().len(), 3);
1976 mock.lock().await.assert_call(DataCategory::Session, 3);
1977 }
1978
1979 #[tokio::test]
1980 async fn test_enforce_limit_sessions() {
1981 let mut envelope = envelope![Session, Session, Event];
1982
1983 let mock = mock_limiter(&[DataCategory::Session]);
1984 let (_, limits) = enforce_and_apply(mock.clone(), &mut envelope, None).await;
1985
1986 assert!(limits.is_limited());
1988 assert_eq!(envelope.envelope().len(), 1);
1989 mock.lock().await.assert_call(DataCategory::Error, 1);
1990 mock.lock().await.assert_call(DataCategory::Session, 2);
1991 }
1992
1993 #[tokio::test]
1994 #[cfg(feature = "processing")]
1995 async fn test_enforce_limit_assumed_event() {
1996 let mut envelope = envelope![];
1997
1998 let mock = mock_limiter(&[DataCategory::Transaction]);
1999 let (_, limits) =
2000 enforce_and_apply(mock.clone(), &mut envelope, Some(DataCategory::Transaction)).await;
2001
2002 assert!(limits.is_limited());
2003 assert!(envelope.envelope().is_empty()); mock.lock().await.assert_call(DataCategory::Transaction, 1);
2005 }
2006
2007 #[tokio::test]
2008 #[cfg(feature = "processing")]
2009 async fn test_enforce_limit_assumed_attachments() {
2010 let mut envelope = envelope![Attachment, Attachment];
2011
2012 let mock = mock_limiter(&[DataCategory::Error]);
2013 let (_, limits) =
2014 enforce_and_apply(mock.clone(), &mut envelope, Some(DataCategory::Error)).await;
2015
2016 assert!(limits.is_limited());
2017 assert!(envelope.envelope().is_empty());
2018 mock.lock().await.assert_call(DataCategory::Error, 1);
2019 }
2020
2021 #[tokio::test]
2022 async fn test_enforce_transaction() {
2023 let mut envelope = envelope![Transaction];
2024
2025 let mock = mock_limiter(&[DataCategory::Transaction]);
2026 let (enforcement, limits) = enforce_and_apply(mock.clone(), &mut envelope, None).await;
2027
2028 assert!(limits.is_limited());
2029 assert!(enforcement.event_indexed.is_active());
2030 assert!(enforcement.event.is_active());
2031 mock.lock().await.assert_call(DataCategory::Transaction, 1);
2032
2033 assert_eq!(
2034 get_outcomes(enforcement),
2035 vec![
2036 (DataCategory::Transaction, 1),
2037 (DataCategory::TransactionIndexed, 1),
2038 (DataCategory::Span, 1),
2039 (DataCategory::SpanIndexed, 1),
2040 ]
2041 );
2042 }
2043
2044 #[tokio::test]
2045 async fn test_enforce_transaction_non_indexed() {
2046 let mut envelope = envelope![Transaction, Profile];
2047 let scoping = envelope.scoping();
2048
2049 let mock = mock_limiter(&[DataCategory::TransactionIndexed]);
2050
2051 let mock_clone = mock.clone();
2052 let limiter = EnvelopeLimiter::new(CheckLimits::NonIndexed, move |s, q| {
2053 let mock_clone = mock_clone.clone();
2054 async move {
2055 let mut mock = mock_clone.lock().await;
2056 mock.check(s, q)
2057 }
2058 });
2059 let (enforcement, limits) = limiter
2060 .compute(envelope.envelope_mut(), &scoping)
2061 .await
2062 .unwrap();
2063 enforcement.clone().apply_with_outcomes(&mut envelope);
2064
2065 assert!(!limits.is_limited());
2066 assert!(!enforcement.event_indexed.is_active());
2067 assert!(!enforcement.event.is_active());
2068 assert!(!enforcement.profiles_indexed.is_active());
2069 assert!(!enforcement.profiles.is_active());
2070 assert!(!enforcement.spans.is_active());
2071 assert!(!enforcement.spans_indexed.is_active());
2072 mock.lock().await.assert_call(DataCategory::Transaction, 1);
2073 mock.lock().await.assert_call(DataCategory::Profile, 1);
2074 mock.lock().await.assert_call(DataCategory::Span, 1);
2075 }
2076
2077 #[tokio::test]
2078 async fn test_enforce_transaction_no_indexing_quota() {
2079 let mut envelope = envelope![Transaction];
2080
2081 let mock = mock_limiter(&[DataCategory::TransactionIndexed]);
2082 let (enforcement, limits) = enforce_and_apply(mock.clone(), &mut envelope, None).await;
2083
2084 assert!(limits.is_limited());
2085 assert!(enforcement.event_indexed.is_active());
2086 assert!(!enforcement.event.is_active());
2087 mock.lock().await.assert_call(DataCategory::Transaction, 1);
2088 mock.lock()
2089 .await
2090 .assert_call(DataCategory::TransactionIndexed, 1);
2091 }
2092
2093 #[tokio::test]
2094 async fn test_enforce_transaction_attachment_enforced() {
2095 let mut envelope = envelope![Transaction, Attachment];
2096
2097 let mock = mock_limiter(&[DataCategory::Transaction]);
2098 let (enforcement, _) = enforce_and_apply(mock.clone(), &mut envelope, None).await;
2099
2100 assert!(enforcement.event.is_active());
2101 assert!(enforcement.attachments_limits.event.is_active());
2102 mock.lock().await.assert_call(DataCategory::Transaction, 1);
2103 }
2104
2105 fn get_outcomes(enforcement: Enforcement) -> Vec<(DataCategory, usize)> {
2106 enforcement
2107 .get_outcomes()
2108 .map(|(_, data_category, quantity)| (data_category, quantity))
2109 .collect::<Vec<_>>()
2110 }
2111
2112 #[tokio::test]
2113 async fn test_enforce_transaction_profile_enforced() {
2114 let mut envelope = envelope![Transaction, Profile];
2115
2116 let mock = mock_limiter(&[DataCategory::Transaction]);
2117 let (enforcement, _) = enforce_and_apply(mock.clone(), &mut envelope, None).await;
2118
2119 assert!(enforcement.event.is_active());
2120 assert!(enforcement.profiles.is_active());
2121 mock.lock().await.assert_call(DataCategory::Transaction, 1);
2122
2123 assert_eq!(
2124 get_outcomes(enforcement),
2125 vec![
2126 (DataCategory::Transaction, 1),
2127 (DataCategory::TransactionIndexed, 1),
2128 (DataCategory::Profile, 1),
2129 (DataCategory::ProfileIndexed, 1),
2130 (DataCategory::Span, 1),
2131 (DataCategory::SpanIndexed, 1),
2132 ]
2133 );
2134 }
2135
2136 #[tokio::test]
2137 async fn test_enforce_transaction_standalone_profile_enforced() {
2138 let mut envelope = envelope![Profile];
2140
2141 let mock = mock_limiter(&[DataCategory::Transaction]);
2142 let (enforcement, _) = enforce_and_apply(mock.clone(), &mut envelope, None).await;
2143
2144 assert!(enforcement.profiles.is_active());
2145 mock.lock().await.assert_call(DataCategory::Profile, 1);
2146 mock.lock().await.assert_call(DataCategory::Transaction, 0);
2147
2148 assert_eq!(
2149 get_outcomes(enforcement),
2150 vec![
2151 (DataCategory::Profile, 1),
2152 (DataCategory::ProfileIndexed, 1),
2153 ]
2154 );
2155 }
2156
2157 #[tokio::test]
2158 async fn test_enforce_transaction_attachment_enforced_indexing_quota() {
2159 let mut envelope = envelope![Transaction, Attachment];
2160 set_extracted(envelope.envelope_mut(), ItemType::Transaction);
2161
2162 let mock = mock_limiter(&[DataCategory::TransactionIndexed]);
2163 let (enforcement, _) = enforce_and_apply(mock.clone(), &mut envelope, None).await;
2164
2165 assert!(!enforcement.event.is_active());
2166 assert!(enforcement.event_indexed.is_active());
2167 assert!(enforcement.attachments_limits.event.is_active());
2168 mock.lock().await.assert_call(DataCategory::Transaction, 1);
2169 mock.lock()
2170 .await
2171 .assert_call(DataCategory::TransactionIndexed, 1);
2172
2173 assert_eq!(
2174 get_outcomes(enforcement),
2175 vec![
2176 (DataCategory::TransactionIndexed, 1),
2177 (DataCategory::Attachment, 10),
2178 (DataCategory::AttachmentItem, 1),
2179 (DataCategory::SpanIndexed, 1),
2180 ]
2181 );
2182 }
2183
2184 #[tokio::test]
2185 async fn test_enforce_span() {
2186 let mut envelope = envelope![Span, Span];
2187
2188 let mock = mock_limiter(&[DataCategory::Span]);
2189 let (enforcement, limits) = enforce_and_apply(mock.clone(), &mut envelope, None).await;
2190
2191 assert!(limits.is_limited());
2192 assert!(enforcement.spans_indexed.is_active());
2193 assert!(enforcement.spans.is_active());
2194 mock.lock().await.assert_call(DataCategory::Span, 2);
2195
2196 assert_eq!(
2197 get_outcomes(enforcement),
2198 vec![(DataCategory::Span, 2), (DataCategory::SpanIndexed, 2)]
2199 );
2200 }
2201
2202 #[tokio::test]
2203 async fn test_enforce_span_no_indexing_quota() {
2204 let mut envelope = envelope![Span, Span];
2205
2206 let mock = mock_limiter(&[DataCategory::SpanIndexed]);
2207 let (enforcement, limits) = enforce_and_apply(mock.clone(), &mut envelope, None).await;
2208
2209 assert!(limits.is_limited());
2210 assert!(enforcement.spans_indexed.is_active());
2211 assert!(!enforcement.spans.is_active());
2212 mock.lock().await.assert_call(DataCategory::Span, 2);
2213 mock.lock().await.assert_call(DataCategory::SpanIndexed, 2);
2214
2215 assert_eq!(
2216 get_outcomes(enforcement),
2217 vec![(DataCategory::SpanIndexed, 2)]
2218 );
2219 }
2220
2221 #[tokio::test]
2222 async fn test_enforce_span_metrics_extracted_no_indexing_quota() {
2223 let mut envelope = envelope![Span, Span];
2224 set_extracted(envelope.envelope_mut(), ItemType::Span);
2225
2226 let mock = mock_limiter(&[DataCategory::SpanIndexed]);
2227 let (enforcement, limits) = enforce_and_apply(mock.clone(), &mut envelope, None).await;
2228
2229 assert!(limits.is_limited());
2230 assert!(enforcement.spans_indexed.is_active());
2231 assert!(!enforcement.spans.is_active());
2232 mock.lock().await.assert_call(DataCategory::Span, 2);
2233 mock.lock().await.assert_call(DataCategory::SpanIndexed, 2);
2234
2235 assert_eq!(
2236 get_outcomes(enforcement),
2237 vec![(DataCategory::SpanIndexed, 2)]
2238 );
2239 }
2240
2241 #[test]
2242 fn test_source_quantity_for_total_quantity() {
2243 let dsn = "https://e12d836b15bb49d7bbf99e64295d995b:@sentry.io/42"
2244 .parse()
2245 .unwrap();
2246 let request_meta = RequestMeta::new(dsn);
2247
2248 let mut envelope = Envelope::from_request(None, request_meta);
2249
2250 let mut item = Item::new(ItemType::MetricBuckets);
2251 item.set_source_quantities(SourceQuantities {
2252 transactions: 5,
2253 spans: 0,
2254 buckets: 5,
2255 });
2256 envelope.add_item(item);
2257
2258 let mut item = Item::new(ItemType::MetricBuckets);
2259 item.set_source_quantities(SourceQuantities {
2260 transactions: 2,
2261 spans: 0,
2262 buckets: 3,
2263 });
2264 envelope.add_item(item);
2265
2266 let summary = EnvelopeSummary::compute(&envelope);
2267
2268 assert_eq!(summary.secondary_transaction_quantity, 7);
2269 }
2270
2271 #[tokio::test]
2272 async fn test_enforce_limit_logs_count() {
2273 let mut envelope = envelope![Log, Log];
2274
2275 let mock = mock_limiter(&[DataCategory::LogItem]);
2276 let (enforcement, limits) = enforce_and_apply(mock.clone(), &mut envelope, None).await;
2277
2278 assert!(limits.is_limited());
2279 assert_eq!(envelope.envelope().len(), 0);
2280 mock.lock().await.assert_call(DataCategory::LogItem, 2);
2281
2282 assert_eq!(
2283 get_outcomes(enforcement),
2284 vec![(DataCategory::LogItem, 2), (DataCategory::LogByte, 20)]
2285 );
2286 }
2287
2288 #[tokio::test]
2289 async fn test_enforce_limit_logs_bytes() {
2290 let mut envelope = envelope![Log, Log];
2291
2292 let mock = mock_limiter(&[DataCategory::LogByte]);
2293 let (enforcement, limits) = enforce_and_apply(mock.clone(), &mut envelope, None).await;
2294
2295 assert!(limits.is_limited());
2296 assert_eq!(envelope.envelope().len(), 0);
2297 mock.lock().await.assert_call(DataCategory::LogItem, 2);
2298 mock.lock().await.assert_call(DataCategory::LogByte, 20);
2299
2300 assert_eq!(
2301 get_outcomes(enforcement),
2302 vec![(DataCategory::LogItem, 2), (DataCategory::LogByte, 20)]
2303 );
2304 }
2305
2306 #[tokio::test]
2307 async fn test_enforce_standalone_span_attachment() {
2308 let test_cases = &[
2309 RateLimitTestCase {
2310 name: "span_limit",
2311 denied_categories: &[DataCategory::Span],
2312 expect_attachment_limit_active: true,
2313 expected_limiter_calls: &[(DataCategory::Span, 0)],
2314 expected_outcomes: &[
2315 (DataCategory::Attachment, 7),
2316 (DataCategory::AttachmentItem, 1),
2317 ],
2318 },
2319 RateLimitTestCase {
2320 name: "span_indexed_limit",
2321 denied_categories: &[DataCategory::SpanIndexed],
2322 expect_attachment_limit_active: true,
2323 expected_limiter_calls: &[(DataCategory::Span, 0), (DataCategory::SpanIndexed, 0)],
2324 expected_outcomes: &[
2325 (DataCategory::Attachment, 7),
2326 (DataCategory::AttachmentItem, 1),
2327 ],
2328 },
2329 RateLimitTestCase {
2330 name: "attachment_limit",
2331 denied_categories: &[DataCategory::Attachment],
2332 expect_attachment_limit_active: true,
2333 expected_limiter_calls: &[
2334 (DataCategory::Span, 0),
2335 (DataCategory::SpanIndexed, 0),
2336 (DataCategory::Attachment, 7),
2337 ],
2338 expected_outcomes: &[
2339 (DataCategory::Attachment, 7),
2340 (DataCategory::AttachmentItem, 1),
2341 ],
2342 },
2343 RateLimitTestCase {
2344 name: "attachment_indexed_limit",
2345 denied_categories: &[DataCategory::AttachmentItem],
2346 expect_attachment_limit_active: true,
2347 expected_limiter_calls: &[
2348 (DataCategory::Span, 0),
2349 (DataCategory::SpanIndexed, 0),
2350 (DataCategory::Attachment, 7),
2351 (DataCategory::AttachmentItem, 1),
2352 ],
2353 expected_outcomes: &[
2354 (DataCategory::Attachment, 7),
2355 (DataCategory::AttachmentItem, 1),
2356 ],
2357 },
2358 RateLimitTestCase {
2359 name: "transaction_limit",
2360 denied_categories: &[DataCategory::Transaction],
2361 expect_attachment_limit_active: false,
2362 expected_limiter_calls: &[
2363 (DataCategory::Span, 0),
2364 (DataCategory::SpanIndexed, 0),
2365 (DataCategory::Attachment, 7),
2366 (DataCategory::AttachmentItem, 1),
2367 ],
2368 expected_outcomes: &[],
2369 },
2370 RateLimitTestCase {
2371 name: "error_limit",
2372 denied_categories: &[DataCategory::Error],
2373 expect_attachment_limit_active: false,
2374 expected_limiter_calls: &[
2375 (DataCategory::Span, 0),
2376 (DataCategory::SpanIndexed, 0),
2377 (DataCategory::Attachment, 7),
2378 (DataCategory::AttachmentItem, 1),
2379 ],
2380 expected_outcomes: &[],
2381 },
2382 RateLimitTestCase {
2383 name: "no_limits",
2384 denied_categories: &[],
2385 expect_attachment_limit_active: false,
2386 expected_limiter_calls: &[
2387 (DataCategory::Span, 0),
2388 (DataCategory::SpanIndexed, 0),
2389 (DataCategory::Attachment, 7),
2390 (DataCategory::AttachmentItem, 1),
2391 ],
2392 expected_outcomes: &[],
2393 },
2394 ];
2395
2396 for RateLimitTestCase {
2397 name,
2398 denied_categories,
2399 expect_attachment_limit_active,
2400 expected_limiter_calls,
2401 expected_outcomes,
2402 } in test_cases
2403 {
2404 let mut envelope = envelope![];
2405 envelope
2406 .envelope_mut()
2407 .add_item(trace_attachment_item(7, Some(ParentId::SpanId(None))));
2408
2409 let mock = mock_limiter(denied_categories);
2410 let (enforcement, _) = enforce_and_apply(mock.clone(), &mut envelope, None).await;
2411
2412 for &(category, quantity) in *expected_limiter_calls {
2413 mock.lock().await.assert_call(category, quantity);
2414 }
2415
2416 assert_eq!(
2417 enforcement.attachments_limits.span.bytes.is_active(),
2418 *expect_attachment_limit_active,
2419 "{name}: span_attachment byte limit mismatch"
2420 );
2421 assert_eq!(
2422 enforcement.attachments_limits.span.count.is_active(),
2423 *expect_attachment_limit_active,
2424 "{name}: span_attachment count limit mismatch"
2425 );
2426
2427 assert_eq!(
2428 get_outcomes(enforcement),
2429 *expected_outcomes,
2430 "{name}: outcome mismatch"
2431 );
2432 }
2433 }
2434
2435 #[tokio::test]
2436 async fn test_enforce_span_with_span_attachment() {
2437 let test_cases = &[
2438 RateLimitTestCase {
2439 name: "span_limit",
2440 denied_categories: &[DataCategory::Span, DataCategory::Attachment], expect_attachment_limit_active: true,
2442 expected_limiter_calls: &[(DataCategory::Span, 1)],
2443 expected_outcomes: &[
2444 (DataCategory::Attachment, 7),
2445 (DataCategory::AttachmentItem, 1),
2446 (DataCategory::Span, 1),
2447 (DataCategory::SpanIndexed, 1),
2448 ],
2449 },
2450 RateLimitTestCase {
2451 name: "span_indexed_limit",
2452 denied_categories: &[DataCategory::SpanIndexed, DataCategory::Attachment],
2453 expect_attachment_limit_active: true,
2454 expected_limiter_calls: &[(DataCategory::Span, 1), (DataCategory::SpanIndexed, 1)],
2455 expected_outcomes: &[
2456 (DataCategory::Attachment, 7),
2457 (DataCategory::AttachmentItem, 1),
2458 (DataCategory::SpanIndexed, 1),
2459 ],
2460 },
2461 RateLimitTestCase {
2462 name: "attachment_limit",
2463 denied_categories: &[DataCategory::Attachment],
2464 expect_attachment_limit_active: true,
2465 expected_limiter_calls: &[
2466 (DataCategory::Span, 1),
2467 (DataCategory::SpanIndexed, 1),
2468 (DataCategory::Attachment, 7),
2469 ],
2470 expected_outcomes: &[
2471 (DataCategory::Attachment, 7),
2472 (DataCategory::AttachmentItem, 1),
2473 ],
2474 },
2475 RateLimitTestCase {
2476 name: "attachment_indexed_limit",
2477 denied_categories: &[DataCategory::AttachmentItem],
2478 expect_attachment_limit_active: true,
2479 expected_limiter_calls: &[
2480 (DataCategory::Span, 1),
2481 (DataCategory::SpanIndexed, 1),
2482 (DataCategory::Attachment, 7),
2483 (DataCategory::AttachmentItem, 1),
2484 ],
2485 expected_outcomes: &[
2486 (DataCategory::Attachment, 7),
2487 (DataCategory::AttachmentItem, 1),
2488 ],
2489 },
2490 RateLimitTestCase {
2491 name: "transaction_limit",
2492 denied_categories: &[DataCategory::Transaction],
2493 expect_attachment_limit_active: false,
2494 expected_limiter_calls: &[
2495 (DataCategory::Span, 1),
2496 (DataCategory::SpanIndexed, 1),
2497 (DataCategory::Attachment, 7),
2498 (DataCategory::AttachmentItem, 1),
2499 ],
2500 expected_outcomes: &[],
2501 },
2502 RateLimitTestCase {
2503 name: "error_limit",
2504 denied_categories: &[DataCategory::Error],
2505 expect_attachment_limit_active: false,
2506 expected_limiter_calls: &[
2507 (DataCategory::Span, 1),
2508 (DataCategory::SpanIndexed, 1),
2509 (DataCategory::Attachment, 7),
2510 (DataCategory::AttachmentItem, 1),
2511 ],
2512 expected_outcomes: &[],
2513 },
2514 RateLimitTestCase {
2515 name: "no_limits",
2516 denied_categories: &[],
2517 expect_attachment_limit_active: false,
2518 expected_limiter_calls: &[
2519 (DataCategory::Span, 1),
2520 (DataCategory::SpanIndexed, 1),
2521 (DataCategory::Attachment, 7),
2522 (DataCategory::AttachmentItem, 1),
2523 ],
2524 expected_outcomes: &[],
2525 },
2526 ];
2527
2528 for RateLimitTestCase {
2529 name,
2530 denied_categories,
2531 expect_attachment_limit_active,
2532 expected_limiter_calls,
2533 expected_outcomes,
2534 } in test_cases
2535 {
2536 let mut envelope = envelope![Span];
2537 envelope
2538 .envelope_mut()
2539 .add_item(trace_attachment_item(7, Some(ParentId::SpanId(None))));
2540
2541 let mock = mock_limiter(denied_categories);
2542 let (enforcement, _) = enforce_and_apply(mock.clone(), &mut envelope, None).await;
2543
2544 for &(category, quantity) in *expected_limiter_calls {
2545 mock.lock().await.assert_call(category, quantity);
2546 }
2547
2548 assert_eq!(
2549 enforcement.attachments_limits.span.bytes.is_active(),
2550 *expect_attachment_limit_active,
2551 "{name}: span_attachment byte limit mismatch"
2552 );
2553 assert_eq!(
2554 enforcement.attachments_limits.span.count.is_active(),
2555 *expect_attachment_limit_active,
2556 "{name}: span_attachment count limit mismatch"
2557 );
2558
2559 assert_eq!(
2560 get_outcomes(enforcement),
2561 *expected_outcomes,
2562 "{name}: outcome mismatch"
2563 );
2564 }
2565 }
2566
2567 #[tokio::test]
2568 async fn test_enforce_transaction_span_attachment() {
2569 let test_cases = &[
2570 RateLimitTestCase {
2571 name: "span_limit",
2572 denied_categories: &[DataCategory::Span],
2573 expect_attachment_limit_active: true,
2574 expected_limiter_calls: &[
2575 (DataCategory::Transaction, 1),
2576 (DataCategory::TransactionIndexed, 1),
2577 (DataCategory::Span, 1),
2578 ],
2579 expected_outcomes: &[
2580 (DataCategory::Attachment, 7),
2581 (DataCategory::AttachmentItem, 1),
2582 (DataCategory::Span, 1),
2583 (DataCategory::SpanIndexed, 1),
2584 ],
2585 },
2586 RateLimitTestCase {
2587 name: "transaction_limit",
2588 denied_categories: &[DataCategory::Transaction],
2589 expect_attachment_limit_active: true,
2590 expected_limiter_calls: &[(DataCategory::Transaction, 1)],
2591 expected_outcomes: &[
2592 (DataCategory::Transaction, 1),
2593 (DataCategory::TransactionIndexed, 1),
2594 (DataCategory::Attachment, 7),
2595 (DataCategory::AttachmentItem, 1),
2596 (DataCategory::Span, 1),
2597 (DataCategory::SpanIndexed, 1),
2598 ],
2599 },
2600 RateLimitTestCase {
2601 name: "error_limit",
2602 denied_categories: &[DataCategory::Error],
2603 expect_attachment_limit_active: false,
2604 expected_limiter_calls: &[
2605 (DataCategory::Transaction, 1),
2606 (DataCategory::TransactionIndexed, 1),
2607 (DataCategory::Span, 1),
2608 (DataCategory::SpanIndexed, 1),
2609 (DataCategory::Attachment, 7),
2610 (DataCategory::AttachmentItem, 1),
2611 ],
2612 expected_outcomes: &[],
2613 },
2614 RateLimitTestCase {
2615 name: "no_limits",
2616 denied_categories: &[],
2617 expect_attachment_limit_active: false,
2618 expected_limiter_calls: &[
2619 (DataCategory::Transaction, 1),
2620 (DataCategory::TransactionIndexed, 1),
2621 (DataCategory::Span, 1),
2622 (DataCategory::SpanIndexed, 1),
2623 (DataCategory::Attachment, 7),
2624 (DataCategory::AttachmentItem, 1),
2625 ],
2626 expected_outcomes: &[],
2627 },
2628 ];
2629
2630 for RateLimitTestCase {
2631 name,
2632 denied_categories,
2633 expect_attachment_limit_active,
2634 expected_limiter_calls,
2635 expected_outcomes,
2636 } in test_cases
2637 {
2638 let mut envelope = envelope![Transaction];
2639 envelope
2640 .envelope_mut()
2641 .add_item(trace_attachment_item(7, Some(ParentId::SpanId(None))));
2642
2643 let mock = mock_limiter(denied_categories);
2644 let (enforcement, _) = enforce_and_apply(mock.clone(), &mut envelope, None).await;
2645
2646 for &(category, quantity) in *expected_limiter_calls {
2647 mock.lock().await.assert_call(category, quantity);
2648 }
2649
2650 assert_eq!(
2651 enforcement.attachments_limits.span.bytes.is_active(),
2652 *expect_attachment_limit_active,
2653 "{name}: span_attachment byte limit mismatch"
2654 );
2655 assert_eq!(
2656 enforcement.attachments_limits.span.count.is_active(),
2657 *expect_attachment_limit_active,
2658 "{name}: span_attachment count limit mismatch"
2659 );
2660
2661 assert_eq!(
2662 get_outcomes(enforcement),
2663 *expected_outcomes,
2664 "{name}: outcome mismatch"
2665 );
2666 }
2667 }
2668
2669 #[tokio::test]
2670 async fn test_enforce_standalone_trace_attachment() {
2671 let test_cases = &[
2672 RateLimitTestCase {
2673 name: "attachment_limit",
2674 denied_categories: &[DataCategory::Attachment],
2675 expect_attachment_limit_active: true,
2676 expected_limiter_calls: &[(DataCategory::Attachment, 7)],
2677 expected_outcomes: &[
2678 (DataCategory::Attachment, 7),
2679 (DataCategory::AttachmentItem, 1),
2680 ],
2681 },
2682 RateLimitTestCase {
2683 name: "attachment_limit_and_attachment_item_limit",
2684 denied_categories: &[DataCategory::Attachment, DataCategory::AttachmentItem],
2685 expect_attachment_limit_active: true,
2686 expected_limiter_calls: &[(DataCategory::Attachment, 7)],
2687 expected_outcomes: &[
2688 (DataCategory::Attachment, 7),
2689 (DataCategory::AttachmentItem, 1),
2690 ],
2691 },
2692 RateLimitTestCase {
2693 name: "attachment_item_limit",
2694 denied_categories: &[DataCategory::AttachmentItem],
2695 expect_attachment_limit_active: true,
2696 expected_limiter_calls: &[
2697 (DataCategory::Attachment, 7),
2698 (DataCategory::AttachmentItem, 1),
2699 ],
2700 expected_outcomes: &[
2701 (DataCategory::Attachment, 7),
2702 (DataCategory::AttachmentItem, 1),
2703 ],
2704 },
2705 RateLimitTestCase {
2706 name: "no_limits",
2707 denied_categories: &[],
2708 expect_attachment_limit_active: false,
2709 expected_limiter_calls: &[
2710 (DataCategory::Attachment, 7),
2711 (DataCategory::AttachmentItem, 1),
2712 ],
2713 expected_outcomes: &[],
2714 },
2715 ];
2716
2717 for RateLimitTestCase {
2718 name,
2719 denied_categories,
2720 expect_attachment_limit_active,
2721 expected_limiter_calls,
2722 expected_outcomes,
2723 } in test_cases
2724 {
2725 let mut envelope = envelope![];
2726 envelope
2727 .envelope_mut()
2728 .add_item(trace_attachment_item(7, None));
2729
2730 let mock = mock_limiter(denied_categories);
2731 let (enforcement, _) = enforce_and_apply(mock.clone(), &mut envelope, None).await;
2732
2733 for &(category, quantity) in *expected_limiter_calls {
2734 mock.lock().await.assert_call(category, quantity);
2735 }
2736
2737 assert_eq!(
2738 enforcement.attachments_limits.trace.bytes.is_active(),
2739 *expect_attachment_limit_active,
2740 "{name}: trace_attachment byte limit mismatch"
2741 );
2742 assert_eq!(
2743 enforcement.attachments_limits.trace.count.is_active(),
2744 *expect_attachment_limit_active,
2745 "{name}: trace_attachment count limit mismatch"
2746 );
2747
2748 assert_eq!(
2749 get_outcomes(enforcement),
2750 *expected_outcomes,
2751 "{name}: outcome mismatch"
2752 );
2753 }
2754 }
2755}