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