From e16c4f37ce5420e0c6e4a6b2f9a0e64164828602 Mon Sep 17 00:00:00 2001 From: Dmitri Plotnikov Date: Fri, 2 Oct 2026 13:58:24 -0700 Subject: [PATCH] Split CelEncoderExtensions into CelEncoderCompilerLibrary and CelEncoderRuntimeLibrary to allow its usage in Lite runtime. PiperOrigin-RevId: 992522208 --- extensions/BUILD.bazel | 17 ++ .../main/java/dev/cel/extensions/BUILD.bazel | 54 +++++- .../extensions/CelEncoderCompilerLibrary.java | 131 +++++++++++++ .../cel/extensions/CelEncoderExtensions.java | 107 +++++------ .../extensions/CelEncoderRuntimeLibrary.java | 125 +++++++++++++ .../test/java/dev/cel/extensions/BUILD.bazel | 5 +- .../extensions/CelEncoderExtensionsTest.java | 173 ++++++++++++++++++ .../src/test/java/dev/cel/runtime/BUILD.bazel | 1 + .../runtime/CelLiteRuntimeAndroidTest.java | 13 ++ .../java/dev/cel/testing/compiled/BUILD.bazel | 7 + 10 files changed, 568 insertions(+), 65 deletions(-) create mode 100644 extensions/src/main/java/dev/cel/extensions/CelEncoderCompilerLibrary.java create mode 100644 extensions/src/main/java/dev/cel/extensions/CelEncoderRuntimeLibrary.java diff --git a/extensions/BUILD.bazel b/extensions/BUILD.bazel index 74d9a4ba2..4e15feb90 100644 --- a/extensions/BUILD.bazel +++ b/extensions/BUILD.bazel @@ -102,3 +102,20 @@ java_library( visibility = ["//:internal"], exports = ["//extensions/src/main/java/dev/cel/extensions:bindings"], ) + +java_library( + name = "encoders_compiler_library", + visibility = ["//:internal"], + exports = ["//extensions/src/main/java/dev/cel/extensions:encoders_compiler_library"], +) + +java_library( + name = "encoders_runtime_library", + visibility = ["//:internal"], + exports = ["//extensions/src/main/java/dev/cel/extensions:encoders_runtime_library"], +) + +cel_android_library( + name = "encoders_runtime_library_android", + exports = ["//extensions/src/main/java/dev/cel/extensions:encoders_runtime_library_android"], +) diff --git a/extensions/src/main/java/dev/cel/extensions/BUILD.bazel b/extensions/src/main/java/dev/cel/extensions/BUILD.bazel index bd555d72c..e5892b686 100644 --- a/extensions/src/main/java/dev/cel/extensions/BUILD.bazel +++ b/extensions/src/main/java/dev/cel/extensions/BUILD.bazel @@ -236,17 +236,18 @@ java_library( ], ) +# build_cleaner: disable=deps java_library( name = "encoders", srcs = ["CelEncoderExtensions.java"], deps = [ + ":encoders_compiler_library", + ":encoders_runtime_library", + ":extension_library", "//checker:checker_builder", - "//common:compiler_common", + "//common:cel_function_decl", "//common:options", - "//common/types", - "//common/values:cel_byte_string", "//compiler:compiler_builder", - "//extensions:extension_library", "//runtime", "//runtime:function_binding", "@maven//:com_google_errorprone_error_prone_annotations", @@ -255,6 +256,51 @@ java_library( ], ) +java_library( + name = "encoders_compiler_library", + srcs = ["CelEncoderCompilerLibrary.java"], + tags = [ + ], + deps = [ + ":extension_library", + "//checker:checker_builder", + "//common:cel_function_decl", + "//common:cel_overload_decl", + "//common/types", + "//compiler:compiler_builder", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + ], +) + +java_library( + name = "encoders_runtime_library", + srcs = ["CelEncoderRuntimeLibrary.java"], + tags = [ + ], + deps = [ + "//common/values:cel_byte_string", + "//runtime:function_binding", + "//runtime:lite_runtime", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + ], +) + +cel_android_library( + name = "encoders_runtime_library_android", + srcs = ["CelEncoderRuntimeLibrary.java"], + tags = [ + ], + deps = [ + "//common/values:cel_byte_string", + "//runtime:function_binding_android", + "//runtime:lite_runtime_android", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven_android//:com_google_guava_guava", + ], +) + java_library( name = "optional_library", srcs = ["CelOptionalLibrary.java"], diff --git a/extensions/src/main/java/dev/cel/extensions/CelEncoderCompilerLibrary.java b/extensions/src/main/java/dev/cel/extensions/CelEncoderCompilerLibrary.java new file mode 100644 index 000000000..6a668e78f --- /dev/null +++ b/extensions/src/main/java/dev/cel/extensions/CelEncoderCompilerLibrary.java @@ -0,0 +1,131 @@ +// Copyright 2023 Google LLC +// +// 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 +// +// https://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 dev.cel.extensions; + +import static com.google.common.collect.ImmutableSet.toImmutableSet; + +import com.google.common.collect.ImmutableSet; +import com.google.errorprone.annotations.Immutable; +import dev.cel.checker.CelCheckerBuilder; +import dev.cel.common.CelFunctionDecl; +import dev.cel.common.CelOverloadDecl; +import dev.cel.common.types.SimpleType; +import dev.cel.compiler.CelCompilerLibrary; +import java.util.Set; + +/** Internal implementation of CEL Encoder compile-time extensions. */ +@Immutable +public final class CelEncoderCompilerLibrary + implements CelCompilerLibrary, CelExtensionLibrary.FeatureSet { + + /** Enumeration of functions for Encoder compile-time extension. */ + public enum Function { + DECODE( + CelFunctionDecl.newFunctionDeclaration( + "base64.decode", + CelOverloadDecl.newGlobalOverload( + "base64_decode_string", SimpleType.BYTES, SimpleType.STRING))), + ENCODE( + CelFunctionDecl.newFunctionDeclaration( + "base64.encode", + CelOverloadDecl.newGlobalOverload( + "base64_encode_bytes", SimpleType.STRING, SimpleType.BYTES))); + + private final CelFunctionDecl functionDecl; + + public String getFunction() { + return functionDecl.name(); + } + + public CelFunctionDecl getFunctionDecl() { + return functionDecl; + } + + Function(CelFunctionDecl functionDecl) { + this.functionDecl = functionDecl; + } + } + + private static final class Library implements CelExtensionLibrary { + private final CelEncoderCompilerLibrary version0; + + Library() { + version0 = new CelEncoderCompilerLibrary(0, ImmutableSet.copyOf(Function.values())); + } + + @Override + public String name() { + return "encoders"; + } + + @Override + public ImmutableSet versions() { + return ImmutableSet.of(version0); + } + } + + private static final Library LIBRARY = new Library(); + + public static CelExtensionLibrary library() { + return LIBRARY; + } + + /** Returns the latest version of the 'encoders' compiler extension. */ + public static CelEncoderCompilerLibrary encoders() { + return library().latest(); + } + + /** Returns the specified version of the 'encoders' compiler extension. */ + public static CelEncoderCompilerLibrary encoders(int version) { + return library().version(version); + } + + /** Returns the 'encoders' compiler extension with only the specified functions. */ + public static CelEncoderCompilerLibrary encoders(Function... functions) { + return encoders(ImmutableSet.copyOf(functions)); + } + + /** Returns the 'encoders' compiler extension with only the specified functions. */ + public static CelEncoderCompilerLibrary encoders(Set functions) { + return new CelEncoderCompilerLibrary(functions); + } + + private final ImmutableSet functions; + private final int version; + + CelEncoderCompilerLibrary(Set functions) { + this(-1, functions); + } + + private CelEncoderCompilerLibrary(int version, Set functions) { + this.version = version; + this.functions = ImmutableSet.copyOf(functions); + } + + @Override + public int version() { + return version; + } + + @Override + public ImmutableSet functions() { + return functions.stream().map(Function::getFunctionDecl).collect(toImmutableSet()); + } + + @Override + public void setCheckerOptions(CelCheckerBuilder checkerBuilder) { + functions.forEach(function -> checkerBuilder.addFunctionDeclarations(function.functionDecl)); + } +} diff --git a/extensions/src/main/java/dev/cel/extensions/CelEncoderExtensions.java b/extensions/src/main/java/dev/cel/extensions/CelEncoderExtensions.java index 498b8555e..53d17de6c 100644 --- a/extensions/src/main/java/dev/cel/extensions/CelEncoderExtensions.java +++ b/extensions/src/main/java/dev/cel/extensions/CelEncoderExtensions.java @@ -22,9 +22,6 @@ import dev.cel.checker.CelCheckerBuilder; import dev.cel.common.CelFunctionDecl; import dev.cel.common.CelOptions; -import dev.cel.common.CelOverloadDecl; -import dev.cel.common.types.SimpleType; -import dev.cel.common.values.CelByteString; import dev.cel.compiler.CelCompilerLibrary; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.CelRuntimeBuilder; @@ -35,78 +32,61 @@ /** Internal implementation of Encoder Extensions. */ @Immutable -public class CelEncoderExtensions +public final class CelEncoderExtensions implements CelCompilerLibrary, CelRuntimeLibrary, CelExtensionLibrary.FeatureSet { private static final Encoder BASE64_ENCODER = Base64.getEncoder(); private static final Decoder BASE64_DECODER = Base64.getDecoder(); - private final ImmutableSet functions; - private final CelOptions celOptions; - + /** Denotes the encoder extension function. */ enum Function { DECODE( - CelFunctionDecl.newFunctionDeclaration( - "base64.decode", - CelOverloadDecl.newGlobalOverload( - "base64_decode_string", SimpleType.BYTES, SimpleType.STRING)), - CelFunctionBinding.from( - "base64_decode_string", - String.class, - str -> CelByteString.of(BASE64_DECODER.decode(str))), + CelEncoderCompilerLibrary.Function.DECODE, CelFunctionBinding.from( "base64_decode_string", String.class, str -> ByteString.copyFrom(BASE64_DECODER.decode(str)))), ENCODE( - CelFunctionDecl.newFunctionDeclaration( - "base64.encode", - CelOverloadDecl.newGlobalOverload( - "base64_encode_bytes", SimpleType.STRING, SimpleType.BYTES)), - CelFunctionBinding.from( - "base64_encode_bytes", - CelByteString.class, - bytes -> BASE64_ENCODER.encodeToString(bytes.toByteArray())), + CelEncoderCompilerLibrary.Function.ENCODE, CelFunctionBinding.from( "base64_encode_bytes", ByteString.class, - bytes -> BASE64_ENCODER.encodeToString(bytes.toByteArray()))), - ; + bytes -> BASE64_ENCODER.encodeToString(bytes.toByteArray()))); - private final CelFunctionDecl functionDecl; - private final CelFunctionBinding nativeBytesFunctionBinding; + private final CelEncoderCompilerLibrary.Function compilerFunction; private final CelFunctionBinding protoBytesFunctionBinding; String getFunction() { - return functionDecl.name(); + return compilerFunction.getFunction(); } Function( - CelFunctionDecl functionDecl, - CelFunctionBinding nativeBytesFunctionBinding, + CelEncoderCompilerLibrary.Function compilerFunction, CelFunctionBinding protoBytesFunctionBinding) { - this.functionDecl = functionDecl; - this.nativeBytesFunctionBinding = nativeBytesFunctionBinding; + this.compilerFunction = compilerFunction; this.protoBytesFunctionBinding = protoBytesFunctionBinding; } } private static final class Library implements CelExtensionLibrary { - private final CelEncoderExtensions version0; + private final ImmutableSet versions; + + private Library(CelOptions celOptions) { + versions = + CelEncoderCompilerLibrary.library().versions().stream() + .map(compilerLibrary -> new CelEncoderExtensions(celOptions, compilerLibrary)) + .collect(toImmutableSet()); + } @Override public String name() { - return "encoders"; + return CelEncoderCompilerLibrary.library().name(); } @Override public ImmutableSet versions() { - return ImmutableSet.of(version0); - } - - private Library(CelOptions celOptions) { - this.version0 = new CelEncoderExtensions(celOptions); + return versions; } } @@ -114,40 +94,47 @@ static CelExtensionLibrary library(CelOptions celOptions) return new Library(celOptions); } + private final CelEncoderCompilerLibrary compilerLibrary; + private final CelEncoderRuntimeLibrary encoderRuntime; + private final ImmutableSet functions; + private final CelOptions celOptions; + + CelEncoderExtensions(CelOptions celOptions) { + this(celOptions, CelEncoderCompilerLibrary.encoders()); + } + + private CelEncoderExtensions(CelOptions celOptions, CelEncoderCompilerLibrary compilerLibrary) { + this.compilerLibrary = compilerLibrary; + this.encoderRuntime = CelEncoderRuntimeLibrary.encoders(compilerLibrary.version()); + this.celOptions = celOptions; + this.functions = ImmutableSet.copyOf(Function.values()); + } + @Override public int version() { - return 0; + return compilerLibrary.version(); } @Override public ImmutableSet functions() { - return functions.stream().map(f -> f.functionDecl).collect(toImmutableSet()); + return compilerLibrary.functions(); } @Override public void setCheckerOptions(CelCheckerBuilder checkerBuilder) { - functions.forEach(function -> checkerBuilder.addFunctionDeclarations(function.functionDecl)); + compilerLibrary.setCheckerOptions(checkerBuilder); } - @SuppressWarnings("Immutable") // Instances of java.util.Base64 are immutable @Override public void setRuntimeOptions(CelRuntimeBuilder runtimeBuilder) { - functions.forEach( - function -> { - if (celOptions.evaluateCanonicalTypesToNativeValues()) { - runtimeBuilder.addFunctionBindings( - CelFunctionBinding.fromOverloads( - function.getFunction(), function.nativeBytesFunctionBinding)); - } else { - runtimeBuilder.addFunctionBindings( - CelFunctionBinding.fromOverloads( - function.getFunction(), function.protoBytesFunctionBinding)); - } - }); - } - - CelEncoderExtensions(CelOptions celOptions) { - this.celOptions = celOptions; - this.functions = ImmutableSet.copyOf(Function.values()); + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + runtimeBuilder.addFunctionBindings(encoderRuntime.newFunctionBindings()); + } else { + functions.forEach( + function -> + runtimeBuilder.addFunctionBindings( + CelFunctionBinding.fromOverloads( + function.getFunction(), function.protoBytesFunctionBinding))); + } } } diff --git a/extensions/src/main/java/dev/cel/extensions/CelEncoderRuntimeLibrary.java b/extensions/src/main/java/dev/cel/extensions/CelEncoderRuntimeLibrary.java new file mode 100644 index 000000000..2f4eced9a --- /dev/null +++ b/extensions/src/main/java/dev/cel/extensions/CelEncoderRuntimeLibrary.java @@ -0,0 +1,125 @@ +// Copyright 2023 Google LLC +// +// 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 +// +// https://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 dev.cel.extensions; + +import com.google.common.collect.ImmutableSet; +import com.google.errorprone.annotations.Immutable; +import dev.cel.common.values.CelByteString; +import dev.cel.runtime.CelFunctionBinding; +import dev.cel.runtime.CelLiteRuntimeBuilder; +import dev.cel.runtime.CelLiteRuntimeLibrary; +import java.util.Base64; +import java.util.Base64.Decoder; +import java.util.Base64.Encoder; +import java.util.Set; + +/** Runtime implementation of CEL Encoder extension functions. */ +@Immutable +public final class CelEncoderRuntimeLibrary implements CelLiteRuntimeLibrary { + + private static final Encoder BASE64_ENCODER = Base64.getEncoder(); + + private static final Decoder BASE64_DECODER = Base64.getDecoder(); + + /** Enumeration of runtime function bindings for the Encoder extension. */ + public enum Function { + DECODE( + "base64.decode", + ImmutableSet.of( + CelFunctionBinding.from( + "base64_decode_string", String.class, CelEncoderRuntimeLibrary::decode))), + ENCODE( + "base64.encode", + ImmutableSet.of( + CelFunctionBinding.from( + "base64_encode_bytes", CelByteString.class, CelEncoderRuntimeLibrary::encode))); + + private final String functionName; + private final ImmutableSet functionBindings; + + public String getFunction() { + return functionName; + } + + public ImmutableSet getFunctionBindings() { + return functionBindings; + } + + Function(String functionName, ImmutableSet bindings) { + this.functionName = functionName; + this.functionBindings = bindings; + } + } + + private static final CelEncoderRuntimeLibrary VERSION_0 = + new CelEncoderRuntimeLibrary(ImmutableSet.copyOf(Function.values())); + + /** Returns the latest version of the 'encoders' runtime functions. */ + public static CelEncoderRuntimeLibrary encoders() { + return VERSION_0; + } + + /** Returns the specified version of the 'encoders' runtime functions. */ + public static CelEncoderRuntimeLibrary encoders(int version) { + switch (version) { + case 0: + case Integer.MAX_VALUE: + return VERSION_0; + default: + throw new IllegalArgumentException("Unsupported 'encoders' extension version " + version); + } + } + + /** Returns the 'encoders' runtime functions with only the specified functions. */ + public static CelEncoderRuntimeLibrary encoders(Function... functions) { + return encoders(ImmutableSet.copyOf(functions)); + } + + /** Returns the 'encoders' runtime functions with only the specified functions. */ + public static CelEncoderRuntimeLibrary encoders(Set functions) { + return new CelEncoderRuntimeLibrary(functions); + } + + private final ImmutableSet functions; + + CelEncoderRuntimeLibrary(Set functions) { + this.functions = ImmutableSet.copyOf(functions); + } + + @Override + public void setRuntimeOptions(CelLiteRuntimeBuilder runtimeBuilder) { + runtimeBuilder.addFunctionBindings(newFunctionBindings()); + } + + /** Creates the {@link CelFunctionBinding}s for the configured encoder functions. */ + public ImmutableSet newFunctionBindings() { + ImmutableSet.Builder builder = ImmutableSet.builder(); + for (Function function : functions) { + if (!function.functionBindings.isEmpty()) { + builder.addAll( + CelFunctionBinding.fromOverloads(function.functionName, function.functionBindings)); + } + } + return builder.build(); + } + + private static CelByteString decode(String str) { + return CelByteString.of(BASE64_DECODER.decode(str)); + } + + private static String encode(CelByteString bytes) { + return BASE64_ENCODER.encodeToString(bytes.toByteArray()); + } +} diff --git a/extensions/src/test/java/dev/cel/extensions/BUILD.bazel b/extensions/src/test/java/dev/cel/extensions/BUILD.bazel index cc063e79a..8812bd7cc 100644 --- a/extensions/src/test/java/dev/cel/extensions/BUILD.bazel +++ b/extensions/src/test/java/dev/cel/extensions/BUILD.bazel @@ -34,6 +34,8 @@ java_library( "//compiler", "//compiler:compiler_builder", "//extensions", + "//extensions:encoders_compiler_library", + "//extensions:encoders_runtime_library", "//extensions:extension_library", "//extensions:math", "//extensions:math_compiler_library", @@ -62,9 +64,10 @@ java_library( "@cel_spec//proto/cel/expr/conformance/proto3:test_all_types_java_proto", "@cel_spec//proto/cel/expr/conformance/test:simple_java_proto", "@maven//:com_google_guava_guava", - "@maven//:com_google_protobuf_protobuf_java", + "@maven//:com_google_protobuf_protobuf_java", # unuseddeps: keep "@maven//:com_google_testparameterinjector_test_parameter_injector", "@maven//:junit_junit", + "@maven_android//:com_google_protobuf_protobuf_javalite", ], ) diff --git a/extensions/src/test/java/dev/cel/extensions/CelEncoderExtensionsTest.java b/extensions/src/test/java/dev/cel/extensions/CelEncoderExtensionsTest.java index afeaa9105..d98fc0700 100644 --- a/extensions/src/test/java/dev/cel/extensions/CelEncoderExtensionsTest.java +++ b/extensions/src/test/java/dev/cel/extensions/CelEncoderExtensionsTest.java @@ -19,14 +19,24 @@ import static org.junit.Assert.assertThrows; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.protobuf.ByteString; import com.google.testing.junit.testparameterinjector.TestParameterInjector; import dev.cel.bundle.Cel; +import dev.cel.bundle.CelFactory; +import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelFunctionDecl; import dev.cel.common.CelOptions; import dev.cel.common.CelValidationException; import dev.cel.common.types.SimpleType; import dev.cel.common.values.CelByteString; +import dev.cel.compiler.CelCompiler; +import dev.cel.compiler.CelCompilerFactory; import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.CelLiteRuntime; +import dev.cel.runtime.CelLiteRuntimeFactory; +import dev.cel.runtime.CelRuntime; +import dev.cel.runtime.CelRuntimeFactory; import org.junit.Assume; import org.junit.Test; import org.junit.runner.RunWith; @@ -120,5 +130,168 @@ public void decode_malformedBase64Char_throwsEvaluationException() throws Except assertThat(e).hasCauseThat().hasMessageThat().contains("Illegal base64 character"); } + @Test + public void separateLibraryAndRuntime_allFunctions_success() throws Exception { + CelCompiler celCompiler = + CelCompilerFactory.standardCelCompilerBuilder() + .addLibraries(CelEncoderCompilerLibrary.encoders()) + .build(); + CelLiteRuntime celLiteRuntime = + CelLiteRuntimeFactory.newLiteRuntimeBuilder() + .addLibraries(CelEncoderRuntimeLibrary.encoders()) + .build(); + + CelAbstractSyntaxTree ast = + celCompiler.compile("base64.decode(base64.encode(b'hello'))").getAst(); + CelByteString result = (CelByteString) celLiteRuntime.createProgram(ast).eval(); + + assertThat(result).isEqualTo(CelByteString.copyFromUtf8("hello")); + } + + @Test + public void separateLibraryAndRuntime_versioned_success() throws Exception { + CelCompiler celCompiler = + CelCompilerFactory.standardCelCompilerBuilder() + .addLibraries(CelEncoderCompilerLibrary.encoders(0)) + .build(); + CelRuntime celRuntime = + CelRuntimeFactory.standardCelRuntimeBuilder() + .addFunctionBindings(CelEncoderRuntimeLibrary.encoders(0).newFunctionBindings()) + .build(); + + CelAbstractSyntaxTree ast = celCompiler.compile("base64.encode(b'hello')").getAst(); + String result = (String) celRuntime.createProgram(ast).eval(); + + assertThat(result).isEqualTo("aGVsbG8="); + } + + @Test + public void separateLibraryAndRuntime_subsetOfFunctions_success() throws Exception { + CelCompiler celCompiler = + CelCompilerFactory.standardCelCompilerBuilder() + .addLibraries( + CelEncoderCompilerLibrary.encoders(CelEncoderCompilerLibrary.Function.ENCODE)) + .build(); + CelRuntime celRuntime = + CelRuntimeFactory.standardCelRuntimeBuilder() + .addFunctionBindings( + CelEncoderRuntimeLibrary.encoders(CelEncoderRuntimeLibrary.Function.ENCODE) + .newFunctionBindings()) + .build(); + + CelAbstractSyntaxTree ast = celCompiler.compile("base64.encode(b'hello')").getAst(); + String result = (String) celRuntime.createProgram(ast).eval(); + + assertThat(result).isEqualTo("aGVsbG8="); + assertThrows( + CelValidationException.class, + () -> celCompiler.compile("base64.decode('aGVsbG8=')").getAst()); + } + + @Test + public void separateLibraryAndRuntime_setOfFunctions_success() throws Exception { + CelEncoderCompilerLibrary compilerLibrary = + CelEncoderCompilerLibrary.encoders( + ImmutableSet.of(CelEncoderCompilerLibrary.Function.ENCODE)); + assertThat(compilerLibrary.version()).isEqualTo(-1); + + CelCompiler celCompiler = + CelCompilerFactory.standardCelCompilerBuilder() + .addLibraries(compilerLibrary) + .build(); + CelRuntime celRuntime = + CelRuntimeFactory.standardCelRuntimeBuilder() + .addFunctionBindings( + CelEncoderRuntimeLibrary.encoders( + ImmutableSet.of(CelEncoderRuntimeLibrary.Function.ENCODE)) + .newFunctionBindings()) + .build(); + + CelAbstractSyntaxTree ast = celCompiler.compile("base64.encode(b'hello')").getAst(); + String result = (String) celRuntime.createProgram(ast).eval(); + + assertThat(result).isEqualTo("aGVsbG8="); + } + + @Test + public void separateLibraryAndRuntime_unsupportedVersion_throws() { + assertThrows(IllegalArgumentException.class, () -> CelEncoderRuntimeLibrary.encoders(99)); + assertThrows(IllegalArgumentException.class, () -> CelEncoderCompilerLibrary.encoders(99)); + } + + @Test + public void separateLibraryAndRuntime_maxVersion_success() { + assertThat(CelEncoderCompilerLibrary.encoders(Integer.MAX_VALUE).version()).isEqualTo(0); + assertThat(CelEncoderRuntimeLibrary.encoders(Integer.MAX_VALUE)).isNotNull(); + } + + @Test + public void compilerLibrary() { + CelExtensionLibrary library = CelEncoderCompilerLibrary.library(); + assertThat(library.name()).isEqualTo("encoders"); + assertThat(library.versions()).isNotEmpty(); + assertThat(library.latest().version()).isEqualTo(0); + assertThat(CelEncoderCompilerLibrary.encoders().version()).isEqualTo(0); + assertThat(CelEncoderCompilerLibrary.encoders(0).version()).isEqualTo(0); + assertThat( + CelEncoderCompilerLibrary.encoders().functions().stream().map(CelFunctionDecl::name)) + .containsExactly("base64.decode", "base64.encode"); + } + + @Test + public void runtimeLibrary_functionEnum() { + assertThat(CelEncoderRuntimeLibrary.Function.DECODE.getFunction()).isEqualTo("base64.decode"); + assertThat(CelEncoderRuntimeLibrary.Function.DECODE.getFunctionBindings()).isNotEmpty(); + assertThat(CelEncoderRuntimeLibrary.Function.ENCODE.getFunction()).isEqualTo("base64.encode"); + assertThat(CelEncoderRuntimeLibrary.Function.ENCODE.getFunctionBindings()).isNotEmpty(); + } + + @Test + public void compilerLibrary_functionEnum() { + assertThat(CelEncoderCompilerLibrary.Function.DECODE.getFunction()).isEqualTo("base64.decode"); + assertThat(CelEncoderCompilerLibrary.Function.DECODE.getFunctionDecl().name()) + .isEqualTo("base64.decode"); + assertThat(CelEncoderCompilerLibrary.Function.ENCODE.getFunction()).isEqualTo("base64.encode"); + assertThat(CelEncoderCompilerLibrary.Function.ENCODE.getFunctionDecl().name()) + .isEqualTo("base64.encode"); + } + + @Test + public void separateLibraryAndRuntime_emptyFunctions() { + assertThat(CelEncoderCompilerLibrary.encoders(ImmutableSet.of()).functions()).isEmpty(); + assertThat(CelEncoderRuntimeLibrary.encoders(ImmutableSet.of()).newFunctionBindings()).isEmpty(); + } + + @Test + @SuppressWarnings("deprecation") // Test only - verifying deprecated no-arg factory method + public void encoders_deprecatedNoArg() throws Exception { + Cel cel = + CelFactory.standardCelBuilder() + .addCompilerLibraries(CelExtensions.encoders()) + .addRuntimeLibraries(CelExtensions.encoders()) + .build(); + assertThat(cel.createProgram(cel.compile("base64.encode(b'hello')").getAst()).eval()) + .isEqualTo("aGVsbG8="); + } + + @Test + @SuppressWarnings("deprecation") // Test only - verifying deprecated evaluateCanonicalTypesToNativeValues option + public void encoders_evaluateCanonicalTypesToNativeValuesDisabled_producesAndConsumesByteString() + throws Exception { + CelOptions options = CelOptions.current().evaluateCanonicalTypesToNativeValues(false).build(); + Cel cel = + CelFactory.standardCelBuilder() + .setOptions(options) + .addCompilerLibraries(CelExtensions.encoders(options)) + .addRuntimeLibraries(CelExtensions.encoders(options)) + .build(); + + ByteString decodedBytes = + (ByteString) cel.createProgram(cel.compile("base64.decode('aGVsbG8=')").getAst()).eval(); + assertThat(decodedBytes).isEqualTo(ByteString.copyFromUtf8("hello")); + String encodedString = + (String) cel.createProgram(cel.compile("base64.encode(b'hello')").getAst()).eval(); + assertThat(encodedString).isEqualTo("aGVsbG8="); + } } diff --git a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel index 3146e5a8b..5b2517b7c 100644 --- a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel @@ -194,6 +194,7 @@ cel_android_local_test( "//common/values:cel_byte_string", "//common/values:cel_value_provider_android", "//common/values:proto_message_lite_value_provider_android", + "//extensions:encoders_runtime_library_android", "//extensions:math_runtime_library_android", "//extensions:sets_runtime_library_android", "//extensions:strings_runtime_library_android", diff --git a/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeAndroidTest.java b/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeAndroidTest.java index 7941d7cc6..426988efe 100644 --- a/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeAndroidTest.java +++ b/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeAndroidTest.java @@ -45,6 +45,7 @@ import dev.cel.expr.conformance.proto3.NestedTestAllTypesCelLiteDescriptor; import dev.cel.expr.conformance.proto3.TestAllTypes; import dev.cel.expr.conformance.proto3.TestAllTypesCelLiteDescriptor; +import dev.cel.extensions.CelEncoderRuntimeLibrary; import dev.cel.extensions.CelMathRuntimeLibrary; import dev.cel.extensions.CelSetsRuntimeLibrary; import dev.cel.extensions.CelStringRuntimeLibrary; @@ -765,6 +766,18 @@ public void eval_setsExtension() throws Exception { assertThat(runtime.createProgram(ast).eval()).isEqualTo(true); } + @Test + public void eval_encodersExtension() throws Exception { + CelLiteRuntime runtime = + CelLiteRuntimeFactory.newLiteRuntimeBuilder() + .addLibraries(CelEncoderRuntimeLibrary.encoders()) + .build(); + // Expr: base64.encode(b'hello') + CelAbstractSyntaxTree ast = readCheckedExpr("compiled_encoders_encode"); + + assertThat(runtime.createProgram(ast).eval()).isEqualTo("aGVsbG8="); + } + private enum CelOptionsTestCase { UNSIGNED_LONG_DISABLED(newBaseTestOptions().enableUnsignedLongs(false).build()), UNWRAP_WKT_DISABLED(newBaseTestOptions().unwrapWellKnownTypesOnFunctionDispatch(false).build()), diff --git a/testing/src/main/java/dev/cel/testing/compiled/BUILD.bazel b/testing/src/main/java/dev/cel/testing/compiled/BUILD.bazel index b6cdeadff..3f1d569a5 100644 --- a/testing/src/main/java/dev/cel/testing/compiled/BUILD.bazel +++ b/testing/src/main/java/dev/cel/testing/compiled/BUILD.bazel @@ -45,6 +45,7 @@ java_library( ":compiled_comprehension", ":compiled_comprehension_exists", ":compiled_custom_functions", + ":compiled_encoders_encode", ":compiled_extended_env", ":compiled_extensions", ":compiled_hello_world", @@ -107,6 +108,12 @@ compile_cel( expression = "cel.bind(x, 10, math.greatest([1,x])) < int(' 11 '.trim()) && optional.none().orValue(true) && [].flatten() == []", ) +compile_cel( + name = "compiled_encoders_encode", + environment = "//testing/environment:all_extensions", + expression = "base64.encode(b'hello')", +) + compile_cel( name = "compiled_math_greatest", environment = "//testing/environment:all_extensions",