{"id":19840734,"url":"https://github.com/projectnessie/cel-java","last_synced_at":"2025-04-08T08:11:37.292Z","repository":{"id":37399234,"uuid":"372440718","full_name":"projectnessie/cel-java","owner":"projectnessie","description":"Common Expression Language for Java","archived":false,"fork":false,"pushed_at":"2025-03-31T23:54:35.000Z","size":1494,"stargazers_count":89,"open_issues_count":12,"forks_count":19,"subscribers_count":9,"default_branch":"main","last_synced_at":"2025-04-01T04:54:07.221Z","etag":null,"topics":["java"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/projectnessie.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-05-31T08:47:03.000Z","updated_at":"2025-03-31T23:54:39.000Z","dependencies_parsed_at":"2023-10-27T19:37:29.666Z","dependency_job_id":"8242fcad-b7c8-43bc-a121-0d2fb0912846","html_url":"https://github.com/projectnessie/cel-java","commit_stats":{"total_commits":608,"total_committers":16,"mean_commits":38.0,"dds":0.5986842105263157,"last_synced_commit":"8e5e1bbf16467be03325b98f0eaa805aaa364eff"},"previous_names":[],"tags_count":39,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/projectnessie%2Fcel-java","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/projectnessie%2Fcel-java/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/projectnessie%2Fcel-java/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/projectnessie%2Fcel-java/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/projectnessie","download_url":"https://codeload.github.com/projectnessie/cel-java/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247801170,"owners_count":20998339,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["java"],"created_at":"2024-11-12T12:27:59.576Z","updated_at":"2025-04-08T08:11:37.255Z","avatar_url":"https://github.com/projectnessie.png","language":"Java","readme":"# Java implementation of Common-Expression-Language (CEL)\n\n[![CI](https://github.com/projectnessie/cel-java/actions/workflows/main.yml/badge.svg)](https://github.com/projectnessie/cel-java/actions/workflows/main.yml)\n[![Maven Central](https://img.shields.io/maven-central/v/org.projectnessie.cel/cel-core)](https://search.maven.org/artifact/org.projectnessie.cel/cel-core)\n\nThis is a Java port of the [Common-Expression-Language (CEL)](https://opensource.google/projects/cel).\n\nThe CEL specification can be found [here](https://github.com/google/cel-spec).\n\n## Getting started\n\nThe easiest way to get started is to add a dependency to your Maven project\n```xml\n\u003cdependencyManagement\u003e\n  \u003cdependencies\u003e\n    \u003cdependency\u003e\n      \u003cgroupId\u003eorg.projectnessie.cel\u003c/groupId\u003e\n      \u003cartifactId\u003ecel-bom\u003c/artifactId\u003e\n      \u003cversion\u003e0.5.1\u003c/version\u003e\n      \u003ctype\u003epom\u003c/type\u003e\n      \u003cscope\u003eimport\u003c/scope\u003e\n    \u003c/dependency\u003e\n  \u003c/dependencies\u003e\n\u003c/dependencyManagement\u003e\n\n\u003cdependencies\u003e\n  \u003cdependency\u003e\n    \u003cgroupId\u003eorg.projectnessie.cel\u003c/groupId\u003e\n    \u003cartifactId\u003ecel-tools\u003c/artifactId\u003e\n  \u003c/dependency\u003e\n\u003c/dependencies\u003e\n```\nor Gradle project.\n```groovy\ndependencies {\n  implementation(enforcedPlatform(\"org.projectnessie.cel:cel-bom:0.5.1\"))\n  implementation(\"org.projectnessie.cel:cel-tools\")\n}\n```\n\n(Note: `cel-bom` is available for CEL-Java version 0.3.0 and newer.)\n\nThe `cel-tools` artifact provides a simple entry point `ScriptHost` to produce `Script` instances.\nA very simple start:\n\n```java\nimport com.google.api.expr.v1alpha1.Decl;\nimport java.util.HashMap;\nimport java.util.Map;\nimport org.projectnessie.cel.checker.Decls;\nimport org.projectnessie.cel.tools.Script;\nimport org.projectnessie.cel.tools.ScriptHost;\n\npublic class MyClass {\n  public void myScriptUsage() {\n    // Build the script factory\n    ScriptHost scriptHost = ScriptHost.newBuilder().build();\n\n    // create the script, will be parsed and checked\n    Script script = scriptHost.buildScript(\"x + ' ' + y\")\n        .withDeclarations(\n            // Variable declarations - we need `x` and `y` in this example\n            Decls.newVar(\"x\", Decls.String),\n            Decls.newVar(\"y\", Decls.String))\n        .build();\n\n    Map\u003cString, Object\u003e arguments = new HashMap\u003c\u003e();\n    arguments.put(\"x\", \"hello\");\n    arguments.put(\"y\", \"world\");\n\n    String result = script.execute(String.class, arguments);\n\n    System.out.println(result); // Prints \"hello world\"\n  }\n}\n```\n\n## Protobuf and Jackson and plain Java objects\n\nProtobuf (via `com.google.protobuf:protobuf-java`) objects and schema is supported out of the box.\n\n### Protobuf example\n\n```protobuf\nsyntax = \"proto3\";\n\nmessage MyPojo {\n  string Property1 = 1;\n}\n```\n\n```java\npublic class MyClass {\n  public Boolean evalWithProtobuf() {\n    ScriptHost scriptHost = ScriptHost.newBuilder().build();\n\n    Script script =\n        scriptHost\n            .buildScript(\"inp.Property1 == checkName\")\n            .withDeclarations(\n                // protobuf types need the type's full name\n                Decls.newVar(\"inp\", Decls.newObjectType(MyPojo.getDescriptor().getFullName())),\n                Decls.newVar(\"checkName\", Decls.String))\n            // protobuf types need the default instance\n            .withTypes(MyPojo.getDefaultInstance())\n            .build();\n\n    MyPojo pojo = MyPojo.newBuilder().setProperty1(\"test\").build();\n\n    String checkName = \"test\";\n\n    Map\u003cString, Object\u003e arguments = new HashMap\u003c\u003e();\n    arguments.put(\"inp\", pojo);\n    arguments.put(\"checkName\", checkName);\n\n    Boolean result = script.execute(Boolean.class, arguments);\n\n    return result;\n  }\n}\n```\n\n### Jackson example\n\nIt is also possible to use plain Java and Jackson objects as arguments by using the \n`org.projectnessie.cel.types.jackson.JacksonRegistry` in `org.projectnessie.cel:cel-jackson`.\n\nCode sample similar to the one above. It takes a user-provided object type `MyInput`.\n```java\nimport org.projectnessie.cel.types.jackson.JacksonRegistry;\n\npublic class MyClass {\n  public Boolean evalWithJacksonObject(MyInput input, String checkName) {\n    // Build the script factory\n    ScriptHost scriptHost = ScriptHost.newBuilder()\n        // IMPORTANT: use the Jackson registry\n        .registry(JacksonRegistry.newRegistry())\n        .build();\n\n    // Create the script, will be parsed and checked.\n    // It checks whether the property `name` in the \"Jackson-ized\" class `MyInput` is\n    // equal to the value of `checkName`.\n    Script script = scriptHost.buildScript(\"inp.name == checkName\")\n        // Variable declarations - we need `inp` +  `checkName` in this example\n        .withDeclarations(\n            // types for Jackson need the fully qualified class name \n            Decls.newVar(\"inp\", Decls.newObjectType(MyInput.class.getName())),\n            Decls.newVar(\"checkName\", Decls.String))\n        // Register our Jackson object input type (as a java.lang.Class)\n        .withTypes(MyInput.class)\n        .build();\n\n    Map\u003cString, Object\u003e arguments = new HashMap\u003c\u003e();\n    arguments.put(\"inp\", input);\n    arguments.put(\"checkName\", checkName);\n\n    Boolean result = script.execute(Boolean.class, arguments);\n\n    return result;\n  }\n}\n```\n\nNote that the Jackson field-names are used as property names in CEL-Java. It is not necessary to\nannotate \"plain Java\" classes with Jackson annotations.\n\nTo use the `JacksonRegistry` in your application code, add the `cel-jackson` dependency in\naddition to `cel-core` or `cel-tools`.\n\n```xml\n\u003cdependencyManagement\u003e\n  \u003cdependencies\u003e\n    \u003cdependency\u003e\n      \u003cgroupId\u003eorg.projectnessie.cel\u003c/groupId\u003e\n      \u003cartifactId\u003ecel-bom\u003c/artifactId\u003e\n      \u003cversion\u003e0.5.1\u003c/version\u003e\n      \u003ctype\u003epom\u003c/type\u003e\n      \u003cscope\u003eimport\u003c/scope\u003e\n    \u003c/dependency\u003e\n  \u003c/dependencies\u003e\n\u003c/dependencyManagement\u003e\n\n\u003cdependencies\u003e\n  \u003cdependency\u003e\n    \u003cgroupId\u003eorg.projectnessie.cel\u003c/groupId\u003e\n    \u003cartifactId\u003ecel-jackson\u003c/artifactId\u003e\n  \u003c/dependency\u003e\n  \u003cdependency\u003e\n    \u003cgroupId\u003eorg.projectnessie.cel\u003c/groupId\u003e\n    \u003cartifactId\u003ecel-tools\u003c/artifactId\u003e\n  \u003c/dependency\u003e\n\u003c/dependencies\u003e\n```\nor Gradle project.\n```groovy\ndependencies {\n  implementation(enforcedPlatform(\"org.projectnessie.cel:cel-bom:0.5.1\"))\n  implementation(\"org.projectnessie.cel:cel-tools\")\n  implementation(\"org.projectnessie.cel:cel-jackson\")\n}\n```\n\n## Dependency-free artifact\n\nThe `org.projectnessie.cel:cel-standalone` contains everything from CEL-Java and has no dependencies.\nIt comes with relocated protobuf dependencies.\n\nUsing `cel-standalone` is especially useful when your project requires different versions of\n`protobuf-java`.\n\nIf you need CEL-Java's Jackson functionality, include the Jackson dependencies in your project.\n\nUse _either_ `cel-tools` _or_ `cel-standalone` - never both!\n\n## Motivation to have a CEL-Java port\n\nThe [Common Expression Language](https://github.com/google/cel-spec/) allows simple computations\nagainst data structures.\n\n[Project Nessie](https://projectnessie.org/) aims to use CEL to enforce security policies and\nfor various filtering expressions.\n\nThis Java implementation of CEL is based on the [CEL-Go](https://github.com/google/cel-go)\nimplementation.   \n\nTyped data structures should be defined using protobuf, but arbitrary data structures using\nJava wrapper data types (like `java.lang.Long`/`Double`/`String`), lists (`java.util.List`) and maps\n(`java.util.Map`) work, too.\n\nThe following example expression (from the [CEL-Go codelab exercise7](https://github.com/google/cel-go/blob/master/codelab/solution/codelab.go))\n```groovy\njwt.extra_claims.exists(c, c.startsWith('group'))\n  \u0026\u0026 jwt.extra_claims.filter(c, c.startsWith('group'))\n    .all(c, jwt.extra_claims[c]\n    .all(g, g.endsWith('@acme.co')))\n```\ncan be used to check whether the 'extra_claims' map of a JWT contains an entry with a key starting\nwith `group` and a value ending with `@acme.co`.\n\nThe JWT argument can be expressed using a non-protobuf data structure representing the JSON-web-token:\n```java\nimport java.util.List;\nimport java.util.Map;\n\nMap\u003cString, Object\u003e jwt = Map.of(\n    \"jwt\", Map.of(\n            \"sub\", \"serviceAccount:delegate@acme.co\",\n            \"aud\", \"my-project\",\n            \"iss\", \"auth.acme.com:12350\",\n            \"extra_claims\", Map.of(\n                \"group1\", List.of(\"admin@acme.co\", \"analyst@acme.co\"),\n                \"labels\", List.of(\"metadata\", \"prod\", \"pii\"),\n                \"groupN\", List.of(\"forever@acme.co\")\n            )\n        )\n    );\n```\n\n## Unsigned 64-bit `uint`\n\nNote that the [CEL type system](https://github.com/google/cel-spec/blob/master/doc/langdef.md#values)\nhas 2 64-bit integer types: a signed 64-bit integer `int` and an unsigned 64-bit integer `uint`.\nObjects/fields of different types must be explicitly casted in CEL. The \"primitive\" Java wrapper\ntype class for the 64-bit unsigned `uint` in CEL-Java is `org.projectnessie.cel.common.ULong`.\nIf you do not explicitly define a `uint` type or indirectly use `uint` via protobuf, you will\nprobably never notice it.\n\n## Arbitrary Java classes\n\nCEL-Java does *not* support access arbitrary Java classes. This means, you cannot access\n\"standard Java functionality\" from a CEL expression nor is it intended or planned to do so.\n\nCEL is intentionally non-turing-complete, this means it ends in a finite amount of time, has no\nloops or other \"blocking\" operations.\n\nYou can however provide own custom functionality as a library, which then provides functions\nto CEL scripts running in environments that have been configured to use that library.\n\n## Adding custom functions\n\nCustom functions can be easily added by implementing the [`org.projectnessie.cel.Library`](https://github.com/projectnessie/cel-java/blob/main/core/src/main/java/org/projectnessie/cel/Library.java)\ninterface. The interface provides the necessary declarations (function definitions) via\n`List\u003cEnvOption\u003e getCompileOptions()` and the function implementations via \n`List\u003cProgramOption\u003e getProgramOptions()`. Examples are\n[here (`StdLibrary` class)](./core/src/main/java/org/projectnessie/cel/Library.java),\n[here (`StringsLb` class)](./core/src/main/java/org/projectnessie/cel/extension/StringsLib.java),\n[here (`MyLib` class)](./tools/src/test/java/org/projectnessie/cel/tools/ScriptHostTest.java),\n[here](https://github.com/google/cel-go/blob/master/ext/encoders.go) and\n[here](https://github.com/google/cel-go/blob/master/ext/strings.go)\n\n## Building and testing CEL-Java\n\nThe CEL-Java repo uses git submodules to pull in required APIs from Google and the CEL-spec.\nThose submodules are required to build the CEL-Java project.\n\nYou need to run `git submodule init` and `git submodule update` after a fresh clone of this repo.\n\nBuild requirements:\n* Java 11 or newer, it's a Gradle-wrapper build (it's fast ;) )\n\nRuntime requirements:\n* Java 8 or newer\n\n`./gradlew publishToMavenLocal` deploy the current development to the local Maven repo, in\ncase you want to pull it the CEL-Java \"snapshot\" artifacts another project.\n\n`./gradlew test` builds the production code and runs the unit tests.\n\nThe project uses the Google Java code style and uses the Spotless plugin. Run\n`./gradlew spotlessApply` to fix formatting issues.\n\nTo run the CEL-spec conformance tests, Go, the bazel build tool plus toolchains are required.\nForm the CEL-Java repo, just run `conformance/run-conformance-tests.sh`. That script performs\nthe necessary Gradle and bazel builds.\n\n## CEL-Java implementation specifics\n\n### Not yet implemented\n\n* JSON extension ([see spec](https://github.com/google/cel-spec/blob/master/doc/langdef.md#json-data-conversion) and for example `nonFinite` in `com_github_golang_protobuf/jsonpb/decode.go`, around line 441)\n* Encoders extension ([like in Go](https://github.com/google/cel-go/blob/master/ext/encoders.go)),\n  not difficult to port to Java, it's just work to be done at some point.\n\n### Unsigned integer\n\nJava does not have a native (primitive) type \"unsigned int/long\" or `uint32`/`uint64`.\nSupport for the CEL type `uint` is therefore a bit more work in Java.\n\nTo maintain conformance to the CEL-spec, the CEL-Java implementation treats CEL's `uint` type\ndifferently. This means, that for example the expression `123 == 123u` is *not* true, but\n`123u == 123u` and `123 == 123` are.\n\nTL;DR If you have a `uint32`/`uint64` in your protobuf objects or use `uint`s in your CEL\nexpression, you *must* wrap those with the `org.projectnessie.cel.common.ULong` type.\n\n### Unclear double-to-int rounding behavior\n\nRounding/truncating of numeric values, especially when converting the CEL type `double` to\n`int` or `uint`. The CEL spec says: _CEL provides no way to control the finer points of\nfloating-point arithmetic, such as expression evaluation, **rounding mode**, or exception handling.\nHowever, any two not-a-number values will compare equal even if their underlying properties are\ndifferent._ ([see spec](https://github.com/google/cel-spec/blob/master/doc/langdef.md#numeric-values)).\n\nThe technical situation is ambiguous. The CEL-Go unit test\n`common/types/double_test.go/TestDoubleConvertToType` asserts on the result `-5` for the CEL\nexpression `int(-4.5)`, because CEL-Go uses the `math.Round(float64)` function.\n\nSince the CEL-spec is not clear, and the CEL-conformance-tests assert on double-to-int \"truncation\"\n(aka think Java-ish: `double doubleValue; long res = (long) doubleValue;`), the CEL-Java\nimplementation just implements the functionality that passes the CEL-spec conformance tests.\n\n(Note: the implementation of Go's `math.Round(float64)` behaves differently to Java's\n`Math.round(double)` (or `Math.rint()`) and a 1:1 port of the CEL-Go behavior is rather not\nthat trivial.)\n\nNote: The CEL-Go implementation does not pass the CEL-spec conformance tests:\n\n    --- FAIL: TestSimpleFile/conversions/int/double_truncate (0.01s)\n        simple_test.go:219: double_truncate: Eval got [int64_value:2], want [int64_value:1]\n    --- FAIL: TestSimpleFile/conversions/int/double_truncate_neg (0.01s)\n        simple_test.go:219: double_truncate_neg: Eval got [int64_value:-8], want [int64_value:-7]\n    --- FAIL: TestSimpleFile/conversions/int/double_half_pos (0.01s)\n        simple_test.go:219: double_half_pos: Eval got [int64_value:12], want [int64_value:11]\n    --- FAIL: TestSimpleFile/conversions/int/double_half_neg (0.01s)\n        simple_test.go:219: double_half_neg: Eval got [int64_value:-4], want [int64_value:-3]\n    --- FAIL: TestSimpleFile/conversions/uint/double_truncate (0.01s)\n        simple_test.go:219: double_truncate: Eval got [uint64_value:2], want [uint64_value:1]\n    --- FAIL: TestSimpleFile/conversions/uint/double_half (0.01s)\n        simple_test.go:219: double_half: Eval got [uint64_value:26], want [uint64_value:25]\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprojectnessie%2Fcel-java","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fprojectnessie%2Fcel-java","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprojectnessie%2Fcel-java/lists"}