1+ use anyhow:: Context ;
12use url:: Url ;
23use uuid:: Uuid ;
34
@@ -45,6 +46,9 @@ pub const CALLBACK_INTERVAL: &str = callback_interval!();
4546/* Default interval value in seconds */
4647const DEFAULT_INTERVAL_SECONDS : u64 = 5 ;
4748
49+ /* Default extra config value */
50+ const DEFAULT_EXTRA_CONFIG : & str = "" ;
51+
4852macro_rules! retry_interval {
4953 ( ) => {
5054 match option_env!( "IMIX_RETRY_INTERVAL" ) {
@@ -103,99 +107,65 @@ fn get_transport_type(uri: &str) -> crate::c2::transport::Type {
103107 *
104108 * Example: https://example.com?interval=10&extra={"key":"value"}
105109 */
106- fn parse_transports (
107- uri_string : & str ,
108- default_callback_interval : u64 ,
109- default_extra_config : String ,
110- ) -> Vec < Transport > {
110+ fn parse_transports ( uri_string : & str ) -> Vec < Transport > {
111111 uri_string
112112 . split ( ';' )
113113 . filter ( |s| !s. trim ( ) . is_empty ( ) )
114- . map ( |uri| {
114+ . filter_map ( |uri| {
115115 let uri_trimmed = uri. trim ( ) ;
116-
117- // Parse the URI to extract query parameters
118- let ( base_uri, interval, extra) = parse_dsn (
119- uri_trimmed,
120- default_callback_interval,
121- & default_extra_config,
122- ) ;
123-
124- Transport {
125- uri : base_uri,
126- interval,
127- r#type : get_transport_type ( uri_trimmed) as i32 ,
128- extra,
129- }
116+ parse_dsn ( uri_trimmed) . ok ( )
130117 } )
131118 . collect ( )
132119}
133120
134121/*
135122 * Helper function to parse DSN query parameters
136- * Returns (base_uri, interval, extra_config)
123+ * Returns a Transport struct
137124 */
138- fn parse_dsn ( uri : & str , default_interval : u64 , default_extra : & str ) -> ( String , u64 , String ) {
139- // Try to parse as a URL to extract query parameters
140- match Url :: parse ( uri) {
141- Ok ( parsed_url) => {
142- let mut interval = default_interval;
143- let mut extra = default_extra. to_string ( ) ;
144-
145- // Parse query parameters
146- for ( key, value) in parsed_url. query_pairs ( ) {
147- match key. as_ref ( ) {
148- "interval" => {
149- if let Ok ( parsed_interval) = value. parse :: < u64 > ( ) {
150- interval = parsed_interval;
151- } else {
152- #[ cfg( debug_assertions) ]
153- log:: warn!(
154- "Failed to parse interval parameter '{}', using default" ,
155- value
156- ) ;
157- }
158- }
159- "extra" => {
160- extra = value. to_lowercase ( ) ;
161- }
162- _ => {
163- #[ cfg( debug_assertions) ]
164- log:: debug!( "Ignoring unknown query parameter: {}" , key) ;
165- }
166- }
125+ fn parse_dsn ( uri : & str ) -> anyhow:: Result < Transport > {
126+ // Parse as a URL to extract query parameters
127+ let parsed_url = Url :: parse ( uri)
128+ . with_context ( || format ! ( "Failed to parse URI '{}'" , uri) ) ?;
129+
130+ let mut interval = DEFAULT_INTERVAL_SECONDS ;
131+ let mut extra = DEFAULT_EXTRA_CONFIG . to_string ( ) ;
132+
133+ // Parse query parameters
134+ for ( key, value) in parsed_url. query_pairs ( ) {
135+ match key. as_ref ( ) {
136+ "interval" => {
137+ interval = value. parse :: < u64 > ( )
138+ . with_context ( || format ! ( "Failed to parse interval parameter '{}'" , value) ) ?;
139+ }
140+ "extra" => {
141+ extra = value. to_lowercase ( ) ;
142+ }
143+ _ => {
144+ #[ cfg( debug_assertions) ]
145+ log:: debug!( "Ignoring unknown query parameter: {}" , key) ;
167146 }
168-
169- // Reconstruct the base URI without query parameters
170- let mut base_uri = parsed_url. clone ( ) ;
171- base_uri. set_query ( None ) ;
172- ( base_uri. to_string ( ) , interval, extra)
173- }
174- Err ( _) => {
175- // If URL parsing fails, return the original URI with defaults
176- #[ cfg( debug_assertions) ]
177- log:: warn!( "Failed to parse URI '{}', using defaults" , uri) ;
178- ( uri. to_string ( ) , default_interval, default_extra. to_string ( ) )
179147 }
180148 }
149+
150+ // Reconstruct the base URI without query parameters
151+ let mut base_uri = parsed_url. clone ( ) ;
152+ base_uri. set_query ( None ) ;
153+
154+ Ok ( Transport {
155+ uri : base_uri. to_string ( ) ,
156+ interval,
157+ r#type : get_transport_type ( uri) as i32 ,
158+ extra,
159+ } )
181160}
182161
183162/*
184163 * Helper function to parse callback interval with fallback
185164 */
186- fn parse_callback_interval ( ) -> u64 {
187- match CALLBACK_INTERVAL . parse :: < u64 > ( ) {
188- Ok ( i) => i,
189- Err ( _err) => {
190- #[ cfg( debug_assertions) ]
191- log:: error!(
192- "failed to parse callback interval constant, defaulting to {} seconds: {_err}" ,
193- DEFAULT_INTERVAL_SECONDS
194- ) ;
195-
196- DEFAULT_INTERVAL_SECONDS
197- }
198- }
165+ fn parse_callback_interval ( ) -> anyhow:: Result < u64 > {
166+ CALLBACK_INTERVAL
167+ . parse :: < u64 > ( )
168+ . with_context ( || format ! ( "Failed to parse callback interval constant '{}'" , CALLBACK_INTERVAL ) )
199169}
200170
201171/*
@@ -221,9 +191,7 @@ impl Config {
221191 std:: env:: var ( "IMIX_BEACON_ID" ) . unwrap_or_else ( |_| String :: from ( Uuid :: new_v4 ( ) ) ) ;
222192
223193 // Parse CALLBACK_URI by splitting on ';' to support multiple transports
224- let callback_interval = parse_callback_interval ( ) ;
225- let extra_config = extra ! ( ) ;
226- let transports = parse_transports ( CALLBACK_URI , callback_interval, extra_config) ;
194+ let transports = parse_transports ( CALLBACK_URI ) ;
227195
228196 // Create AvailableTransports with the 0th element as the first active transport
229197 let available_transports = AvailableTransports {
@@ -398,7 +366,7 @@ mod tests {
398366 fn test_empty_uri_filtered ( ) {
399367 // Test that empty URIs are filtered out using parse_transports
400368 let uris = "http://example.com;;https://example2.com" ;
401- let transports = parse_transports ( uris, 5 , String :: new ( ) ) ;
369+ let transports = parse_transports ( uris) ;
402370
403371 assert_eq ! ( transports. len( ) , 2 ) ;
404372 assert_eq ! ( transports[ 0 ] . uri, "http://example.com/" ) ;
@@ -409,7 +377,7 @@ mod tests {
409377 fn test_dsn_with_interval_query_param ( ) {
410378 // Test DSN parsing with interval query parameter
411379 let uris = "https://example.com?interval=10" ;
412- let transports = parse_transports ( uris, 5 , String :: new ( ) ) ;
380+ let transports = parse_transports ( uris) ;
413381
414382 assert_eq ! ( transports. len( ) , 1 ) ;
415383 assert_eq ! ( transports[ 0 ] . uri, "https://example.com/" ) ;
@@ -421,19 +389,19 @@ mod tests {
421389 fn test_dsn_with_extra_query_param ( ) {
422390 // Test DSN parsing with extra query parameter (converted to lowercase)
423391 let uris = "https://example.com?extra=%7B%22key%22%3A%22value%22%7D" ;
424- let transports = parse_transports ( uris, 5 , String :: new ( ) ) ;
392+ let transports = parse_transports ( uris) ;
425393
426394 assert_eq ! ( transports. len( ) , 1 ) ;
427395 assert_eq ! ( transports[ 0 ] . uri, "https://example.com/" ) ;
428- assert_eq ! ( transports[ 0 ] . interval, 5 ) ;
396+ assert_eq ! ( transports[ 0 ] . interval, DEFAULT_INTERVAL_SECONDS ) ;
429397 assert_eq ! ( transports[ 0 ] . extra, r#"{"key":"value"}"# ) ;
430398 }
431399
432400 #[ test]
433401 fn test_dsn_with_both_query_params ( ) {
434402 // Test DSN parsing with both interval and extra query parameters (extra converted to lowercase)
435403 let uris = "https://example.com?interval=15&extra=%7B%22proxy%22%3A%22http%3A%2F%2Fproxy.local%22%7D" ;
436- let transports = parse_transports ( uris, 5 , String :: new ( ) ) ;
404+ let transports = parse_transports ( uris) ;
437405
438406 assert_eq ! ( transports. len( ) , 1 ) ;
439407 assert_eq ! ( transports[ 0 ] . uri, "https://example.com/" ) ;
@@ -445,7 +413,7 @@ mod tests {
445413 fn test_dsn_multiple_uris_with_different_params ( ) {
446414 // Test multiple DSNs with different parameters
447415 let uris = "https://primary.com?interval=10;https://fallback.com?interval=30" ;
448- let transports = parse_transports ( uris, 5 , String :: new ( ) ) ;
416+ let transports = parse_transports ( uris) ;
449417
450418 assert_eq ! ( transports. len( ) , 2 ) ;
451419 assert_eq ! ( transports[ 0 ] . uri, "https://primary.com/" ) ;
@@ -458,35 +426,34 @@ mod tests {
458426 fn test_dsn_no_query_params_uses_defaults ( ) {
459427 // Test that URIs without query parameters use default values
460428 let uris = "https://example.com" ;
461- let default_extra = r#"{"default":"config"}"# . to_string ( ) ;
462- let transports = parse_transports ( uris, 20 , default_extra. clone ( ) ) ;
429+ let transports = parse_transports ( uris) ;
463430
464431 assert_eq ! ( transports. len( ) , 1 ) ;
465432 assert_eq ! ( transports[ 0 ] . uri, "https://example.com/" ) ;
466- assert_eq ! ( transports[ 0 ] . interval, 20 ) ;
467- assert_eq ! ( transports[ 0 ] . extra, default_extra ) ;
433+ assert_eq ! ( transports[ 0 ] . interval, DEFAULT_INTERVAL_SECONDS ) ;
434+ assert_eq ! ( transports[ 0 ] . extra, DEFAULT_EXTRA_CONFIG ) ;
468435 }
469436
470437 #[ test]
471438 fn test_dsn_invalid_interval_uses_default ( ) {
472- // Test that invalid interval values fall back to default
439+ // Test that invalid interval values are filtered out (error bubbles up)
473440 let uris = "https://example.com?interval=invalid" ;
474- let transports = parse_transports ( uris, 7 , String :: new ( ) ) ;
441+ let transports = parse_transports ( uris) ;
475442
476- assert_eq ! ( transports . len ( ) , 1 ) ;
477- assert_eq ! ( transports [ 0 ] . uri , "https://example.com/" ) ;
478- assert_eq ! ( transports[ 0 ] . interval , 7 ) ; // Should use default
443+ // Since parse_dsn now returns Result and invalid intervals bubble up errors,
444+ // the filter_map will filter out this entry
445+ assert_eq ! ( transports. len ( ) , 0 ) ;
479446 }
480447
481448 #[ test]
482449 fn test_dsn_mixed_with_and_without_params ( ) {
483450 // Test mixed URIs (some with params, some without)
484451 let uris = "https://first.com?interval=10;https://second.com;https://third.com?interval=25" ;
485- let transports = parse_transports ( uris, 5 , String :: new ( ) ) ;
452+ let transports = parse_transports ( uris) ;
486453
487454 assert_eq ! ( transports. len( ) , 3 ) ;
488455 assert_eq ! ( transports[ 0 ] . interval, 10 ) ;
489- assert_eq ! ( transports[ 1 ] . interval, 5 ) ; // Uses default
456+ assert_eq ! ( transports[ 1 ] . interval, DEFAULT_INTERVAL_SECONDS ) ; // Uses default
490457 assert_eq ! ( transports[ 2 ] . interval, 25 ) ;
491458 }
492459
@@ -496,7 +463,7 @@ mod tests {
496463 // The url crate should handle the parsing automatically
497464 let uris =
498465 r#"https://example.com?interval=20&extra={"key":"value","nested":{"Foo":"Bar"}}"# ;
499- let transports = parse_transports ( uris, 5 , String :: new ( ) ) ;
466+ let transports = parse_transports ( uris) ;
500467
501468 assert_eq ! ( transports. len( ) , 1 ) ;
502469 assert_eq ! ( transports[ 0 ] . uri, "https://example.com/" ) ;
0 commit comments