11use super :: DateTime ;
2+ use backend_api:: ApiError ;
23use candid:: { CandidType , Decode , Deserialize , Encode } ;
3- use ic_stable_structures:: { storable:: Bound , Storable } ;
4- use std:: borrow:: Cow ;
4+ use ic_stable_structures:: {
5+ storable:: { Blob , Bound } ,
6+ Storable ,
7+ } ;
8+ use std:: { borrow:: Cow , ops:: RangeBounds } ;
59
610pub type LogId = u64 ;
711
@@ -16,18 +20,6 @@ pub struct LogsFilter {
1620
1721impl LogsFilter {
1822 pub fn matches ( & self , log_entry : & LogEntry ) -> bool {
19- if let Some ( before) = & self . before {
20- if log_entry. date_time > * before {
21- return false ;
22- }
23- }
24-
25- if let Some ( after) = & self . after {
26- if log_entry. date_time < * after {
27- return false ;
28- }
29- }
30-
3123 if let Some ( level) = & self . level {
3224 if log_entry. level != * level {
3325 return false ;
@@ -82,10 +74,77 @@ impl Storable for LogEntry {
8274 const BOUND : Bound = Bound :: Unbounded ;
8375}
8476
77+ #[ derive( Debug , Clone , PartialEq , Eq , PartialOrd , Ord ) ]
78+ pub struct LogTimestampKey ( Blob < { Self :: MAX_SIZE as usize } > ) ;
79+
80+ impl LogTimestampKey {
81+ const MAX_SIZE : u32 = <( DateTime , LogId ) >:: BOUND . max_size ( ) ;
82+
83+ pub fn new ( date_time : DateTime , log_id : LogId ) -> Result < Self , ApiError > {
84+ Ok ( Self (
85+ Blob :: try_from ( ( date_time, log_id) . to_bytes ( ) . as_ref ( ) ) . map_err ( |_| {
86+ ApiError :: internal ( & format ! (
87+ "Failed to convert date time {:?} and log id {} to bytes." ,
88+ date_time, log_id
89+ ) )
90+ } ) ?,
91+ ) )
92+ }
93+ }
94+
95+ impl Storable for LogTimestampKey {
96+ fn to_bytes ( & self ) -> Cow < [ u8 ] > {
97+ self . 0 . to_bytes ( )
98+ }
99+
100+ fn from_bytes ( bytes : Cow < [ u8 ] > ) -> Self {
101+ Self ( Blob :: from_bytes ( bytes) )
102+ }
103+
104+ const BOUND : Bound = Bound :: Bounded {
105+ max_size : Self :: MAX_SIZE ,
106+ is_fixed_size : true ,
107+ } ;
108+ }
109+
110+ pub struct LogTimestampRange {
111+ start_bound : LogTimestampKey ,
112+ end_bound : LogTimestampKey ,
113+ }
114+
115+ impl LogTimestampRange {
116+ pub fn new (
117+ min_date_time : Option < DateTime > ,
118+ max_date_time : Option < DateTime > ,
119+ ) -> Result < Self , ApiError > {
120+ let max_date_time = match max_date_time {
121+ Some ( max_date_time) => max_date_time,
122+ None => DateTime :: max ( ) ?,
123+ } ;
124+ Ok ( Self {
125+ start_bound : LogTimestampKey :: new (
126+ min_date_time. unwrap_or_else ( DateTime :: min) ,
127+ LogId :: MIN ,
128+ ) ?,
129+ end_bound : LogTimestampKey :: new ( max_date_time, LogId :: MAX ) ?,
130+ } )
131+ }
132+ }
133+
134+ impl RangeBounds < LogTimestampKey > for LogTimestampRange {
135+ fn start_bound ( & self ) -> std:: ops:: Bound < & LogTimestampKey > {
136+ std:: ops:: Bound :: Included ( & self . start_bound )
137+ }
138+
139+ fn end_bound ( & self ) -> std:: ops:: Bound < & LogTimestampKey > {
140+ std:: ops:: Bound :: Included ( & self . end_bound )
141+ }
142+ }
143+
85144#[ cfg( test) ]
86145mod tests {
87146 use super :: * ;
88- use crate :: fixtures;
147+ use crate :: { fixtures, system_api :: get_date_time } ;
89148 use rstest:: * ;
90149
91150 #[ rstest]
@@ -99,23 +158,27 @@ mod tests {
99158
100159 #[ rstest]
101160 #[ case:: empty_filter( fixtures:: filters:: empty_filter( ) ) ]
102- #[ case:: before_filter_matching( fixtures:: filters:: before_filter_matching( ) ) ]
103- #[ case:: before_filter_not_matching( fixtures:: filters:: before_filter_not_matching( ) ) ]
104- #[ case:: after_filter_matching( fixtures:: filters:: after_filter_matching( ) ) ]
105- #[ case:: after_filter_not_matching( fixtures:: filters:: after_filter_not_matching( ) ) ]
106- #[ case:: time_range_filter_matching( fixtures:: filters:: time_range_filter_matching( ) ) ]
107- #[ case:: time_range_filter_not_matching( fixtures:: filters:: time_range_filter_not_matching( ) ) ]
108161 #[ case:: level_filter_matching( fixtures:: filters:: level_filter_matching( ) ) ]
109162 #[ case:: level_filter_not_matching( fixtures:: filters:: level_filter_not_matching( ) ) ]
110163 #[ case:: context_filter_matching( fixtures:: filters:: context_filter_matching( ) ) ]
111164 #[ case:: context_filter_not_matching( fixtures:: filters:: context_filter_not_matching( ) ) ]
112165 #[ case:: message_filter_matching( fixtures:: filters:: message_filter_matching( ) ) ]
113- #[ case:: message_filter_not_matchingd( fixtures:: filters:: message_filter_not_matching( ) ) ]
114- #[ case:: all_matching( fixtures:: filters:: all_matching( ) ) ]
115- #[ case:: all_not_matching( fixtures:: filters:: all_not_matching( ) ) ]
166+ #[ case:: message_filter_not_matching( fixtures:: filters:: message_filter_not_matching( ) ) ]
116167 fn filter_matches ( #[ case] fixture : ( LogEntry , LogsFilter , bool ) ) {
117168 let ( log_entry, filter, expected) = fixture;
118169
119170 assert_eq ! ( filter. matches( & log_entry) , expected) ;
120171 }
172+
173+ #[ rstest]
174+ fn log_timestamp_key_storable_impl ( ) {
175+ let date_time = get_date_time ( ) . unwrap ( ) ;
176+ let log_id: LogId = 1234 ;
177+
178+ let key = LogTimestampKey :: new ( DateTime :: new ( date_time) . unwrap ( ) , log_id) . unwrap ( ) ;
179+ let serialized_key = key. to_bytes ( ) ;
180+ let deserialized_key = LogTimestampKey :: from_bytes ( serialized_key) ;
181+
182+ assert_eq ! ( key, deserialized_key) ;
183+ }
121184}
0 commit comments