posthog_rs/client/summary.rs
1//! Outcome of an immediate-delivery capture call.
2
3#[cfg(feature = "capture-v1")]
4use std::collections::HashMap;
5
6#[cfg(feature = "capture-v1")]
7use uuid::Uuid;
8
9#[cfg(feature = "capture-v1")]
10use crate::event_v1::{EventResult, EventStatus};
11
12/// The outcome of an immediate capture ([`Client::capture_immediate`] /
13/// [`Client::capture_batch_immediate`]), returned once the SDK has a terminal
14/// result for the batch — the request succeeded, or the retry budget was spent
15/// (which is an [`Err`] instead).
16///
17/// A returned `CaptureSummary` means the capture request itself succeeded (HTTP
18/// `2xx`). On the `capture-v1` pipeline the backend reports a per-event verdict,
19/// so a `2xx` can still leave some events unpersisted (`drop`/`retry`) — check
20/// [`all_persisted`](Self::all_persisted) / [`not_persisted`](Self::not_persisted)
21/// before treating the batch as fully durable. On the v0 pipeline a `2xx`
22/// persists the whole batch, so `all_persisted()` is always `true`.
23///
24/// `#[non_exhaustive]`: fields are read through accessors so more can be added
25/// without breaking callers.
26///
27/// [`Client::capture_immediate`]: crate::Client::capture_immediate
28/// [`Client::capture_batch_immediate`]: crate::Client::capture_batch_immediate
29#[derive(Debug, Clone, Default)]
30#[non_exhaustive]
31pub struct CaptureSummary {
32 submitted: usize,
33 #[cfg(feature = "capture-v1")]
34 results: HashMap<Uuid, EventResult>,
35}
36
37impl CaptureSummary {
38 /// V1 outcome: the number of events sent plus the backend's per-event verdicts.
39 #[cfg(feature = "capture-v1")]
40 pub(crate) fn from_results(submitted: usize, results: HashMap<Uuid, EventResult>) -> Self {
41 Self { submitted, results }
42 }
43
44 /// V0 outcome: a `2xx` persists the whole batch (no per-event verdicts).
45 #[cfg(not(feature = "capture-v1"))]
46 pub(crate) fn delivered(submitted: usize) -> Self {
47 Self { submitted }
48 }
49
50 /// Number of events sent on the wire (after `before_send` filtering).
51 pub fn submitted(&self) -> usize {
52 self.submitted
53 }
54
55 /// Number of submitted events the backend did not persist.
56 ///
57 /// Always `0` on the v0 pipeline (a `2xx` persists the whole batch). On
58 /// `capture-v1` this is `submitted` minus the events with an `ok`/`warning`
59 /// verdict, so it counts both `drop`/`retry` verdicts and any submitted
60 /// event the backend omitted from its response.
61 pub fn not_persisted(&self) -> usize {
62 #[cfg(feature = "capture-v1")]
63 {
64 let persisted = self
65 .results
66 .values()
67 .filter(|r| matches!(r.result, EventStatus::Ok | EventStatus::Warning))
68 .count();
69 self.submitted.saturating_sub(persisted)
70 }
71 #[cfg(not(feature = "capture-v1"))]
72 {
73 0
74 }
75 }
76
77 /// Whether every submitted event was persisted (`not_persisted() == 0`).
78 ///
79 /// Note this is **vacuously true when nothing was sent** (`submitted() == 0`),
80 /// which is what a disabled client or a fully `before_send`-filtered batch
81 /// returns. Callers that advance durable state on the strength of an immediate
82 /// delivery (e.g. committing an upstream offset) must therefore also check
83 /// `submitted()` against the number of events they intended to send — do not
84 /// gate durability on `all_persisted()` alone.
85 pub fn all_persisted(&self) -> bool {
86 self.not_persisted() == 0
87 }
88
89 /// Per-event server verdicts (`capture-v1` only). Includes persisted
90 /// (`ok`/`warning`) and unpersisted (`drop`/`retry`) verdicts; filter by
91 /// [`EventStatus`](crate::EventStatus) to isolate failures. May omit events
92 /// the backend did not report on — see [`not_persisted`](Self::not_persisted).
93 #[cfg(feature = "capture-v1")]
94 pub fn event_results(&self) -> &HashMap<Uuid, EventResult> {
95 &self.results
96 }
97}