diff --git a/README.md b/README.md index 73b23a1db..6108186be 100644 --- a/README.md +++ b/README.md @@ -28,18 +28,78 @@ scripting language is too resource intensive. --- +* [Quick Start](#quick-start) * [Overview](#overview) * [Environment Setup](#environment-setup) - * [Parse and Check](#parse-and-check) - * [Macros](#macros) - * [Evaluate](#evaluate) - * [Errors](#Errors) + * [Parsing](#parsing) + * [Checking](#checking) + * [Macros](#macros) + * [Evaluation](#evaluation) + * [Errors](#errors) + * [Extensions](#extensions) * [Install](#install) * [Common Questions](#common-questions) * [License](#license) --- +## Quick Start + +### Install + +CEL-Java is available in Maven Central Repository. [Download the JARs here][8] or add the following to your build dependencies: + +**Maven (pom.xml)**: + +```xml + + dev.cel + cel + 0.2.0 + +``` + +**Gradle** + +```gradle +implementation 'dev.cel:cel:0.2.0' +``` + +Then run this example: + +```java +import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.CelValidationException; +import dev.cel.common.types.SimpleType; +import dev.cel.compiler.CelCompiler; +import dev.cel.compiler.CelCompilerFactory; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.CelRuntime; +import dev.cel.runtime.CelRuntimeFactory; +import java.util.Map; + +public class HelloWorld { + // Construct the compilation and runtime environments. + // These instances are immutable and thus trivially thread-safe and amenable to caching. + private static final CelCompiler CEL_COMPILER = + CelCompilerFactory.standardCelCompilerBuilder().addVar("my_var", SimpleType.STRING).build(); + private static final CelRuntime CEL_RUNTIME = + CelRuntimeFactory.standardCelRuntimeBuilder().build(); + + public void run() throws CelValidationException, CelEvaluationException { + // Compile the expression into an Abstract Syntax Tree. + CelAbstractSyntaxTree ast = CEL_COMPILER.compile("my_var + '!'").getAst(); + + // Plan an executable program instance. + CelRuntime.Program program = CEL_RUNTIME.createProgram(ast); + + // Evaluate the program with an input variable. + String result = (String) program.eval(Map.of("my_var", "Hello World")); + System.out.println(result); // 'Hello World!' + } +} +``` + ## Overview Determine the variables and functions you want to provide to CEL. Parse and @@ -48,22 +108,91 @@ against some input. Checking is optional, but strongly encouraged. ### Environment Setup -This section will be completed once parser and type-checker has been added. +Configuration for the entire CEL stack can be done all at once via the +`CelFactory.standardCelBuilder()`, or can be composed into compilation and +evaluation via the `CelCompilerFactory` and `CelRuntimeFactory`. + +The simplest form of CEL usage is as follows: + +```java +Cel cel = CelFactory.standardCelBuilder().build(); +``` + +More commonly, your application will want to configure type-checking separately +from the runtime. Use `CelCompilerFactory` to construct a compilation +environment and declare the types, macros, variables, and functions to use with +your CEL application: + +```java +// Example environment for the following expression: +// resource.name.startsWith('/groups/' + group) +CelCompiler cel = CelCompilerFactory.standardCelCompilerBuilder() + .setStandardMacros(CelStandardMacro.HAS) + .setContainer("google.rpc.context.AttributeContext") + .addMessageTypes(AttributeContext.getDescriptor()) + .addVar("resource", + StructTypeReference.create("google.rpc.context.AttributeContext.Resource")) + .addVar("group", SimpleType.STRING) + .build(); +``` + +More information about the features which are supported on the builder may be +found in the [`CelCompilerBuilder`][9]. + +### Parsing + +Some CEL use cases only require parsing of an expression in order to be useful. +For example, one example might want to check whether the expression contains any +nested comprehensions, or possibly to pass the parsed expression to a C++ or Go +binary for evaluation. Presently, Java does not support parse-only evaluation. + +```java +CelValidationResult parseResult = + cel.parse("resource.name.startsWith('/groups/' + group)"); +try { + return parseResult.getAst(); +} catch (CelValidationException e) { + // Handle exception... +} +``` + +### Checking -### Parse and Check +Type-checking is performed on `CelAbstractSyntaxTree` values to ensure that the +expression is well formed and all variable and function references are defined. -Parsing and type-checking support are currently not available in Java but will -be added in the near future. In the interim, you may consider -leveraging [Go implementation of CEL][4] -to produce a type-checked expression to evaluate it in CEL-Java. +Type-checking can be performed immediately after parsing an expression: + +```java +try { + CelValidationResult parseResult = + cel.parse("resource.name.startsWith('/groups/' + group)"); + CelValidationResult checkResult = cel.check(parseResult.getAst()); + return checkResult.getAst(); +} catch (CelValidationException e) { + // Handle exception... +} +``` + +Or, the parse and type-check can be combined into the `compile` call. This is +likely the more common need. + +```java +CelValidationResult compileResult = + cel.compile("resource.name.startsWith('/groups/' + group)"); +try { + return compileResult.getAst(); +} catch (CelValidationException e) { + // Handle exception... +} +``` #### Macros -Macros are optional but enabled by default. Macros were introduced to support -optional CEL features that might not be desired in all use cases without the -syntactic burden and complexity such features might desire if they were part of -the core CEL syntax. Macros are expanded at parse time and their expansions are -type-checked at check time. +Macros were introduced to support optional CEL features that might not be +desired in all use cases without the syntactic burden and complexity such +features might desire if they were part of the core CEL syntax. Macros are +expanded at parse time and their expansions are type-checked at check time. For example, when macros are enabled it is possible to support bounded iteration / fold operators. The macros `all`, `exists`, `exists_one`, `filter`, and `map` @@ -87,28 +216,37 @@ has(message.field) Both cases traditionally require special syntax at the language level, but these features are exposed via macros in CEL. -### Evaluate - -Now, evaluate for fun and profit. The evaluation is thread-safe and side-effect -free. Many different inputs can be sent to the same `cel.Program` and if fields -are present in the input, but not referenced in the expression, they are -ignored. +Refer to the [CEL Specification][10] for full listings of available macros. To +leverage them, simply set the desired macros via `setStandardMacros` on the +builder: ```java -import dev.cel.common.CelAbstractSyntaxTree; -import dev.cel.runtime.CelRuntimeFactory; +CelCompiler.standardCelBuilder() + .setStandardMacros(CelStandardMacro.STANDARD_MACROS) +``` -CelRuntime celRuntime = CelRuntimeFactory.standardCelRuntimeBuilder().build(); -CelAbstractSyntaxTree ast = CelProtoAbstractSyntaxTree.fromCheckedExpr(checkedExpr).getAst(); +### Evaluation + +Expressions can be evaluated using once they are type-checked/compiled by +creating a `CelRuntime.Program` from a `CelAbstractSyntaxTree`: +```java +CelRuntime celRuntime = CelRuntimeFactory.standardCelRuntimeBuilder().build(); try { - CelRuntime.Program program = celRuntime.createProgram(ast); - Object evaluatedResult = program.eval(parameterValues); + CelRuntime.Program program = celRuntime.createProgram(compileResult.getAst()); + return program.eval( + ImmutableMap.of( + "resource", Resource.newBuilder().setName("/groups/").build(), + "group", "admin" + )); } catch (CelEvaluationException e) { - throw new IllegalArgumentException("Evaluation error has occurred.",e); + // Handle evaluation exceptions ... } ``` +The evaluation is thread-safe and side effect free thus many different inputs can +be sent to the same `cel.Program`. + #### Partial State In distributed apps it is not uncommon to have edge caches and central services. @@ -148,29 +286,42 @@ possibly relevant to the result. ### Errors -This section will be completed once parser and type-checker has been added. +Parse and check errors have friendly error messages with pointers to where the +issues occur in source: -## Install +```sh +ERROR: :1:40: undefined field 'undefined' + | TestAllTypes{single_int32: 1, undefined: 2} + | .......................................^`, +``` -CEL-Java is available in Maven Central Repository. [Download the JARs here][6] or add the following to your build dependencies: +Both the parsed and checked expressions contain source position information +about each node that appears in the output AST. This information can be used +to determine error locations at evaluation time as well. -**Maven (pom.xml)**: +### Extensions -```xml - - dev.cel - runtime - 0.1.0 - -``` +CEL-Java offers a suite of [canonical extensions][11] to support commonly +needed features that falls outside the CEL specification. -**Gradle** +Examples: -```gradle -implementation 'dev.cel:runtime:0.1.0' -``` +```java +// String manipulation +'hello hello'.replace('he', 'we') // returns 'wello wello' +'hello hello hello'.split(' ') // returns ['hello', 'hello', 'hello'] + +// Math extensions +math.greatest(-42.0, -21.5, -100.0) // -21.5 +math.least(-42.0, -21.5, -100.0) // -100.0 -Note: if you are already using `com.google.api.expr.v1alpha1` protobuf definitions, you also need to take `dev:cel:v1alpha1:0.1.0` as a dependency and leverage `CelProtoV1Alpha1AbstractSyntaxTree` class to convert your protobuf objects. Please note that v1alpha1 is now deprecated and new consumers should opt to use `dev.cel.expr` protos instead. +// Proto extensions +proto.getExt(msg, google.expr.proto2.test.int32_ext) // returns int value + +// Local bindings +cel.bind(a, 'hello', + cel.bind(b, 'world', a + b + b + a)) // "helloworldworldhello" +``` ## Common Questions @@ -208,18 +359,18 @@ runtime bindings and error handling to do the right thing. ### How can I contribute? * See [CONTRIBUTING.md](./CONTRIBUTING.md) to get started. -* Use [GitHub Issues][4] to request features or report bugs. +* Use [GitHub Issues][7] to request features or report bugs. ### Dependencies Java 8 or newer is required. -| Library | Version | Details | -|-----------------------|---------|----------------------| -| [Guava][2] | 31.1 | N/A | -| [RE2/J][3] | 1.7 | N/A | -| [Protocol Buffers][4] | 3.21.11 | Full or lite runtime | -| [ANTLR4][5] | 4.11.1 | Java runtime | +| Library | Details | +|-----------------------|----------------------| +| [Guava][3] | N/A | +| [RE2/J][4] | N/A | +| [Protocol Buffers][5] | Full or lite runtime | +| [ANTLR4][6] | Java runtime | ## License @@ -228,13 +379,13 @@ Released under the [Apache License](LICENSE). Disclaimer: This is not an official Google product. [1]: https://github.com/google/cel-spec - [2]: https://groups.google.com/forum/#!forum/cel-java-discuss - -[3]: https://github.com/google/cel-cpp - -[4]: https://github.com/google/cel-go - -[5]: https://github.com/google/cel-java/issues - -[6]: https://search.maven.org/search?q=g:dev.cel \ No newline at end of file +[3]: https://github.com/google/guava +[4]: https://github.com/google/re2j +[5]: https://github.com/protocolbuffers/protobuf/tree/master/java +[6]: https://github.com/antlr/antlr4/tree/master/runtime/Java +[7]: https://github.com/google/cel-java/issues +[8]: https://search.maven.org/search?q=g:dev.cel +[9]: https://github.com/google/cel-java/blob/main/compiler/src/main/java/dev/cel/compiler/CelCompilerBuilder.java +[10]: https://github.com/google/cel-spec/blob/master/doc/langdef.md#macros +[11]: https://github.com/google/cel-java/blob/main/extensions/src/main/java/dev/cel/extensions/README.md