1#[cfg(debug_assertions)]
2use std::collections::BTreeMap;
3use std::convert::Infallible;
4use std::fmt;
5use std::iter::FusedIterator;
6use std::mem::ManuallyDrop;
7use std::net::IpAddr;
8use std::sync::Arc;
9use std::sync::atomic::{AtomicBool, Ordering};
10
11use chrono::{DateTime, Utc};
12use itertools::Either;
13use relay_quotas::{DataCategory, Scoping};
14use relay_system::Addr;
15use smallvec::SmallVec;
16
17use crate::Envelope;
18use crate::endpoints::common::BadStoreRequest;
19use crate::extractors::RequestMeta;
20use crate::managed::{Counted, ManagedEnvelope, Quantities};
21use crate::services::outcome::{DiscardReason, Outcome, TrackOutcome};
22use crate::services::processor::ProcessingError;
23
24#[cfg(debug_assertions)]
25mod debug;
26#[cfg(test)]
27mod test;
28
29#[cfg(test)]
30pub use self::test::*;
31
32pub trait OutcomeError {
34 type Error;
36
37 fn consume(self) -> (Option<Outcome>, Self::Error);
41}
42
43impl OutcomeError for Outcome {
44 type Error = ();
45
46 fn consume(self) -> (Option<Outcome>, Self::Error) {
47 (self, ()).consume()
48 }
49}
50
51impl OutcomeError for Option<Outcome> {
52 type Error = ();
53
54 fn consume(self) -> (Option<Outcome>, Self::Error) {
55 (self, ()).consume()
56 }
57}
58
59impl<E> OutcomeError for (Outcome, E) {
60 type Error = E;
61
62 fn consume(self) -> (Option<Outcome>, Self::Error) {
63 (Some(self.0), self.1)
64 }
65}
66
67impl<E> OutcomeError for (Option<Outcome>, E) {
68 type Error = E;
69
70 fn consume(self) -> (Option<Outcome>, Self::Error) {
71 self
72 }
73}
74
75impl OutcomeError for ProcessingError {
76 type Error = Self;
77
78 fn consume(self) -> (Option<Outcome>, Self::Error) {
79 (self.to_outcome(), self)
80 }
81}
82
83impl OutcomeError for Infallible {
84 type Error = Self;
85
86 fn consume(self) -> (Option<Outcome>, Self::Error) {
87 match self {}
88 }
89}
90
91impl OutcomeError for BadStoreRequest {
92 type Error = Self;
93
94 fn consume(self) -> (Option<Outcome>, Self) {
95 (self.to_outcome(), self)
96 }
97}
98
99#[derive(Debug, Clone, Copy)]
104#[must_use = "a rejection must be propagated"]
105pub struct Rejected<T>(T);
106
107impl<T> Rejected<T> {
108 pub fn into_inner(self) -> T {
110 self.0
111 }
112
113 pub fn map<F, S>(self, f: F) -> Rejected<S>
115 where
116 F: FnOnce(T) -> S,
117 {
118 Rejected(f(self.0))
119 }
120}
121
122impl<T> std::error::Error for Rejected<T>
123where
124 T: std::error::Error,
125{
126 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
127 self.0.source()
128 }
129}
130
131impl<T> std::fmt::Display for Rejected<T>
132where
133 T: std::fmt::Display,
134{
135 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136 self.0.fmt(f)
137 }
138}
139
140impl<T> axum::response::IntoResponse for Rejected<T>
141where
142 T: axum::response::IntoResponse,
143{
144 fn into_response(self) -> axum::response::Response {
145 self.0.into_response()
146 }
147}
148
149pub struct Managed<T: Counted> {
151 value: T,
152 meta: Arc<Meta>,
153 done: AtomicBool,
154}
155
156impl Managed<Box<Envelope>> {
157 pub fn from_envelope(envelope: Box<Envelope>, outcome_aggregator: Addr<TrackOutcome>) -> Self {
159 let meta = Arc::new(Meta {
160 outcome_aggregator,
161 received_at: envelope.received_at(),
162 scoping: envelope.meta().get_partial_scoping().into_scoping(),
163 remote_addr: envelope.meta().remote_addr(),
164 });
165
166 Self::from_parts(envelope, meta)
167 }
168}
169
170pub trait RetainMut<I> {
172 fn retain_mut(&mut self, f: impl FnMut(&mut I) -> bool);
174}
175
176impl<I> RetainMut<I> for Vec<I> {
177 fn retain_mut(&mut self, f: impl FnMut(&mut I) -> bool) {
178 Vec::retain_mut(self, f)
179 }
180}
181impl<I, const N: usize> RetainMut<I> for SmallVec<[I; N]> {
182 fn retain_mut(&mut self, f: impl FnMut(&mut I) -> bool) {
183 SmallVec::retain_mut(self, f)
184 }
185}
186
187impl<T: Counted> Managed<T> {
188 pub fn with_meta_from_managed_envelope(envelope: &ManagedEnvelope, value: T) -> Self {
193 Self::from_parts(
194 value,
195 Arc::new(Meta {
196 outcome_aggregator: envelope.outcome_aggregator().clone(),
197 received_at: envelope.received_at(),
198 scoping: envelope.scoping(),
199 remote_addr: envelope.meta().remote_addr(),
200 }),
201 )
202 }
203
204 pub fn with_meta_from_request_meta(
206 request_meta: &RequestMeta,
207 outcome_aggregator: &Addr<TrackOutcome>,
208 value: T,
209 ) -> Self {
210 Self::from_parts(
211 value,
212 Arc::new(Meta {
213 outcome_aggregator: outcome_aggregator.clone(),
214 received_at: request_meta.received_at(),
215 scoping: request_meta.get_partial_scoping().into_scoping(),
216 remote_addr: request_meta.remote_addr(),
217 }),
218 )
219 }
220
221 pub fn wrap<S>(&self, other: S) -> Managed<S>
223 where
224 S: Counted,
225 {
226 Managed::from_parts(other, Arc::clone(&self.meta))
227 }
228
229 pub fn boxed(self) -> Managed<Box<T>> {
231 self.map(|value, _| Box::new(value))
232 }
233
234 pub fn received_at(&self) -> DateTime<Utc> {
236 self.meta.received_at
237 }
238
239 pub fn scoping(&self) -> Scoping {
241 self.meta.scoping
242 }
243
244 pub fn remote_addr(&self) -> Option<IpAddr> {
246 self.meta.remote_addr
247 }
248
249 pub fn scope(&mut self, scoping: Scoping) {
259 let meta = Arc::make_mut(&mut self.meta);
260 meta.scoping = scoping;
261 }
262
263 pub fn merge_with<S, F>(&mut self, other: Managed<S>, f: F)
268 where
269 S: Counted,
270 F: FnOnce(&mut T, S, &mut RecordKeeper),
271 {
272 self.modify(|s, records| {
273 for (category, quantity) in other.quantities() {
274 records.modify_by(category, quantity as isize);
275 }
276 other.accept(|o| f(s, o, records));
277 })
278 }
279 pub fn zip<S>(first: Self, second: Managed<S>) -> Managed<(T, S)>
284 where
285 S: Counted,
286 {
287 debug_assert_eq!(
288 first.scoping(),
289 second.scoping(),
290 "cannot zip Managed values with different metadata"
291 );
292 first.map(|first, records| {
293 for (category, quantity) in second.quantities() {
294 records.modify_by(category, quantity as isize);
295 }
296 let second = second.accept(|second| second);
297 (first, second)
298 })
299 }
300
301 pub fn split_once<F, S, U>(self, f: F) -> (Managed<S>, Managed<U>)
307 where
308 F: FnOnce(T, &mut RecordKeeper) -> (S, U),
309 S: Counted,
310 U: Counted,
311 {
312 debug_assert!(!self.is_done());
313
314 let (value, meta) = self.destructure();
315 let quantities = value.quantities();
316
317 let mut records = RecordKeeper::new(&meta, quantities);
318
319 let (a, b) = f(value, &mut records);
320
321 let mut quantities = a.quantities();
322 quantities.extend(b.quantities());
323 records.success(quantities);
324
325 (
326 Managed::from_parts(a, Arc::clone(&meta)),
327 Managed::from_parts(b, meta),
328 )
329 }
330
331 pub fn split<F, I, S>(self, f: F) -> Split<I::IntoIter, I::Item>
336 where
337 F: FnOnce(T) -> I,
338 I: IntoIterator<Item = S>,
339 S: Counted,
340 {
341 self.split_with_context(|value| (f(value), ())).0
342 }
343
344 pub fn split_with_context<F, I, S, C>(self, f: F) -> (Split<I::IntoIter, I::Item>, C)
349 where
350 F: FnOnce(T) -> (I, C),
351 I: IntoIterator<Item = S>,
352 S: Counted,
353 {
354 debug_assert!(!self.is_done());
355
356 let (value, meta) = self.destructure();
357 #[cfg(debug_assertions)]
358 let quantities = value.quantities();
359
360 let (items, context) = f(value);
361
362 (
363 Split {
364 #[cfg(debug_assertions)]
365 quantities,
366 items: items.into_iter(),
367 meta,
368 exhausted: false,
369 },
370 context,
371 )
372 }
373
374 pub fn retain<S, I, U, E, V>(&mut self, select: S, mut retain: U)
410 where
411 S: FnOnce(&mut T) -> &mut V,
412 I: Counted,
413 U: FnMut(&mut I, &mut RecordKeeper<'_>) -> Result<(), E>,
414 E: OutcomeError,
415 V: RetainMut<I>,
416 {
417 self.retain_with_context(
418 |inner| (select(inner), &()),
419 |item, _, records| retain(item, records),
420 );
421 }
422
423 pub fn retain_with_context<S, C, I, U, E, V>(&mut self, select: S, mut retain: U)
460 where
461 for<'a> S: FnOnce(&'a mut T) -> (&'a mut V, &'a C),
466 I: Counted,
467 U: FnMut(&mut I, &C, &mut RecordKeeper<'_>) -> Result<(), E>,
468 E: OutcomeError,
469 V: RetainMut<I>,
470 {
471 self.modify(|inner, records| {
472 let (items, ctx) = select(inner);
473 items.retain_mut(|item| match retain(item, ctx, records) {
474 Ok(()) => true,
475 Err(err) => {
476 records.reject_err(err, &*item);
477 false
478 }
479 })
480 });
481 }
482
483 pub fn map<S, F>(self, f: F) -> Managed<S>
487 where
488 F: FnOnce(T, &mut RecordKeeper) -> S,
489 S: Counted,
490 {
491 self.try_map(move |inner, records| Ok::<_, Infallible>(f(inner, records)))
492 .unwrap_or_else(|e| match e.0 {})
493 }
494
495 pub fn try_map<S, F, E>(self, f: F) -> Result<Managed<S>, Rejected<E::Error>>
503 where
504 F: FnOnce(T, &mut RecordKeeper) -> Result<S, E>,
505 S: Counted,
506 E: OutcomeError,
507 {
508 debug_assert!(!self.is_done());
509
510 let (value, meta) = self.destructure();
511 let quantities = value.quantities();
512
513 let mut records = RecordKeeper::new(&meta, quantities);
514
515 match f(value, &mut records) {
516 Ok(value) => {
517 records.success(value.quantities());
518 Ok(Managed::from_parts(value, meta))
519 }
520 Err(err) => Err(records.failure(err)),
521 }
522 }
523
524 pub fn modify<F>(&mut self, f: F)
528 where
529 F: FnOnce(&mut T, &mut RecordKeeper),
530 {
531 self.try_modify(move |inner, records| {
532 f(inner, records);
533 Ok::<_, Infallible>(())
534 })
535 .unwrap_or_else(|e| match e {})
536 }
537
538 pub fn try_modify<F, E>(&mut self, f: F) -> Result<(), Rejected<E::Error>>
546 where
547 F: FnOnce(&mut T, &mut RecordKeeper) -> Result<(), E>,
548 E: OutcomeError,
549 {
550 debug_assert!(!self.is_done());
551
552 let quantities = self.value.quantities();
553 let mut records = RecordKeeper::new(&self.meta, quantities);
554
555 match f(&mut self.value, &mut records) {
556 Ok(()) => {
557 records.success(self.value.quantities());
558 Ok(())
559 }
560 Err(err) => {
561 let err = records.failure(err);
562 self.done.store(true, Ordering::Relaxed);
563 Err(err)
564 }
565 }
566 }
567
568 pub fn accept<F, S>(self, f: F) -> S
576 where
577 F: FnOnce(T) -> S,
578 {
579 self.try_accept(|item| Ok::<_, Infallible>(f(item)))
580 .unwrap_or_else(|err| match err.0 {})
581 }
582
583 pub fn try_accept<F, S, E>(self, f: F) -> Result<S, Rejected<E::Error>>
591 where
592 F: FnOnce(T) -> Result<S, E>,
593 E: OutcomeError,
594 {
595 debug_assert!(!self.is_done());
596
597 let (value, meta) = self.destructure();
598 let records = RecordKeeper::new(&meta, value.quantities());
599
600 match f(value) {
601 Ok(value) => {
602 records.accept();
603 Ok(value)
604 }
605 Err(err) => Err(records.failure(err)),
606 }
607 }
608
609 #[track_caller]
617 pub fn internal_error(&self, reason: &'static str) -> Rejected<()> {
618 relay_log::error!("internal error: {reason}");
619 debug_assert!(false, "internal error: {reason}");
620 self.reject_err((Outcome::Invalid(DiscardReason::Internal), ()))
621 }
622
623 pub fn reject_err<E>(&self, error: E) -> Rejected<E::Error>
625 where
626 E: OutcomeError,
627 {
628 debug_assert!(!self.is_done());
629
630 let (outcome, error) = error.consume();
631 self.do_reject(outcome);
632 Rejected(error)
633 }
634
635 fn do_reject(&self, outcome: Option<Outcome>) {
636 let is_done = self.done.fetch_or(true, Ordering::Relaxed);
639
640 let Some(outcome) = outcome else {
642 return;
643 };
644
645 if !is_done {
651 for (category, quantity) in self.value.quantities() {
652 self.meta.track_outcome(outcome.clone(), category, quantity);
653 }
654 }
655 }
656
657 fn destructure(self) -> (T, Arc<Meta>) {
664 let this = ManuallyDrop::new(self);
674 let Managed { value, meta, done } = &*this;
675
676 let value = unsafe { std::ptr::read(value) };
677 let meta = unsafe { std::ptr::read(meta) };
678 let done = unsafe { std::ptr::read(done) };
679 debug_assert!(
682 !done.load(Ordering::Relaxed),
683 "a `done` managed should never be destructured"
684 );
685
686 (value, meta)
687 }
688
689 fn from_parts(value: T, meta: Arc<Meta>) -> Self {
690 Self {
691 value,
692 meta,
693 done: AtomicBool::new(false),
694 }
695 }
696
697 fn is_done(&self) -> bool {
698 self.done.load(Ordering::Relaxed)
699 }
700}
701
702impl<T: Counted> Drop for Managed<T> {
703 fn drop(&mut self) {
704 self.do_reject(Some(Outcome::Invalid(DiscardReason::Internal)));
705 }
706}
707
708impl<T: Counted + fmt::Debug> fmt::Debug for Managed<T> {
709 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
710 write!(f, "Managed<{}>[", std::any::type_name::<T>())?;
711 for (i, (category, quantity)) in self.value.quantities().iter().enumerate() {
712 if i > 0 {
713 write!(f, ",")?;
714 }
715 write!(f, "{category}:{quantity}")?;
716 }
717 write!(f, "](")?;
718 self.value.fmt(f)?;
719 write!(f, ")")
720 }
721}
722
723impl<T: Counted> Managed<Option<T>> {
724 pub fn transpose(self) -> Option<Managed<T>> {
726 let (o, meta) = self.destructure();
727 o.map(|t| Managed::from_parts(t, meta))
728 }
729}
730
731impl<L: Counted, R: Counted> Managed<Either<L, R>> {
732 pub fn transpose(self) -> Either<Managed<L>, Managed<R>> {
734 let (either, meta) = self.destructure();
735 match either {
736 Either::Left(value) => Either::Left(Managed::from_parts(value, meta)),
737 Either::Right(value) => Either::Right(Managed::from_parts(value, meta)),
738 }
739 }
740}
741
742impl From<Managed<Box<Envelope>>> for ManagedEnvelope {
743 fn from(value: Managed<Box<Envelope>>) -> Self {
744 let (value, meta) = value.destructure();
745 let mut envelope = ManagedEnvelope::new(value, meta.outcome_aggregator.clone());
746 envelope.scope(meta.scoping);
747 envelope
748 }
749}
750
751impl<T: Counted> AsRef<T> for Managed<T> {
752 fn as_ref(&self) -> &T {
753 &self.value
754 }
755}
756
757impl<T: Counted> std::ops::Deref for Managed<T> {
758 type Target = T;
759
760 fn deref(&self) -> &Self::Target {
761 &self.value
762 }
763}
764
765#[derive(Debug, Clone)]
767struct Meta {
768 outcome_aggregator: Addr<TrackOutcome>,
770 received_at: DateTime<Utc>,
774 scoping: Scoping,
776 remote_addr: Option<IpAddr>,
778}
779
780impl Meta {
781 pub fn track_outcome(&self, outcome: Outcome, category: DataCategory, quantity: usize) {
782 self.outcome_aggregator.send(TrackOutcome {
783 timestamp: self.received_at,
784 scoping: self.scoping,
785 outcome,
786 event_id: None,
787 remote_addr: self.remote_addr,
788 category,
789 quantity: quantity as _,
790 });
791 }
792}
793
794pub struct RecordKeeper<'a> {
797 meta: &'a Meta,
798 on_drop: Quantities,
799 #[cfg(debug_assertions)]
800 lenient: SmallVec<[DataCategory; 1]>,
801 #[cfg(debug_assertions)]
802 modifications: BTreeMap<DataCategory, isize>,
803 in_flight: SmallVec<[(DataCategory, usize, Option<Outcome>); 2]>,
804}
805
806impl<'a> RecordKeeper<'a> {
807 fn new(meta: &'a Meta, quantities: Quantities) -> Self {
808 Self {
809 meta,
810 on_drop: quantities,
811 #[cfg(debug_assertions)]
812 lenient: Default::default(),
813 #[cfg(debug_assertions)]
814 modifications: Default::default(),
815 in_flight: Default::default(),
816 }
817 }
818
819 pub fn lenient(&mut self, category: DataCategory) {
826 let _category = category;
827 #[cfg(debug_assertions)]
828 self.lenient.push(_category);
829 }
830
831 pub fn modify_by(&mut self, category: DataCategory, offset: isize) {
839 let _category = category;
840 let _offset = offset;
841 #[cfg(debug_assertions)]
842 {
843 *self.modifications.entry(_category).or_default() += offset;
844 }
845 }
846
847 pub fn add<T: Counted>(&mut self, item: T) {
851 for (category, quantities) in item.quantities() {
852 self.modify_by(category, quantities as isize);
853 }
854 }
855
856 pub fn remove<T: Counted>(&mut self, item: &T) {
860 for (category, quantities) in item.quantities() {
861 self.modify_by(category, -(quantities as isize));
862 }
863 }
864
865 fn failure<E>(mut self, error: E) -> Rejected<E::Error>
869 where
870 E: OutcomeError,
871 {
872 let (outcome, error) = error.consume();
873
874 if let Some(outcome) = outcome {
875 for (category, quantity) in std::mem::take(&mut self.on_drop) {
876 self.meta.track_outcome(outcome.clone(), category, quantity);
877 }
878 }
879
880 Rejected(error)
881 }
882
883 fn accept(mut self) {
891 debug_assert!(
892 self.in_flight.is_empty(),
893 "records accepted, but intermediate outcomes tracked"
894 );
895 self.on_drop.clear();
896 }
897
898 fn success(mut self, new: Quantities) {
904 let original = std::mem::take(&mut self.on_drop);
905 self.assert_quantities(original, new);
906
907 self.on_drop.clear();
908 for (category, quantity, outcome) in std::mem::take(&mut self.in_flight) {
909 if let Some(outcome) = outcome {
910 self.meta.track_outcome(outcome, category, quantity);
911 }
912 }
913 }
914
915 #[cfg(debug_assertions)]
920 fn assert_quantities(&self, original: Quantities, new: Quantities) {
921 macro_rules! emit {
922 ($category:expr, $($tt:tt)*) => {{
923 match self.lenient.contains(&$category) {
924 true => relay_log::debug!($($tt)*),
927 false => {
928 relay_log::error!("Original: {original:?}");
929 relay_log::error!("New: {new:?}");
930 relay_log::error!("Modifications: {:?}", self.modifications);
931 relay_log::error!("In Flight: {:?}", self.in_flight);
932 panic!($($tt)*)
933 }
934 }
935 }};
936 }
937
938 let mut sums = debug::Quantities::from(&original).0;
939 for (category, offset) in &self.modifications {
940 let v = sums.entry(*category).or_default();
941 match v.checked_add_signed(*offset) {
942 Some(result) => *v = result,
943 None => emit!(
944 category,
945 "Attempted to modify original quantity {v} into the negative ({offset})"
946 ),
947 }
948 }
949
950 for (category, quantity, outcome) in &self.in_flight {
951 match sums.get_mut(category) {
952 Some(c) if *c >= *quantity => *c -= *quantity,
953 Some(c) => emit!(
954 category,
955 "Emitted {quantity} outcomes ({outcome:?}) for {category}, but there were only {c} items in the category originally"
956 ),
957 None => emit!(
958 category,
959 "Emitted {quantity} outcomes ({outcome:?}) for {category}, but there never was an item in this category"
960 ),
961 }
962 }
963
964 for (category, quantity) in &new {
965 match sums.get_mut(category) {
966 Some(c) if *c >= *quantity => *c -= *quantity,
967 Some(c) => emit!(
968 category,
969 "New item has {quantity} items in category '{category}', but original (after emitted outcomes) only has {c} left"
970 ),
971 None => emit!(
972 category,
973 "New item has {quantity} items in category '{category}', but after emitted outcomes there are none left"
974 ),
975 }
976 }
977
978 for (category, quantity) in sums {
979 if quantity > 0 {
980 emit!(
981 category,
982 "Missing outcomes or mismatched quantity in category '{category}', off by {quantity}"
983 );
984 }
985 }
986 }
987
988 #[cfg(not(debug_assertions))]
989 fn assert_quantities(&self, _: Quantities, _: Quantities) {}
990}
991
992impl<'a> Drop for RecordKeeper<'a> {
993 fn drop(&mut self) {
994 for (category, quantity) in std::mem::take(&mut self.on_drop) {
995 self.meta.track_outcome(
996 Outcome::Invalid(DiscardReason::Internal),
997 category,
998 quantity,
999 );
1000 }
1001 }
1002}
1003
1004impl RecordKeeper<'_> {
1005 pub fn or_default<T, E, Q>(&mut self, r: Result<T, E>, q: Q) -> T
1010 where
1011 T: Default,
1012 E: OutcomeError,
1013 Q: Counted,
1014 {
1015 match r {
1016 Ok(result) => result,
1017 Err(err) => {
1018 self.reject_err(err, q);
1019 T::default()
1020 }
1021 }
1022 }
1023
1024 pub fn reject_err<E, Q>(&mut self, err: E, q: Q) -> E::Error
1029 where
1030 E: OutcomeError,
1031 Q: Counted,
1032 {
1033 let (outcome, err) = err.consume();
1034 for (category, quantity) in q.quantities() {
1035 self.in_flight.push((category, quantity, outcome.clone()))
1036 }
1037 err
1038 }
1039
1040 #[track_caller]
1044 pub fn internal_error<E, Q>(&mut self, error: E, q: Q)
1045 where
1046 E: std::error::Error + 'static,
1047 Q: Counted,
1048 {
1049 relay_log::error!(error = &error as &dyn std::error::Error, "internal error");
1050 debug_assert!(false, "internal error: {error}");
1051 self.reject_err((Outcome::Invalid(DiscardReason::Internal), ()), q);
1052 }
1053}
1054
1055pub struct Split<I, S>
1057where
1058 I: Iterator<Item = S>,
1059 S: Counted,
1060{
1061 #[cfg(debug_assertions)]
1062 quantities: Quantities,
1063 items: I,
1064 meta: Arc<Meta>,
1065 exhausted: bool,
1066}
1067
1068impl<I, S> Split<I, S>
1069where
1070 I: Iterator<Item = S>,
1071 S: Counted,
1072{
1073 #[cfg(debug_assertions)]
1076 fn subtract(&mut self, q: Quantities) {
1077 for (category, quantities) in q {
1078 let Some(orig_quantities) = self
1079 .quantities
1080 .iter_mut()
1081 .find_map(|(c, q)| (*c == category).then_some(q))
1082 else {
1083 debug_assert!(
1084 false,
1085 "mismatching quantities, item split into category {category}, \
1086 which originally was not present"
1087 );
1088 continue;
1089 };
1090
1091 if *orig_quantities >= quantities {
1092 *orig_quantities -= quantities;
1093 } else {
1094 debug_assert!(
1095 false,
1096 "in total more items produced in category {category} than originally available"
1097 );
1098 }
1099 }
1100 }
1101}
1102
1103impl<I, S> Iterator for Split<I, S>
1104where
1105 I: Iterator<Item = S>,
1106 S: Counted,
1107{
1108 type Item = Managed<S>;
1109
1110 fn next(&mut self) -> Option<Self::Item> {
1111 let next = match self.items.next() {
1112 Some(next) => next,
1113 None => {
1114 self.exhausted = true;
1115 return None;
1116 }
1117 };
1118
1119 #[cfg(debug_assertions)]
1120 self.subtract(next.quantities());
1121
1122 Some(Managed::from_parts(next, Arc::clone(&self.meta)))
1123 }
1124}
1125
1126impl<I, S> Drop for Split<I, S>
1127where
1128 I: Iterator<Item = S>,
1129 S: Counted,
1130{
1131 fn drop(&mut self) {
1132 #[cfg(debug_assertions)]
1134 if self.exhausted {
1135 for (category, quantities) in &self.quantities {
1136 debug_assert!(
1137 *quantities == 0,
1138 "items split, but still {quantities} remaining in category {category}"
1139 );
1140 }
1141 }
1142
1143 if self.exhausted {
1144 return;
1145 }
1146
1147 for item in &mut self.items {
1153 for (category, quantity) in item.quantities() {
1154 self.meta.track_outcome(
1155 Outcome::Invalid(DiscardReason::Internal),
1156 category,
1157 quantity,
1158 );
1159 }
1160 }
1161 }
1162}
1163
1164impl<I, S> FusedIterator for Split<I, S>
1165where
1166 I: Iterator<Item = S> + FusedIterator,
1167 S: Counted,
1168{
1169}
1170
1171#[cfg(test)]
1172mod tests {
1173 use super::*;
1174
1175 use relay_base_schema::project::ProjectId;
1176
1177 struct CountedVec(Vec<u32>);
1178
1179 impl Counted for CountedVec {
1180 fn quantities(&self) -> Quantities {
1181 smallvec::smallvec![(DataCategory::Error, self.0.len())]
1182 }
1183 }
1184
1185 struct CountedValue(u32);
1186
1187 impl Counted for CountedValue {
1188 fn quantities(&self) -> Quantities {
1189 smallvec::smallvec![(DataCategory::Error, 1)]
1190 }
1191 }
1192
1193 #[test]
1194 fn test_reject_err_no_outcome() {
1195 let value = CountedVec(vec![0, 1, 2, 3, 4, 5]);
1196 let (managed, mut handle) = Managed::for_test(value).build();
1197
1198 let _ = managed.reject_err((None, ()));
1200 handle.assert_no_outcomes();
1201
1202 drop(managed);
1204 handle.assert_no_outcomes();
1205 }
1206
1207 #[test]
1208 fn test_merge() {
1209 let (mut a, mut handle_a) = Managed::for_test(CountedVec(vec![1, 2])).build();
1210 let (b, mut handle_b) = Managed::for_test(CountedVec(vec![3, 4])).build();
1211
1212 a.merge_with(b, |a, b, _| a.0.extend(b.0));
1213
1214 assert_eq!(a.0, vec![1, 2, 3, 4]);
1215 drop(a);
1216 handle_a.assert_internal_outcome(DataCategory::Error, 4);
1217 handle_b.assert_no_outcomes();
1218 }
1219
1220 #[test]
1221 fn test_zip_into_tuple() {
1222 let (a, mut handle_a) = Managed::for_test(CountedVec(vec![1, 2])).build();
1223 let (b, mut handle_b) = Managed::for_test(CountedValue(3)).build();
1224
1225 let z = Managed::zip(a, b);
1226
1227 assert_eq!((z.as_ref().0).0, vec![1, 2]);
1228 assert_eq!((z.as_ref().1).0, 3);
1229 drop(z);
1230 handle_a.assert_internal_outcome(DataCategory::Error, 2);
1231 handle_a.assert_internal_outcome(DataCategory::Error, 1);
1232 handle_b.assert_no_outcomes();
1233 }
1234
1235 #[test]
1236 fn test_zip_rejects_different_metadata() {
1237 let (a, mut handle_a) = Managed::for_test(CountedVec(vec![1, 2])).build();
1238 let (b, mut handle_b) = Managed::for_test(CountedValue(3))
1239 .scoping(Scoping {
1240 project_id: ProjectId::new(45),
1241 ..a.scoping()
1242 })
1243 .build();
1244
1245 let result = std::panic::catch_unwind(move || {
1246 Managed::zip(a, b);
1247 });
1248
1249 assert!(
1250 result.is_err(),
1251 "cannot zip Managed values with different metadata"
1252 );
1253 handle_a.assert_internal_outcome(DataCategory::Error, 2);
1254 handle_b.assert_internal_outcome(DataCategory::Error, 1);
1255 }
1256
1257 #[test]
1258 fn test_merge_mismatched_records_should_panic() {
1259 let (mut a, mut handle_a) = Managed::for_test(CountedVec(vec![1, 2])).build();
1260 let (b, _handle_b) = Managed::for_test(CountedVec(vec![3, 4])).build();
1261
1262 let r = std::panic::catch_unwind(move || {
1263 a.merge_with(b, |_a, _b, _| {});
1264 });
1265
1266 assert!(
1267 r.is_err(),
1268 "expected merge to panic because of mismatched outcome records"
1269 );
1270 handle_a.assert_internal_outcome(DataCategory::Error, 2);
1271 }
1272
1273 #[test]
1274 fn test_split_fully_consumed() {
1275 let value = CountedVec(vec![0, 1, 2, 3, 4, 5]);
1276 let (managed, mut handle) = Managed::for_test(value).build();
1277
1278 let s = managed
1279 .split(|value| value.0.into_iter().map(CountedValue))
1280 .collect::<Vec<_>>();
1282
1283 handle.assert_no_outcomes();
1284
1285 for (i, s) in s.into_iter().enumerate() {
1286 assert_eq!(s.as_ref().0, i as u32);
1287 let outcome = Outcome::Invalid(DiscardReason::Cors);
1288 let _ = s.reject_err((outcome.clone(), ()));
1289 handle.assert_outcome(&outcome, DataCategory::Error, 1);
1290 }
1291 }
1292
1293 #[test]
1294 fn test_split_partially_consumed_emits_remaining() {
1295 let value = CountedVec(vec![0, 1, 2, 3, 4, 5]);
1296 let (managed, mut handle) = Managed::for_test(value).build();
1297
1298 let mut s = managed.split(|value| value.0.into_iter().map(CountedValue));
1299 handle.assert_no_outcomes();
1300
1301 drop(s.next());
1302 handle.assert_internal_outcome(DataCategory::Error, 1);
1303 drop(s.next());
1304 handle.assert_internal_outcome(DataCategory::Error, 1);
1305 drop(s.next());
1306 handle.assert_internal_outcome(DataCategory::Error, 1);
1307 handle.assert_no_outcomes();
1308
1309 drop(s);
1310
1311 handle.assert_internal_outcome(DataCategory::Error, 1);
1312 handle.assert_internal_outcome(DataCategory::Error, 1);
1313 handle.assert_internal_outcome(DataCategory::Error, 1);
1314 }
1315
1316 #[test]
1317 fn test_split_changing_quantities_should_panic() {
1318 let value = CountedVec(vec![0, 1, 2, 3, 4, 5]);
1319 let (managed, mut handle) = Managed::for_test(value).build();
1320
1321 let mut s = managed.split(|_| std::iter::once(CountedValue(0)));
1322
1323 s.next().unwrap().accept(|_| {});
1324 handle.assert_no_outcomes();
1325
1326 assert!(s.next().is_none());
1327
1328 let r = std::panic::catch_unwind(move || {
1329 drop(s);
1330 });
1331
1332 assert!(
1333 r.is_err(),
1334 "expected split to panic because of mismatched (not enough) outcomes"
1335 );
1336 }
1337
1338 #[test]
1339 fn test_split_more_outcomes_than_before_should_panic() {
1340 let value = CountedVec(vec![0]);
1341 let (managed, mut handle) = Managed::for_test(value).build();
1342
1343 let mut s = managed.split(|_| vec![CountedValue(0), CountedValue(2)].into_iter());
1344
1345 s.next().unwrap().accept(|_| {});
1346 handle.assert_no_outcomes();
1347
1348 let r = std::panic::catch_unwind(move || {
1349 s.next();
1350 });
1351
1352 assert!(
1353 r.is_err(),
1354 "expected split to panic because of mismatched (too many) outcomes"
1355 );
1356 }
1357
1358 #[test]
1359 fn test_split_changing_categories_should_panic() {
1360 struct Special;
1361 impl Counted for Special {
1362 fn quantities(&self) -> Quantities {
1363 smallvec::smallvec![(DataCategory::Error, 1), (DataCategory::Transaction, 1)]
1364 }
1365 }
1366
1367 let value = CountedVec(vec![0]);
1368 let (managed, _handle) = Managed::for_test(value).build();
1369
1370 let mut s = managed.split(|value| value.0.into_iter().map(|_| Special));
1371
1372 let r = std::panic::catch_unwind(move || {
1373 let _ = s.next();
1374 });
1375
1376 assert!(
1377 r.is_err(),
1378 "expected split to panic because of mismatched outcome categories"
1379 );
1380 }
1381
1382 #[test]
1383 fn test_split_assert_fused() {
1384 fn only_fused<T: FusedIterator>(_: T) {}
1385
1386 let (managed, mut handle) = Managed::for_test(CountedVec(vec![0])).build();
1387 only_fused(managed.split(|value| value.0.into_iter().map(CountedValue)));
1388 handle.assert_internal_outcome(DataCategory::Error, 1);
1389 }
1390}