@@ -540,15 +540,25 @@ impl BidParamOverrideEngine {
540540 ) -> Result < Self , Report < TrustedServerError > > {
541541 let mut rules = Vec :: new ( ) ;
542542
543- for ( bidder, set) in & config. bid_param_overrides {
543+ let mut bidder_overrides = config. bid_param_overrides . iter ( ) . collect :: < Vec < _ > > ( ) ;
544+ bidder_overrides. sort_by ( |( left, _) , ( right, _) | left. as_str ( ) . cmp ( right. as_str ( ) ) ) ;
545+
546+ for ( bidder, set) in bidder_overrides {
544547 rules. push ( CompiledBidParamOverrideRule :: from_bidder_override (
545548 bidder. as_str ( ) ,
546549 set,
547550 ) ?) ;
548551 }
549552
550- for ( bidder, zone_overrides) in & config. bid_param_zone_overrides {
551- for ( zone, set) in zone_overrides {
553+ let mut zone_overrides = config. bid_param_zone_overrides . iter ( ) . collect :: < Vec < _ > > ( ) ;
554+ zone_overrides. sort_by ( |( left, _) , ( right, _) | left. as_str ( ) . cmp ( right. as_str ( ) ) ) ;
555+
556+ for ( bidder, zone_override_sets) in zone_overrides {
557+ let mut sorted_zone_overrides = zone_override_sets. iter ( ) . collect :: < Vec < _ > > ( ) ;
558+ sorted_zone_overrides
559+ . sort_by ( |( left, _) , ( right, _) | left. as_str ( ) . cmp ( right. as_str ( ) ) ) ;
560+
561+ for ( zone, set) in sorted_zone_overrides {
552562 rules. push ( CompiledBidParamOverrideRule :: from_zone_override (
553563 bidder. as_str ( ) ,
554564 zone. as_str ( ) ,
@@ -585,7 +595,7 @@ impl CompiledBidParamOverrideRule {
585595 set : & serde_json:: Map < String , Json > ,
586596 ) -> Result < Self , Report < TrustedServerError > > {
587597 Self :: new (
588- Some ( bidder. to_string ( ) ) ,
598+ Some ( bidder) ,
589599 None ,
590600 set,
591601 & format ! ( "integrations.prebid.bid_param_overrides.{bidder}" ) ,
@@ -598,16 +608,16 @@ impl CompiledBidParamOverrideRule {
598608 set : & serde_json:: Map < String , Json > ,
599609 ) -> Result < Self , Report < TrustedServerError > > {
600610 Self :: new (
601- Some ( bidder. to_string ( ) ) ,
602- Some ( zone. to_string ( ) ) ,
611+ Some ( bidder) ,
612+ Some ( zone) ,
603613 set,
604614 & format ! ( "integrations.prebid.bid_param_zone_overrides.{bidder}.{zone}" ) ,
605615 )
606616 }
607617
608618 fn new (
609- bidder : Option < String > ,
610- zone : Option < String > ,
619+ bidder : Option < & str > ,
620+ zone : Option < & str > ,
611621 set : & serde_json:: Map < String , Json > ,
612622 source : & str ,
613623 ) -> Result < Self , Report < TrustedServerError > > {
@@ -653,26 +663,28 @@ impl TryFrom<BidParamOverrideRule> for CompiledBidParamOverrideRule {
653663
654664 fn try_from ( rule : BidParamOverrideRule ) -> Result < Self , Self :: Error > {
655665 Self :: new (
656- rule. when . bidder ,
657- rule. when . zone ,
666+ rule. when . bidder . as_deref ( ) ,
667+ rule. when . zone . as_deref ( ) ,
658668 & rule. set ,
659669 "integrations.prebid.bid_param_override_rules[*]" ,
660670 )
661671 }
662672}
663673
664674fn validate_override_matcher_string (
665- value : String ,
675+ value : & str ,
666676 field : & str ,
667677 source : & str ,
668678) -> Result < String , Report < TrustedServerError > > {
669- if value. trim ( ) . is_empty ( ) {
679+ let trimmed = value. trim ( ) ;
680+
681+ if trimmed. is_empty ( ) {
670682 return Err ( Report :: new ( TrustedServerError :: Configuration {
671683 message : format ! ( "{source}.{field} must not be empty" ) ,
672684 } ) ) ;
673685 }
674686
675- Ok ( value )
687+ Ok ( trimmed . to_string ( ) )
676688}
677689
678690fn non_empty_override_object (
@@ -3412,6 +3424,14 @@ set = { placementId = "explicit_header" }
34123424 Json :: Object ( map)
34133425 }
34143426
3427+ fn rule_signature ( rule : & CompiledBidParamOverrideRule ) -> String {
3428+ format ! (
3429+ "{}:{}" ,
3430+ rule. bidder. as_deref( ) . unwrap_or( "*" ) ,
3431+ rule. zone. as_deref( ) . unwrap_or( "*" )
3432+ )
3433+ }
3434+
34153435 #[ test]
34163436 fn engine_normalizes_static_compatibility_rule ( ) {
34173437 let mut config = base_config ( ) ;
@@ -3592,6 +3612,91 @@ set = { placementId = "explicit_header" }
35923612 assert_eq ! ( params[ "keep" ] , "yes" , "should preserve unrelated params" ) ;
35933613 }
35943614
3615+ #[ test]
3616+ fn compile_rule_trims_matcher_whitespace ( ) {
3617+ let rule = BidParamOverrideRule {
3618+ when : BidParamOverrideWhen {
3619+ bidder : Some ( " kargo " . to_string ( ) ) ,
3620+ zone : Some ( " header " . to_string ( ) ) ,
3621+ } ,
3622+ set : json_object ( json ! ( { "placementId" : "trimmed" } ) ) ,
3623+ } ;
3624+
3625+ let compiled = CompiledBidParamOverrideRule :: try_from ( rule)
3626+ . expect ( "should compile rule with surrounding whitespace" ) ;
3627+
3628+ assert_eq ! (
3629+ compiled. bidder. as_deref( ) ,
3630+ Some ( "kargo" ) ,
3631+ "should store trimmed bidder matcher"
3632+ ) ;
3633+ assert_eq ! (
3634+ compiled. zone. as_deref( ) ,
3635+ Some ( "header" ) ,
3636+ "should store trimmed zone matcher"
3637+ ) ;
3638+ assert ! (
3639+ compiled. matches( BidParamOverrideFacts {
3640+ bidder: "kargo" ,
3641+ zone: Some ( "header" ) ,
3642+ } ) ,
3643+ "trimmed matchers should match normalized request facts"
3644+ ) ;
3645+ }
3646+
3647+ #[ test]
3648+ fn engine_compiles_compatibility_rules_in_sorted_matcher_order ( ) {
3649+ let expected = [
3650+ "alpha:*" ,
3651+ "beta:*" ,
3652+ "gamma:*" ,
3653+ "kargo:footer" ,
3654+ "kargo:header" ,
3655+ "openx:sidebar" ,
3656+ ] ;
3657+
3658+ for iteration in 0 ..16 {
3659+ let mut config = base_config ( ) ;
3660+ config. bid_param_overrides = HashMap :: from ( [
3661+ ( "gamma" . to_string ( ) , json_object ( json ! ( { "networkId" : 3 } ) ) ) ,
3662+ ( "alpha" . to_string ( ) , json_object ( json ! ( { "networkId" : 1 } ) ) ) ,
3663+ ( "beta" . to_string ( ) , json_object ( json ! ( { "networkId" : 2 } ) ) ) ,
3664+ ] ) ;
3665+ config. bid_param_zone_overrides = HashMap :: from ( [
3666+ (
3667+ "openx" . to_string ( ) ,
3668+ HashMap :: from ( [ (
3669+ "sidebar" . to_string ( ) ,
3670+ json_object ( json ! ( { "placementId" : "openx-sidebar" } ) ) ,
3671+ ) ] ) ,
3672+ ) ,
3673+ (
3674+ "kargo" . to_string ( ) ,
3675+ HashMap :: from ( [
3676+ (
3677+ "header" . to_string ( ) ,
3678+ json_object ( json ! ( { "placementId" : "kargo-header" } ) ) ,
3679+ ) ,
3680+ (
3681+ "footer" . to_string ( ) ,
3682+ json_object ( json ! ( { "placementId" : "kargo-footer" } ) ) ,
3683+ ) ,
3684+ ] ) ,
3685+ ) ,
3686+ ] ) ;
3687+
3688+ let engine = BidParamOverrideEngine :: try_from_config ( & config)
3689+ . expect ( "should compile compatibility overrides" ) ;
3690+ let actual = engine. rules . iter ( ) . map ( rule_signature) . collect :: < Vec < _ > > ( ) ;
3691+
3692+ assert_eq ! (
3693+ actual,
3694+ expected,
3695+ "compatibility rules should compile in sorted matcher order on iteration {iteration}"
3696+ ) ;
3697+ }
3698+ }
3699+
35953700 #[ test]
35963701 fn compile_rule_rejects_empty_when ( ) {
35973702 let rule = BidParamOverrideRule {
0 commit comments