@@ -189,43 +189,42 @@ pub const fn map_log_level_to_filter(log_level: LogLevel) -> LevelFilter {
189189
190190/// Parses a string into a [`LevelFilter`].
191191///
192- /// # Panics
192+ /// # Errors
193193///
194- /// Panics if the provided string is not a valid `LevelFilter`.
195- #[ must_use]
196- pub fn parse_level_filter_str ( s : & str ) -> LevelFilter {
194+ /// Returns an error if the provided string is not a valid `LevelFilter`.
195+ pub fn parse_level_filter_str ( s : & str ) -> anyhow:: Result < LevelFilter > {
197196 let mut log_level_str = s. to_string ( ) . to_uppercase ( ) ;
198197 if log_level_str == "WARNING" {
199198 log_level_str = "WARN" . to_string ( ) ;
200199 }
201200 LevelFilter :: from_str ( & log_level_str)
202- . unwrap_or_else ( |_| panic ! ( "Invalid `LevelFilter` string, was {log_level_str} " ) )
201+ . map_err ( |_| anyhow :: anyhow !( "Invalid log level string: '{s}' " ) )
203202}
204203
205- #[ must_use]
206204/// Parses component-specific log levels from a JSON value map.
207205///
208- /// # Panics
206+ /// # Errors
209207///
210- /// Panics if a JSON value in the map is not a string representing a log level.
208+ /// Returns an error if a JSON value in the map is not a string or is not a valid log level.
211209pub fn parse_component_levels (
212210 original_map : Option < HashMap < String , serde_json:: Value > > ,
213- ) -> HashMap < Ustr , LevelFilter > {
211+ ) -> anyhow :: Result < HashMap < Ustr , LevelFilter > > {
214212 match original_map {
215213 Some ( map) => {
216214 let mut new_map = HashMap :: new ( ) ;
217215 for ( key, value) in map {
218216 let ustr_key = Ustr :: from ( & key) ;
219- // Expect the JSON value to be a string representing a log level
220- let s = value
221- . as_str ( )
222- . expect ( "Invalid component log level: expected string" ) ;
223- let lvl = parse_level_filter_str ( s) ;
217+ let s = value. as_str ( ) . ok_or_else ( || {
218+ anyhow:: anyhow!(
219+ "Component log level for '{key}' must be a string, got: {value}"
220+ )
221+ } ) ?;
222+ let lvl = parse_level_filter_str ( s) ?;
224223 new_map. insert ( ustr_key, lvl) ;
225224 }
226- new_map
225+ Ok ( new_map)
227226 }
228- None => HashMap :: new ( ) ,
227+ None => Ok ( HashMap :: new ( ) ) ,
229228 }
230229}
231230
@@ -253,3 +252,111 @@ pub fn log_task_aborted(task_name: &str) {
253252pub fn log_task_error ( task_name : & str , e : & anyhow:: Error ) {
254253 tracing:: error!( "Error in task '{task_name}': {e}" ) ;
255254}
255+
256+ #[ cfg( test) ]
257+ mod tests {
258+ use rstest:: rstest;
259+
260+ use super :: * ;
261+
262+ #[ rstest]
263+ #[ case( "DEBUG" , LevelFilter :: Debug ) ]
264+ #[ case( "debug" , LevelFilter :: Debug ) ]
265+ #[ case( "Debug" , LevelFilter :: Debug ) ]
266+ #[ case( "DeBuG" , LevelFilter :: Debug ) ]
267+ #[ case( "INFO" , LevelFilter :: Info ) ]
268+ #[ case( "info" , LevelFilter :: Info ) ]
269+ #[ case( "WARNING" , LevelFilter :: Warn ) ]
270+ #[ case( "warning" , LevelFilter :: Warn ) ]
271+ #[ case( "WARN" , LevelFilter :: Warn ) ]
272+ #[ case( "warn" , LevelFilter :: Warn ) ]
273+ #[ case( "ERROR" , LevelFilter :: Error ) ]
274+ #[ case( "error" , LevelFilter :: Error ) ]
275+ #[ case( "OFF" , LevelFilter :: Off ) ]
276+ #[ case( "off" , LevelFilter :: Off ) ]
277+ #[ case( "TRACE" , LevelFilter :: Trace ) ]
278+ #[ case( "trace" , LevelFilter :: Trace ) ]
279+ fn test_parse_level_filter_str_case_insensitive (
280+ #[ case] input : & str ,
281+ #[ case] expected : LevelFilter ,
282+ ) {
283+ let result = parse_level_filter_str ( input) . unwrap ( ) ;
284+ assert_eq ! ( result, expected) ;
285+ }
286+
287+ #[ rstest]
288+ #[ case( "INVALID" ) ]
289+ #[ case( "DEBG" ) ]
290+ #[ case( "WARNINGG" ) ]
291+ #[ case( "" ) ]
292+ #[ case( "INFO123" ) ]
293+ fn test_parse_level_filter_str_invalid_returns_error ( #[ case] invalid_input : & str ) {
294+ let result = parse_level_filter_str ( invalid_input) ;
295+
296+ assert ! ( result. is_err( ) ) ;
297+ assert ! (
298+ result
299+ . unwrap_err( )
300+ . to_string( )
301+ . contains( "Invalid log level" )
302+ ) ;
303+ }
304+
305+ #[ rstest]
306+ fn test_parse_component_levels_valid ( ) {
307+ let mut map = HashMap :: new ( ) ;
308+ map. insert (
309+ "Strategy1" . to_string ( ) ,
310+ serde_json:: Value :: String ( "DEBUG" . to_string ( ) ) ,
311+ ) ;
312+ map. insert (
313+ "Strategy2" . to_string ( ) ,
314+ serde_json:: Value :: String ( "info" . to_string ( ) ) ,
315+ ) ;
316+
317+ let result = parse_component_levels ( Some ( map) ) . unwrap ( ) ;
318+
319+ assert_eq ! ( result. len( ) , 2 ) ;
320+ assert_eq ! ( result[ & Ustr :: from( "Strategy1" ) ] , LevelFilter :: Debug ) ;
321+ assert_eq ! ( result[ & Ustr :: from( "Strategy2" ) ] , LevelFilter :: Info ) ;
322+ }
323+
324+ #[ rstest]
325+ fn test_parse_component_levels_non_string_value_returns_error ( ) {
326+ let mut map = HashMap :: new ( ) ;
327+ map. insert (
328+ "Strategy1" . to_string ( ) ,
329+ serde_json:: Value :: Number ( 123 . into ( ) ) ,
330+ ) ;
331+
332+ let result = parse_component_levels ( Some ( map) ) ;
333+
334+ assert ! ( result. is_err( ) ) ;
335+ assert ! ( result. unwrap_err( ) . to_string( ) . contains( "must be a string" ) ) ;
336+ }
337+
338+ #[ rstest]
339+ fn test_parse_component_levels_invalid_level_returns_error ( ) {
340+ let mut map = HashMap :: new ( ) ;
341+ map. insert (
342+ "Strategy1" . to_string ( ) ,
343+ serde_json:: Value :: String ( "INVALID_LEVEL" . to_string ( ) ) ,
344+ ) ;
345+
346+ let result = parse_component_levels ( Some ( map) ) ;
347+
348+ assert ! ( result. is_err( ) ) ;
349+ assert ! (
350+ result
351+ . unwrap_err( )
352+ . to_string( )
353+ . contains( "Invalid log level" )
354+ ) ;
355+ }
356+
357+ #[ rstest]
358+ fn test_parse_component_levels_none_returns_empty ( ) {
359+ let result = parse_component_levels ( None ) . unwrap ( ) ;
360+ assert_eq ! ( result. len( ) , 0 ) ;
361+ }
362+ }
0 commit comments