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    /// DEPRECATED: Use SeerUser instead.
186    ///
187    /// PreventUser
188    ///
189    /// This is the data category to count the number of assigned Prevent Users.
190    ///
191    /// SDK rate limiting behavior: ignore.
192    PreventUser = 29,
193    /// PreventReview
194    ///
195    /// This is the data category to count the number of Prevent review events.
196    ///
197    /// SDK rate limiting behavior: ignore.
198    PreventReview = 30,
199    /// Size analysis
200    ///
201    /// This is the data category to count the number of size analyses performed.
202    /// 'Size analysis' a static binary analysis of a preprod build artifact
203    /// (e.g. the .apk of an Android app or MacOS .app).
204    /// When enabled there will typically be one such analysis per uploaded artifact.
205    ///
206    /// SDK rate limiting behavior: ignore.
207    SizeAnalysis = 31,
208    /// InstallableBuild
209    ///
210    /// This is the data category to count the number of installable builds.
211    /// It counts the number of artifacts uploaded *not* the number of times the
212    /// artifacts are downloaded for installation.
213    /// When enabled there will typically be one 'InstallableBuild' per uploaded artifact.
214    ///
215    /// SDK rate limiting behavior: ignore.
216    InstallableBuild = 32,
217    /// TraceMetric
218    ///
219    /// This is the data category to count the number of trace metric items.
220    TraceMetric = 33,
221    /// SeerUser
222    ///
223    /// This is the data category to count the number of Seer users.
224    ///
225    /// SDK rate limiting behavior: ignore.
226    SeerUser = 34,
227    //
228    // IMPORTANT: After adding a new entry to DataCategory, go to the `relay-cabi` subfolder and run
229    // `make header` to regenerate the C-binding. This allows using the data category from Python.
230    // Rerun this step every time the **code name** of the variant is updated.
231    //
232    /// Any other data category not known by this Relay.
233    #[serde(other)]
234    Unknown = -1,
235}
236
237impl DataCategory {
238    /// Returns the data category corresponding to the given name.
239    pub fn from_name(string: &str) -> Self {
240        // TODO: This should probably use serde.
241        match string {
242            "default" => Self::Default,
243            "error" => Self::Error,
244            "transaction" => Self::Transaction,
245            "security" => Self::Security,
246            "attachment" => Self::Attachment,
247            "session" => Self::Session,
248            "profile" => Self::Profile,
249            "profile_indexed" => Self::ProfileIndexed,
250            "replay" => Self::Replay,
251            "transaction_processed" => Self::TransactionProcessed,
252            "transaction_indexed" => Self::TransactionIndexed,
253            "monitor" => Self::Monitor,
254            "span" => Self::Span,
255            "log_item" => Self::LogItem,
256            "log_byte" => Self::LogByte,
257            "monitor_seat" => Self::MonitorSeat,
258            "feedback" => Self::UserReportV2,
259            "user_report_v2" => Self::UserReportV2,
260            "metric_bucket" => Self::MetricBucket,
261            "span_indexed" => Self::SpanIndexed,
262            "profile_duration" => Self::ProfileDuration,
263            "profile_duration_ui" => Self::ProfileDurationUi,
264            "profile_chunk" => Self::ProfileChunk,
265            "profile_chunk_ui" => Self::ProfileChunkUi,
266            "metric_second" => Self::MetricSecond,
267            "replay_video" => Self::DoNotUseReplayVideo,
268            "uptime" => Self::Uptime,
269            "attachment_item" => Self::AttachmentItem,
270            "seer_autofix" => Self::SeerAutofix,
271            "seer_scanner" => Self::SeerScanner,
272            "prevent_user" => Self::PreventUser,
273            "prevent_review" => Self::PreventReview,
274            "size_analysis" => Self::SizeAnalysis,
275            "installable_build" => Self::InstallableBuild,
276            "trace_metric" => Self::TraceMetric,
277            "seer_user" => Self::SeerUser,
278            _ => Self::Unknown,
279        }
280    }
281
282    /// Returns the canonical name of this data category.
283    pub fn name(self) -> &'static str {
284        // TODO: This should probably use serde.
285        match self {
286            Self::Default => "default",
287            Self::Error => "error",
288            Self::Transaction => "transaction",
289            Self::Security => "security",
290            Self::Attachment => "attachment",
291            Self::Session => "session",
292            Self::Profile => "profile",
293            Self::ProfileIndexed => "profile_indexed",
294            Self::Replay => "replay",
295            Self::DoNotUseReplayVideo => "replay_video",
296            Self::TransactionProcessed => "transaction_processed",
297            Self::TransactionIndexed => "transaction_indexed",
298            Self::Monitor => "monitor",
299            Self::Span => "span",
300            Self::LogItem => "log_item",
301            Self::LogByte => "log_byte",
302            Self::MonitorSeat => "monitor_seat",
303            Self::UserReportV2 => "feedback",
304            Self::MetricBucket => "metric_bucket",
305            Self::SpanIndexed => "span_indexed",
306            Self::ProfileDuration => "profile_duration",
307            Self::ProfileDurationUi => "profile_duration_ui",
308            Self::ProfileChunk => "profile_chunk",
309            Self::ProfileChunkUi => "profile_chunk_ui",
310            Self::MetricSecond => "metric_second",
311            Self::Uptime => "uptime",
312            Self::AttachmentItem => "attachment_item",
313            Self::SeerAutofix => "seer_autofix",
314            Self::SeerScanner => "seer_scanner",
315            Self::PreventUser => "prevent_user",
316            Self::PreventReview => "prevent_review",
317            Self::SizeAnalysis => "size_analysis",
318            Self::InstallableBuild => "installable_build",
319            Self::TraceMetric => "trace_metric",
320            Self::SeerUser => "seer_user",
321            Self::Unknown => "unknown",
322        }
323    }
324
325    /// Returns true if the DataCategory refers to an error (i.e an error event).
326    pub fn is_error(self) -> bool {
327        matches!(self, Self::Error | Self::Default | Self::Security)
328    }
329
330    /// Returns the numeric value for this outcome.
331    pub fn value(self) -> Option<u8> {
332        // negative values (Internal and Unknown) cannot be sent as
333        // outcomes (internally so!)
334        (self as i8).try_into().ok()
335    }
336
337    /// Returns a dedicated category for indexing if this data can be converted to metrics.
338    ///
339    /// This returns `None` for most data categories.
340    pub fn index_category(self) -> Option<Self> {
341        match self {
342            Self::Transaction => Some(Self::TransactionIndexed),
343            Self::Span => Some(Self::SpanIndexed),
344            Self::Profile => Some(Self::ProfileIndexed),
345            _ => None,
346        }
347    }
348
349    /// Returns `true` if this data category is an indexed data category.
350    pub fn is_indexed(self) -> bool {
351        matches!(
352            self,
353            Self::TransactionIndexed | Self::SpanIndexed | Self::ProfileIndexed
354        )
355    }
356}
357
358impl fmt::Display for DataCategory {
359    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
360        write!(f, "{}", self.name())
361    }
362}
363
364impl FromStr for DataCategory {
365    type Err = ();
366
367    fn from_str(string: &str) -> Result<Self, Self::Err> {
368        Ok(Self::from_name(string))
369    }
370}
371
372impl From<EventType> for DataCategory {
373    fn from(ty: EventType) -> Self {
374        match ty {
375            EventType::Default | EventType::Error | EventType::Nel => Self::Error,
376            EventType::Transaction => Self::Transaction,
377            EventType::Csp | EventType::Hpkp | EventType::ExpectCt | EventType::ExpectStaple => {
378                Self::Security
379            }
380            EventType::UserReportV2 => Self::UserReportV2,
381        }
382    }
383}
384
385impl TryFrom<u8> for DataCategory {
386    type Error = UnknownDataCategory;
387
388    fn try_from(value: u8) -> Result<Self, UnknownDataCategory> {
389        match value {
390            0 => Ok(Self::Default),
391            1 => Ok(Self::Error),
392            2 => Ok(Self::Transaction),
393            3 => Ok(Self::Security),
394            4 => Ok(Self::Attachment),
395            5 => Ok(Self::Session),
396            6 => Ok(Self::Profile),
397            7 => Ok(Self::Replay),
398            8 => Ok(Self::TransactionProcessed),
399            9 => Ok(Self::TransactionIndexed),
400            10 => Ok(Self::Monitor),
401            11 => Ok(Self::ProfileIndexed),
402            12 => Ok(Self::Span),
403            13 => Ok(Self::MonitorSeat),
404            14 => Ok(Self::UserReportV2),
405            15 => Ok(Self::MetricBucket),
406            16 => Ok(Self::SpanIndexed),
407            17 => Ok(Self::ProfileDuration),
408            18 => Ok(Self::ProfileChunk),
409            19 => Ok(Self::MetricSecond),
410            20 => Ok(Self::DoNotUseReplayVideo),
411            21 => Ok(Self::Uptime),
412            22 => Ok(Self::AttachmentItem),
413            23 => Ok(Self::LogItem),
414            24 => Ok(Self::LogByte),
415            25 => Ok(Self::ProfileDurationUi),
416            26 => Ok(Self::ProfileChunkUi),
417            27 => Ok(Self::SeerAutofix),
418            28 => Ok(Self::SeerScanner),
419            29 => Ok(Self::PreventUser),
420            30 => Ok(Self::PreventReview),
421            31 => Ok(Self::SizeAnalysis),
422            32 => Ok(Self::InstallableBuild),
423            33 => Ok(Self::TraceMetric),
424            34 => Ok(Self::SeerUser),
425            other => Err(UnknownDataCategory(other)),
426        }
427    }
428}
429
430#[cfg(test)]
431mod tests {
432    use super::*;
433
434    #[test]
435    pub fn test_last_variant_conversion() {
436        // If this test fails, update the numeric bounds so that the first assertion
437        // maps to the last variant in the enum and the second assertion produces an error
438        // that the DataCategory does not exist.
439        assert_eq!(DataCategory::try_from(34), Ok(DataCategory::SeerUser));
440        assert_eq!(DataCategory::try_from(35), Err(UnknownDataCategory(35)));
441    }
442}