Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 127 additions & 0 deletions sentry-types/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,3 +246,130 @@ mod hex_tests {
);
}
}

/// A macro which can wrap any number of enum definitions to make them "indexed."
///
/// Specifically, the macro adds an implementation to each enum, which contains the following:
/// - `const VARIANT_COUNT: usize`: the total number of variants in the enum.
/// - `const fn as_index(&self) -> usize`: the unique zero-based index of the enum variant.
/// This is implemented without relying on `as`-casting.
/// - `fn iter_variants() -> impl Iterator<Item = Self>`: An iterator over all enum variants in
/// the index order.
///
/// Both of the added items have the same visibility as the enum itself.
///
/// This is super useful, for example, if you want to store something for each variant. Rather
/// than using a `HashMap`, it is possible to allocate a fixed-length array of length
/// `VARIANT_COUNT`, indexed by `as_index`.
macro_rules! indexed_enum {
() => {};

{
$(#[$meta:meta])*
$vis:vis enum $name:ident {
$(
$(#[$variant_meta:meta])*
$variant:ident
),* $(,)?
}
$($rest:tt)*
} => {
$(#[$meta])*
$vis enum $name {
$(
$(#[$variant_meta])*
$variant,
)*
}

impl $name {
/// The number of variants in this enum.
$vis const VARIANT_COUNT: usize = indexed_enum!(@count $($variant),*);

indexed_enum!(@methods $vis, [] [] 0usize; $($variant),*);
}

indexed_enum! {
$($rest)*
}
};

(@methods $vis:vis, [$($as_index_arms:tt)*] [$($variants_array:expr),*] $idx:expr;) => {
/// Returns this variant's unique zero-based index.
///
/// The index satisfies `0 <= self.as_index() < Self::VARIANT_COUNT`.
$vis const fn as_index(&self) -> usize {
match *self {
$($as_index_arms)*
}
}

/// Returns an iterator over the enum variants in index order.
$vis fn iter_variants() -> impl ::std::iter::Iterator<Item = Self> {
<_ as ::std::iter::IntoIterator>::into_iter([$($variants_array),*])
}
};

(@methods $vis:vis, [$($as_index_arms:tt)*] [$($variants_array:expr),*] $idx:expr; $variant:ident $(, $rest:ident)*) => {
indexed_enum!(
@methods
$vis,
[$($as_index_arms)* Self::$variant => $idx,]
[$($variants_array,)* Self::$variant]
$idx + 1usize;
$($rest),*
);
};

(@count) => { 0usize };

(@count $variant:ident $(, $rest:ident)*) => {
1usize + indexed_enum!(@count $($rest),*)
};
}

#[cfg(test)]
mod tests {
indexed_enum! {
/// A test enum to test the `indexed_enum!` macro.
enum IndexedEnumTest {
V0,
V1,
V2,
V3,
}
}

#[test]
fn variant_count_is_accurate() {
assert_eq!(IndexedEnumTest::VARIANT_COUNT, 4);
}

#[test]
fn as_index_returns_unique_index() {
let mut indexes_seen = [false; IndexedEnumTest::VARIANT_COUNT];

for variant in [
IndexedEnumTest::V0,
IndexedEnumTest::V1,
IndexedEnumTest::V2,
IndexedEnumTest::V3,
] {
let index = variant.as_index();
assert!(!indexes_seen[index]);
indexes_seen[index] = true;
}

assert!(indexes_seen.into_iter().all(|seen| seen))
}

#[test]
fn variant_iter_is_in_index_order() {
let iter_length = IndexedEnumTest::iter_variants()
.enumerate()
.map(|(index, variant)| assert_eq!(index, variant.as_index()))
.count();

assert_eq!(iter_length, IndexedEnumTest::VARIANT_COUNT)
}
}
46 changes: 46 additions & 0 deletions sentry-types/src/protocol/client_report/list.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//! Module with code for representing the underlying list of client reports.

use serde::ser::SerializeSeq as _;
use serde::{Serialize, Serializer};

use super::{DataCategory, DiscardReason};

/// An entry in a client report.
///
/// Contains the quantity dropped for a certain category and reason.
#[derive(Debug, Serialize)]
pub struct ClientReportItem {
category: DataCategory,
reason: DiscardReason,
quantity: u64,
}

#[derive(Debug)]
pub(super) struct ClientReportList(Vec<ClientReportItem>);

impl ClientReportItem {
/// Create a new [`ClientReportItem`].
pub fn new(category: DataCategory, reason: DiscardReason, quantity: u64) -> Self {
Self {
category,
reason,
quantity,
}
}
}

impl Serialize for ClientReportList {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let seq = serializer.serialize_seq(Some(self.0.len()))?;

self.0
.iter()
.try_fold(seq, |mut seq, item| {
seq.serialize_element(&item).map(|()| seq)
})?
.end()
}
}
48 changes: 48 additions & 0 deletions sentry-types/src/protocol/client_report/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//! Module containing types related to [Client Reports].
//!
//! [Client Reports]: https://develop.sentry.dev/sdk/telemetry/client-reports/

use std::time::SystemTime;

use serde::Serialize;

use self::list::ClientReportList;
use crate::utils;

pub use self::list::ClientReportItem;

mod list;

/// A [client report].
///
/// [client report]: https://develop.sentry.dev/sdk/telemetry/client-reports/
#[derive(Debug, Serialize)]
pub struct ClientReport {
#[serde(with = "utils::ts_seconds_float")]
timestamp: SystemTime,
discarded_events: ClientReportList,
}

indexed_enum! {
/// The reason why a telemetry item was discarded.
///
/// Valid discard reasons are listed in the [develop docs]; this enum may only define a subset of
/// these data categories, but we will add further categories as we begin using them in the SDK.
///
/// [develop docs]: https://develop.sentry.dev/sdk/telemetry/client-reports/#discard-reasons-1
#[derive(Debug, Serialize, PartialEq, Eq, Hash, Clone, Copy)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum DiscardReason {}

/// The category of data which was dropped.
///
/// Valid categories are listed in the [develop docs]; this enum may only define a subset of these
/// valid data categories, but we will add further categories as we begin using them in the SDK.
///
/// [develop docs]: https://develop.sentry.dev/sdk/foundations/transport/rate-limiting/#definitions
#[derive(Debug, Serialize, PartialEq, Eq, Hash, Clone, Copy)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum DataCategory {}
}
1 change: 1 addition & 0 deletions sentry-types/src/protocol/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub const LATEST: u16 = 7;
pub use v7 as latest;

mod attachment;
mod client_report;
mod envelope;
mod monitor;
mod session;
Expand Down
1 change: 1 addition & 0 deletions sentry-types/src/protocol/v7.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pub use uuid::Uuid;
use crate::utils::{display_from_str_opt, ts_rfc3339_opt, ts_seconds_float};

pub use super::attachment::*;
pub use super::client_report::{ClientReport, ClientReportItem, DataCategory, DiscardReason};
pub use super::envelope::*;
pub use super::monitor::*;
pub use super::session::*;
Expand Down
Loading