Skip to main content

gglib_core/ports/
loop_guard_trips.rs

1//! Outbound ports for the loop guard's log: a sink and a reader.
2//!
3//! The proxy writes to the sink on the request path; the daemon, the CLI and
4//! the GUI read through the reader.
5//!
6//! Two traits because the two sides have nothing in common but the data. The
7//! sink is synchronous and must never block or fail a request — the proxy
8//! holds it as an `Option`, and no sink at all makes recording a no-op, the
9//! [`UsageSink`](super::UsageSink) shape. The reader is asynchronous and only
10//! ever runs off the request path. `gglib-db` implements both; nothing on the
11//! proxy's side of the boundary knows it is `SQLite`.
12//!
13//! What the log holds and why is [`crate::domain::loop_guard_log`].
14
15use async_trait::async_trait;
16
17use super::RepositoryError;
18use crate::domain::loop_guard_log::{LoopGuardTripDay, LoopGuardTripEvent};
19use crate::settings::LoopGuardMode;
20
21/// Where the loop guard's step records what it did.
22///
23/// Both methods run on the request path, so an implementation must return at
24/// once: queue, count and move on. A write it cannot make is counted and
25/// dropped, never waited for, and never turned into an error for the request
26/// being guarded; the count reaches a person only as a warning in the log of
27/// the process that holds the sink.
28pub trait LoopGuardTripSink: Send + Sync {
29    /// Record one decision the guard took.
30    fn record_trip(&self, event: LoopGuardTripEvent);
31
32    /// Count one request the guard scanned under `mode`, on the day `at_secs`
33    /// falls on. Called for every scanned request, trip or not — it is the
34    /// denominator every trip is read against.
35    fn record_scan(&self, model_name: &str, mode: LoopGuardMode, at_secs: u64);
36}
37
38/// Reads the loop guard's log back.
39#[async_trait]
40pub trait LoopGuardTripLog: Send + Sync {
41    /// Every day from `first_day` on (an [`epoch_day`]) that either table
42    /// holds a row for, one entry per model, gglib version and mode, newest
43    /// day first. A day with scans and no trips is included, with `trips` at
44    /// zero; so is a trip whose scan was lost, with `scanned` at zero.
45    ///
46    /// [`epoch_day`]: crate::domain::loop_guard_log::epoch_day
47    async fn summary(&self, first_day: i64) -> Result<Vec<LoopGuardTripDay>, RepositoryError>;
48}