Skip to content

Commit 04fe52b

Browse files
committed
sentry - Event improvements & new enum EventType
1 parent 7d6a276 commit 04fe52b

File tree

1 file changed

+200
-39
lines changed

1 file changed

+200
-39
lines changed

primitives/src/sentry.rs

Lines changed: 200 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ use std::{
1818
};
1919
use thiserror::Error;
2020

21+
pub use event::{Event, EventType, CLICK, IMPRESSION};
22+
2123
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
2224
#[serde(rename_all = "camelCase")]
2325
/// Channel Accounting response
@@ -127,51 +129,126 @@ pub mod message {
127129
}
128130
}
129131

130-
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash)]
131-
#[serde(tag = "type", rename_all = "SCREAMING_SNAKE_CASE")]
132-
pub enum Event {
133-
#[serde(rename_all = "camelCase")]
134-
Impression {
135-
publisher: Address,
136-
ad_unit: Option<IPFS>,
137-
ad_slot: Option<IPFS>,
138-
referrer: Option<String>,
139-
},
140-
#[serde(rename_all = "camelCase")]
141-
Click {
142-
publisher: Address,
143-
ad_unit: Option<IPFS>,
144-
ad_slot: Option<IPFS>,
145-
referrer: Option<String>,
146-
},
147-
}
132+
mod event {
133+
use once_cell::sync::Lazy;
134+
use parse_display::{Display, FromStr};
135+
use serde::{Deserialize, Serialize};
136+
use std::fmt;
148137

149-
impl Event {
150-
pub fn is_click_event(&self) -> bool {
151-
matches!(self, Event::Click { .. })
138+
use crate::{Address, IPFS};
139+
140+
pub static IMPRESSION: EventType = EventType::Impression;
141+
pub static CLICK: EventType = EventType::Click;
142+
143+
/// We use these statics to create the `as_str()` method for a value with a `'static` lifetime
144+
/// the `parse_display::Display` derive macro does not impl such methods
145+
static IMPRESSION_STRING: Lazy<String> = Lazy::new(|| EventType::Impression.to_string());
146+
static CLICK_STRING: Lazy<String> = Lazy::new(|| EventType::Click.to_string());
147+
148+
#[derive(Debug, Display, FromStr, Serialize, Deserialize, Hash, Ord, Eq, PartialEq, PartialOrd, Clone, Copy)]
149+
#[display(style = "SNAKE_CASE")]
150+
#[serde(rename_all="SCREAMING_SNAKE_CASE")]
151+
pub enum EventType {
152+
Impression,
153+
Click,
152154
}
153155

154-
pub fn is_impression_event(&self) -> bool {
155-
matches!(self, Event::Impression { .. })
156+
impl EventType {
157+
pub fn as_str(&self) -> &str {
158+
match self {
159+
EventType::Impression => IMPRESSION_STRING.as_str(),
160+
EventType::Click => CLICK_STRING.as_str(),
161+
}
162+
}
156163
}
157164

158-
pub fn as_str(&self) -> &str {
159-
self.as_ref()
165+
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash)]
166+
#[serde(tag = "type", rename_all = "SCREAMING_SNAKE_CASE")]
167+
pub enum Event {
168+
#[serde(rename_all = "camelCase")]
169+
Impression {
170+
publisher: Address,
171+
ad_unit: Option<IPFS>,
172+
ad_slot: Option<IPFS>,
173+
referrer: Option<String>,
174+
},
175+
#[serde(rename_all = "camelCase")]
176+
Click {
177+
publisher: Address,
178+
ad_unit: Option<IPFS>,
179+
ad_slot: Option<IPFS>,
180+
referrer: Option<String>,
181+
},
160182
}
161-
}
162183

163-
impl AsRef<str> for Event {
164-
fn as_ref(&self) -> &str {
165-
match *self {
166-
Event::Impression { .. } => "IMPRESSION",
167-
Event::Click { .. } => "CLICK",
184+
impl Event {
185+
pub fn is_click_event(&self) -> bool {
186+
matches!(self, Event::Click { .. })
187+
}
188+
189+
pub fn is_impression_event(&self) -> bool {
190+
matches!(self, Event::Impression { .. })
191+
}
192+
193+
pub fn as_str(&self) -> &str {
194+
self.as_ref()
195+
}
196+
197+
pub fn event_type(&self) -> EventType {
198+
self.into()
168199
}
169200
}
170-
}
171201

172-
impl fmt::Display for Event {
173-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174-
f.write_str(self.as_ref())
202+
impl From<&Event> for EventType {
203+
fn from(event: &Event) -> Self {
204+
match event {
205+
Event::Impression { .. } => EventType::Impression,
206+
Event::Click { .. } => EventType::Click,
207+
}
208+
}
209+
}
210+
211+
impl AsRef<str> for Event {
212+
fn as_ref(&self) -> &'static str {
213+
match self {
214+
Event::Impression { .. } => EventType::Impression.as_str(),
215+
Event::Click { .. } => EventType::Click.as_str(),
216+
}
217+
}
218+
}
219+
220+
impl fmt::Display for Event {
221+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
222+
f.write_str(self.as_ref())
223+
}
224+
}
225+
226+
#[cfg(test)]
227+
mod test {
228+
use crate::sentry::event::{CLICK_STRING, IMPRESSION_STRING};
229+
230+
use super::EventType;
231+
232+
#[test]
233+
fn event_type_parsing_and_de_serialization() {
234+
let impression_parse = "IMPRESSION"
235+
.parse::<EventType>()
236+
.expect("Should parse IMPRESSION");
237+
let click_parse = "CLICK".parse::<EventType>().expect("Should parse CLICK");
238+
let impression_json =
239+
serde_json::from_value::<EventType>(serde_json::Value::String("IMPRESSION".into()))
240+
.expect("Should deserialize");
241+
let click_json =
242+
serde_json::from_value::<EventType>(serde_json::Value::String("CLICK".into()))
243+
.expect("Should deserialize");
244+
245+
assert_eq!(IMPRESSION_STRING.as_str(), "IMPRESSION");
246+
assert_eq!(CLICK_STRING.as_str(), "CLICK");
247+
assert_eq!(EventType::Impression, impression_parse);
248+
assert_eq!(EventType::Impression, impression_json);
249+
assert_eq!(EventType::Click, click_parse);
250+
assert_eq!(EventType::Click, click_json);
251+
}
175252
}
176253
}
177254

@@ -188,7 +265,7 @@ pub struct UpdateAnalytics {
188265
pub hostname: Option<String>,
189266
pub country: Option<String>,
190267
pub os_name: OperatingSystem,
191-
pub event_type: String,
268+
pub event_type: EventType,
192269
pub amount_to_add: UnifiedNum,
193270
pub count_to_add: i32,
194271
}
@@ -206,7 +283,7 @@ pub struct Analytics {
206283
pub hostname: Option<String>,
207284
pub country: Option<String>,
208285
pub os_name: OperatingSystem,
209-
pub event_type: String,
286+
pub event_type: EventType,
210287
pub payout_amount: UnifiedNum,
211288
pub payout_count: u32,
212289
}
@@ -217,11 +294,37 @@ pub struct FetchedAnalytics {
217294
// time is represented as a timestamp
218295
#[serde(with = "ts_milliseconds")]
219296
pub time: DateTime<Utc>,
220-
pub value: UnifiedNum,
297+
pub value: FetchedMetric,
221298
// We can't know the exact segment type but it can always be represented as a string
222299
pub segment: Option<String>,
223300
}
224301

302+
/// The value of the requested analytics [`Metric`].
303+
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
304+
#[serde(untagged)]
305+
pub enum FetchedMetric {
306+
Count(u32),
307+
Paid(UnifiedNum),
308+
}
309+
310+
impl FetchedMetric {
311+
/// Returns the count if it's a [`FetchedMetric::Count`] or `None` otherwise.
312+
pub fn get_count(&self) -> Option<u32> {
313+
match self {
314+
FetchedMetric::Count(count) => Some(*count),
315+
FetchedMetric::Paid(_) => None,
316+
}
317+
}
318+
319+
/// Returns the paid amount if it's a [`FetchedMetric::Paid`] or `None` otherwise.
320+
pub fn get_paid(&self) -> Option<UnifiedNum> {
321+
match self {
322+
FetchedMetric::Count(_) => None,
323+
FetchedMetric::Paid(paid) => Some(*paid),
324+
}
325+
}
326+
}
327+
225328
#[derive(Debug, Error, PartialEq, Eq)]
226329
#[error("Minutes ({minutes}), seconds ({seconds}) & nanoseconds ({nanoseconds}) should all be set to 0 (zero)")]
227330
pub struct DateHourError {
@@ -818,8 +921,11 @@ pub mod campaign_create {
818921

819922
#[cfg(feature = "postgres")]
820923
mod postgres {
821-
use super::{Analytics, DateHour, MessageResponse, ValidatorMessage};
924+
use super::{
925+
Analytics, DateHour, FetchedAnalytics, FetchedMetric, MessageResponse, ValidatorMessage, EventType,
926+
};
822927
use crate::{
928+
analytics::{AnalyticsQuery, Metric},
823929
sentry::EventAggregate,
824930
validator::{messages::Type as MessageType, MessageTypes},
825931
};
@@ -959,6 +1065,61 @@ mod postgres {
9591065
accepts!(TIMESTAMPTZ);
9601066
to_sql_checked!();
9611067
}
1068+
1069+
impl<'a> FromSql<'a> for EventType {
1070+
fn from_sql(
1071+
ty: &Type,
1072+
raw: &'a [u8],
1073+
) -> Result<Self, Box<dyn std::error::Error + Sync + Send>> {
1074+
let event_string = <&str as FromSql>::from_sql(ty, raw)?;
1075+
1076+
Ok(event_string.parse()?)
1077+
}
1078+
accepts!(VARCHAR, TEXT);
1079+
}
1080+
1081+
impl ToSql for EventType {
1082+
fn to_sql(
1083+
&self,
1084+
ty: &Type,
1085+
w: &mut BytesMut,
1086+
) -> Result<IsNull, Box<dyn std::error::Error + Sync + Send>> {
1087+
self.as_str().to_sql(ty, w)
1088+
}
1089+
1090+
accepts!(VARCHAR, TEXT);
1091+
to_sql_checked!();
1092+
}
1093+
1094+
/// This implementation handles the conversion of a fetched query [`Row`] to [`FetchedAnalytics`]
1095+
/// [`FetchedAnalytics`] requires additional context, apart from [`Row`], using the [`AnalyticsQuery`].
1096+
impl From<(&AnalyticsQuery, &Row)> for FetchedAnalytics {
1097+
/// # Panics
1098+
///
1099+
/// When a field is missing in the [`Row`].
1100+
fn from((query, row): (&AnalyticsQuery, &Row)) -> Self {
1101+
// Since segment_by is a dynamic value/type it can't be passed to from<&Row> so we're building the object here
1102+
let segment_value = match query.segment_by.as_ref() {
1103+
Some(_segment_by) => row.get("segment_by"),
1104+
None => None,
1105+
};
1106+
let time = row.get::<_, DateTime<Utc>>("timeframe_time");
1107+
let value = match &query.metric {
1108+
Metric::Paid => FetchedMetric::Paid(row.get("value")),
1109+
Metric::Count => {
1110+
// `integer` fields map to `i32`
1111+
let count: i32 = row.get("value");
1112+
// Count can only be positive, so use unsigned value
1113+
FetchedMetric::Count(count.unsigned_abs())
1114+
}
1115+
};
1116+
FetchedAnalytics {
1117+
time,
1118+
value,
1119+
segment: segment_value,
1120+
}
1121+
}
1122+
}
9621123
}
9631124

9641125
#[cfg(test)]

0 commit comments

Comments
 (0)