1616
1717import com .google .protobuf .Descriptors ;
1818import com .google .protobuf .Message ;
19+ import com .google .re2j .Matcher ;
20+ import com .google .re2j .Pattern ;
21+ import com .google .re2j .PatternSyntaxException ;
22+ import dev .cel .common .CelOptions ;
1923import dev .cel .common .types .CelType ;
2024import dev .cel .common .types .SimpleType ;
2125import dev .cel .common .values .CelByteString ;
2832import java .util .List ;
2933import java .util .Locale ;
3034import java .util .Set ;
31- import java .util .regex . Pattern ;
35+ import java .util .concurrent . ConcurrentMap ;
3236
3337/** Defines custom function overloads (the implementation). */
3438final class CustomOverload {
@@ -39,11 +43,14 @@ final class CustomOverload {
3943 "^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\ .[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$" );
4044
4145 /**
42- * Create custom function overload list .
46+ * Create a list of custom function overloads .
4347 *
48+ * @param patternCache cache used by the {@code matches}/{@code matches_string} overrides.
49+ * @param celOptions CEL options the enclosing runtime is built with.
4450 * @return a list of overloaded functions.
4551 */
46- static List <CelFunctionBinding > create () {
52+ static List <CelFunctionBinding > create (
53+ ConcurrentMap <String , Pattern > patternCache , CelOptions celOptions ) {
4754 ArrayList <CelFunctionBinding > bindings = new ArrayList <>();
4855 bindings .addAll (
4956 Arrays .asList (
@@ -65,7 +72,9 @@ static List<CelFunctionBinding> create() {
6572 celIsNan (),
6673 celIsInfUnary (),
6774 celIsInfBinary (),
68- celIsHostAndPort ()));
75+ celIsHostAndPort (),
76+ celMatches (patternCache , celOptions ),
77+ celMatchesString (patternCache , celOptions )));
6978 bindings .addAll (celUnique ());
7079 return Collections .unmodifiableList (bindings );
7180 }
@@ -356,6 +365,41 @@ private static CelFunctionBinding celIsHostAndPort() {
356365 CustomOverload ::isHostAndPort );
357366 }
358367
368+ /** Caching replacement for CEL's global {@code matches(string, string)}. */
369+ @ SuppressWarnings ("Immutable" )
370+ private static CelFunctionBinding celMatches (
371+ ConcurrentMap <String , Pattern > patternCache , CelOptions celOptions ) {
372+ return CelFunctionBinding .from (
373+ "matches" ,
374+ String .class ,
375+ String .class ,
376+ (value , regex ) -> matches (patternCache , celOptions , value , regex ));
377+ }
378+
379+ /** Caching replacement for CEL's member-style {@code string.matches(string)}. */
380+ @ SuppressWarnings ("Immutable" )
381+ private static CelFunctionBinding celMatchesString (
382+ ConcurrentMap <String , Pattern > patternCache , CelOptions celOptions ) {
383+ return CelFunctionBinding .from (
384+ "matches_string" ,
385+ String .class ,
386+ String .class ,
387+ (value , regex ) -> matches (patternCache , celOptions , value , regex ));
388+ }
389+
390+ private static boolean matches (
391+ ConcurrentMap <String , Pattern > cache , CelOptions celOptions , String value , String regex )
392+ throws CelEvaluationException {
393+ Pattern pattern ;
394+ try {
395+ pattern = cache .computeIfAbsent (regex , Pattern ::compile );
396+ } catch (PatternSyntaxException e ) {
397+ throw new CelEvaluationException ("failed to compile regex: " + e .getMessage (), e );
398+ }
399+ Matcher matcher = pattern .matcher (value );
400+ return celOptions .enableRegexPartialMatch () ? matcher .find () : matcher .matches ();
401+ }
402+
359403 /**
360404 * Returns true if the string is a valid host/port pair, for example "example.com:8080".
361405 *
0 commit comments