Skip to main content

offline_protocol/telemetry/pipe/
host.rs

1//! What the host tells the pipe about itself: the platform every batch
2//! envelope names, and the application lifecycle the session boundaries
3//! follow.
4//!
5//! The binding fills these in, never the application: an `os` the app could
6//! set is an `os` the app could set wrong, and the ingest keys its
7//! per-platform aggregates on it.
8
9use offline_protocol_telemetry_wire::OsKind;
10
11/// The operating system the SDK is running on, as the ingest names it.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum TelemetryOs {
14    /// Apple iOS.
15    Ios,
16    /// Android.
17    Android,
18    /// Linux.
19    Linux,
20    /// Apple macOS.
21    Macos,
22    /// Microsoft Windows.
23    Windows,
24    /// A host the binding could not classify.
25    Other,
26}
27
28impl TelemetryOs {
29    /// The host this binary was compiled for, with no version information.
30    ///
31    /// The bindings pass the real major version alongside; this is the
32    /// fallback for a Rust embedder that has nothing better to say.
33    pub fn current() -> Self {
34        if cfg!(target_os = "ios") {
35            Self::Ios
36        } else if cfg!(target_os = "android") {
37            Self::Android
38        } else if cfg!(target_os = "linux") {
39            Self::Linux
40        } else if cfg!(target_os = "macos") {
41            Self::Macos
42        } else if cfg!(target_os = "windows") {
43            Self::Windows
44        } else {
45            Self::Other
46        }
47    }
48
49    pub(crate) fn wire(self) -> OsKind {
50        match self {
51            Self::Ios => OsKind::Ios,
52            Self::Android => OsKind::Android,
53            Self::Linux => OsKind::Linux,
54            Self::Macos => OsKind::Macos,
55            Self::Windows => OsKind::Windows,
56            Self::Other => OsKind::Other,
57        }
58    }
59
60    pub(crate) fn user_agent_token(self) -> &'static str {
61        match self {
62            Self::Ios => "ios",
63            Self::Android => "android",
64            Self::Linux => "linux",
65            Self::Macos => "macos",
66            Self::Windows => "windows",
67            Self::Other => "other",
68        }
69    }
70}
71
72/// The application lifecycle states the pipe draws session boundaries from.
73///
74/// `Background` closes a session (summary, then a flush); the first `Active`
75/// after a `Background` opens the next one. `Inactive` is the transient state
76/// iOS reports around a notification or an incoming call and is ignored: it
77/// is not a boundary a user would recognise as leaving the app.
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79pub enum AppState {
80    /// The application is in the foreground.
81    Active,
82    /// The application has left the foreground.
83    Background,
84    /// A transient foreground interruption; ignored.
85    Inactive,
86}
87
88/// Host facts supplied at `enable_telemetry`.
89#[derive(Debug, Clone, Copy, PartialEq, Eq)]
90pub struct TelemetryHost {
91    /// The host operating system.
92    pub os: TelemetryOs,
93    /// Its major version (`18` for iOS 18.x; the API level on Android).
94    pub os_major: u16,
95    /// The application state at the moment telemetry is enabled, so a pipe
96    /// enabled by a background launch closes its first session on the next
97    /// foreground rather than waiting for a round trip it may never see.
98    pub app_state: AppState,
99}
100
101impl TelemetryHost {
102    /// A host that is active right now.
103    pub fn new(os: TelemetryOs, os_major: u16) -> Self {
104        Self {
105            os,
106            os_major,
107            app_state: AppState::Active,
108        }
109    }
110
111    /// Sets the application state at enable time.
112    pub fn with_app_state(mut self, app_state: AppState) -> Self {
113        self.app_state = app_state;
114        self
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    #[test]
123    fn every_os_has_a_wire_form_and_a_user_agent_token() {
124        for os in [
125            TelemetryOs::Ios,
126            TelemetryOs::Android,
127            TelemetryOs::Linux,
128            TelemetryOs::Macos,
129            TelemetryOs::Windows,
130            TelemetryOs::Other,
131        ] {
132            let wire = serde_json::to_value(os.wire()).expect("serializes");
133            assert_eq!(
134                wire,
135                serde_json::Value::String(os.user_agent_token().into())
136            );
137        }
138    }
139
140    #[test]
141    fn current_platform_is_one_of_the_named_hosts() {
142        // On the machines this suite runs on, the answer is never `Other`.
143        assert_ne!(TelemetryOs::current(), TelemetryOs::Other);
144    }
145}