This is an automated email from the ASF dual-hosted git repository.

hsluoyz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/casbin-jcasbin.git

commit 3258a4e4aa3169823057ccc25acec52c32831e6a
Author: AlabLan <[email protected]>
AuthorDate: Tue Jun 16 22:54:40 2026 +0800

    feat: add keyGet3 function (#522)
---
 .../java/org/casbin/jcasbin/model/FunctionMap.java |  1 +
 .../org/casbin/jcasbin/util/BuiltInFunctions.java  | 35 ++++++++++++++++
 .../casbin/jcasbin/util/function/KeyGet3Func.java  | 47 ++++++++++++++++++++++
 .../jcasbin/main/BuiltInFunctionsUnitTest.java     | 40 ++++++++++++++++++
 .../java/org/casbin/jcasbin/main/TestUtil.java     |  4 ++
 5 files changed, 127 insertions(+)

diff --git a/src/main/java/org/casbin/jcasbin/model/FunctionMap.java 
b/src/main/java/org/casbin/jcasbin/model/FunctionMap.java
index 7be2407..82a4464 100644
--- a/src/main/java/org/casbin/jcasbin/model/FunctionMap.java
+++ b/src/main/java/org/casbin/jcasbin/model/FunctionMap.java
@@ -84,6 +84,7 @@ public class FunctionMap {
         fm.addFunction("keyMatch5", new KeyMatch5Func());
         fm.addFunction("keyGet", new KeyGetFunc());
         fm.addFunction("keyGet2", new KeyGet2Func());
+        fm.addFunction("keyGet3", new KeyGet3Func());
         fm.addFunction("regexMatch", new RegexMatchFunc());
         fm.addFunction("ipMatch", new IPMatchFunc());
         fm.addFunction("eval", new EvalFunc());
diff --git a/src/main/java/org/casbin/jcasbin/util/BuiltInFunctions.java 
b/src/main/java/org/casbin/jcasbin/util/BuiltInFunctions.java
index e192eff..168d5f7 100644
--- a/src/main/java/org/casbin/jcasbin/util/BuiltInFunctions.java
+++ b/src/main/java/org/casbin/jcasbin/util/BuiltInFunctions.java
@@ -275,6 +275,41 @@ public class BuiltInFunctions {
         return "";
     }
 
+    /**
+     * KeyGet3 returns value matched pattern. For example, "/resource1" 
matches "/{resource}", if the pathVar == "resource", then "resource1" will be 
returned.
+     * This is similar with KeyGet2(), except using "/proxy/{id}" instead of 
"/proxy/:id".
+     *
+     * @param key1    the first argument.
+     * @param key2    the second argument.
+     * @param pathVar the name of the variable to retrieve from the matched 
pattern.
+     * @return the matched part.
+     */
+    public static String keyGet3Func(String key1, String key2, String pathVar) 
{
+        key2 = key2.replace("/*", "/.*");
+        String regexp = "\\{[^/]+?\\}";
+        Pattern re = Pattern.compile(regexp);
+        Matcher keys = re.matcher(key2);
+        List<String> keysList = new ArrayList<>();
+        while (keys.find()) {
+            keysList.add(keys.group());
+        }
+        key2 = re.matcher(key2).replaceAll("([^/]+?)");
+        // Escape { and } for Java regex compatibility since { is treated as 
repetition quantifier
+        key2 = key2.replace("{", "\\{").replace("}", "\\}");
+        key2 = "^" + key2 + "$";
+        Pattern re2 = Pattern.compile(key2);
+        Matcher values = re2.matcher(key1);
+        if (!values.find()) {
+            return "";
+        }
+        for (int i = 0; i < keysList.size(); i++) {
+            if (pathVar.equals(keysList.get(i).substring(1, 
keysList.get(i).length() - 1))) {
+                return values.group(i + 1);
+            }
+        }
+        return "";
+    }
+
     /**
      * regexMatch determines whether key1 matches the pattern of key2 in 
regular expression.
      *
diff --git a/src/main/java/org/casbin/jcasbin/util/function/KeyGet3Func.java 
b/src/main/java/org/casbin/jcasbin/util/function/KeyGet3Func.java
new file mode 100644
index 0000000..a0b63fc
--- /dev/null
+++ b/src/main/java/org/casbin/jcasbin/util/function/KeyGet3Func.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2026 The casbin Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.casbin.jcasbin.util.function;
+
+import com.googlecode.aviator.runtime.function.AbstractFunction;
+import com.googlecode.aviator.runtime.function.FunctionUtils;
+import com.googlecode.aviator.runtime.type.AviatorObject;
+import com.googlecode.aviator.runtime.type.AviatorString;
+import org.casbin.jcasbin.util.BuiltInFunctions;
+
+import java.util.Map;
+
+/**
+ * KeyGet3Func is the wrapper for keyGet3.
+ *
+ * @author yanglif
+ * @since 2021/02/09
+ */
+public class KeyGet3Func extends AbstractFunction {
+    @Override
+    public AviatorObject call(Map<String, Object> env, AviatorObject arg1, 
AviatorObject arg2, AviatorObject arg3) {
+        String key1 = FunctionUtils.getStringValue(arg1, env);
+        String key2 = FunctionUtils.getStringValue(arg2, env);
+        String pathVar = FunctionUtils.getStringValue(arg3, env);
+
+        return new AviatorString(BuiltInFunctions.keyGet3Func(key1, key2, 
pathVar));
+    }
+
+    @Override
+    public String getName() {
+        return "keyGet3";
+    }
+}
diff --git 
a/src/test/java/org/casbin/jcasbin/main/BuiltInFunctionsUnitTest.java 
b/src/test/java/org/casbin/jcasbin/main/BuiltInFunctionsUnitTest.java
index 462c208..7f86e83 100644
--- a/src/test/java/org/casbin/jcasbin/main/BuiltInFunctionsUnitTest.java
+++ b/src/test/java/org/casbin/jcasbin/main/BuiltInFunctionsUnitTest.java
@@ -214,6 +214,46 @@ public class BuiltInFunctionsUnitTest {
         testKeyGet2("/alice/all", "/:/all", "", "");
     }
 
+    @Test
+    public void TestKeyGet3() {
+        // KeyGet3() is similar with KeyGet2(), except using "/proxy/{id}" 
instead of "/proxy/:id".
+        testKeyGet3("/foo", "/foo", "id", "");
+        testKeyGet3("/foo", "/foo*", "id", "");
+        testKeyGet3("/foo", "/foo/*", "id", "");
+        testKeyGet3("/foo/bar", "/foo", "id", "");
+        testKeyGet3("/foo/bar", "/foo*", "id", "");
+        testKeyGet3("/foo/bar", "/foo/*", "id", "");
+        testKeyGet3("/foobar", "/foo", "id", "");
+        testKeyGet3("/foobar", "/foo*", "id", "");
+        testKeyGet3("/foobar", "/foo/*", "id", "");
+
+        testKeyGet3("/", "/{resource}", "resource", "");
+        testKeyGet3("/resource1", "/{resource}", "resource", "resource1");
+        testKeyGet3("/myid", "/{id}/using/{resId}", "id", "");
+        testKeyGet3("/myid/using/myresid", "/{id}/using/{resId}", "id", 
"myid");
+        testKeyGet3("/myid/using/myresid", "/{id}/using/{resId}", "resId", 
"myresid");
+
+        testKeyGet3("/proxy/myid", "/proxy/{id}/*", "id", "");
+        testKeyGet3("/proxy/myid/", "/proxy/{id}/*", "id", "myid");
+        testKeyGet3("/proxy/myid/res", "/proxy/{id}/*", "id", "myid");
+        testKeyGet3("/proxy/myid/res/res2", "/proxy/{id}/*", "id", "myid");
+        testKeyGet3("/proxy/myid/res/res2/res3", "/proxy/{id}/*", "id", 
"myid");
+        testKeyGet3("/proxy/", "/proxy/{id}/*", "id", "");
+
+        testKeyGet3("/api/group1_group_name/project1_admin/info", 
"/api/{proj}_admin/info", "proj", "");
+        testKeyGet3("/{id/using/myresid", "/{id/using/{resId}", "resId", 
"myresid");
+        testKeyGet3("/{id/using/myresid/status}", 
"/{id/using/{resId}/status}", "resId", "myresid");
+
+        testKeyGet3("/proxy/myid/res/res2/res3", "/proxy/{id}/*/{res}", "res", 
"res3");
+        testKeyGet3("/api/project1_admin/info", "/api/{proj}_admin/info", 
"proj", "project1");
+        testKeyGet3("/api/group1_group_name/project1_admin/info", 
"/api/{g}_{gn}/{proj}_admin/info",
+            "g", "group1");
+        testKeyGet3("/api/group1_group_name/project1_admin/info", 
"/api/{g}_{gn}/{proj}_admin/info",
+            "gn", "group_name");
+        testKeyGet3("/api/group1_group_name/project1_admin/info", 
"/api/{g}_{gn}/{proj}_admin/info",
+            "proj", "project1");
+    }
+
     @Test
     public void testRegexMatchFunc() {
         testRegexMatch("/topic/create", "/topic/create", true);
diff --git a/src/test/java/org/casbin/jcasbin/main/TestUtil.java 
b/src/test/java/org/casbin/jcasbin/main/TestUtil.java
index c7684d2..e86d1be 100644
--- a/src/test/java/org/casbin/jcasbin/main/TestUtil.java
+++ b/src/test/java/org/casbin/jcasbin/main/TestUtil.java
@@ -273,6 +273,10 @@ public class TestUtil {
         assertEquals(res, BuiltInFunctions.keyGet2Func(key1, key2, pathVar));
     }
 
+    static void testKeyGet3(String key1, String key2, String pathVar, String 
res) {
+        assertEquals(res, BuiltInFunctions.keyGet3Func(key1, key2, pathVar));
+    }
+
     static void testEval(String eval, Map<String, Object> env, 
AviatorEvaluatorInstance aviatorEval, boolean res) {
         assertEquals(res, BuiltInFunctions.eval(eval, env, aviatorEval));
     }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to