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}