@@ -108,8 +108,21 @@ pub mod log;
108
108
pub mod tracing;
109
109
110
110
/// Logging flags to `#[command(flatten)]` into your CLI
111
- #[ derive( clap:: Args , Debug , Clone , Copy , Default ) ]
111
+ #[ derive( clap:: Args , Debug , Clone , Copy , Default , PartialEq , Eq ) ]
112
112
#[ command( about = None , long_about = None ) ]
113
+ #[ cfg_attr( feature = "serde" , derive( serde:: Serialize , serde:: Deserialize ) ) ]
114
+ #[ cfg_attr(
115
+ feature = "serde" ,
116
+ serde(
117
+ from = "VerbosityFilter" ,
118
+ into = "VerbosityFilter" ,
119
+ bound( serialize = "L: Clone" )
120
+ )
121
+ ) ]
122
+ #[ cfg_attr(
123
+ feature = "serde" ,
124
+ doc = r#"This type serializes to a string representation of the log level, e.g. `"Debug"`"#
125
+ ) ]
113
126
pub struct Verbosity < L : LogLevel = ErrorLevel > {
114
127
#[ arg(
115
128
long,
@@ -200,6 +213,21 @@ impl<L: LogLevel> fmt::Display for Verbosity<L> {
200
213
}
201
214
}
202
215
216
+ impl < L : LogLevel > From < Verbosity < L > > for VerbosityFilter {
217
+ fn from ( verbosity : Verbosity < L > ) -> Self {
218
+ verbosity. filter ( )
219
+ }
220
+ }
221
+
222
+ impl < L : LogLevel > From < VerbosityFilter > for Verbosity < L > {
223
+ fn from ( filter : VerbosityFilter ) -> Self {
224
+ let default = L :: default_filter ( ) ;
225
+ let verbose = filter. value ( ) . saturating_sub ( default. value ( ) ) ;
226
+ let quiet = default. value ( ) . saturating_sub ( filter. value ( ) ) ;
227
+ Verbosity :: new ( verbose, quiet)
228
+ }
229
+ }
230
+
203
231
/// Customize the default log-level and associated help
204
232
pub trait LogLevel {
205
233
/// Baseline level before applying `--verbose` and `--quiet`
@@ -230,6 +258,7 @@ pub trait LogLevel {
230
258
///
231
259
/// Used to calculate the log level and filter.
232
260
#[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
261
+ #[ cfg_attr( feature = "serde" , derive( serde:: Serialize , serde:: Deserialize ) ) ]
233
262
pub enum VerbosityFilter {
234
263
Off ,
235
264
Error ,
@@ -244,15 +273,7 @@ impl VerbosityFilter {
244
273
///
245
274
/// Negative values will decrease the verbosity, while positive values will increase it.
246
275
fn with_offset ( & self , offset : i16 ) -> VerbosityFilter {
247
- let value = match self {
248
- Self :: Off => 0_i16 ,
249
- Self :: Error => 1 ,
250
- Self :: Warn => 2 ,
251
- Self :: Info => 3 ,
252
- Self :: Debug => 4 ,
253
- Self :: Trace => 5 ,
254
- } ;
255
- match value. saturating_add ( offset) {
276
+ match i16:: from ( self . value ( ) ) . saturating_add ( offset) {
256
277
i16:: MIN ..=0 => Self :: Off ,
257
278
1 => Self :: Error ,
258
279
2 => Self :: Warn ,
@@ -261,6 +282,20 @@ impl VerbosityFilter {
261
282
5 ..=i16:: MAX => Self :: Trace ,
262
283
}
263
284
}
285
+
286
+ /// Get the numeric value of the filter.
287
+ ///
288
+ /// This is an internal representation of the filter level used only for conversion / offset.
289
+ fn value ( & self ) -> u8 {
290
+ match self {
291
+ Self :: Off => 0 ,
292
+ Self :: Error => 1 ,
293
+ Self :: Warn => 2 ,
294
+ Self :: Info => 3 ,
295
+ Self :: Debug => 4 ,
296
+ Self :: Trace => 5 ,
297
+ }
298
+ }
264
299
}
265
300
266
301
impl fmt:: Display for VerbosityFilter {
@@ -277,7 +312,7 @@ impl fmt::Display for VerbosityFilter {
277
312
}
278
313
279
314
/// Default to [`VerbosityFilter::Error`]
280
- #[ derive( Copy , Clone , Debug , Default ) ]
315
+ #[ derive( Copy , Clone , Debug , Default , PartialEq , Eq ) ]
281
316
pub struct ErrorLevel ;
282
317
283
318
impl LogLevel for ErrorLevel {
@@ -287,7 +322,7 @@ impl LogLevel for ErrorLevel {
287
322
}
288
323
289
324
/// Default to [`VerbosityFilter::Warn`]
290
- #[ derive( Copy , Clone , Debug , Default ) ]
325
+ #[ derive( Copy , Clone , Debug , Default , PartialEq , Eq ) ]
291
326
pub struct WarnLevel ;
292
327
293
328
impl LogLevel for WarnLevel {
@@ -297,7 +332,7 @@ impl LogLevel for WarnLevel {
297
332
}
298
333
299
334
/// Default to [`VerbosityFilter::Info`]
300
- #[ derive( Copy , Clone , Debug , Default ) ]
335
+ #[ derive( Copy , Clone , Debug , Default , PartialEq , Eq ) ]
301
336
pub struct InfoLevel ;
302
337
303
338
impl LogLevel for InfoLevel {
@@ -307,7 +342,7 @@ impl LogLevel for InfoLevel {
307
342
}
308
343
309
344
/// Default to [`VerbosityFilter::Debug`]
310
- #[ derive( Copy , Clone , Debug , Default ) ]
345
+ #[ derive( Copy , Clone , Debug , Default , PartialEq , Eq ) ]
311
346
pub struct DebugLevel ;
312
347
313
348
impl LogLevel for DebugLevel {
@@ -317,7 +352,7 @@ impl LogLevel for DebugLevel {
317
352
}
318
353
319
354
/// Default to [`VerbosityFilter::Trace`]
320
- #[ derive( Copy , Clone , Debug , Default ) ]
355
+ #[ derive( Copy , Clone , Debug , Default , PartialEq , Eq ) ]
321
356
pub struct TraceLevel ;
322
357
323
358
impl LogLevel for TraceLevel {
@@ -327,7 +362,7 @@ impl LogLevel for TraceLevel {
327
362
}
328
363
329
364
/// Default to [`VerbosityFilter::Off`] (no logging)
330
- #[ derive( Copy , Clone , Debug , Default ) ]
365
+ #[ derive( Copy , Clone , Debug , Default , PartialEq , Eq ) ]
331
366
pub struct OffLevel ;
332
367
333
368
impl LogLevel for OffLevel {
@@ -491,4 +526,85 @@ mod test {
491
526
assert_filter :: < TraceLevel > ( verbose, quiet, expected_filter) ;
492
527
}
493
528
}
529
+
530
+ #[ test]
531
+ fn from_verbosity_filter ( ) {
532
+ for & filter in & [
533
+ VerbosityFilter :: Off ,
534
+ VerbosityFilter :: Error ,
535
+ VerbosityFilter :: Warn ,
536
+ VerbosityFilter :: Info ,
537
+ VerbosityFilter :: Debug ,
538
+ VerbosityFilter :: Trace ,
539
+ ] {
540
+ assert_eq ! ( Verbosity :: <OffLevel >:: from( filter) . filter( ) , filter) ;
541
+ assert_eq ! ( Verbosity :: <ErrorLevel >:: from( filter) . filter( ) , filter) ;
542
+ assert_eq ! ( Verbosity :: <WarnLevel >:: from( filter) . filter( ) , filter) ;
543
+ assert_eq ! ( Verbosity :: <InfoLevel >:: from( filter) . filter( ) , filter) ;
544
+ assert_eq ! ( Verbosity :: <DebugLevel >:: from( filter) . filter( ) , filter) ;
545
+ assert_eq ! ( Verbosity :: <TraceLevel >:: from( filter) . filter( ) , filter) ;
546
+ }
547
+ }
548
+ }
549
+
550
+ #[ cfg( feature = "serde" ) ]
551
+ #[ cfg( test) ]
552
+ mod serde_tests {
553
+ use super :: * ;
554
+
555
+ use clap:: Parser ;
556
+ use serde:: { Deserialize , Serialize } ;
557
+
558
+ #[ derive( Debug , Parser , Serialize , Deserialize ) ]
559
+ struct Cli {
560
+ meaning_of_life : u8 ,
561
+ #[ command( flatten) ]
562
+ verbosity : Verbosity < InfoLevel > ,
563
+ }
564
+
565
+ #[ test]
566
+ fn serialize_toml ( ) {
567
+ let cli = Cli {
568
+ meaning_of_life : 42 ,
569
+ verbosity : Verbosity :: new ( 2 , 1 ) ,
570
+ } ;
571
+ let toml = toml:: to_string ( & cli) . unwrap ( ) ;
572
+ assert_eq ! ( toml, "meaning_of_life = 42\n verbosity = \" Debug\" \n " ) ;
573
+ }
574
+
575
+ #[ test]
576
+ fn deserialize_toml ( ) {
577
+ let toml = "meaning_of_life = 42\n verbosity = \" Debug\" \n " ;
578
+ let cli: Cli = toml:: from_str ( toml) . unwrap ( ) ;
579
+ assert_eq ! ( cli. meaning_of_life, 42 ) ;
580
+ assert_eq ! ( cli. verbosity. filter( ) , VerbosityFilter :: Debug ) ;
581
+ }
582
+
583
+ /// Tests that the `Verbosity` can be serialized and deserialized correctly from an a token.
584
+ #[ test]
585
+ fn serde_round_trips ( ) {
586
+ use serde_test:: { assert_tokens, Token } ;
587
+
588
+ for ( filter, variant) in [
589
+ ( VerbosityFilter :: Off , "Off" ) ,
590
+ ( VerbosityFilter :: Error , "Error" ) ,
591
+ ( VerbosityFilter :: Warn , "Warn" ) ,
592
+ ( VerbosityFilter :: Info , "Info" ) ,
593
+ ( VerbosityFilter :: Debug , "Debug" ) ,
594
+ ( VerbosityFilter :: Trace , "Trace" ) ,
595
+ ] {
596
+ let tokens = & [ Token :: UnitVariant {
597
+ name : "VerbosityFilter" ,
598
+ variant,
599
+ } ] ;
600
+
601
+ // `assert_tokens` checks both serialization and deserialization.
602
+ assert_tokens ( & Verbosity :: < OffLevel > :: from ( filter) , tokens) ;
603
+ assert_tokens ( & Verbosity :: < ErrorLevel > :: from ( filter) , tokens) ;
604
+ assert_tokens ( & Verbosity :: < WarnLevel > :: from ( filter) , tokens) ;
605
+ assert_tokens ( & Verbosity :: < InfoLevel > :: from ( filter) , tokens) ;
606
+ assert_tokens ( & Verbosity :: < DebugLevel > :: from ( filter) , tokens) ;
607
+ assert_tokens ( & Verbosity :: < TraceLevel > :: from ( filter) , tokens) ;
608
+ }
609
+ }
494
610
}
0 commit comments