relay_base_schema/
data_category.rs

1//! Defines the [`DataCategory`] type that classifies data Relay can handle.
2
3use std::fmt;
4use std::str::FromStr;
5
6use serde::{Deserialize, Serialize};
7
8use crate::events::EventType;
9
10/// An error that occurs if a number cannot be converted into a [`DataCategory`].
11#[derive(Debug, PartialEq, thiserror::Error)]
12#[error("Unknown numeric data category {0} can not be converted into a DataCategory.")]
13pub struct UnknownDataCategory(pub u8);
14
15/// Classifies the type of data that is being ingested.
16#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
17#[serde(rename_all = "snake_case")]
18#[repr(i8)]
19pub enum DataCategory {
20    /// Reserved and unused.
21    Default = 0,
22    /// Error events and Events with an `event_type` not explicitly listed below.
23    Error = 1,
24    /// Transaction events.
25    Transaction = 2,
26    /// Events with an event type of `csp`, `hpkp`, `expectct` and `expectstaple`.
27    Security = 3,
28    /// An attachment. Quantity is the size of the attachment in bytes.
29    Attachment = 4,
30    /// Session updates. Quantity is the number of updates in the batch.
31    Session = 5,
32    /// Profile
33    ///
34    /// This is the category for processed profiles (all profiles, whether or not we store them).
35    Profile = 6,
36    /// Session Replays
37    Replay = 7,
38    /// DEPRECATED: A transaction for which metrics were extracted.
39    ///
40    /// This category is now obsolete because the `Transaction` variant will represent
41    /// processed transactions from now on.
42    TransactionProcessed = 8,
43    /// Indexed transaction events.
44    ///
45    /// This is the category for transaction payloads that were accepted and stored in full. In
46    /// contrast, `transaction` only guarantees that metrics have been accepted for the transaction.
47    TransactionIndexed = 9,
48    /// Monitor check-ins.
49    Monitor = 10,
50    /// Indexed Profile
51    ///
52    /// This is the category for indexed profiles that will be stored later.
53    ProfileIndexed = 11,
54    /// Span
55    ///
56    /// This is the category for spans from which we extracted metrics from.
57    Span = 12,
58    /// Monitor Seat
59    ///
60    /// Represents a monitor job that has scheduled monitor checkins. The seats are not ingested
61    /// but we define it here to prevent clashing values since this data category enumeration
62    /// is also used outside of Relay via the Python package.
63    MonitorSeat = 13,
64    /// User Feedback
65    ///
66    /// Represents a User Feedback processed.
67    /// Currently standardized on name UserReportV2 to avoid clashing with the old UserReport.
68    /// TODO(jferg): Rename this to UserFeedback once old UserReport is deprecated.
69    UserReportV2 = 14,
70    /// Metric buckets.
71    MetricBucket = 15,
72    /// SpanIndexed
73    ///
74    /// This is the category for spans we store in full.
75    SpanIndexed = 16,
76    /// ProfileDuration
77    ///
78    /// This data category is used to count the number of milliseconds per indexed profile chunk,
79    /// excluding UI profile chunks.
80    ProfileDuration = 17,
81    /// ProfileChunk
82    ///
83    /// This is a count of profile chunks received. It will not be used for billing but will be
84    /// useful for customers to track what's being dropped.
85    ProfileChunk = 18,
86    /// MetricSecond
87    ///
88    /// Reserved by billing to summarize the bucketed product of metric volume
89    /// and metric cardinality. Defined here so as not to clash with future
90    /// categories.
91    MetricSecond = 19,
92    /// Replay Video
93    ///
94    /// This is the data category for Session Replays produced via a video recording.
95    DoNotUseReplayVideo = 20,
96    /// This is the data category for Uptime monitors.
97    Uptime = 21,
98    /// Counts the number of individual attachments, as opposed to the number of bytes in an attachment.
99    AttachmentItem = 22,
100    /// LogItem
101    ///
102    /// This is the category for logs for which we store the count log events for users for measuring
103    /// missing breadcrumbs, and count of logs for rate limiting purposes.
104    LogItem = 23,
105    /// LogByte
106    ///
107    /// This is the category for logs for which we store log event total bytes for users.
108    LogByte = 24,
109    /// Profile duration of a UI profile.
110    ///
111    /// This data category is used to count the number of milliseconds per indexed UI profile
112    /// chunk.
113    ///
114    /// See also: [`Self::ProfileDuration`]
115    ProfileDurationUi = 25,
116    /// UI Profile Chunk.
117    ///
118    /// This data category is used to count the number of milliseconds per indexed UI profile
119    /// chunk.
120    ///
121    /// See also: [`Self::ProfileChunk`]
122    ProfileChunkUi = 26,
123    /// This is the data category to count Seer Autofix run events.
124    SeerAutofix = 27,
125    /// This is the data category to count Seer Scanner run events.
126    SeerScanner = 28,
127    /// PreventUser
128    ///
129    /// This is the data category to count the number of assigned Prevent Users.
130    PreventUser = 29,
131    /// PreventReview
132    ///
133    /// This is the data category to count the number of Prevent review events.
134    PreventReview = 30,
135    /// Size analysis
136    ///
137    /// This is the data category to count the number of size analyses performed.
138    /// 'Size analysis' a static binary analysis of a preprod build artifact
139    /// (e.g. the .apk of an Android app or MacOS .app).
140    /// When enabled there will typically be one such analysis per uploaded artifact.
141    SizeAnalysis = 31,
142    /// InstallableBuild
143    ///
144    /// This is the data category to count the number of installable builds.
145    /// It counts the number of artifacts uploaded *not* the number of times the
146    /// artifacts are downloaded for installation.
147    /// When enabled there will typically be one 'InstallableBuild' per uploaded artifact.
148    InstallableBuild = 32,
149    /// TraceMetric
150    ///
151    /// This is the data category to count the number of trace metric items.
152    TraceMetric = 33,
153    //
154    // IMPORTANT: After adding a new entry to DataCategory, go to the `relay-cabi` subfolder and run
155    // `make header` to regenerate the C-binding. This allows using the data category from Python.
156    // Rerun this step every time the **code name** of the variant is updated.
157    //
158    /// Any other data category not known by this Relay.
159    #[serde(other)]
160    Unknown = -1,
161}
162
163impl DataCategory {
164    /// Returns the data category corresponding to the given name.
165    pub fn from_name(string: &str) -> Self {
166        // TODO: This should probably use serde.
167        match string {
168            "default" => Self::Default,
169            "error" => Self::Error,
170            "transaction" => Self::Transaction,
171            "security" => Self::Security,
172            "attachment" => Self::Attachment,
173            "session" => Self::Session,
174            "profile" => Self::Profile,
175            "profile_indexed" => Self::ProfileIndexed,
176            "replay" => Self::Replay,
177            "transaction_processed" => Self::TransactionProcessed,
178            "transaction_indexed" => Self::TransactionIndexed,
179            "monitor" => Self::Monitor,
180            "span" => Self::Span,
181            "log_item" => Self::LogItem,
182            "log_byte" => Self::LogByte,
183            "monitor_seat" => Self::MonitorSeat,
184            "feedback" => Self::UserReportV2,
185            "user_report_v2" => Self::UserReportV2,
186            "metric_bucket" => Self::MetricBucket,
187            "span_indexed" => Self::SpanIndexed,
188            "profile_duration" => Self::ProfileDuration,
189            "profile_duration_ui" => Self::ProfileDurationUi,
190            "profile_chunk" => Self::ProfileChunk,
191            "profile_chunk_ui" => Self::ProfileChunkUi,
192            "metric_second" => Self::MetricSecond,
193            "replay_video" => Self::DoNotUseReplayVideo,
194            "uptime" => Self::Uptime,
195            "attachment_item" => Self::AttachmentItem,
196            "seer_autofix" => Self::SeerAutofix,
197            "seer_scanner" => Self::SeerScanner,
198            "prevent_user" => Self::PreventUser,
199            "prevent_review" => Self::PreventReview,
200            "size_analysis" => Self::SizeAnalysis,
201            "installable_build" => Self::InstallableBuild,
202            "trace_metric" => Self::TraceMetric,
203            _ => Self::Unknown,
204        }
205    }
206
207    /// Returns the canonical name of this data category.
208    pub fn name(self) -> &'static str {
209        // TODO: This should probably use serde.
210        match self {
211            Self::Default => "default",
212            Self::Error => "error",
213            Self::Transaction => "transaction",
214            Self::Security => "security",
215            Self::Attachment => "attachment",
216            Self::Session => "session",
217            Self::Profile => "profile",
218            Self::ProfileIndexed => "profile_indexed",
219            Self::Replay => "replay",
220            Self::DoNotUseReplayVideo => "replay_video",
221            Self::TransactionProcessed => "transaction_processed",
222            Self::TransactionIndexed => "transaction_indexed",
223            Self::Monitor => "monitor",
224            Self::Span => "span",
225            Self::LogItem => "log_item",
226            Self::LogByte => "log_byte",
227            Self::MonitorSeat => "monitor_seat",
228            Self::UserReportV2 => "feedback",
229            Self::MetricBucket => "metric_bucket",
230            Self::SpanIndexed => "span_indexed",
231            Self::ProfileDuration => "profile_duration",
232            Self::ProfileDurationUi => "profile_duration_ui",
233            Self::ProfileChunk => "profile_chunk",
234            Self::ProfileChunkUi => "profile_chunk_ui",
235            Self::MetricSecond => "metric_second",
236            Self::Uptime => "uptime",
237            Self::AttachmentItem => "attachment_item",
238            Self::SeerAutofix => "seer_autofix",
239            Self::SeerScanner => "seer_scanner",
240            Self::PreventUser => "prevent_user",
241            Self::PreventReview => "prevent_review",
242            Self::SizeAnalysis => "size_analysis",
243            Self::InstallableBuild => "installable_build",
244            Self::TraceMetric => "trace_metric",
245            Self::Unknown => "unknown",
246        }
247    }
248
249    /// Returns true if the DataCategory refers to an error (i.e an error event).
250    pub fn is_error(self) -> bool {
251        matches!(self, Self::Error | Self::Default | Self::Security)
252    }
253
254    /// Returns the numeric value for this outcome.
255    pub fn value(self) -> Option<u8> {
256        // negative values (Internal and Unknown) cannot be sent as
257        // outcomes (internally so!)
258        (self as i8).try_into().ok()
259    }
260
261    /// Returns a dedicated category for indexing if this data can be converted to metrics.
262    ///
263    /// This returns `None` for most data categories.
264    pub fn index_category(self) -> Option<Self> {
265        match self {
266            Self::Transaction => Some(Self::TransactionIndexed),
267            Self::Span => Some(Self::SpanIndexed),
268            Self::Profile => Some(Self::ProfileIndexed),
269            _ => None,
270        }
271    }
272
273    /// Returns `true` if this data category is an indexed data category.
274    pub fn is_indexed(self) -> bool {
275        matches!(
276            self,
277            Self::TransactionIndexed | Self::SpanIndexed | Self::ProfileIndexed
278        )
279    }
280}
281
282impl fmt::Display for DataCategory {
283    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
284        write!(f, "{}", self.name())
285    }
286}
287
288impl FromStr for DataCategory {
289    type Err = ();
290
291    fn from_str(string: &str) -> Result<Self, Self::Err> {
292        Ok(Self::from_name(string))
293    }
294}
295
296impl From<EventType> for DataCategory {
297    fn from(ty: EventType) -> Self {
298        match ty {
299            EventType::Default | EventType::Error | EventType::Nel => Self::Error,
300            EventType::Transaction => Self::Transaction,
301            EventType::Csp | EventType::Hpkp | EventType::ExpectCt | EventType::ExpectStaple => {
302                Self::Security
303            }
304            EventType::UserReportV2 => Self::UserReportV2,
305        }
306    }
307}
308
309impl TryFrom<u8> for DataCategory {
310    type Error = UnknownDataCategory;
311
312    fn try_from(value: u8) -> Result<Self, UnknownDataCategory> {
313        match value {
314            0 => Ok(Self::Default),
315            1 => Ok(Self::Error),
316            2 => Ok(Self::Transaction),
317            3 => Ok(Self::Security),
318            4 => Ok(Self::Attachment),
319            5 => Ok(Self::Session),
320            6 => Ok(Self::Profile),
321            7 => Ok(Self::Replay),
322            8 => Ok(Self::TransactionProcessed),
323            9 => Ok(Self::TransactionIndexed),
324            10 => Ok(Self::Monitor),
325            11 => Ok(Self::ProfileIndexed),
326            12 => Ok(Self::Span),
327            13 => Ok(Self::MonitorSeat),
328            14 => Ok(Self::UserReportV2),
329            15 => Ok(Self::MetricBucket),
330            16 => Ok(Self::SpanIndexed),
331            17 => Ok(Self::ProfileDuration),
332            18 => Ok(Self::ProfileChunk),
333            19 => Ok(Self::MetricSecond),
334            20 => Ok(Self::DoNotUseReplayVideo),
335            21 => Ok(Self::Uptime),
336            22 => Ok(Self::AttachmentItem),
337            23 => Ok(Self::LogItem),
338            24 => Ok(Self::LogByte),
339            25 => Ok(Self::ProfileDurationUi),
340            26 => Ok(Self::ProfileChunkUi),
341            27 => Ok(Self::SeerAutofix),
342            28 => Ok(Self::SeerScanner),
343            29 => Ok(Self::PreventUser),
344            30 => Ok(Self::PreventReview),
345            31 => Ok(Self::SizeAnalysis),
346            32 => Ok(Self::InstallableBuild),
347            33 => Ok(Self::TraceMetric),
348            other => Err(UnknownDataCategory(other)),
349        }
350    }
351}
352
353#[cfg(test)]
354mod tests {
355    use super::*;
356
357    #[test]
358    pub fn test_last_variant_conversion() {
359        // If this test fails, update the numeric bounds so that the first assertion
360        // maps to the last variant in the enum and the second assertion produces an error
361        // that the DataCategory does not exist.
362        assert_eq!(DataCategory::try_from(33), Ok(DataCategory::TraceMetric));
363        assert_eq!(DataCategory::try_from(34), Err(UnknownDataCategory(34)));
364    }
365}