@@ -18,6 +18,8 @@ use std::{
18
18
} ;
19
19
use thiserror:: Error ;
20
20
21
+ pub use event:: { Event , EventType , CLICK , IMPRESSION } ;
22
+
21
23
#[ derive( Serialize , Deserialize , Debug , Clone , PartialEq , Eq ) ]
22
24
#[ serde( rename_all = "camelCase" ) ]
23
25
/// Channel Accounting response
@@ -127,51 +129,126 @@ pub mod message {
127
129
}
128
130
}
129
131
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;
148
137
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 ,
152
154
}
153
155
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
+ }
156
163
}
157
164
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
+ } ,
160
182
}
161
- }
162
183
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 ( )
168
199
}
169
200
}
170
- }
171
201
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
+ }
175
252
}
176
253
}
177
254
@@ -188,7 +265,7 @@ pub struct UpdateAnalytics {
188
265
pub hostname : Option < String > ,
189
266
pub country : Option < String > ,
190
267
pub os_name : OperatingSystem ,
191
- pub event_type : String ,
268
+ pub event_type : EventType ,
192
269
pub amount_to_add : UnifiedNum ,
193
270
pub count_to_add : i32 ,
194
271
}
@@ -206,7 +283,7 @@ pub struct Analytics {
206
283
pub hostname : Option < String > ,
207
284
pub country : Option < String > ,
208
285
pub os_name : OperatingSystem ,
209
- pub event_type : String ,
286
+ pub event_type : EventType ,
210
287
pub payout_amount : UnifiedNum ,
211
288
pub payout_count : u32 ,
212
289
}
@@ -217,11 +294,37 @@ pub struct FetchedAnalytics {
217
294
// time is represented as a timestamp
218
295
#[ serde( with = "ts_milliseconds" ) ]
219
296
pub time : DateTime < Utc > ,
220
- pub value : UnifiedNum ,
297
+ pub value : FetchedMetric ,
221
298
// We can't know the exact segment type but it can always be represented as a string
222
299
pub segment : Option < String > ,
223
300
}
224
301
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
+
225
328
#[ derive( Debug , Error , PartialEq , Eq ) ]
226
329
#[ error( "Minutes ({minutes}), seconds ({seconds}) & nanoseconds ({nanoseconds}) should all be set to 0 (zero)" ) ]
227
330
pub struct DateHourError {
@@ -818,8 +921,11 @@ pub mod campaign_create {
818
921
819
922
#[ cfg( feature = "postgres" ) ]
820
923
mod postgres {
821
- use super :: { Analytics , DateHour , MessageResponse , ValidatorMessage } ;
924
+ use super :: {
925
+ Analytics , DateHour , FetchedAnalytics , FetchedMetric , MessageResponse , ValidatorMessage , EventType ,
926
+ } ;
822
927
use crate :: {
928
+ analytics:: { AnalyticsQuery , Metric } ,
823
929
sentry:: EventAggregate ,
824
930
validator:: { messages:: Type as MessageType , MessageTypes } ,
825
931
} ;
@@ -959,6 +1065,61 @@ mod postgres {
959
1065
accepts ! ( TIMESTAMPTZ ) ;
960
1066
to_sql_checked ! ( ) ;
961
1067
}
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
+ }
962
1123
}
963
1124
964
1125
#[ cfg( test) ]
0 commit comments