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 fn failure<E>(mut self, error: E) -> Rejected<E::Error>
851 where
852 E: OutcomeError,
853 {
854 let (outcome, error) = error.consume();
855
856 if let Some(outcome) = outcome {
857 for (category, quantity) in std::mem::take(&mut self.on_drop) {
858 self.meta.track_outcome(outcome.clone(), category, quantity);
859 }
860 }
861
862 Rejected(error)
863 }
864
865 fn accept(mut self) {
873 debug_assert!(
874 self.in_flight.is_empty(),
875 "records accepted, but intermediate outcomes tracked"
876 );
877 self.on_drop.clear();
878 }
879
880 fn success(mut self, new: Quantities) {
886 let original = std::mem::take(&mut self.on_drop);
887 self.assert_quantities(original, new);
888
889 self.on_drop.clear();
890 for (category, quantity, outcome) in std::mem::take(&mut self.in_flight) {
891 if let Some(outcome) = outcome {
892 self.meta.track_outcome(outcome, category, quantity);
893 }
894 }
895 }
896
897 #[cfg(debug_assertions)]
902 fn assert_quantities(&self, original: Quantities, new: Quantities) {
903 macro_rules! emit {
904 ($category:expr, $($tt:tt)*) => {{
905 match self.lenient.contains(&$category) {
906 true => relay_log::debug!($($tt)*),
909 false => {
910 relay_log::error!("Original: {original:?}");
911 relay_log::error!("New: {new:?}");
912 relay_log::error!("Modifications: {:?}", self.modifications);
913 relay_log::error!("In Flight: {:?}", self.in_flight);
914 panic!($($tt)*)
915 }
916 }
917 }};
918 }
919
920 let mut sums = debug::Quantities::from(&original).0;
921 for (category, offset) in &self.modifications {
922 let v = sums.entry(*category).or_default();
923 match v.checked_add_signed(*offset) {
924 Some(result) => *v = result,
925 None => emit!(
926 category,
927 "Attempted to modify original quantity {v} into the negative ({offset})"
928 ),
929 }
930 }
931
932 for (category, quantity, outcome) in &self.in_flight {
933 match sums.get_mut(category) {
934 Some(c) if *c >= *quantity => *c -= *quantity,
935 Some(c) => emit!(
936 category,
937 "Emitted {quantity} outcomes ({outcome:?}) for {category}, but there were only {c} items in the category originally"
938 ),
939 None => emit!(
940 category,
941 "Emitted {quantity} outcomes ({outcome:?}) for {category}, but there never was an item in this category"
942 ),
943 }
944 }
945
946 for (category, quantity) in &new {
947 match sums.get_mut(category) {
948 Some(c) if *c >= *quantity => *c -= *quantity,
949 Some(c) => emit!(
950 category,
951 "New item has {quantity} items in category '{category}', but original (after emitted outcomes) only has {c} left"
952 ),
953 None => emit!(
954 category,
955 "New item has {quantity} items in category '{category}', but after emitted outcomes there are none left"
956 ),
957 }
958 }
959
960 for (category, quantity) in sums {
961 if quantity > 0 {
962 emit!(
963 category,
964 "Missing outcomes or mismatched quantity in category '{category}', off by {quantity}"
965 );
966 }
967 }
968 }
969
970 #[cfg(not(debug_assertions))]
971 fn assert_quantities(&self, _: Quantities, _: Quantities) {}
972}
973
974impl<'a> Drop for RecordKeeper<'a> {
975 fn drop(&mut self) {
976 for (category, quantity) in std::mem::take(&mut self.on_drop) {
977 self.meta.track_outcome(
978 Outcome::Invalid(DiscardReason::Internal),
979 category,
980 quantity,
981 );
982 }
983 }
984}
985
986impl RecordKeeper<'_> {
987 pub fn or_default<T, E, Q>(&mut self, r: Result<T, E>, q: Q) -> T
992 where
993 T: Default,
994 E: OutcomeError,
995 Q: Counted,
996 {
997 match r {
998 Ok(result) => result,
999 Err(err) => {
1000 self.reject_err(err, q);
1001 T::default()
1002 }
1003 }
1004 }
1005
1006 pub fn reject_err<E, Q>(&mut self, err: E, q: Q) -> E::Error
1011 where
1012 E: OutcomeError,
1013 Q: Counted,
1014 {
1015 let (outcome, err) = err.consume();
1016 for (category, quantity) in q.quantities() {
1017 self.in_flight.push((category, quantity, outcome.clone()))
1018 }
1019 err
1020 }
1021
1022 #[track_caller]
1026 pub fn internal_error<E, Q>(&mut self, error: E, q: Q)
1027 where
1028 E: std::error::Error + 'static,
1029 Q: Counted,
1030 {
1031 relay_log::error!(error = &error as &dyn std::error::Error, "internal error");
1032 debug_assert!(false, "internal error: {error}");
1033 self.reject_err((Outcome::Invalid(DiscardReason::Internal), ()), q);
1034 }
1035}
1036
1037pub struct Split<I, S>
1039where
1040 I: Iterator<Item = S>,
1041 S: Counted,
1042{
1043 #[cfg(debug_assertions)]
1044 quantities: Quantities,
1045 items: I,
1046 meta: Arc<Meta>,
1047 exhausted: bool,
1048}
1049
1050impl<I, S> Split<I, S>
1051where
1052 I: Iterator<Item = S>,
1053 S: Counted,
1054{
1055 #[cfg(debug_assertions)]
1058 fn subtract(&mut self, q: Quantities) {
1059 for (category, quantities) in q {
1060 let Some(orig_quantities) = self
1061 .quantities
1062 .iter_mut()
1063 .find_map(|(c, q)| (*c == category).then_some(q))
1064 else {
1065 debug_assert!(
1066 false,
1067 "mismatching quantities, item split into category {category}, \
1068 which originally was not present"
1069 );
1070 continue;
1071 };
1072
1073 if *orig_quantities >= quantities {
1074 *orig_quantities -= quantities;
1075 } else {
1076 debug_assert!(
1077 false,
1078 "in total more items produced in category {category} than originally available"
1079 );
1080 }
1081 }
1082 }
1083}
1084
1085impl<I, S> Iterator for Split<I, S>
1086where
1087 I: Iterator<Item = S>,
1088 S: Counted,
1089{
1090 type Item = Managed<S>;
1091
1092 fn next(&mut self) -> Option<Self::Item> {
1093 let next = match self.items.next() {
1094 Some(next) => next,
1095 None => {
1096 self.exhausted = true;
1097 return None;
1098 }
1099 };
1100
1101 #[cfg(debug_assertions)]
1102 self.subtract(next.quantities());
1103
1104 Some(Managed::from_parts(next, Arc::clone(&self.meta)))
1105 }
1106}
1107
1108impl<I, S> Drop for Split<I, S>
1109where
1110 I: Iterator<Item = S>,
1111 S: Counted,
1112{
1113 fn drop(&mut self) {
1114 #[cfg(debug_assertions)]
1116 if self.exhausted {
1117 for (category, quantities) in &self.quantities {
1118 debug_assert!(
1119 *quantities == 0,
1120 "items split, but still {quantities} remaining in category {category}"
1121 );
1122 }
1123 }
1124
1125 if self.exhausted {
1126 return;
1127 }
1128
1129 for item in &mut self.items {
1135 for (category, quantity) in item.quantities() {
1136 self.meta.track_outcome(
1137 Outcome::Invalid(DiscardReason::Internal),
1138 category,
1139 quantity,
1140 );
1141 }
1142 }
1143 }
1144}
1145
1146impl<I, S> FusedIterator for Split<I, S>
1147where
1148 I: Iterator<Item = S> + FusedIterator,
1149 S: Counted,
1150{
1151}
1152
1153#[cfg(test)]
1154mod tests {
1155 use super::*;
1156
1157 use relay_base_schema::project::ProjectId;
1158
1159 struct CountedVec(Vec<u32>);
1160
1161 impl Counted for CountedVec {
1162 fn quantities(&self) -> Quantities {
1163 smallvec::smallvec![(DataCategory::Error, self.0.len())]
1164 }
1165 }
1166
1167 struct CountedValue(u32);
1168
1169 impl Counted for CountedValue {
1170 fn quantities(&self) -> Quantities {
1171 smallvec::smallvec![(DataCategory::Error, 1)]
1172 }
1173 }
1174
1175 #[test]
1176 fn test_reject_err_no_outcome() {
1177 let value = CountedVec(vec![0, 1, 2, 3, 4, 5]);
1178 let (managed, mut handle) = Managed::for_test(value).build();
1179
1180 let _ = managed.reject_err((None, ()));
1182 handle.assert_no_outcomes();
1183
1184 drop(managed);
1186 handle.assert_no_outcomes();
1187 }
1188
1189 #[test]
1190 fn test_merge() {
1191 let (mut a, mut handle_a) = Managed::for_test(CountedVec(vec![1, 2])).build();
1192 let (b, mut handle_b) = Managed::for_test(CountedVec(vec![3, 4])).build();
1193
1194 a.merge_with(b, |a, b, _| a.0.extend(b.0));
1195
1196 assert_eq!(a.0, vec![1, 2, 3, 4]);
1197 drop(a);
1198 handle_a.assert_internal_outcome(DataCategory::Error, 4);
1199 handle_b.assert_no_outcomes();
1200 }
1201
1202 #[test]
1203 fn test_zip_into_tuple() {
1204 let (a, mut handle_a) = Managed::for_test(CountedVec(vec![1, 2])).build();
1205 let (b, mut handle_b) = Managed::for_test(CountedValue(3)).build();
1206
1207 let z = Managed::zip(a, b);
1208
1209 assert_eq!((z.as_ref().0).0, vec![1, 2]);
1210 assert_eq!((z.as_ref().1).0, 3);
1211 drop(z);
1212 handle_a.assert_internal_outcome(DataCategory::Error, 2);
1213 handle_a.assert_internal_outcome(DataCategory::Error, 1);
1214 handle_b.assert_no_outcomes();
1215 }
1216
1217 #[test]
1218 fn test_zip_rejects_different_metadata() {
1219 let (a, mut handle_a) = Managed::for_test(CountedVec(vec![1, 2])).build();
1220 let (b, mut handle_b) = Managed::for_test(CountedValue(3))
1221 .scoping(Scoping {
1222 project_id: ProjectId::new(45),
1223 ..a.scoping()
1224 })
1225 .build();
1226
1227 let result = std::panic::catch_unwind(move || {
1228 Managed::zip(a, b);
1229 });
1230
1231 assert!(
1232 result.is_err(),
1233 "cannot zip Managed values with different metadata"
1234 );
1235 handle_a.assert_internal_outcome(DataCategory::Error, 2);
1236 handle_b.assert_internal_outcome(DataCategory::Error, 1);
1237 }
1238
1239 #[test]
1240 fn test_merge_mismatched_records_should_panic() {
1241 let (mut a, mut handle_a) = Managed::for_test(CountedVec(vec![1, 2])).build();
1242 let (b, _handle_b) = Managed::for_test(CountedVec(vec![3, 4])).build();
1243
1244 let r = std::panic::catch_unwind(move || {
1245 a.merge_with(b, |_a, _b, _| {});
1246 });
1247
1248 assert!(
1249 r.is_err(),
1250 "expected merge to panic because of mismatched outcome records"
1251 );
1252 handle_a.assert_internal_outcome(DataCategory::Error, 2);
1253 }
1254
1255 #[test]
1256 fn test_split_fully_consumed() {
1257 let value = CountedVec(vec![0, 1, 2, 3, 4, 5]);
1258 let (managed, mut handle) = Managed::for_test(value).build();
1259
1260 let s = managed
1261 .split(|value| value.0.into_iter().map(CountedValue))
1262 .collect::<Vec<_>>();
1264
1265 handle.assert_no_outcomes();
1266
1267 for (i, s) in s.into_iter().enumerate() {
1268 assert_eq!(s.as_ref().0, i as u32);
1269 let outcome = Outcome::Invalid(DiscardReason::Cors);
1270 let _ = s.reject_err((outcome.clone(), ()));
1271 handle.assert_outcome(&outcome, DataCategory::Error, 1);
1272 }
1273 }
1274
1275 #[test]
1276 fn test_split_partially_consumed_emits_remaining() {
1277 let value = CountedVec(vec![0, 1, 2, 3, 4, 5]);
1278 let (managed, mut handle) = Managed::for_test(value).build();
1279
1280 let mut s = managed.split(|value| value.0.into_iter().map(CountedValue));
1281 handle.assert_no_outcomes();
1282
1283 drop(s.next());
1284 handle.assert_internal_outcome(DataCategory::Error, 1);
1285 drop(s.next());
1286 handle.assert_internal_outcome(DataCategory::Error, 1);
1287 drop(s.next());
1288 handle.assert_internal_outcome(DataCategory::Error, 1);
1289 handle.assert_no_outcomes();
1290
1291 drop(s);
1292
1293 handle.assert_internal_outcome(DataCategory::Error, 1);
1294 handle.assert_internal_outcome(DataCategory::Error, 1);
1295 handle.assert_internal_outcome(DataCategory::Error, 1);
1296 }
1297
1298 #[test]
1299 fn test_split_changing_quantities_should_panic() {
1300 let value = CountedVec(vec![0, 1, 2, 3, 4, 5]);
1301 let (managed, mut handle) = Managed::for_test(value).build();
1302
1303 let mut s = managed.split(|_| std::iter::once(CountedValue(0)));
1304
1305 s.next().unwrap().accept(|_| {});
1306 handle.assert_no_outcomes();
1307
1308 assert!(s.next().is_none());
1309
1310 let r = std::panic::catch_unwind(move || {
1311 drop(s);
1312 });
1313
1314 assert!(
1315 r.is_err(),
1316 "expected split to panic because of mismatched (not enough) outcomes"
1317 );
1318 }
1319
1320 #[test]
1321 fn test_split_more_outcomes_than_before_should_panic() {
1322 let value = CountedVec(vec![0]);
1323 let (managed, mut handle) = Managed::for_test(value).build();
1324
1325 let mut s = managed.split(|_| vec![CountedValue(0), CountedValue(2)].into_iter());
1326
1327 s.next().unwrap().accept(|_| {});
1328 handle.assert_no_outcomes();
1329
1330 let r = std::panic::catch_unwind(move || {
1331 s.next();
1332 });
1333
1334 assert!(
1335 r.is_err(),
1336 "expected split to panic because of mismatched (too many) outcomes"
1337 );
1338 }
1339
1340 #[test]
1341 fn test_split_changing_categories_should_panic() {
1342 struct Special;
1343 impl Counted for Special {
1344 fn quantities(&self) -> Quantities {
1345 smallvec::smallvec![(DataCategory::Error, 1), (DataCategory::Transaction, 1)]
1346 }
1347 }
1348
1349 let value = CountedVec(vec![0]);
1350 let (managed, _handle) = Managed::for_test(value).build();
1351
1352 let mut s = managed.split(|value| value.0.into_iter().map(|_| Special));
1353
1354 let r = std::panic::catch_unwind(move || {
1355 let _ = s.next();
1356 });
1357
1358 assert!(
1359 r.is_err(),
1360 "expected split to panic because of mismatched outcome categories"
1361 );
1362 }
1363
1364 #[test]
1365 fn test_split_assert_fused() {
1366 fn only_fused<T: FusedIterator>(_: T) {}
1367
1368 let (managed, mut handle) = Managed::for_test(CountedVec(vec![0])).build();
1369 only_fused(managed.split(|value| value.0.into_iter().map(CountedValue)));
1370 handle.assert_internal_outcome(DataCategory::Error, 1);
1371 }
1372}