Skip to content

Commit 611b2cd

Browse files
authored
feat(types): add setters for envelope headers (#868)
1 parent 03ae06a commit 611b2cd

5 files changed

Lines changed: 191 additions & 15 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66

77
- feat(log): support kv feature of log (#851) by @lcian
88
- Attributes added to a `log` record using the `kv` feature are now recorded as attributes on the log sent to Sentry.
9+
- feat(types): add all the missing supported envelope headers ([#867](https://github.com/getsentry/sentry-rust/pull/867)) by @lcian
10+
- feat(types): add setters for envelope headers ([#868](https://github.com/getsentry/sentry-rust/pull/868)) by @lcian
11+
- It's now possible to set all of the [envelope headers](https://develop.sentry.dev/sdk/data-model/envelopes/#headers) supported by the protocol when constructing envelopes.
912

1013
### Behavioral changes
1114

sentry-types/src/protocol/envelope.rs

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ pub enum EnvelopeError {
4242

4343
/// The supported [Sentry Envelope Headers](https://develop.sentry.dev/sdk/data-model/envelopes/#headers).
4444
#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
45-
struct EnvelopeHeaders {
45+
pub struct EnvelopeHeaders {
4646
#[serde(default, skip_serializing_if = "Option::is_none")]
4747
event_id: Option<Uuid>,
4848
#[serde(default, skip_serializing_if = "Option::is_none")]
@@ -59,6 +59,49 @@ struct EnvelopeHeaders {
5959
trace: Option<DynamicSamplingContext>,
6060
}
6161

62+
impl EnvelopeHeaders {
63+
/// Creates empty Envelope headers.
64+
pub fn new() -> EnvelopeHeaders {
65+
Default::default()
66+
}
67+
68+
/// Sets the Event ID.
69+
#[must_use]
70+
pub fn with_event_id(mut self, event_id: Uuid) -> Self {
71+
self.event_id = Some(event_id);
72+
self
73+
}
74+
75+
/// Sets the DSN.
76+
#[must_use]
77+
pub fn with_dsn(mut self, dsn: Dsn) -> Self {
78+
self.dsn = Some(dsn);
79+
self
80+
}
81+
82+
/// Sets the SDK information.
83+
#[must_use]
84+
pub fn with_sdk(mut self, sdk: ClientSdkInfo) -> Self {
85+
self.sdk = Some(sdk);
86+
self
87+
}
88+
89+
/// Sets the time this envelope was sent at.
90+
/// This timestamp should be generated as close as possible to the transmission of the event.
91+
#[must_use]
92+
pub fn with_sent_at(mut self, sent_at: SystemTime) -> Self {
93+
self.sent_at = Some(sent_at);
94+
self
95+
}
96+
97+
/// Sets the Dynamic Sampling Context.
98+
#[must_use]
99+
pub fn with_trace(mut self, trace: DynamicSamplingContext) -> Self {
100+
self.trace = Some(trace);
101+
self
102+
}
103+
}
104+
62105
/// An Envelope Item Type.
63106
#[derive(Clone, Debug, Eq, PartialEq, Deserialize)]
64107
#[non_exhaustive]
@@ -334,6 +377,18 @@ impl Envelope {
334377
EnvelopeItemIter { inner }
335378
}
336379

380+
/// Returns the Envelope headers.
381+
pub fn headers(&self) -> &EnvelopeHeaders {
382+
&self.headers
383+
}
384+
385+
/// Sets the Envelope headers.
386+
#[must_use]
387+
pub fn with_headers(mut self, headers: EnvelopeHeaders) -> Self {
388+
self.headers = headers;
389+
self
390+
}
391+
337392
/// Returns the Envelopes Uuid, if any.
338393
pub fn uuid(&self) -> Option<&Uuid> {
339394
self.headers.event_id.as_ref()
@@ -646,7 +701,7 @@ mod test {
646701

647702
use super::*;
648703
use crate::protocol::v7::{
649-
Level, MonitorCheckInStatus, MonitorConfig, MonitorSchedule, SessionAttributes,
704+
Level, MonitorCheckInStatus, MonitorConfig, MonitorSchedule, SampleRand, SessionAttributes,
650705
SessionStatus, Span,
651706
};
652707

@@ -1039,6 +1094,21 @@ some content
10391094
assert_eq!(bytes, serialized.as_bytes());
10401095
}
10411096

1097+
#[test]
1098+
fn test_sample_rand_rounding() {
1099+
let envelope = Envelope::new().with_headers(
1100+
EnvelopeHeaders::new().with_trace(
1101+
DynamicSamplingContext::new()
1102+
.with_sample_rand(SampleRand::try_from(0.999_999_9).unwrap()),
1103+
),
1104+
);
1105+
let expected = br#"{"trace":{"sample_rand":"0.999999"}}
1106+
"#;
1107+
1108+
let serialized = to_str(envelope);
1109+
assert_eq!(expected, serialized.as_bytes());
1110+
}
1111+
10421112
// Test all possible item types in a single envelope
10431113
#[test]
10441114
fn test_deserialize_serialized() {
@@ -1116,7 +1186,6 @@ some content
11161186
.into();
11171187

11181188
let mut envelope: Envelope = Envelope::new();
1119-
11201189
envelope.add_item(event);
11211190
envelope.add_item(transaction);
11221191
envelope.add_item(session);

sentry-types/src/protocol/v7.rs

Lines changed: 106 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2339,7 +2339,7 @@ impl<'de> Deserialize<'de> for LogAttribute {
23392339

23402340
/// An ID that identifies an organization in the Sentry backend.
23412341
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)]
2342-
struct OrganizationId(u64);
2342+
pub struct OrganizationId(u64);
23432343

23442344
impl From<u64> for OrganizationId {
23452345
fn from(value: u64) -> Self {
@@ -2363,25 +2363,51 @@ impl std::fmt::Display for OrganizationId {
23632363

23642364
/// A random number generated at the start of a trace by the head of trace SDK.
23652365
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)]
2366-
struct SampleRand(f64);
2366+
pub struct SampleRand(f64);
23672367

2368-
impl From<f64> for SampleRand {
2369-
fn from(value: f64) -> Self {
2370-
Self(value)
2368+
/// An error that indicates failure to construct a SampleRand.
2369+
#[derive(Debug, Error)]
2370+
pub enum InvalidSampleRandError {
2371+
/// Indicates that the given value cannot be converted to a f64 succesfully.
2372+
#[error("failed to parse f64: {0}")]
2373+
InvalidFloat(#[from] std::num::ParseFloatError),
2374+
2375+
/// Indicates that the given float is outside of the valid range for a sample rand, that is the
2376+
/// half-open interval [0.0, 1.0).
2377+
#[error("sample rand value out of admissible interval [0.0, 1.0)")]
2378+
OutOfRange,
2379+
}
2380+
2381+
impl TryFrom<f64> for SampleRand {
2382+
type Error = InvalidSampleRandError;
2383+
2384+
fn try_from(value: f64) -> Result<Self, Self::Error> {
2385+
if !(0.0..1.0).contains(&value) {
2386+
return Err(InvalidSampleRandError::OutOfRange);
2387+
}
2388+
Ok(Self(value))
23712389
}
23722390
}
23732391

23742392
impl std::str::FromStr for SampleRand {
2375-
type Err = std::num::ParseFloatError;
2393+
type Err = InvalidSampleRandError;
23762394

23772395
fn from_str(s: &str) -> Result<Self, Self::Err> {
2378-
s.parse().map(Self)
2396+
let x: f64 = s.parse().map_err(InvalidSampleRandError::InvalidFloat)?;
2397+
Self::try_from(x)
23792398
}
23802399
}
23812400

23822401
impl std::fmt::Display for SampleRand {
23832402
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2384-
write!(f, "{:.6}", self.0)
2403+
// Special case: "{:.6}" would round values greater than or equal to 0.9999995 to 1.0,
2404+
// as Rust uses [rounding half-to-even](https://doc.rust-lang.org/std/fmt/#precision).
2405+
// Round to 0.999999 instead to comply with spec.
2406+
if self.0 >= 0.9999995 {
2407+
write!(f, "0.999999")
2408+
} else {
2409+
write!(f, "{:.6}", self.0)
2410+
}
23852411
}
23862412
}
23872413

@@ -2392,8 +2418,8 @@ impl std::fmt::Display for SampleRand {
23922418
/// This feature allows users to specify target sample rates for each project via the frontend instead of requiring an application redeployment.
23932419
/// The backend needs additional information from the SDK to support these features, contained in
23942420
/// the Dynamic Sampling Context.
2395-
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
2396-
pub(crate) struct DynamicSamplingContext {
2421+
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
2422+
pub struct DynamicSamplingContext {
23972423
// Strictly required fields
23982424
// Still typed as optional, as when deserializing an envelope created by an older SDK they might still be missing
23992425
#[serde(default, skip_serializing_if = "Option::is_none")]
@@ -2432,3 +2458,73 @@ pub(crate) struct DynamicSamplingContext {
24322458
)]
24332459
org_id: Option<OrganizationId>,
24342460
}
2461+
2462+
impl DynamicSamplingContext {
2463+
/// Creates an empty Dynamic Sampling Context.
2464+
pub fn new() -> Self {
2465+
Default::default()
2466+
}
2467+
2468+
/// Sets the trace ID.
2469+
#[must_use]
2470+
pub fn with_trace_id(mut self, trace_id: TraceId) -> Self {
2471+
self.trace_id = Some(trace_id);
2472+
self
2473+
}
2474+
2475+
/// Sets the DSN public key.
2476+
#[must_use]
2477+
pub fn with_public_key(mut self, public_key: String) -> Self {
2478+
self.public_key = Some(public_key);
2479+
self
2480+
}
2481+
2482+
/// Sets the sample rate.
2483+
#[must_use]
2484+
pub fn with_sample_rate(mut self, sample_rate: f32) -> Self {
2485+
self.sample_rate = Some(sample_rate);
2486+
self
2487+
}
2488+
2489+
/// Sets the sample random value generated by the head of trace SDK.
2490+
#[must_use]
2491+
pub fn with_sample_rand(mut self, sample_rand: SampleRand) -> Self {
2492+
self.sample_rand = Some(sample_rand);
2493+
self
2494+
}
2495+
2496+
/// Sets the sampled flag.
2497+
#[must_use]
2498+
pub fn with_sampled(mut self, sampled: bool) -> Self {
2499+
self.sampled = Some(sampled);
2500+
self
2501+
}
2502+
2503+
/// Sets the release.
2504+
#[must_use]
2505+
pub fn with_release(mut self, release: String) -> Self {
2506+
self.release = Some(release);
2507+
self
2508+
}
2509+
2510+
/// Sets the environment.
2511+
#[must_use]
2512+
pub fn with_environment(mut self, environment: String) -> Self {
2513+
self.environment = Some(environment);
2514+
self
2515+
}
2516+
2517+
/// Sets the transaction.
2518+
#[must_use]
2519+
pub fn with_transaction(mut self, transaction: String) -> Self {
2520+
self.transaction = Some(transaction);
2521+
self
2522+
}
2523+
2524+
/// Sets the organization ID.
2525+
#[must_use]
2526+
pub fn with_org_id(mut self, org_id: OrganizationId) -> Self {
2527+
self.org_id = Some(org_id);
2528+
self
2529+
}
2530+
}

sentry/src/transports/thread.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ use std::time::Duration;
77
use super::ratelimit::{RateLimiter, RateLimitingCategory};
88
use crate::{sentry_debug, Envelope};
99

10-
#[expect(clippy::large_enum_variant)]
10+
#[expect(
11+
clippy::large_enum_variant,
12+
reason = "In normal usage this is usually SendEnvelope, the other variants are only used when \
13+
the user manually calls transport.flush() or when the transport is shut down."
14+
)]
1115
enum Task {
1216
SendEnvelope(Envelope),
1317
Flush(SyncSender<()>),

sentry/src/transports/tokio_thread.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ use std::time::Duration;
77
use super::ratelimit::{RateLimiter, RateLimitingCategory};
88
use crate::{sentry_debug, Envelope};
99

10-
#[expect(clippy::large_enum_variant)]
10+
#[expect(
11+
clippy::large_enum_variant,
12+
reason = "In normal usage this is usually SendEnvelope, the other variants are only used when \
13+
the user manually calls transport.flush() or when the transport is shut down."
14+
)]
1115
enum Task {
1216
SendEnvelope(Envelope),
1317
Flush(SyncSender<()>),

0 commit comments

Comments
 (0)