@@ -18,6 +18,7 @@ use pest::iterators::{Pair, Pairs};
1818use pest:: pratt_parser:: { Assoc , Op , PrattParser } ;
1919use pest:: Parser ;
2020use rand:: Rng ;
21+ use regex:: RegexBuilder ;
2122use semver:: Version ;
2223
2324#[ cfg( feature = "hostname" ) ]
@@ -594,6 +595,30 @@ fn string_fragment_constraint(node: Pairs<Rule>) -> CompileResult<RuleFragment>
594595 } ) )
595596}
596597
598+ fn regex_constraint ( node : Pairs < Rule > ) -> CompileResult < RuleFragment > {
599+ let [ context_getter_node, constraint_type_node, regex_pattern_node] = drain ( node) ?;
600+ let context_getter = context_value ( context_getter_node. into_inner ( ) ) ;
601+ let regex_pattern = string ( regex_pattern_node) ;
602+
603+ let mut regex_builder = RegexBuilder :: new ( & regex_pattern) ;
604+
605+ if constraint_type_node. as_str ( ) == "matches_regex_ignoring_case" {
606+ regex_builder. case_insensitive ( true ) ;
607+ }
608+
609+ let Ok ( regex) = regex_builder. build ( ) else {
610+ return Ok ( Box :: new ( move |_context : & Context | false ) ) ;
611+ } ;
612+
613+ Ok ( Box :: new ( move |context : & Context | {
614+ if let Some ( value) = context_getter ( context) {
615+ regex. is_match ( value. as_ref ( ) )
616+ } else {
617+ false
618+ }
619+ } ) )
620+ }
621+
597622fn constraint ( mut node : Pairs < Rule > ) -> CompileResult < RuleFragment > {
598623 let first = node. next ( ) ;
599624 let second = node. next ( ) ;
@@ -607,6 +632,7 @@ fn constraint(mut node: Pairs<Rule>) -> CompileResult<RuleFragment> {
607632 let constraint = match child. as_rule ( ) {
608633 Rule :: date_constraint => date_constraint ( child. into_inner ( ) ) ,
609634 Rule :: numeric_constraint => numeric_constraint ( child. into_inner ( ) ) ,
635+ Rule :: regex_constraint => regex_constraint ( child. into_inner ( ) ) ,
610636 Rule :: semver_constraint => semver_constraint ( child. into_inner ( ) ) ,
611637 Rule :: rollout_constraint => rollout_constraint ( child. into_inner ( ) ) , //TODO: Do we need to support inversion here?
612638 Rule :: default_strategy_constraint => default_strategy_constraint ( child. into_inner ( ) ) ,
@@ -1097,6 +1123,22 @@ mod tests {
10971123 assert ! ( rule( & context) ) ;
10981124 }
10991125
1126+ #[ test]
1127+ fn evaluate_regex_match ( ) {
1128+ let rule = compile_rule ( "user_id matches_regex \" ^[^@]+@[^@]+$\" " ) . unwrap ( ) ;
1129+ let context = context_from_user_id ( "test@example.com" ) ;
1130+
1131+ assert ! ( rule( & context) ) ;
1132+ }
1133+
1134+ #[ test]
1135+ fn evaluate_regex_match_ignoring_case ( ) {
1136+ let rule = compile_rule ( "user_id matches_regex_ignoring_case \" ^[^@]+@[^@]+$\" " ) . unwrap ( ) ;
1137+ let context = context_from_user_id ( "TEST@EXAMPLE.COM" ) ;
1138+
1139+ assert ! ( rule( & context) ) ;
1140+ }
1141+
11001142 #[ cfg( feature = "hostname" ) ]
11011143 mod hostname_tests {
11021144 use serial_test:: serial;
0 commit comments