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}