@@ -2,8 +2,10 @@ use core::str;
22
33use config:: { Config , Environment , File , FileFormat } ;
44use error_stack:: { Report , ResultExt } ;
5+ use regex:: Regex ;
56use serde:: { de:: DeserializeOwned , Deserialize , Deserializer , Serialize } ;
67use serde_json:: Value as JsonValue ;
8+ use std:: sync:: OnceLock ;
79use url:: Url ;
810use validator:: { Validate , ValidationError } ;
911
@@ -96,6 +98,31 @@ impl Synthetic {
9698 }
9799}
98100
101+ #[ derive( Debug , Default , Deserialize , Serialize , Validate ) ]
102+ pub struct Handler {
103+ #[ validate( length( min = 1 ) , custom( function = validate_path) ) ]
104+ pub path : String ,
105+ #[ validate( length( min = 1 ) ) ]
106+ pub username : String ,
107+ #[ validate( length( min = 1 ) ) ]
108+ pub password : String ,
109+ #[ serde( skip, default ) ]
110+ #[ validate( skip) ]
111+ regex : OnceLock < Regex > ,
112+ }
113+
114+ impl Handler {
115+ fn compiled_regex ( & self ) -> & Regex {
116+ self . regex . get_or_init ( || {
117+ Regex :: new ( & self . path ) . expect ( "configuration validation should ensure regex compiles" )
118+ } )
119+ }
120+
121+ pub fn matches_path ( & self , path : & str ) -> bool {
122+ self . compiled_regex ( ) . is_match ( path)
123+ }
124+ }
125+
99126#[ derive( Debug , Default , Deserialize , Serialize , Validate ) ]
100127pub struct Settings {
101128 #[ validate( nested) ]
@@ -104,6 +131,9 @@ pub struct Settings {
104131 pub prebid : Prebid ,
105132 #[ validate( nested) ]
106133 pub synthetic : Synthetic ,
134+ #[ serde( default , deserialize_with = "vec_from_seq_or_map" ) ]
135+ #[ validate( nested) ]
136+ pub handlers : Vec < Handler > ,
107137}
108138
109139#[ allow( unused) ]
@@ -163,6 +193,22 @@ impl Settings {
163193 message : "Failed to deserialize configuration" . to_string ( ) ,
164194 } )
165195 }
196+
197+ #[ must_use]
198+ pub fn handler_for_path ( & self , path : & str ) -> Option < & Handler > {
199+ self . handlers
200+ . iter ( )
201+ . find ( |handler| handler. matches_path ( path) )
202+ }
203+ }
204+
205+ fn validate_path ( value : & str ) -> Result < ( ) , ValidationError > {
206+ Regex :: new ( value) . map ( |_| ( ) ) . map_err ( |err| {
207+ let mut validation_error = ValidationError :: new ( "invalid_regex" ) ;
208+ validation_error. add_param ( "value" . into ( ) , & value) ;
209+ validation_error. add_param ( "message" . into ( ) , & err. to_string ( ) ) ;
210+ validation_error
211+ } )
166212}
167213
168214// Helper: allow Vec fields to deserialize from either a JSON array or a map of numeric indices.
@@ -400,6 +446,59 @@ mod tests {
400446 ) ;
401447 }
402448
449+ #[ test]
450+ fn test_handlers_override_with_env ( ) {
451+ let toml_str = crate_test_settings_str ( ) ;
452+
453+ let origin_key = format ! (
454+ "{}{}PUBLISHER{}ORIGIN_URL" ,
455+ ENVIRONMENT_VARIABLE_PREFIX ,
456+ ENVIRONMENT_VARIABLE_SEPARATOR ,
457+ ENVIRONMENT_VARIABLE_SEPARATOR
458+ ) ;
459+ let path_key = format ! (
460+ "{}{}HANDLERS{}0{}PATH" ,
461+ ENVIRONMENT_VARIABLE_PREFIX ,
462+ ENVIRONMENT_VARIABLE_SEPARATOR ,
463+ ENVIRONMENT_VARIABLE_SEPARATOR ,
464+ ENVIRONMENT_VARIABLE_SEPARATOR
465+ ) ;
466+ let username_key = format ! (
467+ "{}{}HANDLERS{}0{}USERNAME" ,
468+ ENVIRONMENT_VARIABLE_PREFIX ,
469+ ENVIRONMENT_VARIABLE_SEPARATOR ,
470+ ENVIRONMENT_VARIABLE_SEPARATOR ,
471+ ENVIRONMENT_VARIABLE_SEPARATOR
472+ ) ;
473+ let password_key = format ! (
474+ "{}{}HANDLERS{}0{}PASSWORD" ,
475+ ENVIRONMENT_VARIABLE_PREFIX ,
476+ ENVIRONMENT_VARIABLE_SEPARATOR ,
477+ ENVIRONMENT_VARIABLE_SEPARATOR ,
478+ ENVIRONMENT_VARIABLE_SEPARATOR
479+ ) ;
480+
481+ temp_env:: with_var (
482+ origin_key,
483+ Some ( "https://origin.test-publisher.com" ) ,
484+ || {
485+ temp_env:: with_var ( path_key, Some ( "^/env-handler" ) , || {
486+ temp_env:: with_var ( username_key, Some ( "env-user" ) , || {
487+ temp_env:: with_var ( password_key, Some ( "env-pass" ) , || {
488+ let settings = Settings :: from_toml ( & toml_str)
489+ . expect ( "Settings should load from env" ) ;
490+ assert_eq ! ( settings. handlers. len( ) , 1 ) ;
491+ let handler = & settings. handlers [ 0 ] ;
492+ assert_eq ! ( handler. path, "^/env-handler" ) ;
493+ assert_eq ! ( handler. username, "env-user" ) ;
494+ assert_eq ! ( handler. password, "env-pass" ) ;
495+ } ) ;
496+ } ) ;
497+ } ) ;
498+ } ,
499+ ) ;
500+ }
501+
403502 #[ test]
404503 fn test_settings_extra_fields ( ) {
405504 let toml_str = crate_test_settings_str ( ) + "\n hello = 1" ;
0 commit comments