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_event_schema::protocol::EventId;
14use relay_quotas::{DataCategory, Scoping};
15use relay_system::Addr;
16use smallvec::SmallVec;
17
18use crate::Envelope;
19use crate::endpoints::common::BadStoreRequest;
20use crate::extractors::RequestMeta;
21use crate::managed::{Counted, ManagedEnvelope, Quantities};
22use crate::services::outcome::{DiscardReason, Outcome, TrackOutcome};
23use crate::services::processor::ProcessingError;
24
25#[cfg(debug_assertions)]
26mod debug;
27#[cfg(test)]
28mod test;
29
30#[cfg(test)]
31pub use self::test::*;
32
33pub trait OutcomeError {
35 type Error;
37
38 fn consume(self) -> (Option<Outcome>, Self::Error);
42}
43
44impl OutcomeError for Outcome {
45 type Error = ();
46
47 fn consume(self) -> (Option<Outcome>, Self::Error) {
48 (self, ()).consume()
49 }
50}
51
52impl OutcomeError for Option<Outcome> {
53 type Error = ();
54
55 fn consume(self) -> (Option<Outcome>, Self::Error) {
56 (self, ()).consume()
57 }
58}
59
60impl<E> OutcomeError for (Outcome, E) {
61 type Error = E;
62
63 fn consume(self) -> (Option<Outcome>, Self::Error) {
64 (Some(self.0), self.1)
65 }
66}
67
68impl<E> OutcomeError for (Option<Outcome>, E) {
69 type Error = E;
70
71 fn consume(self) -> (Option<Outcome>, Self::Error) {
72 self
73 }
74}
75
76impl OutcomeError for ProcessingError {
77 type Error = Self;
78
79 fn consume(self) -> (Option<Outcome>, Self::Error) {
80 (self.to_outcome(), self)
81 }
82}
83
84impl OutcomeError for Infallible {
85 type Error = Self;
86
87 fn consume(self) -> (Option<Outcome>, Self::Error) {
88 match self {}
89 }
90}
91
92impl OutcomeError for BadStoreRequest {
93 type Error = Self;
94
95 fn consume(self) -> (Option<Outcome>, Self) {
96 (self.to_outcome(), self)
97 }
98}
99
100#[derive(Debug, Clone, Copy)]
105#[must_use = "a rejection must be propagated"]
106pub struct Rejected<T>(T);
107
108impl<T> Rejected<T> {
109 pub fn into_inner(self) -> T {
111 self.0
112 }
113
114 pub fn map<F, S>(self, f: F) -> Rejected<S>
116 where
117 F: FnOnce(T) -> S,
118 {
119 Rejected(f(self.0))
120 }
121}
122
123impl<T> std::error::Error for Rejected<T>
124where
125 T: std::error::Error,
126{
127 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
128 self.0.source()
129 }
130}
131
132impl<T> std::fmt::Display for Rejected<T>
133where
134 T: std::fmt::Display,
135{
136 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137 self.0.fmt(f)
138 }
139}
140
141impl<T> axum::response::IntoResponse for Rejected<T>
142where
143 T: axum::response::IntoResponse,
144{
145 fn into_response(self) -> axum::response::Response {
146 self.0.into_response()
147 }
148}
149
150pub struct Managed<T: Counted> {
152 value: T,
153 meta: Arc<Meta>,
154 done: AtomicBool,
155}
156
157impl Managed<Box<Envelope>> {
158 pub fn from_envelope(envelope: Box<Envelope>, outcome_aggregator: Addr<TrackOutcome>) -> Self {
160 let meta = Arc::new(Meta {
161 outcome_aggregator,
162 received_at: envelope.received_at(),
163 scoping: envelope.meta().get_partial_scoping().into_scoping(),
164 event_id: envelope.event_id(),
165 remote_addr: envelope.meta().remote_addr(),
166 });
167
168 Self::from_parts(envelope, meta)
169 }
170}
171
172pub trait RetainMut<I> {
174 fn retain_mut(&mut self, f: impl FnMut(&mut I) -> bool);
176}
177
178impl<I> RetainMut<I> for Vec<I> {
179 fn retain_mut(&mut self, f: impl FnMut(&mut I) -> bool) {
180 Vec::retain_mut(self, f)
181 }
182}
183impl<I, const N: usize> RetainMut<I> for SmallVec<[I; N]> {
184 fn retain_mut(&mut self, f: impl FnMut(&mut I) -> bool) {
185 SmallVec::retain_mut(self, f)
186 }
187}
188
189impl<T: Counted> Managed<T> {
190 pub fn with_meta_from_managed_envelope(envelope: &ManagedEnvelope, value: T) -> Self {
195 Self::from_parts(
196 value,
197 Arc::new(Meta {
198 outcome_aggregator: envelope.outcome_aggregator().clone(),
199 received_at: envelope.received_at(),
200 scoping: envelope.scoping(),
201 event_id: envelope.envelope().event_id(),
202 remote_addr: envelope.meta().remote_addr(),
203 }),
204 )
205 }
206
207 pub fn with_meta_from_request_meta(
209 request_meta: &RequestMeta,
210 outcome_aggregator: &Addr<TrackOutcome>,
211 value: T,
212 ) -> Self {
213 Self::from_parts(
214 value,
215 Arc::new(Meta {
216 outcome_aggregator: outcome_aggregator.clone(),
217 received_at: request_meta.received_at(),
218 scoping: request_meta.get_partial_scoping().into_scoping(),
219 event_id: None,
220 remote_addr: request_meta.remote_addr(),
221 }),
222 )
223 }
224
225 pub fn wrap<S>(&self, other: S) -> Managed<S>
227 where
228 S: Counted,
229 {
230 Managed::from_parts(other, Arc::clone(&self.meta))
231 }
232
233 pub fn boxed(self) -> Managed<Box<T>> {
235 self.map(|value, _| Box::new(value))
236 }
237
238 pub fn received_at(&self) -> DateTime<Utc> {
240 self.meta.received_at
241 }
242
243 pub fn scoping(&self) -> Scoping {
245 self.meta.scoping
246 }
247
248 pub fn remote_addr(&self) -> Option<IpAddr> {
250 self.meta.remote_addr
251 }
252
253 pub fn scope(&mut self, scoping: Scoping) {
263 let meta = Arc::make_mut(&mut self.meta);
264 meta.scoping = scoping;
265 }
266
267 pub fn merge_with<S, F>(&mut self, other: Managed<S>, f: F)
272 where
273 S: Counted,
274 F: FnOnce(&mut T, S, &mut RecordKeeper),
275 {
276 self.modify(|s, records| {
277 for (category, quantity) in other.quantities() {
278 records.modify_by(category, quantity as isize);
279 }
280 other.accept(|o| f(s, o, records));
281 })
282 }
283 pub fn zip<S>(first: Self, second: Managed<S>) -> Managed<(T, S)>
288 where
289 S: Counted,
290 {
291 debug_assert_eq!(
292 first.scoping(),
293 second.scoping(),
294 "cannot zip Managed values with different metadata"
295 );
296 first.map(|first, records| {
297 for (category, quantity) in second.quantities() {
298 records.modify_by(category, quantity as isize);
299 }
300 let second = second.accept(|second| second);
301 (first, second)
302 })
303 }
304
305 pub fn split_once<F, S, U>(self, f: F) -> (Managed<S>, Managed<U>)
311 where
312 F: FnOnce(T, &mut RecordKeeper) -> (S, U),
313 S: Counted,
314 U: Counted,
315 {
316 debug_assert!(!self.is_done());
317
318 let (value, meta) = self.destructure();
319 let quantities = value.quantities();
320
321 let mut records = RecordKeeper::new(&meta, quantities);
322
323 let (a, b) = f(value, &mut records);
324
325 let mut quantities = a.quantities();
326 quantities.extend(b.quantities());
327 records.success(quantities);
328
329 (
330 Managed::from_parts(a, Arc::clone(&meta)),
331 Managed::from_parts(b, meta),
332 )
333 }
334
335 pub fn split<F, I, S>(self, f: F) -> Split<I::IntoIter, I::Item>
340 where
341 F: FnOnce(T) -> I,
342 I: IntoIterator<Item = S>,
343 S: Counted,
344 {
345 self.split_with_context(|value| (f(value), ())).0
346 }
347
348 pub fn split_with_context<F, I, S, C>(self, f: F) -> (Split<I::IntoIter, I::Item>, C)
353 where
354 F: FnOnce(T) -> (I, C),
355 I: IntoIterator<Item = S>,
356 S: Counted,
357 {
358 debug_assert!(!self.is_done());
359
360 let (value, meta) = self.destructure();
361 #[cfg(debug_assertions)]
362 let quantities = value.quantities();
363
364 let (items, context) = f(value);
365
366 (
367 Split {
368 #[cfg(debug_assertions)]
369 quantities,
370 items: items.into_iter(),
371 meta,
372 exhausted: false,
373 },
374 context,
375 )
376 }
377
378 pub fn retain<S, I, U, E, V>(&mut self, select: S, mut retain: U)
414 where
415 S: FnOnce(&mut T) -> &mut V,
416 I: Counted,
417 U: FnMut(&mut I, &mut RecordKeeper<'_>) -> Result<(), E>,
418 E: OutcomeError,
419 V: RetainMut<I>,
420 {
421 self.retain_with_context(
422 |inner| (select(inner), &()),
423 |item, _, records| retain(item, records),
424 );
425 }
426
427 pub fn retain_with_context<S, C, I, U, E, V>(&mut self, select: S, mut retain: U)
464 where
465 for<'a> S: FnOnce(&'a mut T) -> (&'a mut V, &'a C),
470 I: Counted,
471 U: FnMut(&mut I, &C, &mut RecordKeeper<'_>) -> Result<(), E>,
472 E: OutcomeError,
473 V: RetainMut<I>,
474 {
475 self.modify(|inner, records| {
476 let (items, ctx) = select(inner);
477 items.retain_mut(|item| match retain(item, ctx, records) {
478 Ok(()) => true,
479 Err(err) => {
480 records.reject_err(err, &*item);
481 false
482 }
483 })
484 });
485 }
486
487 pub fn map<S, F>(self, f: F) -> Managed<S>
491 where
492 F: FnOnce(T, &mut RecordKeeper) -> S,
493 S: Counted,
494 {
495 self.try_map(move |inner, records| Ok::<_, Infallible>(f(inner, records)))
496 .unwrap_or_else(|e| match e.0 {})
497 }
498
499 pub fn try_map<S, F, E>(self, f: F) -> Result<Managed<S>, Rejected<E::Error>>
507 where
508 F: FnOnce(T, &mut RecordKeeper) -> Result<S, E>,
509 S: Counted,
510 E: OutcomeError,
511 {
512 debug_assert!(!self.is_done());
513
514 let (value, meta) = self.destructure();
515 let quantities = value.quantities();
516
517 let mut records = RecordKeeper::new(&meta, quantities);
518
519 match f(value, &mut records) {
520 Ok(value) => {
521 records.success(value.quantities());
522 Ok(Managed::from_parts(value, meta))
523 }
524 Err(err) => Err(records.failure(err)),
525 }
526 }
527
528 pub fn modify<F>(&mut self, f: F)
532 where
533 F: FnOnce(&mut T, &mut RecordKeeper),
534 {
535 self.try_modify(move |inner, records| {
536 f(inner, records);
537 Ok::<_, Infallible>(())
538 })
539 .unwrap_or_else(|e| match e {})
540 }
541
542 pub fn try_modify<F, E>(&mut self, f: F) -> Result<(), Rejected<E::Error>>
550 where
551 F: FnOnce(&mut T, &mut RecordKeeper) -> Result<(), E>,
552 E: OutcomeError,
553 {
554 debug_assert!(!self.is_done());
555
556 let quantities = self.value.quantities();
557 let mut records = RecordKeeper::new(&self.meta, quantities);
558
559 match f(&mut self.value, &mut records) {
560 Ok(()) => {
561 records.success(self.value.quantities());
562 Ok(())
563 }
564 Err(err) => {
565 let err = records.failure(err);
566 self.done.store(true, Ordering::Relaxed);
567 Err(err)
568 }
569 }
570 }
571
572 pub fn accept<F, S>(self, f: F) -> S
580 where
581 F: FnOnce(T) -> S,
582 {
583 self.try_accept(|item| Ok::<_, Infallible>(f(item)))
584 .unwrap_or_else(|err| match err.0 {})
585 }
586
587 pub fn try_accept<F, S, E>(self, f: F) -> Result<S, Rejected<E::Error>>
595 where
596 F: FnOnce(T) -> Result<S, E>,
597 E: OutcomeError,
598 {
599 debug_assert!(!self.is_done());
600
601 let (value, meta) = self.destructure();
602 let records = RecordKeeper::new(&meta, value.quantities());
603
604 match f(value) {
605 Ok(value) => {
606 records.accept();
607 Ok(value)
608 }
609 Err(err) => Err(records.failure(err)),
610 }
611 }
612
613 #[track_caller]
621 pub fn internal_error(&self, reason: &'static str) -> Rejected<()> {
622 relay_log::error!("internal error: {reason}");
623 debug_assert!(false, "internal error: {reason}");
624 self.reject_err((Outcome::Invalid(DiscardReason::Internal), ()))
625 }
626
627 pub fn reject_err<E>(&self, error: E) -> Rejected<E::Error>
629 where
630 E: OutcomeError,
631 {
632 debug_assert!(!self.is_done());
633
634 let (outcome, error) = error.consume();
635 self.do_reject(outcome);
636 Rejected(error)
637 }
638
639 fn do_reject(&self, outcome: Option<Outcome>) {
640 let is_done = self.done.fetch_or(true, Ordering::Relaxed);
643
644 let Some(outcome) = outcome else {
646 return;
647 };
648
649 if !is_done {
655 for (category, quantity) in self.value.quantities() {
656 self.meta.track_outcome(outcome.clone(), category, quantity);
657 }
658 }
659 }
660
661 fn destructure(self) -> (T, Arc<Meta>) {
668 let this = ManuallyDrop::new(self);
678 let Managed { value, meta, done } = &*this;
679
680 let value = unsafe { std::ptr::read(value) };
681 let meta = unsafe { std::ptr::read(meta) };
682 let done = unsafe { std::ptr::read(done) };
683 debug_assert!(
686 !done.load(Ordering::Relaxed),
687 "a `done` managed should never be destructured"
688 );
689
690 (value, meta)
691 }
692
693 fn from_parts(value: T, meta: Arc<Meta>) -> Self {
694 Self {
695 value,
696 meta,
697 done: AtomicBool::new(false),
698 }
699 }
700
701 fn is_done(&self) -> bool {
702 self.done.load(Ordering::Relaxed)
703 }
704}
705
706impl<T: Counted> Drop for Managed<T> {
707 fn drop(&mut self) {
708 self.do_reject(Some(Outcome::Invalid(DiscardReason::Internal)));
709 }
710}
711
712impl<T: Counted + fmt::Debug> fmt::Debug for Managed<T> {
713 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
714 write!(f, "Managed<{}>[", std::any::type_name::<T>())?;
715 for (i, (category, quantity)) in self.value.quantities().iter().enumerate() {
716 if i > 0 {
717 write!(f, ",")?;
718 }
719 write!(f, "{category}:{quantity}")?;
720 }
721 write!(f, "](")?;
722 self.value.fmt(f)?;
723 write!(f, ")")
724 }
725}
726
727impl<T: Counted> Managed<Option<T>> {
728 pub fn transpose(self) -> Option<Managed<T>> {
730 let (o, meta) = self.destructure();
731 o.map(|t| Managed::from_parts(t, meta))
732 }
733}
734
735impl<L: Counted, R: Counted> Managed<Either<L, R>> {
736 pub fn transpose(self) -> Either<Managed<L>, Managed<R>> {
738 let (either, meta) = self.destructure();
739 match either {
740 Either::Left(value) => Either::Left(Managed::from_parts(value, meta)),
741 Either::Right(value) => Either::Right(Managed::from_parts(value, meta)),
742 }
743 }
744}
745
746impl From<Managed<Box<Envelope>>> for ManagedEnvelope {
747 fn from(value: Managed<Box<Envelope>>) -> Self {
748 let (value, meta) = value.destructure();
749 let mut envelope = ManagedEnvelope::new(value, meta.outcome_aggregator.clone());
750 envelope.scope(meta.scoping);
751 envelope
752 }
753}
754
755impl<T: Counted> AsRef<T> for Managed<T> {
756 fn as_ref(&self) -> &T {
757 &self.value
758 }
759}
760
761impl<T: Counted> std::ops::Deref for Managed<T> {
762 type Target = T;
763
764 fn deref(&self) -> &Self::Target {
765 &self.value
766 }
767}
768
769#[derive(Debug, Clone)]
771struct Meta {
772 outcome_aggregator: Addr<TrackOutcome>,
774 received_at: DateTime<Utc>,
778 scoping: Scoping,
780 event_id: Option<EventId>,
782 remote_addr: Option<IpAddr>,
784}
785
786impl Meta {
787 pub fn track_outcome(&self, outcome: Outcome, category: DataCategory, quantity: usize) {
788 self.outcome_aggregator.send(TrackOutcome {
789 timestamp: self.received_at,
790 scoping: self.scoping,
791 outcome,
792 event_id: self.event_id,
793 remote_addr: self.remote_addr,
794 category,
795 quantity: quantity as _,
796 });
797 }
798}
799
800pub struct RecordKeeper<'a> {
803 meta: &'a Meta,
804 on_drop: Quantities,
805 #[cfg(debug_assertions)]
806 lenient: SmallVec<[DataCategory; 1]>,
807 #[cfg(debug_assertions)]
808 modifications: BTreeMap<DataCategory, isize>,
809 in_flight: SmallVec<[(DataCategory, usize, Option<Outcome>); 2]>,
810}
811
812impl<'a> RecordKeeper<'a> {
813 fn new(meta: &'a Meta, quantities: Quantities) -> Self {
814 Self {
815 meta,
816 on_drop: quantities,
817 #[cfg(debug_assertions)]
818 lenient: Default::default(),
819 #[cfg(debug_assertions)]
820 modifications: Default::default(),
821 in_flight: Default::default(),
822 }
823 }
824
825 pub fn lenient(&mut self, category: DataCategory) {
832 let _category = category;
833 #[cfg(debug_assertions)]
834 self.lenient.push(_category);
835 }
836
837 pub fn modify_by(&mut self, category: DataCategory, offset: isize) {
845 let _category = category;
846 let _offset = offset;
847 #[cfg(debug_assertions)]
848 {
849 *self.modifications.entry(_category).or_default() += offset;
850 }
851 }
852
853 fn failure<E>(mut self, error: E) -> Rejected<E::Error>
857 where
858 E: OutcomeError,
859 {
860 let (outcome, error) = error.consume();
861
862 if let Some(outcome) = outcome {
863 for (category, quantity) in std::mem::take(&mut self.on_drop) {
864 self.meta.track_outcome(outcome.clone(), category, quantity);
865 }
866 }
867
868 Rejected(error)
869 }
870
871 fn accept(mut self) {
879 debug_assert!(
880 self.in_flight.is_empty(),
881 "records accepted, but intermediate outcomes tracked"
882 );
883 self.on_drop.clear();
884 }
885
886 fn success(mut self, new: Quantities) {
892 let original = std::mem::take(&mut self.on_drop);
893 self.assert_quantities(original, new);
894
895 self.on_drop.clear();
896 for (category, quantity, outcome) in std::mem::take(&mut self.in_flight) {
897 if let Some(outcome) = outcome {
898 self.meta.track_outcome(outcome, category, quantity);
899 }
900 }
901 }
902
903 #[cfg(debug_assertions)]
908 fn assert_quantities(&self, original: Quantities, new: Quantities) {
909 macro_rules! emit {
910 ($category:expr, $($tt:tt)*) => {{
911 match self.lenient.contains(&$category) {
912 true => relay_log::debug!($($tt)*),
915 false => {
916 relay_log::error!("Original: {original:?}");
917 relay_log::error!("New: {new:?}");
918 relay_log::error!("Modifications: {:?}", self.modifications);
919 relay_log::error!("In Flight: {:?}", self.in_flight);
920 panic!($($tt)*)
921 }
922 }
923 }};
924 }
925
926 let mut sums = debug::Quantities::from(&original).0;
927 for (category, offset) in &self.modifications {
928 let v = sums.entry(*category).or_default();
929 match v.checked_add_signed(*offset) {
930 Some(result) => *v = result,
931 None => emit!(
932 category,
933 "Attempted to modify original quantity {v} into the negative ({offset})"
934 ),
935 }
936 }
937
938 for (category, quantity, outcome) in &self.in_flight {
939 match sums.get_mut(category) {
940 Some(c) if *c >= *quantity => *c -= *quantity,
941 Some(c) => emit!(
942 category,
943 "Emitted {quantity} outcomes ({outcome:?}) for {category}, but there were only {c} items in the category originally"
944 ),
945 None => emit!(
946 category,
947 "Emitted {quantity} outcomes ({outcome:?}) for {category}, but there never was an item in this category"
948 ),
949 }
950 }
951
952 for (category, quantity) in &new {
953 match sums.get_mut(category) {
954 Some(c) if *c >= *quantity => *c -= *quantity,
955 Some(c) => emit!(
956 category,
957 "New item has {quantity} items in category '{category}', but original (after emitted outcomes) only has {c} left"
958 ),
959 None => emit!(
960 category,
961 "New item has {quantity} items in category '{category}', but after emitted outcomes there are none left"
962 ),
963 }
964 }
965
966 for (category, quantity) in sums {
967 if quantity > 0 {
968 emit!(
969 category,
970 "Missing outcomes or mismatched quantity in category '{category}', off by {quantity}"
971 );
972 }
973 }
974 }
975
976 #[cfg(not(debug_assertions))]
977 fn assert_quantities(&self, _: Quantities, _: Quantities) {}
978}
979
980impl<'a> Drop for RecordKeeper<'a> {
981 fn drop(&mut self) {
982 for (category, quantity) in std::mem::take(&mut self.on_drop) {
983 self.meta.track_outcome(
984 Outcome::Invalid(DiscardReason::Internal),
985 category,
986 quantity,
987 );
988 }
989 }
990}
991
992impl RecordKeeper<'_> {
993 pub fn or_default<T, E, Q>(&mut self, r: Result<T, E>, q: Q) -> T
998 where
999 T: Default,
1000 E: OutcomeError,
1001 Q: Counted,
1002 {
1003 match r {
1004 Ok(result) => result,
1005 Err(err) => {
1006 self.reject_err(err, q);
1007 T::default()
1008 }
1009 }
1010 }
1011
1012 pub fn reject_err<E, Q>(&mut self, err: E, q: Q) -> E::Error
1017 where
1018 E: OutcomeError,
1019 Q: Counted,
1020 {
1021 let (outcome, err) = err.consume();
1022 for (category, quantity) in q.quantities() {
1023 self.in_flight.push((category, quantity, outcome.clone()))
1024 }
1025 err
1026 }
1027
1028 #[track_caller]
1032 pub fn internal_error<E, Q>(&mut self, error: E, q: Q)
1033 where
1034 E: std::error::Error + 'static,
1035 Q: Counted,
1036 {
1037 relay_log::error!(error = &error as &dyn std::error::Error, "internal error");
1038 debug_assert!(false, "internal error: {error}");
1039 self.reject_err((Outcome::Invalid(DiscardReason::Internal), ()), q);
1040 }
1041}
1042
1043pub struct Split<I, S>
1045where
1046 I: Iterator<Item = S>,
1047 S: Counted,
1048{
1049 #[cfg(debug_assertions)]
1050 quantities: Quantities,
1051 items: I,
1052 meta: Arc<Meta>,
1053 exhausted: bool,
1054}
1055
1056impl<I, S> Split<I, S>
1057where
1058 I: Iterator<Item = S>,
1059 S: Counted,
1060{
1061 #[cfg(debug_assertions)]
1064 fn subtract(&mut self, q: Quantities) {
1065 for (category, quantities) in q {
1066 let Some(orig_quantities) = self
1067 .quantities
1068 .iter_mut()
1069 .find_map(|(c, q)| (*c == category).then_some(q))
1070 else {
1071 debug_assert!(
1072 false,
1073 "mismatching quantities, item split into category {category}, \
1074 which originally was not present"
1075 );
1076 continue;
1077 };
1078
1079 if *orig_quantities >= quantities {
1080 *orig_quantities -= quantities;
1081 } else {
1082 debug_assert!(
1083 false,
1084 "in total more items produced in category {category} than originally available"
1085 );
1086 }
1087 }
1088 }
1089}
1090
1091impl<I, S> Iterator for Split<I, S>
1092where
1093 I: Iterator<Item = S>,
1094 S: Counted,
1095{
1096 type Item = Managed<S>;
1097
1098 fn next(&mut self) -> Option<Self::Item> {
1099 let next = match self.items.next() {
1100 Some(next) => next,
1101 None => {
1102 self.exhausted = true;
1103 return None;
1104 }
1105 };
1106
1107 #[cfg(debug_assertions)]
1108 self.subtract(next.quantities());
1109
1110 Some(Managed::from_parts(next, Arc::clone(&self.meta)))
1111 }
1112}
1113
1114impl<I, S> Drop for Split<I, S>
1115where
1116 I: Iterator<Item = S>,
1117 S: Counted,
1118{
1119 fn drop(&mut self) {
1120 #[cfg(debug_assertions)]
1122 if self.exhausted {
1123 for (category, quantities) in &self.quantities {
1124 debug_assert!(
1125 *quantities == 0,
1126 "items split, but still {quantities} remaining in category {category}"
1127 );
1128 }
1129 }
1130
1131 if self.exhausted {
1132 return;
1133 }
1134
1135 for item in &mut self.items {
1141 for (category, quantity) in item.quantities() {
1142 self.meta.track_outcome(
1143 Outcome::Invalid(DiscardReason::Internal),
1144 category,
1145 quantity,
1146 );
1147 }
1148 }
1149 }
1150}
1151
1152impl<I, S> FusedIterator for Split<I, S>
1153where
1154 I: Iterator<Item = S> + FusedIterator,
1155 S: Counted,
1156{
1157}
1158
1159#[cfg(test)]
1160mod tests {
1161 use super::*;
1162
1163 use relay_base_schema::project::ProjectId;
1164
1165 struct CountedVec(Vec<u32>);
1166
1167 impl Counted for CountedVec {
1168 fn quantities(&self) -> Quantities {
1169 smallvec::smallvec![(DataCategory::Error, self.0.len())]
1170 }
1171 }
1172
1173 struct CountedValue(u32);
1174
1175 impl Counted for CountedValue {
1176 fn quantities(&self) -> Quantities {
1177 smallvec::smallvec![(DataCategory::Error, 1)]
1178 }
1179 }
1180
1181 #[test]
1182 fn test_reject_err_no_outcome() {
1183 let value = CountedVec(vec![0, 1, 2, 3, 4, 5]);
1184 let (managed, mut handle) = Managed::for_test(value).build();
1185
1186 let _ = managed.reject_err((None, ()));
1188 handle.assert_no_outcomes();
1189
1190 drop(managed);
1192 handle.assert_no_outcomes();
1193 }
1194
1195 #[test]
1196 fn test_merge() {
1197 let (mut a, mut handle_a) = Managed::for_test(CountedVec(vec![1, 2])).build();
1198 let (b, mut handle_b) = Managed::for_test(CountedVec(vec![3, 4])).build();
1199
1200 a.merge_with(b, |a, b, _| a.0.extend(b.0));
1201
1202 assert_eq!(a.0, vec![1, 2, 3, 4]);
1203 drop(a);
1204 handle_a.assert_internal_outcome(DataCategory::Error, 4);
1205 handle_b.assert_no_outcomes();
1206 }
1207
1208 #[test]
1209 fn test_zip_into_tuple() {
1210 let (a, mut handle_a) = Managed::for_test(CountedVec(vec![1, 2])).build();
1211 let (b, mut handle_b) = Managed::for_test(CountedValue(3)).build();
1212
1213 let z = Managed::zip(a, b);
1214
1215 assert_eq!((z.as_ref().0).0, vec![1, 2]);
1216 assert_eq!((z.as_ref().1).0, 3);
1217 drop(z);
1218 handle_a.assert_internal_outcome(DataCategory::Error, 2);
1219 handle_a.assert_internal_outcome(DataCategory::Error, 1);
1220 handle_b.assert_no_outcomes();
1221 }
1222
1223 #[test]
1224 fn test_zip_rejects_different_metadata() {
1225 let (a, mut handle_a) = Managed::for_test(CountedVec(vec![1, 2])).build();
1226 let (b, mut handle_b) = Managed::for_test(CountedValue(3))
1227 .scoping(Scoping {
1228 project_id: ProjectId::new(45),
1229 ..a.scoping()
1230 })
1231 .build();
1232
1233 let result = std::panic::catch_unwind(move || {
1234 Managed::zip(a, b);
1235 });
1236
1237 assert!(
1238 result.is_err(),
1239 "cannot zip Managed values with different metadata"
1240 );
1241 handle_a.assert_internal_outcome(DataCategory::Error, 2);
1242 handle_b.assert_internal_outcome(DataCategory::Error, 1);
1243 }
1244
1245 #[test]
1246 fn test_merge_mismatched_records_should_panic() {
1247 let (mut a, mut handle_a) = Managed::for_test(CountedVec(vec![1, 2])).build();
1248 let (b, _handle_b) = Managed::for_test(CountedVec(vec![3, 4])).build();
1249
1250 let r = std::panic::catch_unwind(move || {
1251 a.merge_with(b, |_a, _b, _| {});
1252 });
1253
1254 assert!(
1255 r.is_err(),
1256 "expected merge to panic because of mismatched outcome records"
1257 );
1258 handle_a.assert_internal_outcome(DataCategory::Error, 2);
1259 }
1260
1261 #[test]
1262 fn test_split_fully_consumed() {
1263 let value = CountedVec(vec![0, 1, 2, 3, 4, 5]);
1264 let (managed, mut handle) = Managed::for_test(value).build();
1265
1266 let s = managed
1267 .split(|value| value.0.into_iter().map(CountedValue))
1268 .collect::<Vec<_>>();
1270
1271 handle.assert_no_outcomes();
1272
1273 for (i, s) in s.into_iter().enumerate() {
1274 assert_eq!(s.as_ref().0, i as u32);
1275 let outcome = Outcome::Invalid(DiscardReason::Cors);
1276 let _ = s.reject_err((outcome.clone(), ()));
1277 handle.assert_outcome(&outcome, DataCategory::Error, 1);
1278 }
1279 }
1280
1281 #[test]
1282 fn test_split_partially_consumed_emits_remaining() {
1283 let value = CountedVec(vec![0, 1, 2, 3, 4, 5]);
1284 let (managed, mut handle) = Managed::for_test(value).build();
1285
1286 let mut s = managed.split(|value| value.0.into_iter().map(CountedValue));
1287 handle.assert_no_outcomes();
1288
1289 drop(s.next());
1290 handle.assert_internal_outcome(DataCategory::Error, 1);
1291 drop(s.next());
1292 handle.assert_internal_outcome(DataCategory::Error, 1);
1293 drop(s.next());
1294 handle.assert_internal_outcome(DataCategory::Error, 1);
1295 handle.assert_no_outcomes();
1296
1297 drop(s);
1298
1299 handle.assert_internal_outcome(DataCategory::Error, 1);
1300 handle.assert_internal_outcome(DataCategory::Error, 1);
1301 handle.assert_internal_outcome(DataCategory::Error, 1);
1302 }
1303
1304 #[test]
1305 fn test_split_changing_quantities_should_panic() {
1306 let value = CountedVec(vec![0, 1, 2, 3, 4, 5]);
1307 let (managed, mut handle) = Managed::for_test(value).build();
1308
1309 let mut s = managed.split(|_| std::iter::once(CountedValue(0)));
1310
1311 s.next().unwrap().accept(|_| {});
1312 handle.assert_no_outcomes();
1313
1314 assert!(s.next().is_none());
1315
1316 let r = std::panic::catch_unwind(move || {
1317 drop(s);
1318 });
1319
1320 assert!(
1321 r.is_err(),
1322 "expected split to panic because of mismatched (not enough) outcomes"
1323 );
1324 }
1325
1326 #[test]
1327 fn test_split_more_outcomes_than_before_should_panic() {
1328 let value = CountedVec(vec![0]);
1329 let (managed, mut handle) = Managed::for_test(value).build();
1330
1331 let mut s = managed.split(|_| vec![CountedValue(0), CountedValue(2)].into_iter());
1332
1333 s.next().unwrap().accept(|_| {});
1334 handle.assert_no_outcomes();
1335
1336 let r = std::panic::catch_unwind(move || {
1337 s.next();
1338 });
1339
1340 assert!(
1341 r.is_err(),
1342 "expected split to panic because of mismatched (too many) outcomes"
1343 );
1344 }
1345
1346 #[test]
1347 fn test_split_changing_categories_should_panic() {
1348 struct Special;
1349 impl Counted for Special {
1350 fn quantities(&self) -> Quantities {
1351 smallvec::smallvec![(DataCategory::Error, 1), (DataCategory::Transaction, 1)]
1352 }
1353 }
1354
1355 let value = CountedVec(vec![0]);
1356 let (managed, _handle) = Managed::for_test(value).build();
1357
1358 let mut s = managed.split(|value| value.0.into_iter().map(|_| Special));
1359
1360 let r = std::panic::catch_unwind(move || {
1361 let _ = s.next();
1362 });
1363
1364 assert!(
1365 r.is_err(),
1366 "expected split to panic because of mismatched outcome categories"
1367 );
1368 }
1369
1370 #[test]
1371 fn test_split_assert_fused() {
1372 fn only_fused<T: FusedIterator>(_: T) {}
1373
1374 let (managed, mut handle) = Managed::for_test(CountedVec(vec![0])).build();
1375 only_fused(managed.split(|value| value.0.into_iter().map(CountedValue)));
1376 handle.assert_internal_outcome(DataCategory::Error, 1);
1377 }
1378}