typeMasks);
/**
- * Add one or more {@link CelRuntime.CelFunctionBinding} objects to the CEL runtime.
+ * Add one or more {@link CelFunctionBinding} objects to the CEL runtime.
*
* Functions with duplicate overload ids will be replaced in favor of the new overload.
*/
@CanIgnoreReturnValue
- CelBuilder addFunctionBindings(CelRuntime.CelFunctionBinding... bindings);
+ CelBuilder addFunctionBindings(CelFunctionBinding... bindings);
/**
- * Bind a collection of {@link CelRuntime.CelFunctionBinding} objects to the runtime.
+ * Bind a collection of {@link CelFunctionBinding} objects to the runtime.
*
*
Functions with duplicate overload ids will be replaced in favor of the new overload.
*/
@CanIgnoreReturnValue
- CelBuilder addFunctionBindings(Iterable bindings);
+ CelBuilder addFunctionBindings(Iterable bindings);
/** Set the expected {@code resultType} for the type-checked expression. */
@CanIgnoreReturnValue
diff --git a/bundle/src/main/java/dev/cel/bundle/CelEnvironment.java b/bundle/src/main/java/dev/cel/bundle/CelEnvironment.java
new file mode 100644
index 000000000..3bdc7c894
--- /dev/null
+++ b/bundle/src/main/java/dev/cel/bundle/CelEnvironment.java
@@ -0,0 +1,915 @@
+// Copyright 2025 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.bundle;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.collect.ImmutableSet.toImmutableSet;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+import com.google.errorprone.annotations.CheckReturnValue;
+import dev.cel.bundle.CelEnvironment.LibrarySubset.FunctionSelector;
+import dev.cel.checker.CelStandardDeclarations;
+import dev.cel.checker.CelStandardDeclarations.StandardFunction;
+import dev.cel.checker.CelStandardDeclarations.StandardOverload;
+import dev.cel.common.CelContainer;
+import dev.cel.common.CelFunctionDecl;
+import dev.cel.common.CelOptions;
+import dev.cel.common.CelOverloadDecl;
+import dev.cel.common.CelVarDecl;
+import dev.cel.common.Source;
+import dev.cel.common.types.CelType;
+import dev.cel.common.types.CelTypeProvider;
+import dev.cel.common.types.ListType;
+import dev.cel.common.types.MapType;
+import dev.cel.common.types.OptionalType;
+import dev.cel.common.types.SimpleType;
+import dev.cel.common.types.TypeParamType;
+import dev.cel.compiler.CelCompiler;
+import dev.cel.compiler.CelCompilerBuilder;
+import dev.cel.compiler.CelCompilerLibrary;
+import dev.cel.extensions.CelExtensions;
+import dev.cel.parser.CelStandardMacro;
+import dev.cel.runtime.CelRuntimeBuilder;
+import dev.cel.runtime.CelRuntimeLibrary;
+import java.util.Arrays;
+import java.util.Optional;
+
+/**
+ * CelEnvironment is a native representation of a CEL environment for compiler and runtime. This
+ * object is amenable to being serialized into YAML, textproto or other formats as needed.
+ */
+@AutoValue
+public abstract class CelEnvironment {
+
+ @VisibleForTesting
+ static final ImmutableMap CEL_EXTENSION_CONFIG_MAP =
+ ImmutableMap.of(
+ "bindings", CanonicalCelExtension.BINDINGS,
+ "encoders", CanonicalCelExtension.ENCODERS,
+ "lists", CanonicalCelExtension.LISTS,
+ "math", CanonicalCelExtension.MATH,
+ "optional", CanonicalCelExtension.OPTIONAL,
+ "protos", CanonicalCelExtension.PROTOS,
+ "sets", CanonicalCelExtension.SETS,
+ "strings", CanonicalCelExtension.STRINGS);
+
+ /** Environment source in textual format (ex: textproto, YAML). */
+ public abstract Optional source();
+
+ /** Name of the environment. */
+ public abstract String name();
+
+ /**
+ * An optional description of the config (example: location of the file containing the config
+ * content).
+ */
+ public abstract String container();
+
+ /**
+ * An optional description of the environment (example: location of the file containing the config
+ * content).
+ */
+ public abstract String description();
+
+ /** Converts this {@code CelEnvironment} object into a builder. */
+ public abstract Builder toBuilder();
+
+ /**
+ * Canonical extensions to enable in the environment, such as Optional, String and Math
+ * extensions.
+ */
+ public abstract ImmutableSet extensions();
+
+ /** New variable declarations to add in the compilation environment. */
+ public abstract ImmutableSet variables();
+
+ /** New function declarations to add in the compilation environment. */
+ public abstract ImmutableSet functions();
+
+ /** Standard library subset (which macros, functions to include/exclude) */
+ public abstract Optional standardLibrarySubset();
+
+ /** Builder for {@link CelEnvironment}. */
+ @AutoValue.Builder
+ public abstract static class Builder {
+
+ public abstract ImmutableSet.Builder extensionsBuilder();
+
+ // For testing only, to empty out the source.
+ abstract Builder setSource(Optional source);
+
+ public abstract Builder setSource(Source source);
+
+ public abstract Builder setName(String name);
+
+ public abstract Builder setDescription(String description);
+
+ public abstract Builder setContainer(String container);
+
+ @CanIgnoreReturnValue
+ public Builder addExtensions(ExtensionConfig... extensions) {
+ checkNotNull(extensions);
+ return addExtensions(Arrays.asList(extensions));
+ }
+
+ @CanIgnoreReturnValue
+ public Builder addExtensions(Iterable extensions) {
+ checkNotNull(extensions);
+ this.extensionsBuilder().addAll(extensions);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder setVariables(VariableDecl... variables) {
+ return setVariables(ImmutableSet.copyOf(variables));
+ }
+
+ public abstract Builder setVariables(ImmutableSet variables);
+
+ @CanIgnoreReturnValue
+ public Builder setFunctions(FunctionDecl... functions) {
+ return setFunctions(ImmutableSet.copyOf(functions));
+ }
+
+ public abstract Builder setFunctions(ImmutableSet functions);
+
+ public abstract Builder setStandardLibrarySubset(LibrarySubset stdLibrarySubset);
+
+ abstract CelEnvironment autoBuild();
+
+ @CheckReturnValue
+ public final CelEnvironment build() {
+ CelEnvironment env = autoBuild();
+ LibrarySubset librarySubset = env.standardLibrarySubset().orElse(null);
+ if (librarySubset != null) {
+ if (!librarySubset.includedMacros().isEmpty()
+ && !librarySubset.excludedMacros().isEmpty()) {
+ throw new IllegalArgumentException(
+ "Invalid subset: cannot both include and exclude macros");
+ }
+ if (!librarySubset.includedFunctions().isEmpty()
+ && !librarySubset.excludedFunctions().isEmpty()) {
+ throw new IllegalArgumentException(
+ "Invalid subset: cannot both include and exclude functions");
+ }
+ }
+ return env;
+ }
+ }
+
+ /** Creates a new builder to construct a {@link CelEnvironment} instance. */
+ public static Builder newBuilder() {
+ return new AutoValue_CelEnvironment.Builder()
+ .setName("")
+ .setDescription("")
+ .setContainer("")
+ .setVariables(ImmutableSet.of())
+ .setFunctions(ImmutableSet.of());
+ }
+
+ /** Extends the provided {@link CelCompiler} environment with this configuration. */
+ public CelCompiler extend(CelCompiler celCompiler, CelOptions celOptions)
+ throws CelEnvironmentException {
+ try {
+ CelTypeProvider celTypeProvider = celCompiler.getTypeProvider();
+ CelCompilerBuilder compilerBuilder =
+ celCompiler
+ .toCompilerBuilder()
+ .setTypeProvider(celTypeProvider)
+ .setContainer(CelContainer.ofName(container()))
+ .addVarDeclarations(
+ variables().stream()
+ .map(v -> v.toCelVarDecl(celTypeProvider))
+ .collect(toImmutableList()))
+ .addFunctionDeclarations(
+ functions().stream()
+ .map(f -> f.toCelFunctionDecl(celTypeProvider))
+ .collect(toImmutableList()));
+
+ if (!container().isEmpty()) {
+ compilerBuilder.setContainer(CelContainer.ofName(container()));
+ }
+
+ addAllCompilerExtensions(compilerBuilder, celOptions);
+
+ applyStandardLibrarySubset(compilerBuilder);
+
+ return compilerBuilder.build();
+ } catch (RuntimeException e) {
+ throw new CelEnvironmentException(e.getMessage(), e);
+ }
+ }
+
+ /** Extends the provided {@link Cel} environment with this configuration. */
+ public Cel extend(Cel cel, CelOptions celOptions) throws CelEnvironmentException {
+ try {
+ // Casting is necessary to only extend the compiler here
+ CelCompiler celCompiler = extend((CelCompiler) cel, celOptions);
+
+ CelRuntimeBuilder celRuntimeBuilder = cel.toRuntimeBuilder();
+ addAllRuntimeExtensions(celRuntimeBuilder, celOptions);
+
+ return CelFactory.combine(celCompiler, celRuntimeBuilder.build());
+ } catch (RuntimeException e) {
+ throw new CelEnvironmentException(e.getMessage(), e);
+ }
+ }
+
+ private void addAllCompilerExtensions(
+ CelCompilerBuilder celCompilerBuilder, CelOptions celOptions) {
+ // TODO: Add capability to accept user defined exceptions
+ for (ExtensionConfig extensionConfig : extensions()) {
+ CanonicalCelExtension extension = getExtensionOrThrow(extensionConfig.name());
+ if (extension.compilerExtensionProvider() != null) {
+ CelCompilerLibrary celCompilerLibrary =
+ extension
+ .compilerExtensionProvider()
+ .getCelCompilerLibrary(celOptions, extensionConfig.version());
+ celCompilerBuilder.addLibraries(celCompilerLibrary);
+ }
+ }
+ }
+
+ private void addAllRuntimeExtensions(CelRuntimeBuilder celRuntimeBuilder, CelOptions celOptions) {
+ // TODO: Add capability to accept user defined exceptions
+ for (ExtensionConfig extensionConfig : extensions()) {
+ CanonicalCelExtension extension = getExtensionOrThrow(extensionConfig.name());
+ if (extension.runtimeExtensionProvider() != null) {
+ CelRuntimeLibrary celRuntimeLibrary =
+ extension
+ .runtimeExtensionProvider()
+ .getCelRuntimeLibrary(celOptions, extensionConfig.version());
+ celRuntimeBuilder.addLibraries(celRuntimeLibrary);
+ }
+ }
+ }
+
+ private void applyStandardLibrarySubset(CelCompilerBuilder compilerBuilder) {
+ if (!standardLibrarySubset().isPresent()) {
+ return;
+ }
+
+ LibrarySubset librarySubset = standardLibrarySubset().get();
+ if (librarySubset.disabled()) {
+ compilerBuilder.setStandardEnvironmentEnabled(false);
+ return;
+ }
+
+ if (librarySubset.macrosDisabled()) {
+ compilerBuilder.setStandardMacros(ImmutableList.of());
+ } else if (!librarySubset.includedMacros().isEmpty()) {
+ compilerBuilder.setStandardMacros(
+ librarySubset.includedMacros().stream()
+ .flatMap(name -> getStandardMacrosOrThrow(name).stream())
+ .collect(toImmutableSet()));
+ } else if (!librarySubset.excludedMacros().isEmpty()) {
+ ImmutableSet set =
+ librarySubset.excludedMacros().stream()
+ .flatMap(name -> getStandardMacrosOrThrow(name).stream())
+ .collect(toImmutableSet());
+ compilerBuilder.setStandardMacros(
+ CelStandardMacro.STANDARD_MACROS.stream()
+ .filter(macro -> !set.contains(macro))
+ .collect(toImmutableSet()));
+ }
+
+ if (!librarySubset.includedFunctions().isEmpty()) {
+ ImmutableSet includedFunctions = librarySubset.includedFunctions();
+ compilerBuilder
+ .setStandardEnvironmentEnabled(false)
+ .setStandardDeclarations(
+ CelStandardDeclarations.newBuilder()
+ .filterFunctions(
+ (function, overload) ->
+ FunctionSelector.matchesAny(function, overload, includedFunctions))
+ .build());
+ } else if (!librarySubset.excludedFunctions().isEmpty()) {
+ ImmutableSet excludedFunctions = librarySubset.excludedFunctions();
+ compilerBuilder
+ .setStandardEnvironmentEnabled(false)
+ .setStandardDeclarations(
+ CelStandardDeclarations.newBuilder()
+ .filterFunctions(
+ (function, overload) ->
+ !FunctionSelector.matchesAny(function, overload, excludedFunctions))
+ .build());
+ }
+ }
+
+ private static ImmutableSet getStandardMacrosOrThrow(String macroName) {
+ ImmutableSet.Builder builder = ImmutableSet.builder();
+ for (CelStandardMacro macro : CelStandardMacro.STANDARD_MACROS) {
+ if (macro.getFunction().equals(macroName)) {
+ builder.add(macro);
+ }
+ }
+ ImmutableSet macros = builder.build();
+ if (macros.isEmpty()) {
+ throw new IllegalArgumentException("unrecognized standard macro `" + macroName + "'");
+ }
+ return macros;
+ }
+
+ private static CanonicalCelExtension getExtensionOrThrow(String extensionName) {
+ CanonicalCelExtension extension = CEL_EXTENSION_CONFIG_MAP.get(extensionName);
+ if (extension == null) {
+ throw new IllegalArgumentException("Unrecognized extension: " + extensionName);
+ }
+
+ return extension;
+ }
+
+ /** Represents a policy variable declaration. */
+ @AutoValue
+ public abstract static class VariableDecl {
+
+ /** Fully qualified variable name. */
+ public abstract String name();
+
+ /** The type of the variable. */
+ public abstract TypeDecl type();
+
+ /** Builder for {@link VariableDecl}. */
+ @AutoValue.Builder
+ public abstract static class Builder implements RequiredFieldsChecker {
+
+ public abstract Optional name();
+
+ public abstract Optional type();
+
+ public abstract VariableDecl.Builder setName(String name);
+
+ public abstract VariableDecl.Builder setType(TypeDecl typeDecl);
+
+ @Override
+ public ImmutableList requiredFields() {
+ return ImmutableList.of(
+ RequiredField.of("name", this::name), RequiredField.of("type", this::type));
+ }
+
+ /** Builds a new instance of {@link VariableDecl}. */
+ public abstract VariableDecl build();
+ }
+
+ public static VariableDecl.Builder newBuilder() {
+ return new AutoValue_CelEnvironment_VariableDecl.Builder();
+ }
+
+ /** Creates a new builder to construct a {@link VariableDecl} instance. */
+ public static VariableDecl create(String name, TypeDecl type) {
+ return newBuilder().setName(name).setType(type).build();
+ }
+
+ /** Converts this policy variable declaration into a {@link CelVarDecl}. */
+ public CelVarDecl toCelVarDecl(CelTypeProvider celTypeProvider) {
+ return CelVarDecl.newVarDeclaration(name(), type().toCelType(celTypeProvider));
+ }
+ }
+
+ /** Represents a policy function declaration. */
+ @AutoValue
+ public abstract static class FunctionDecl {
+
+ public abstract String name();
+
+ public abstract ImmutableSet overloads();
+
+ /** Builder for {@link FunctionDecl}. */
+ @AutoValue.Builder
+ public abstract static class Builder implements RequiredFieldsChecker {
+
+ public abstract Optional name();
+
+ public abstract Optional> overloads();
+
+ public abstract FunctionDecl.Builder setName(String name);
+
+ public abstract FunctionDecl.Builder setOverloads(ImmutableSet overloads);
+
+ @Override
+ public ImmutableList requiredFields() {
+ return ImmutableList.of(
+ RequiredField.of("name", this::name), RequiredField.of("overloads", this::overloads));
+ }
+
+ /** Builds a new instance of {@link FunctionDecl}. */
+ public abstract FunctionDecl build();
+ }
+
+ /** Creates a new builder to construct a {@link FunctionDecl} instance. */
+ public static FunctionDecl.Builder newBuilder() {
+ return new AutoValue_CelEnvironment_FunctionDecl.Builder();
+ }
+
+ /** Creates a new {@link FunctionDecl} with the provided function name and its overloads. */
+ public static FunctionDecl create(String name, ImmutableSet overloads) {
+ return newBuilder().setName(name).setOverloads(overloads).build();
+ }
+
+ /** Converts this policy function declaration into a {@link CelFunctionDecl}. */
+ public CelFunctionDecl toCelFunctionDecl(CelTypeProvider celTypeProvider) {
+ return CelFunctionDecl.newFunctionDeclaration(
+ name(),
+ overloads().stream()
+ .map(o -> o.toCelOverloadDecl(celTypeProvider))
+ .collect(toImmutableList()));
+ }
+ }
+
+ /** Represents an overload declaration on a policy function. */
+ @AutoValue
+ public abstract static class OverloadDecl {
+
+ /**
+ * A unique overload ID. Required. This should follow the typical naming convention used in CEL
+ * (e.g: targetType_func_argType1_argType...)
+ */
+ public abstract String id();
+
+ /** Target of the function overload if it's a receiver style (example: foo in `foo.f(...)`) */
+ public abstract Optional target();
+
+ /** List of function overload type values. */
+ public abstract ImmutableList arguments();
+
+ /** Return type of the overload. Required. */
+ public abstract TypeDecl returnType();
+
+ /** Builder for {@link OverloadDecl}. */
+ @AutoValue.Builder
+ public abstract static class Builder implements RequiredFieldsChecker {
+
+ public abstract Optional id();
+
+ public abstract Optional returnType();
+
+ public abstract OverloadDecl.Builder setId(String overloadId);
+
+ public abstract OverloadDecl.Builder setTarget(TypeDecl target);
+
+ // This should stay package-private to encourage add/set methods to be used instead.
+ abstract ImmutableList.Builder argumentsBuilder();
+
+ public abstract OverloadDecl.Builder setArguments(ImmutableList args);
+
+ @CanIgnoreReturnValue
+ public OverloadDecl.Builder addArguments(Iterable args) {
+ this.argumentsBuilder().addAll(checkNotNull(args));
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public OverloadDecl.Builder addArguments(TypeDecl... args) {
+ return addArguments(Arrays.asList(args));
+ }
+
+ public abstract OverloadDecl.Builder setReturnType(TypeDecl returnType);
+
+ @Override
+ public ImmutableList requiredFields() {
+ return ImmutableList.of(
+ RequiredField.of("id", this::id), RequiredField.of("return", this::returnType));
+ }
+
+ /** Builds a new instance of {@link OverloadDecl}. */
+ @CheckReturnValue
+ public abstract OverloadDecl build();
+ }
+
+ /** Creates a new builder to construct a {@link OverloadDecl} instance. */
+ public static OverloadDecl.Builder newBuilder() {
+ return new AutoValue_CelEnvironment_OverloadDecl.Builder().setArguments(ImmutableList.of());
+ }
+
+ /** Converts this policy function overload into a {@link CelOverloadDecl}. */
+ public CelOverloadDecl toCelOverloadDecl(CelTypeProvider celTypeProvider) {
+ CelOverloadDecl.Builder builder =
+ CelOverloadDecl.newBuilder()
+ .setIsInstanceFunction(false)
+ .setOverloadId(id())
+ .setResultType(returnType().toCelType(celTypeProvider));
+
+ target()
+ .ifPresent(
+ t ->
+ builder
+ .setIsInstanceFunction(true)
+ .addParameterTypes(t.toCelType(celTypeProvider)));
+
+ for (TypeDecl type : arguments()) {
+ builder.addParameterTypes(type.toCelType(celTypeProvider));
+ }
+
+ return builder.build();
+ }
+ }
+
+ /**
+ * Represents an abstract type declaration used to declare functions and variables in a policy.
+ */
+ @AutoValue
+ public abstract static class TypeDecl {
+
+ public abstract String name();
+
+ public abstract ImmutableList params();
+
+ public abstract boolean isTypeParam();
+
+ /** Builder for {@link TypeDecl}. */
+ @AutoValue.Builder
+ public abstract static class Builder implements RequiredFieldsChecker {
+
+ public abstract Optional name();
+
+ public abstract TypeDecl.Builder setName(String name);
+
+ // This should stay package-private to encourage add/set methods to be used instead.
+ abstract ImmutableList.Builder paramsBuilder();
+
+ public abstract TypeDecl.Builder setParams(ImmutableList typeDecls);
+
+ @CanIgnoreReturnValue
+ public TypeDecl.Builder addParams(TypeDecl... params) {
+ return addParams(Arrays.asList(params));
+ }
+
+ @CanIgnoreReturnValue
+ public TypeDecl.Builder addParams(Iterable params) {
+ this.paramsBuilder().addAll(checkNotNull(params));
+ return this;
+ }
+
+ public abstract TypeDecl.Builder setIsTypeParam(boolean isTypeParam);
+
+ @Override
+ public ImmutableList requiredFields() {
+ return ImmutableList.of(RequiredField.of("type_name", this::name));
+ }
+
+ @CheckReturnValue
+ public abstract TypeDecl build();
+ }
+
+ /** Creates a new {@link TypeDecl} with the provided name. */
+ public static TypeDecl create(String name) {
+ return newBuilder().setName(name).build();
+ }
+
+ public static TypeDecl.Builder newBuilder() {
+ return new AutoValue_CelEnvironment_TypeDecl.Builder().setIsTypeParam(false);
+ }
+
+ /** Converts this type declaration into a {@link CelType}. */
+ public CelType toCelType(CelTypeProvider celTypeProvider) {
+ switch (name()) {
+ case "list":
+ if (params().size() != 1) {
+ throw new IllegalArgumentException(
+ "List type has unexpected param count: " + params().size());
+ }
+
+ CelType elementType = params().get(0).toCelType(celTypeProvider);
+ return ListType.create(elementType);
+ case "map":
+ if (params().size() != 2) {
+ throw new IllegalArgumentException(
+ "Map type has unexpected param count: " + params().size());
+ }
+
+ CelType keyType = params().get(0).toCelType(celTypeProvider);
+ CelType valueType = params().get(1).toCelType(celTypeProvider);
+ return MapType.create(keyType, valueType);
+ default:
+ if (isTypeParam()) {
+ return TypeParamType.create(name());
+ }
+
+ CelType simpleType = SimpleType.findByName(name()).orElse(null);
+ if (simpleType != null) {
+ return simpleType;
+ }
+
+ if (name().equals(OptionalType.NAME)) {
+ checkState(
+ params().size() == 1,
+ "Optional type must have exactly 1 parameter. Found %s",
+ params().size());
+ return OptionalType.create(params().get(0).toCelType(celTypeProvider));
+ }
+
+ return celTypeProvider
+ .findType(name())
+ .orElseThrow(() -> new IllegalArgumentException("Undefined type name: " + name()));
+ }
+ }
+ }
+
+ /**
+ * Represents a configuration for a canonical CEL extension that can be enabled in the
+ * environment.
+ */
+ @AutoValue
+ public abstract static class ExtensionConfig {
+
+ /** Name of the extension (ex: bindings, optional, math, etc).". */
+ public abstract String name();
+
+ /**
+ * Version of the extension. Presently, this field is ignored as CEL-Java extensions are not
+ * versioned.
+ */
+ public abstract int version();
+
+ /** Builder for {@link ExtensionConfig}. */
+ @AutoValue.Builder
+ public abstract static class Builder implements RequiredFieldsChecker {
+
+ public abstract Optional name();
+
+ public abstract Optional version();
+
+ public abstract ExtensionConfig.Builder setName(String name);
+
+ public abstract ExtensionConfig.Builder setVersion(int version);
+
+ @Override
+ public ImmutableList requiredFields() {
+ return ImmutableList.of(RequiredField.of("name", this::name));
+ }
+
+ /** Builds a new instance of {@link ExtensionConfig}. */
+ public abstract ExtensionConfig build();
+ }
+
+ /** Creates a new builder to construct a {@link ExtensionConfig} instance. */
+ public static ExtensionConfig.Builder newBuilder() {
+ return new AutoValue_CelEnvironment_ExtensionConfig.Builder().setVersion(0);
+ }
+
+ /** Create a new extension config with the specified name and version set to 0. */
+ public static ExtensionConfig of(String name) {
+ return of(name, 0);
+ }
+
+ /** Create a new extension config with the specified name and version. */
+ public static ExtensionConfig of(String name, int version) {
+ return newBuilder().setName(name).setVersion(version).build();
+ }
+
+ /** Create a new extension config with the specified name and the latest version. */
+ public static ExtensionConfig latest(String name) {
+ return of(name, Integer.MAX_VALUE);
+ }
+ }
+
+ @VisibleForTesting
+ enum CanonicalCelExtension {
+ BINDINGS((options, version) -> CelExtensions.bindings()),
+ PROTOS((options, version) -> CelExtensions.protos()),
+ ENCODERS(
+ (options, version) -> CelExtensions.encoders(),
+ (options, version) -> CelExtensions.encoders()),
+ MATH(
+ (options, version) -> CelExtensions.math(options, version),
+ (options, version) -> CelExtensions.math(options, version)),
+ OPTIONAL(
+ (options, version) -> CelExtensions.optional(version),
+ (options, version) -> CelExtensions.optional(version)),
+ STRINGS(
+ (options, version) -> CelExtensions.strings(),
+ (options, version) -> CelExtensions.strings()),
+ SETS(
+ (options, version) -> CelExtensions.sets(options),
+ (options, version) -> CelExtensions.sets(options)),
+ LISTS((options, version) -> CelExtensions.lists(), (options, version) -> CelExtensions.lists());
+
+ @SuppressWarnings("ImmutableEnumChecker")
+ private final CompilerExtensionProvider compilerExtensionProvider;
+
+ @SuppressWarnings("ImmutableEnumChecker")
+ private final RuntimeExtensionProvider runtimeExtensionProvider;
+
+ interface CompilerExtensionProvider {
+ CelCompilerLibrary getCelCompilerLibrary(CelOptions options, int version);
+ }
+
+ interface RuntimeExtensionProvider {
+ CelRuntimeLibrary getCelRuntimeLibrary(CelOptions options, int version);
+ }
+
+ CompilerExtensionProvider compilerExtensionProvider() {
+ return compilerExtensionProvider;
+ }
+
+ RuntimeExtensionProvider runtimeExtensionProvider() {
+ return runtimeExtensionProvider;
+ }
+
+ CanonicalCelExtension(CompilerExtensionProvider compilerExtensionProvider) {
+ this.compilerExtensionProvider = compilerExtensionProvider;
+ this.runtimeExtensionProvider = null; // Not all extensions augment the runtime.
+ }
+
+ CanonicalCelExtension(
+ CompilerExtensionProvider compilerExtensionProvider,
+ RuntimeExtensionProvider runtimeExtensionProvider) {
+ this.compilerExtensionProvider = compilerExtensionProvider;
+ this.runtimeExtensionProvider = runtimeExtensionProvider;
+ }
+ }
+
+ /**
+ * LibrarySubset indicates a subset of the macros and function supported by a subsettable library.
+ */
+ @AutoValue
+ public abstract static class LibrarySubset {
+
+ /**
+ * Disabled indicates whether the library has been disabled, typically only used for
+ * default-enabled libraries like stdlib.
+ */
+ public abstract boolean disabled();
+
+ /** DisableMacros disables macros for the given library. */
+ public abstract boolean macrosDisabled();
+
+ /** IncludeMacros specifies a set of macro function names to include in the subset. */
+ public abstract ImmutableSet includedMacros();
+
+ /**
+ * ExcludeMacros specifies a set of macro function names to exclude from the subset.
+ *
+ * Note: if IncludedMacros is non-empty, then ExcludedMacros is ignored.
+ */
+ public abstract ImmutableSet excludedMacros();
+
+ /**
+ * IncludeFunctions specifies a set of functions to include in the subset.
+ *
+ * Note: the overloads specified in the subset need only specify their ID.
+ *
+ *
Note: if IncludedFunctions is non-empty, then ExcludedFunctions is ignored.
+ */
+ public abstract ImmutableSet includedFunctions();
+
+ /**
+ * ExcludeFunctions specifies the set of functions to exclude from the subset.
+ *
+ * Note: the overloads specified in the subset need only specify their ID.
+ */
+ public abstract ImmutableSet excludedFunctions();
+
+ public static Builder newBuilder() {
+ return new AutoValue_CelEnvironment_LibrarySubset.Builder()
+ .setMacrosDisabled(false)
+ .setIncludedMacros(ImmutableSet.of())
+ .setExcludedMacros(ImmutableSet.of())
+ .setIncludedFunctions(ImmutableSet.of())
+ .setExcludedFunctions(ImmutableSet.of());
+ }
+
+ /** Builder for {@link LibrarySubset}. */
+ @AutoValue.Builder
+ public abstract static class Builder {
+
+ public abstract Builder setDisabled(boolean disabled);
+
+ public abstract Builder setMacrosDisabled(boolean disabled);
+
+ public abstract Builder setIncludedMacros(ImmutableSet includedMacros);
+
+ public abstract Builder setExcludedMacros(ImmutableSet excludedMacros);
+
+ public abstract Builder setIncludedFunctions(
+ ImmutableSet includedFunctions);
+
+ public abstract Builder setExcludedFunctions(
+ ImmutableSet excludedFunctions);
+
+ @CheckReturnValue
+ public abstract LibrarySubset build();
+ }
+
+ /**
+ * Represents a function selector, which can be used to configure included/excluded library
+ * functions.
+ */
+ @AutoValue
+ public abstract static class FunctionSelector {
+
+ public abstract String name();
+
+ public abstract ImmutableSet overloads();
+
+ /** Builder for {@link FunctionSelector}. */
+ @AutoValue.Builder
+ public abstract static class Builder implements RequiredFieldsChecker {
+
+ public abstract Optional name();
+
+ public abstract Builder setName(String name);
+
+ public abstract Builder setOverloads(ImmutableSet overloads);
+
+ @Override
+ public ImmutableList requiredFields() {
+ return ImmutableList.of(RequiredField.of("name", this::name));
+ }
+
+ /** Builds a new instance of {@link FunctionSelector}. */
+ public abstract FunctionSelector build();
+ }
+
+ /** Creates a new builder to construct a {@link FunctionSelector} instance. */
+ public static FunctionSelector.Builder newBuilder() {
+ return new AutoValue_CelEnvironment_LibrarySubset_FunctionSelector.Builder()
+ .setOverloads(ImmutableSet.of());
+ }
+
+ public static FunctionSelector create(String name, ImmutableSet overloads) {
+ return newBuilder()
+ .setName(name)
+ .setOverloads(
+ overloads.stream()
+ .map(id -> OverloadSelector.newBuilder().setId(id).build())
+ .collect(toImmutableSet()))
+ .build();
+ }
+
+ private static boolean matchesAny(
+ StandardFunction function,
+ StandardOverload overload,
+ ImmutableSet selectors) {
+ String functionName = function.functionDecl().name();
+ for (FunctionSelector functionSelector : selectors) {
+ if (!functionSelector.name().equals(functionName)) {
+ continue;
+ }
+
+ if (functionSelector.overloads().isEmpty()) {
+ return true;
+ }
+
+ String overloadId = overload.celOverloadDecl().overloadId();
+ for (OverloadSelector overloadSelector : functionSelector.overloads()) {
+ if (overloadSelector.id().equals(overloadId)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ }
+
+ /** Represents an overload selector on a function selector. */
+ @AutoValue
+ public abstract static class OverloadSelector {
+
+ /** An overload ID. Required. Follows the same format as {@link OverloadDecl#id()} */
+ public abstract String id();
+
+ /** Builder for {@link OverloadSelector}. */
+ @AutoValue.Builder
+ public abstract static class Builder implements RequiredFieldsChecker {
+
+ public abstract Optional id();
+
+ public abstract Builder setId(String overloadId);
+
+ @Override
+ public ImmutableList requiredFields() {
+ return ImmutableList.of(RequiredField.of("id", this::id));
+ }
+
+ /** Builds a new instance of {@link OverloadSelector}. */
+ @CheckReturnValue
+ public abstract OverloadSelector build();
+ }
+
+ /** Creates a new builder to construct a {@link OverloadSelector} instance. */
+ public static OverloadSelector.Builder newBuilder() {
+ return new AutoValue_CelEnvironment_LibrarySubset_OverloadSelector.Builder();
+ }
+ }
+ }
+}
diff --git a/bundle/src/main/java/dev/cel/bundle/CelEnvironmentException.java b/bundle/src/main/java/dev/cel/bundle/CelEnvironmentException.java
new file mode 100644
index 000000000..58bb5cdb9
--- /dev/null
+++ b/bundle/src/main/java/dev/cel/bundle/CelEnvironmentException.java
@@ -0,0 +1,29 @@
+// Copyright 2025 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.bundle;
+
+import dev.cel.common.CelException;
+
+/** Checked exception thrown when a CEL environment is misconfigured. */
+public final class CelEnvironmentException extends CelException {
+
+ CelEnvironmentException(String message) {
+ super(message);
+ }
+
+ CelEnvironmentException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/bundle/src/main/java/dev/cel/bundle/CelEnvironmentExporter.java b/bundle/src/main/java/dev/cel/bundle/CelEnvironmentExporter.java
new file mode 100644
index 000000000..d303e0528
--- /dev/null
+++ b/bundle/src/main/java/dev/cel/bundle/CelEnvironmentExporter.java
@@ -0,0 +1,453 @@
+// Copyright 2025 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.bundle;
+
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.collect.ImmutableSet.toImmutableSet;
+import static java.util.Arrays.stream;
+
+import dev.cel.expr.Decl;
+import dev.cel.expr.Decl.FunctionDecl;
+import dev.cel.expr.Decl.FunctionDecl.Overload;
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ListMultimap;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+import dev.cel.bundle.CelEnvironment.ExtensionConfig;
+import dev.cel.bundle.CelEnvironment.LibrarySubset;
+import dev.cel.bundle.CelEnvironment.LibrarySubset.FunctionSelector;
+import dev.cel.bundle.CelEnvironment.OverloadDecl;
+import dev.cel.checker.CelStandardDeclarations.StandardFunction;
+import dev.cel.checker.CelStandardDeclarations.StandardIdentifier;
+import dev.cel.common.CelFunctionDecl;
+import dev.cel.common.CelOptions;
+import dev.cel.common.CelOverloadDecl;
+import dev.cel.common.CelVarDecl;
+import dev.cel.common.internal.EnvVisitable;
+import dev.cel.common.internal.EnvVisitor;
+import dev.cel.common.types.CelProtoTypes;
+import dev.cel.common.types.CelType;
+import dev.cel.common.types.CelTypes;
+import dev.cel.extensions.CelExtensionLibrary;
+import dev.cel.extensions.CelExtensions;
+import dev.cel.parser.CelMacro;
+import dev.cel.parser.CelStandardMacro;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * {@code CelEnvironmentExporter} can be used to export the configuration of a {@link Cel} instance
+ * as a {@link CelEnvironment} object.
+ *
+ * This exporter captures details such as:
+ *
+ *
+ * Standard library subset: Functions and their overloads that are either included or
+ * excluded.
+ * Extension libraries: Names and versions of the extension libraries in use.
+ * Custom declarations: Functions and variables not part of standard or extension libraries.
+ *
+ *
+ * The exporter provides options to control the behavior of the export process, such as the
+ * maximum number of excluded standard functions and overloads before switching to an inclusion
+ * strategy.
+ */
+@AutoValue
+public abstract class CelEnvironmentExporter {
+
+ /**
+ * Maximum number of excluded standard functions and macros before switching to a format that
+ * enumerates all included functions and macros. The default is 5.
+ *
+ *
This setting is primarily for stylistic preferences and the intended use of the resulting
+ * YAML file.
+ *
+ *
For example, if you want almost all the standard library with only a few exceptions (e.g.,
+ * to ban a specific function), you would favor an exclusion-based approach by setting an
+ * appropriate threshold.
+ *
+ *
If you want full control over what is available to the CEL runtime, where no function is
+ * included unless fully vetted, you would favor an inclusion-based approach by setting the
+ * threshold to 0. This may result in a more verbose YAML file.
+ */
+ abstract int maxExcludedStandardFunctions();
+
+ /**
+ * Maximum number of excluded standard function overloads before switching to a exhaustive
+ * enumeration of included overloads. The default is 15.
+ */
+ abstract int maxExcludedStandardFunctionOverloads();
+
+ abstract ImmutableSet>
+ extensionLibraries();
+
+ /** Builder for {@link CelEnvironmentExporter}. */
+ @AutoValue.Builder
+ public abstract static class Builder {
+
+ public abstract Builder setMaxExcludedStandardFunctions(int count);
+
+ public abstract Builder setMaxExcludedStandardFunctionOverloads(int count);
+
+ abstract ImmutableSet.Builder>
+ extensionLibrariesBuilder();
+
+ @CanIgnoreReturnValue
+ public Builder addStandardExtensions(CelOptions options) {
+ addExtensionLibraries(
+ CelExtensions.getExtensionLibrary("bindings", options),
+ CelExtensions.getExtensionLibrary("encoders", options),
+ CelExtensions.getExtensionLibrary("lists", options),
+ CelExtensions.getExtensionLibrary("math", options),
+ CelExtensions.getExtensionLibrary("protos", options),
+ CelExtensions.getExtensionLibrary("regex", options),
+ CelExtensions.getExtensionLibrary("sets", options),
+ CelExtensions.getExtensionLibrary("strings", options));
+ // TODO: add support for remaining standard extensions
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder addExtensionLibraries(
+ CelExtensionLibrary extends CelExtensionLibrary.FeatureSet>... libraries) {
+ extensionLibrariesBuilder().add(libraries);
+ return this;
+ }
+
+ abstract CelEnvironmentExporter autoBuild();
+
+ public CelEnvironmentExporter build() {
+ return autoBuild();
+ }
+ }
+
+ /** Creates a new builder to construct a {@link CelEnvironmentExporter} instance. */
+ public static CelEnvironmentExporter.Builder newBuilder() {
+ return new AutoValue_CelEnvironmentExporter.Builder()
+ .setMaxExcludedStandardFunctions(5)
+ .setMaxExcludedStandardFunctionOverloads(15);
+ }
+
+ /**
+ * Exports a {@link CelEnvironment} that describes the configuration of the given {@link Cel}
+ * instance.
+ *
+ * The exported environment includes:
+ *
+ *
+ * Standard library subset: functions and their overloads that are either included or
+ * excluded from the standard library.
+ * Extension libraries: names and versions of the extension libraries that are used.
+ * Custom declarations: functions and variables that are not part of the standard library or
+ * any of the extension libraries.
+ *
+ */
+ public CelEnvironment export(Cel cel) {
+ // Inventory is a full set of declarations and macros that are found in the configuration of
+ // the supplied CEL instance.
+ //
+ // Once we have the inventory, we attempt to identify sources of these declarations as
+ // standard library and extensions. The identified subsets will be removed from the inventory.
+ //
+ // Whatever is left will be included in the Environment as custom declarations.
+
+ Set inventory = new HashSet<>();
+ collectInventory(inventory, cel);
+
+ CelEnvironment.Builder envBuilder = CelEnvironment.newBuilder();
+ addExtensionConfigsAndRemoveFromInventory(envBuilder, inventory);
+ addStandardLibrarySubsetAndRemoveFromInventory(envBuilder, inventory);
+ addCustomDecls(envBuilder, inventory);
+ return envBuilder.build();
+ }
+
+ /**
+ * Collects all function overloads, variable declarations and macros from the given {@link Cel}
+ * instance and stores them in a map.
+ */
+ private void collectInventory(Set inventory, Cel cel) {
+ ((EnvVisitable) cel)
+ .accept(
+ new EnvVisitor() {
+ @Override
+ public void visitDecl(String name, List decls) {
+ for (Decl decl : decls) {
+ if (decl.hasFunction()) {
+ FunctionDecl function = decl.getFunction();
+ for (Overload overload : function.getOverloadsList()) {
+ inventory.add(
+ NamedOverload.create(
+ decl.getName(), CelOverloadDecl.overloadToCelOverload(overload)));
+ }
+ } else if (decl.hasIdent()) {
+ inventory.add(
+ CelVarDecl.newVarDeclaration(
+ decl.getName(),
+ CelProtoTypes.typeToCelType(decl.getIdent().getType())));
+ }
+ }
+ }
+
+ @Override
+ public void visitMacro(CelMacro macro) {
+ inventory.add(macro);
+ }
+ });
+ }
+
+ /**
+ * Iterates through the available extension libraries, checks if they are included in the
+ * inventory, and adds them to the environment builder. Only the highest version of a library is
+ * added to the builder. If the extension is identified, all corresponding items are removed from
+ * the inventory.
+ */
+ private void addExtensionConfigsAndRemoveFromInventory(
+ CelEnvironment.Builder envBuilder, Set inventory) {
+ ArrayList featureSets = new ArrayList<>();
+
+ for (CelExtensionLibrary extends CelExtensionLibrary.FeatureSet> extensionLibrary :
+ extensionLibraries()) {
+ for (CelExtensionLibrary.FeatureSet featureSet : extensionLibrary.versions()) {
+ featureSets.add(NamedFeatureSet.create(extensionLibrary.name(), featureSet));
+ }
+ }
+
+ featureSets.sort(
+ Comparator.comparing(NamedFeatureSet::name)
+ .thenComparing(nfs -> nfs.featureSet().version())
+ .reversed());
+
+ Set includedExtensions = new HashSet<>();
+ for (NamedFeatureSet lib : featureSets) {
+ if (includedExtensions.contains(lib.name())) {
+ // We only need to infer the highest version library, so we can skip lower versions
+ continue;
+ }
+
+ if (checkIfExtensionIsIncludedAndRemoveFromInventory(inventory, lib.featureSet())) {
+ envBuilder.addExtensions(ExtensionConfig.of(lib.name(), lib.featureSet().version()));
+ includedExtensions.add(lib.name());
+ }
+ }
+ }
+
+ private boolean checkIfExtensionIsIncludedAndRemoveFromInventory(
+ Set inventory, CelExtensionLibrary.FeatureSet featureSet) {
+ ImmutableSet functions = featureSet.functions();
+ ArrayList includedFeatures = new ArrayList<>(functions.size());
+ for (CelFunctionDecl function : functions) {
+ for (CelOverloadDecl overload : function.overloads()) {
+ NamedOverload feature = NamedOverload.create(function.name(), overload);
+ if (!inventory.contains(feature)) {
+ return false;
+ }
+ includedFeatures.add(feature);
+ }
+ }
+
+ ImmutableSet macros = featureSet.macros();
+ for (CelMacro macro : macros) {
+ if (!inventory.contains(macro)) {
+ return false;
+ }
+ includedFeatures.add(macro);
+ }
+
+ // TODO - Add checks for variables.
+
+ inventory.removeAll(includedFeatures);
+ return true;
+ }
+
+ private void addStandardLibrarySubsetAndRemoveFromInventory(
+ CelEnvironment.Builder envBuilder, Set inventory) {
+ // Claim standard identifiers for the standard library
+ for (StandardIdentifier value : StandardIdentifier.values()) {
+ inventory.remove(
+ CelVarDecl.newVarDeclaration(value.identDecl().name(), value.identDecl().type()));
+ }
+
+ Set excludedFunctions = new HashSet<>();
+ Set includedFunctions = new HashSet<>();
+ ListMultimap excludedOverloads = ArrayListMultimap.create();
+ ListMultimap includedOverloads = ArrayListMultimap.create();
+
+ stream(StandardFunction.values())
+ .map(StandardFunction::functionDecl)
+ .forEach(
+ decl -> {
+ String functionName = decl.name();
+ boolean anyOverloadIncluded = false;
+ boolean allOverloadsIncluded = true;
+ for (CelOverloadDecl overload : decl.overloads()) {
+ NamedOverload item = NamedOverload.create(functionName, overload);
+ if (inventory.remove(item)) {
+ anyOverloadIncluded = true;
+ includedOverloads.put(functionName, overload.overloadId());
+ } else {
+ allOverloadsIncluded = false;
+ excludedOverloads.put(functionName, overload.overloadId());
+ }
+ }
+ if (!anyOverloadIncluded) {
+ excludedFunctions.add(functionName);
+ }
+ if (allOverloadsIncluded) {
+ includedFunctions.add(functionName);
+ }
+ });
+
+ Set excludedMacros = new HashSet<>();
+ Set includedMacros = new HashSet<>();
+ stream(CelStandardMacro.values())
+ .map(celStandardMacro -> celStandardMacro.getDefinition())
+ .forEach(
+ macro -> {
+ if (inventory.remove(macro)) {
+ includedMacros.add(macro.getFunction());
+ } else {
+ excludedMacros.add(macro.getFunction());
+ }
+ });
+
+ LibrarySubset.Builder subsetBuilder = LibrarySubset.newBuilder().setDisabled(false);
+ if (excludedFunctions.size() + excludedMacros.size() <= maxExcludedStandardFunctions()
+ && excludedOverloads.size() <= maxExcludedStandardFunctionOverloads()) {
+ subsetBuilder
+ .setExcludedFunctions(buildFunctionSelectors(excludedFunctions, excludedOverloads))
+ .setExcludedMacros(ImmutableSet.copyOf(excludedMacros));
+ } else {
+ subsetBuilder
+ .setIncludedFunctions(buildFunctionSelectors(includedFunctions, includedOverloads))
+ .setIncludedMacros(ImmutableSet.copyOf(includedMacros));
+ }
+
+ envBuilder.setStandardLibrarySubset(subsetBuilder.build());
+ }
+
+ private ImmutableSet buildFunctionSelectors(
+ Set functions, ListMultimap functionToOverloadsMap) {
+ ImmutableSet.Builder functionSelectors = ImmutableSet.builder();
+ for (String excludedFunction : functions) {
+ functionSelectors.add(FunctionSelector.create(excludedFunction, ImmutableSet.of()));
+ }
+
+ for (String functionName : functionToOverloadsMap.keySet()) {
+ if (functions.contains(functionName)) {
+ continue;
+ }
+ functionSelectors.add(
+ FunctionSelector.create(
+ functionName, ImmutableSet.copyOf(functionToOverloadsMap.get(functionName))));
+ }
+ return functionSelectors.build();
+ }
+
+ private void addCustomDecls(CelEnvironment.Builder envBuilder, Set inventory) {
+ // Group "orphaned" function overloads and vars by their names
+ ListMultimap extraOverloads = ArrayListMultimap.create();
+ Map extraVars = new HashMap<>();
+ for (Object item : inventory) {
+ if (item instanceof NamedOverload) {
+ extraOverloads.put(
+ ((NamedOverload) item).functionName(), ((NamedOverload) item).overload());
+ } else if (item instanceof CelVarDecl) {
+ extraVars.put(((CelVarDecl) item).name(), ((CelVarDecl) item).type());
+ }
+ }
+
+ if (!extraOverloads.isEmpty()) {
+ ImmutableSet.Builder functionDeclBuilder =
+ ImmutableSet.builder();
+ for (String functionName : extraOverloads.keySet()) {
+ functionDeclBuilder.add(
+ CelEnvironment.FunctionDecl.create(
+ functionName,
+ extraOverloads.get(functionName).stream()
+ .map(this::toCelEnvOverloadDecl)
+ .collect(toImmutableSet())));
+ }
+ envBuilder.setFunctions(functionDeclBuilder.build());
+ }
+
+ if (!extraVars.isEmpty()) {
+ ImmutableSet.Builder varDeclBuilder = ImmutableSet.builder();
+ for (String ident : extraVars.keySet()) {
+ varDeclBuilder.add(
+ CelEnvironment.VariableDecl.create(ident, toCelEnvTypeDecl(extraVars.get(ident))));
+ }
+ envBuilder.setVariables(varDeclBuilder.build());
+ }
+ }
+
+ private CelEnvironment.OverloadDecl toCelEnvOverloadDecl(CelOverloadDecl overload) {
+ OverloadDecl.Builder builder =
+ OverloadDecl.newBuilder()
+ .setId(overload.overloadId())
+ .setReturnType(toCelEnvTypeDecl(overload.resultType()));
+
+ if (overload.isInstanceFunction()) {
+ builder
+ .setTarget(toCelEnvTypeDecl(overload.parameterTypes().get(0)))
+ .setArguments(
+ overload.parameterTypes().stream()
+ .skip(1)
+ .map(this::toCelEnvTypeDecl)
+ .collect(toImmutableList()));
+ } else {
+ builder.setArguments(
+ overload.parameterTypes().stream()
+ .map(this::toCelEnvTypeDecl)
+ .collect(toImmutableList()));
+ }
+ return builder.build();
+ }
+
+ private CelEnvironment.TypeDecl toCelEnvTypeDecl(CelType type) {
+ return CelEnvironment.TypeDecl.create(CelTypes.format(type));
+ }
+
+ /** Wrapper for CelOverloadDecl, associating it with the corresponding function name. */
+ @AutoValue
+ abstract static class NamedOverload {
+ abstract String functionName();
+
+ abstract CelOverloadDecl overload();
+
+ static NamedOverload create(String functionName, CelOverloadDecl overload) {
+ return new AutoValue_CelEnvironmentExporter_NamedOverload(functionName, overload);
+ }
+ }
+
+ /**
+ * Wrapper for CelExtensionLibrary.FeatureSet, associating it with the corresponding library name.
+ */
+ @AutoValue
+ abstract static class NamedFeatureSet {
+ abstract String name();
+
+ abstract CelExtensionLibrary.FeatureSet featureSet();
+
+ static NamedFeatureSet create(String name, CelExtensionLibrary.FeatureSet featureSet) {
+ return new AutoValue_CelEnvironmentExporter_NamedFeatureSet(name, featureSet);
+ }
+ }
+}
diff --git a/bundle/src/main/java/dev/cel/bundle/CelEnvironmentYamlParser.java b/bundle/src/main/java/dev/cel/bundle/CelEnvironmentYamlParser.java
new file mode 100644
index 000000000..3acb73fa5
--- /dev/null
+++ b/bundle/src/main/java/dev/cel/bundle/CelEnvironmentYamlParser.java
@@ -0,0 +1,648 @@
+// Copyright 2025 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.bundle;
+
+import static dev.cel.common.formats.YamlHelper.ERROR;
+import static dev.cel.common.formats.YamlHelper.assertRequiredFields;
+import static dev.cel.common.formats.YamlHelper.assertYamlType;
+import static dev.cel.common.formats.YamlHelper.newBoolean;
+import static dev.cel.common.formats.YamlHelper.newInteger;
+import static dev.cel.common.formats.YamlHelper.newString;
+import static dev.cel.common.formats.YamlHelper.parseYamlSource;
+import static dev.cel.common.formats.YamlHelper.validateYamlType;
+import static java.util.Collections.singletonList;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+import dev.cel.bundle.CelEnvironment.ExtensionConfig;
+import dev.cel.bundle.CelEnvironment.FunctionDecl;
+import dev.cel.bundle.CelEnvironment.LibrarySubset;
+import dev.cel.bundle.CelEnvironment.LibrarySubset.FunctionSelector;
+import dev.cel.bundle.CelEnvironment.LibrarySubset.OverloadSelector;
+import dev.cel.bundle.CelEnvironment.OverloadDecl;
+import dev.cel.bundle.CelEnvironment.TypeDecl;
+import dev.cel.bundle.CelEnvironment.VariableDecl;
+import dev.cel.common.CelIssue;
+import dev.cel.common.formats.CelFileSource;
+import dev.cel.common.formats.ParserContext;
+import dev.cel.common.formats.YamlHelper.YamlNodeType;
+import dev.cel.common.formats.YamlParserContextImpl;
+import dev.cel.common.internal.CelCodePointArray;
+import org.jspecify.annotations.Nullable;
+import org.yaml.snakeyaml.DumperOptions.FlowStyle;
+import org.yaml.snakeyaml.nodes.MappingNode;
+import org.yaml.snakeyaml.nodes.Node;
+import org.yaml.snakeyaml.nodes.NodeTuple;
+import org.yaml.snakeyaml.nodes.ScalarNode;
+import org.yaml.snakeyaml.nodes.SequenceNode;
+import org.yaml.snakeyaml.nodes.Tag;
+
+/**
+ * CelEnvironmentYamlParser intakes a YAML document that describes the structure of a CEL
+ * environment, parses it then creates a {@link CelEnvironment}.
+ */
+public final class CelEnvironmentYamlParser {
+ // Sentinel values to be returned for various declarations when parsing failure is encountered.
+ private static final TypeDecl ERROR_TYPE_DECL = TypeDecl.create(ERROR);
+ private static final VariableDecl ERROR_VARIABLE_DECL =
+ VariableDecl.create(ERROR, ERROR_TYPE_DECL);
+ private static final FunctionDecl ERROR_FUNCTION_DECL =
+ FunctionDecl.create(ERROR, ImmutableSet.of());
+ private static final ExtensionConfig ERROR_EXTENSION_DECL = ExtensionConfig.of(ERROR);
+ private static final FunctionSelector ERROR_FUNCTION_SELECTOR =
+ FunctionSelector.create(ERROR, ImmutableSet.of());
+
+ /** Generates a new instance of {@code CelEnvironmentYamlParser}. */
+ public static CelEnvironmentYamlParser newInstance() {
+ return new CelEnvironmentYamlParser();
+ }
+
+ /** Parsers the input {@code environmentYamlSource} and returns a {@link CelEnvironment}. */
+ public CelEnvironment parse(String environmentYamlSource) throws CelEnvironmentException {
+ return parse(environmentYamlSource, " ");
+ }
+
+ /**
+ * Parses the input {@code environmentYamlSource} and returns a {@link CelEnvironment}.
+ *
+ * The {@code description} may be used to help tailor error messages for the location where the
+ * {@code environmentYamlSource} originates, e.g. a file name or form UI element.
+ */
+ public CelEnvironment parse(String environmentYamlSource, String description)
+ throws CelEnvironmentException {
+ CelEnvironmentYamlParser.ParserImpl parser = new CelEnvironmentYamlParser.ParserImpl();
+
+ return parser.parseYaml(environmentYamlSource, description);
+ }
+
+ private ImmutableSet parseVariables(ParserContext ctx, Node node) {
+ long valueId = ctx.collectMetadata(node);
+ ImmutableSet.Builder variableSetBuilder = ImmutableSet.builder();
+ if (!assertYamlType(ctx, valueId, node, YamlNodeType.LIST)) {
+ return variableSetBuilder.build();
+ }
+
+ SequenceNode variableListNode = (SequenceNode) node;
+ for (Node elementNode : variableListNode.getValue()) {
+ variableSetBuilder.add(parseVariable(ctx, elementNode));
+ }
+
+ return variableSetBuilder.build();
+ }
+
+ private VariableDecl parseVariable(ParserContext ctx, Node node) {
+ long variableId = ctx.collectMetadata(node);
+ if (!assertYamlType(ctx, variableId, node, YamlNodeType.MAP)) {
+ return ERROR_VARIABLE_DECL;
+ }
+
+ MappingNode variableMap = (MappingNode) node;
+ VariableDecl.Builder builder = VariableDecl.newBuilder();
+ TypeDecl.Builder typeDeclBuilder = null;
+ for (NodeTuple nodeTuple : variableMap.getValue()) {
+ Node keyNode = nodeTuple.getKeyNode();
+ long keyId = ctx.collectMetadata(keyNode);
+ Node valueNode = nodeTuple.getValueNode();
+ String keyName = ((ScalarNode) keyNode).getValue();
+ switch (keyName) {
+ case "name":
+ builder.setName(newString(ctx, valueNode));
+ break;
+ case "type":
+ if (typeDeclBuilder != null) {
+ ctx.reportError(
+ keyId,
+ String.format(
+ "'type' tag cannot be used together with inlined 'type_name', 'is_type_param'"
+ + " or 'params': %s",
+ keyName));
+ break;
+ }
+ builder.setType(parseTypeDecl(ctx, valueNode));
+ break;
+ case "type_name":
+ case "is_type_param":
+ case "params":
+ if (typeDeclBuilder == null) {
+ typeDeclBuilder = TypeDecl.newBuilder();
+ }
+ typeDeclBuilder = parseInlinedTypeDecl(ctx, keyId, keyNode, valueNode, typeDeclBuilder);
+ break;
+ default:
+ ctx.reportError(keyId, String.format("Unsupported variable tag: %s", keyName));
+ break;
+ }
+ }
+
+ if (typeDeclBuilder != null) {
+ if (!assertRequiredFields(ctx, variableId, typeDeclBuilder.getMissingRequiredFieldNames())) {
+ return ERROR_VARIABLE_DECL;
+ }
+ builder.setType(typeDeclBuilder.build());
+ }
+
+ if (!assertRequiredFields(ctx, variableId, builder.getMissingRequiredFieldNames())) {
+ return ERROR_VARIABLE_DECL;
+ }
+
+ return builder.build();
+ }
+
+ private ImmutableSet parseFunctions(ParserContext ctx, Node node) {
+ long valueId = ctx.collectMetadata(node);
+ ImmutableSet.Builder functionSetBuilder = ImmutableSet.builder();
+ if (!assertYamlType(ctx, valueId, node, YamlNodeType.LIST)) {
+ return functionSetBuilder.build();
+ }
+
+ SequenceNode functionListNode = (SequenceNode) node;
+ for (Node elementNode : functionListNode.getValue()) {
+ functionSetBuilder.add(parseFunction(ctx, elementNode));
+ }
+
+ return functionSetBuilder.build();
+ }
+
+ private FunctionDecl parseFunction(ParserContext ctx, Node node) {
+ long functionId = ctx.collectMetadata(node);
+ if (!assertYamlType(ctx, functionId, node, YamlNodeType.MAP)) {
+ return ERROR_FUNCTION_DECL;
+ }
+
+ MappingNode functionMap = (MappingNode) node;
+ FunctionDecl.Builder builder = FunctionDecl.newBuilder();
+ for (NodeTuple nodeTuple : functionMap.getValue()) {
+ Node keyNode = nodeTuple.getKeyNode();
+ long keyId = ctx.collectMetadata(keyNode);
+ Node valueNode = nodeTuple.getValueNode();
+ String keyName = ((ScalarNode) keyNode).getValue();
+ switch (keyName) {
+ case "name":
+ builder.setName(newString(ctx, valueNode));
+ break;
+ case "overloads":
+ builder.setOverloads(parseOverloads(ctx, valueNode));
+ break;
+ default:
+ ctx.reportError(keyId, String.format("Unsupported function tag: %s", keyName));
+ break;
+ }
+ }
+
+ if (!assertRequiredFields(ctx, functionId, builder.getMissingRequiredFieldNames())) {
+ return ERROR_FUNCTION_DECL;
+ }
+
+ return builder.build();
+ }
+
+ private static ImmutableSet parseOverloads(ParserContext ctx, Node node) {
+ long listId = ctx.collectMetadata(node);
+ ImmutableSet.Builder overloadSetBuilder = ImmutableSet.builder();
+ if (!assertYamlType(ctx, listId, node, YamlNodeType.LIST)) {
+ return overloadSetBuilder.build();
+ }
+
+ SequenceNode overloadListNode = (SequenceNode) node;
+ for (Node overloadMapNode : overloadListNode.getValue()) {
+ long overloadMapId = ctx.collectMetadata(overloadMapNode);
+ if (!assertYamlType(ctx, overloadMapId, overloadMapNode, YamlNodeType.MAP)) {
+ continue;
+ }
+
+ MappingNode mapNode = (MappingNode) overloadMapNode;
+ OverloadDecl.Builder overloadDeclBuilder = OverloadDecl.newBuilder();
+ for (NodeTuple nodeTuple : mapNode.getValue()) {
+ Node keyNode = nodeTuple.getKeyNode();
+ long keyId = ctx.collectMetadata(keyNode);
+ if (!assertYamlType(ctx, keyId, keyNode, YamlNodeType.STRING, YamlNodeType.TEXT)) {
+ continue;
+ }
+
+ Node valueNode = nodeTuple.getValueNode();
+ String fieldName = ((ScalarNode) keyNode).getValue();
+ switch (fieldName) {
+ case "id":
+ overloadDeclBuilder.setId(newString(ctx, valueNode));
+ break;
+ case "args":
+ overloadDeclBuilder.addArguments(parseOverloadArguments(ctx, valueNode));
+ break;
+ case "return":
+ overloadDeclBuilder.setReturnType(parseTypeDecl(ctx, valueNode));
+ break;
+ case "target":
+ overloadDeclBuilder.setTarget(parseTypeDecl(ctx, valueNode));
+ break;
+ default:
+ ctx.reportError(keyId, String.format("Unsupported overload tag: %s", fieldName));
+ break;
+ }
+ }
+
+ if (assertRequiredFields(
+ ctx, overloadMapId, overloadDeclBuilder.getMissingRequiredFieldNames())) {
+ overloadSetBuilder.add(overloadDeclBuilder.build());
+ }
+ }
+
+ return overloadSetBuilder.build();
+ }
+
+ private static ImmutableList parseOverloadArguments(
+ ParserContext ctx, Node node) {
+ long listValueId = ctx.collectMetadata(node);
+ if (!assertYamlType(ctx, listValueId, node, YamlNodeType.LIST)) {
+ return ImmutableList.of();
+ }
+ SequenceNode paramsListNode = (SequenceNode) node;
+ ImmutableList.Builder builder = ImmutableList.builder();
+ for (Node elementNode : paramsListNode.getValue()) {
+ builder.add(parseTypeDecl(ctx, elementNode));
+ }
+
+ return builder.build();
+ }
+
+ private static ImmutableSet parseExtensions(ParserContext ctx, Node node) {
+ long valueId = ctx.collectMetadata(node);
+ ImmutableSet.Builder extensionConfigBuilder = ImmutableSet.builder();
+ if (!assertYamlType(ctx, valueId, node, YamlNodeType.LIST)) {
+ return extensionConfigBuilder.build();
+ }
+
+ SequenceNode extensionListNode = (SequenceNode) node;
+ for (Node elementNode : extensionListNode.getValue()) {
+ extensionConfigBuilder.add(parseExtension(ctx, elementNode));
+ }
+
+ return extensionConfigBuilder.build();
+ }
+
+ private static ExtensionConfig parseExtension(ParserContext ctx, Node node) {
+ long extensionId = ctx.collectMetadata(node);
+ if (!assertYamlType(ctx, extensionId, node, YamlNodeType.MAP)) {
+ return ERROR_EXTENSION_DECL;
+ }
+
+ MappingNode extensionMap = (MappingNode) node;
+ ExtensionConfig.Builder builder = ExtensionConfig.newBuilder();
+ for (NodeTuple nodeTuple : extensionMap.getValue()) {
+ Node keyNode = nodeTuple.getKeyNode();
+ long keyId = ctx.collectMetadata(keyNode);
+ Node valueNode = nodeTuple.getValueNode();
+ String keyName = ((ScalarNode) keyNode).getValue();
+ switch (keyName) {
+ case "name":
+ builder.setName(newString(ctx, valueNode));
+ break;
+ case "version":
+ if (validateYamlType(valueNode, YamlNodeType.INTEGER)) {
+ builder.setVersion(newInteger(ctx, valueNode));
+ break;
+ } else if (validateYamlType(valueNode, YamlNodeType.STRING, YamlNodeType.TEXT)) {
+ String versionStr = newString(ctx, valueNode);
+ if (versionStr.equals("latest")) {
+ builder.setVersion(Integer.MAX_VALUE);
+ break;
+ }
+
+ Integer versionInt = tryParse(versionStr);
+ if (versionInt != null) {
+ builder.setVersion(versionInt);
+ break;
+ }
+ // Fall-through
+ }
+ ctx.reportError(keyId, String.format("Unsupported version tag: %s", keyName));
+ break;
+ default:
+ ctx.reportError(keyId, String.format("Unsupported extension tag: %s", keyName));
+ break;
+ }
+ }
+
+ if (!assertRequiredFields(ctx, extensionId, builder.getMissingRequiredFieldNames())) {
+ return ERROR_EXTENSION_DECL;
+ }
+
+ return builder.build();
+ }
+
+ private static LibrarySubset parseLibrarySubset(ParserContext ctx, Node node) {
+ LibrarySubset.Builder builder = LibrarySubset.newBuilder().setDisabled(false);
+ MappingNode subsetMap = (MappingNode) node;
+ for (NodeTuple nodeTuple : subsetMap.getValue()) {
+ Node keyNode = nodeTuple.getKeyNode();
+ long keyId = ctx.collectMetadata(keyNode);
+ Node valueNode = nodeTuple.getValueNode();
+ String keyName = ((ScalarNode) keyNode).getValue();
+ switch (keyName) {
+ case "disabled":
+ builder.setDisabled(newBoolean(ctx, valueNode));
+ break;
+ case "disable_macros":
+ builder.setMacrosDisabled(newBoolean(ctx, valueNode));
+ break;
+ case "include_macros":
+ builder.setIncludedMacros(parseMacroNameSet(ctx, valueNode));
+ break;
+ case "exclude_macros":
+ builder.setExcludedMacros(parseMacroNameSet(ctx, valueNode));
+ break;
+ case "include_functions":
+ builder.setIncludedFunctions(parseFunctionSelectors(ctx, valueNode));
+ break;
+ case "exclude_functions":
+ builder.setExcludedFunctions(parseFunctionSelectors(ctx, valueNode));
+ break;
+ default:
+ ctx.reportError(keyId, String.format("Unsupported library subset tag: %s", keyName));
+ break;
+ }
+ }
+ return builder.build();
+ }
+
+ private static ImmutableSet parseMacroNameSet(ParserContext ctx, Node node) {
+ long valueId = ctx.collectMetadata(node);
+ if (!assertYamlType(ctx, valueId, node, YamlNodeType.LIST)) {
+ return ImmutableSet.of(ERROR);
+ }
+
+ ImmutableSet.Builder builder = ImmutableSet.builder();
+ SequenceNode nameListNode = (SequenceNode) node;
+ for (Node elementNode : nameListNode.getValue()) {
+ long elementId = ctx.collectMetadata(elementNode);
+ if (!assertYamlType(ctx, elementId, elementNode, YamlNodeType.STRING)) {
+ return ImmutableSet.of(ERROR);
+ }
+
+ builder.add(((ScalarNode) elementNode).getValue());
+ }
+ return builder.build();
+ }
+
+ private static ImmutableSet parseFunctionSelectors(
+ ParserContext ctx, Node node) {
+ long valueId = ctx.collectMetadata(node);
+ ImmutableSet.Builder functionSetBuilder = ImmutableSet.builder();
+ if (!assertYamlType(ctx, valueId, node, YamlNodeType.LIST)) {
+ return functionSetBuilder.build();
+ }
+
+ SequenceNode functionListNode = (SequenceNode) node;
+ for (Node elementNode : functionListNode.getValue()) {
+ functionSetBuilder.add(parseFunctionSelector(ctx, elementNode));
+ }
+
+ return functionSetBuilder.build();
+ }
+
+ private static FunctionSelector parseFunctionSelector(ParserContext ctx, Node node) {
+ FunctionSelector.Builder builder = FunctionSelector.newBuilder();
+ long functionId = ctx.collectMetadata(node);
+ if (!assertYamlType(ctx, functionId, node, YamlNodeType.MAP)) {
+ return ERROR_FUNCTION_SELECTOR;
+ }
+
+ MappingNode functionMap = (MappingNode) node;
+ for (NodeTuple nodeTuple : functionMap.getValue()) {
+ Node keyNode = nodeTuple.getKeyNode();
+ long keyId = ctx.collectMetadata(keyNode);
+ Node valueNode = nodeTuple.getValueNode();
+ String keyName = ((ScalarNode) keyNode).getValue();
+ switch (keyName) {
+ case "name":
+ builder.setName(newString(ctx, valueNode));
+ break;
+ case "overloads":
+ builder.setOverloads(parseFunctionOverloadsSelector(ctx, valueNode));
+ break;
+ default:
+ ctx.reportError(keyId, String.format("Unsupported function selector tag: %s", keyName));
+ break;
+ }
+ }
+
+ if (!assertRequiredFields(ctx, functionId, builder.getMissingRequiredFieldNames())) {
+ return ERROR_FUNCTION_SELECTOR;
+ }
+
+ return builder.build();
+ }
+
+ private static ImmutableSet parseFunctionOverloadsSelector(
+ ParserContext ctx, Node node) {
+ long listId = ctx.collectMetadata(node);
+ ImmutableSet.Builder overloadSetBuilder = ImmutableSet.builder();
+ if (!assertYamlType(ctx, listId, node, YamlNodeType.LIST)) {
+ return overloadSetBuilder.build();
+ }
+
+ SequenceNode overloadListNode = (SequenceNode) node;
+ for (Node overloadMapNode : overloadListNode.getValue()) {
+ long overloadMapId = ctx.collectMetadata(overloadMapNode);
+ if (!assertYamlType(ctx, overloadMapId, overloadMapNode, YamlNodeType.MAP)) {
+ continue;
+ }
+
+ MappingNode mapNode = (MappingNode) overloadMapNode;
+ OverloadSelector.Builder overloadDeclBuilder = OverloadSelector.newBuilder();
+ for (NodeTuple nodeTuple : mapNode.getValue()) {
+ Node keyNode = nodeTuple.getKeyNode();
+ long keyId = ctx.collectMetadata(keyNode);
+ if (!assertYamlType(ctx, keyId, keyNode, YamlNodeType.STRING, YamlNodeType.TEXT)) {
+ continue;
+ }
+
+ Node valueNode = nodeTuple.getValueNode();
+ String fieldName = ((ScalarNode) keyNode).getValue();
+ switch (fieldName) {
+ case "id":
+ overloadDeclBuilder.setId(newString(ctx, valueNode));
+ break;
+ default:
+ ctx.reportError(
+ keyId, String.format("Unsupported overload selector tag: %s", fieldName));
+ break;
+ }
+ }
+
+ if (assertRequiredFields(
+ ctx, overloadMapId, overloadDeclBuilder.getMissingRequiredFieldNames())) {
+ overloadSetBuilder.add(overloadDeclBuilder.build());
+ }
+ }
+
+ return overloadSetBuilder.build();
+ }
+
+ private static @Nullable Integer tryParse(String str) {
+ try {
+ return Integer.parseInt(str);
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ }
+
+ @CanIgnoreReturnValue
+ private static TypeDecl.Builder parseInlinedTypeDecl(
+ ParserContext ctx, long keyId, Node keyNode, Node valueNode, TypeDecl.Builder builder) {
+ if (!assertYamlType(ctx, keyId, keyNode, YamlNodeType.STRING, YamlNodeType.TEXT)) {
+ return builder;
+ }
+
+ // Create a synthetic node to make this behave as if a `type: ` parent node actually exists.
+ MappingNode mapNode =
+ new MappingNode(
+ Tag.MAP, /* value= */ singletonList(new NodeTuple(keyNode, valueNode)), FlowStyle.AUTO);
+
+ return parseTypeDeclFields(ctx, mapNode, builder);
+ }
+
+ private static TypeDecl parseTypeDecl(ParserContext ctx, Node node) {
+ TypeDecl.Builder builder = TypeDecl.newBuilder();
+ long id = ctx.collectMetadata(node);
+ if (!assertYamlType(ctx, id, node, YamlNodeType.MAP)) {
+ return ERROR_TYPE_DECL;
+ }
+
+ MappingNode mapNode = (MappingNode) node;
+ return parseTypeDeclFields(ctx, mapNode, builder).build();
+ }
+
+ @CanIgnoreReturnValue
+ private static TypeDecl.Builder parseTypeDeclFields(
+ ParserContext ctx, MappingNode mapNode, TypeDecl.Builder builder) {
+ for (NodeTuple nodeTuple : mapNode.getValue()) {
+ Node keyNode = nodeTuple.getKeyNode();
+ long keyId = ctx.collectMetadata(keyNode);
+ if (!assertYamlType(ctx, keyId, keyNode, YamlNodeType.STRING, YamlNodeType.TEXT)) {
+ continue;
+ }
+
+ Node valueNode = nodeTuple.getValueNode();
+ String fieldName = ((ScalarNode) keyNode).getValue();
+ switch (fieldName) {
+ case "type_name":
+ builder.setName(newString(ctx, valueNode));
+ break;
+ case "is_type_param":
+ builder.setIsTypeParam(newBoolean(ctx, valueNode));
+ break;
+ case "params":
+ long listValueId = ctx.collectMetadata(valueNode);
+ if (!assertYamlType(ctx, listValueId, valueNode, YamlNodeType.LIST)) {
+ break;
+ }
+ SequenceNode paramsListNode = (SequenceNode) valueNode;
+ for (Node elementNode : paramsListNode.getValue()) {
+ builder.addParams(parseTypeDecl(ctx, elementNode));
+ }
+ break;
+ default:
+ ctx.reportError(keyId, String.format("Unsupported type decl tag: %s", fieldName));
+ break;
+ }
+ }
+ return builder;
+ }
+
+ private class ParserImpl {
+
+ private CelEnvironment parseYaml(String source, String description)
+ throws CelEnvironmentException {
+ Node node;
+ try {
+ node =
+ parseYamlSource(source)
+ .orElseThrow(
+ () ->
+ new CelEnvironmentException(
+ String.format("YAML document empty or malformed: %s", source)));
+ } catch (RuntimeException e) {
+ throw new CelEnvironmentException("YAML document is malformed: " + e.getMessage(), e);
+ }
+
+ CelFileSource environmentSource =
+ CelFileSource.newBuilder(CelCodePointArray.fromString(source))
+ .setDescription(description)
+ .build();
+ ParserContext ctx = YamlParserContextImpl.newInstance(environmentSource);
+ CelEnvironment.Builder builder = parseConfig(ctx, node);
+ environmentSource =
+ environmentSource.toBuilder().setPositionsMap(ctx.getIdToOffsetMap()).build();
+
+ if (!ctx.getIssues().isEmpty()) {
+ throw new CelEnvironmentException(
+ CelIssue.toDisplayString(ctx.getIssues(), environmentSource));
+ }
+
+ return builder.setSource(environmentSource).build();
+ }
+
+ private CelEnvironment.Builder parseConfig(ParserContext ctx, Node node) {
+ CelEnvironment.Builder builder = CelEnvironment.newBuilder();
+ long id = ctx.collectMetadata(node);
+ if (!assertYamlType(ctx, id, node, YamlNodeType.MAP)) {
+ return builder;
+ }
+
+ MappingNode rootNode = (MappingNode) node;
+ for (NodeTuple nodeTuple : rootNode.getValue()) {
+ Node keyNode = nodeTuple.getKeyNode();
+ long keyId = ctx.collectMetadata(keyNode);
+ if (!assertYamlType(ctx, keyId, keyNode, YamlNodeType.STRING, YamlNodeType.TEXT)) {
+ continue;
+ }
+
+ Node valueNode = nodeTuple.getValueNode();
+ String fieldName = ((ScalarNode) keyNode).getValue();
+ switch (fieldName) {
+ case "name":
+ builder.setName(newString(ctx, valueNode));
+ break;
+ case "description":
+ builder.setDescription(newString(ctx, valueNode));
+ break;
+ case "container":
+ builder.setContainer(newString(ctx, valueNode));
+ break;
+ case "variables":
+ builder.setVariables(parseVariables(ctx, valueNode));
+ break;
+ case "functions":
+ builder.setFunctions(parseFunctions(ctx, valueNode));
+ break;
+ case "extensions":
+ builder.addExtensions(parseExtensions(ctx, valueNode));
+ break;
+ case "stdlib":
+ builder.setStandardLibrarySubset(parseLibrarySubset(ctx, valueNode));
+ break;
+ default:
+ ctx.reportError(id, "Unknown config tag: " + fieldName);
+ // continue handling the rest of the nodes
+ }
+ }
+
+ return builder;
+ }
+ }
+
+ private CelEnvironmentYamlParser() {}
+}
diff --git a/bundle/src/main/java/dev/cel/bundle/CelEnvironmentYamlSerializer.java b/bundle/src/main/java/dev/cel/bundle/CelEnvironmentYamlSerializer.java
new file mode 100644
index 000000000..687bf4ff9
--- /dev/null
+++ b/bundle/src/main/java/dev/cel/bundle/CelEnvironmentYamlSerializer.java
@@ -0,0 +1,217 @@
+// Copyright 2025 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.bundle;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import dev.cel.bundle.CelEnvironment.LibrarySubset;
+import dev.cel.bundle.CelEnvironment.LibrarySubset.FunctionSelector;
+import dev.cel.bundle.CelEnvironment.LibrarySubset.OverloadSelector;
+import java.util.Comparator;
+import org.yaml.snakeyaml.DumperOptions;
+import org.yaml.snakeyaml.Yaml;
+import org.yaml.snakeyaml.nodes.Node;
+import org.yaml.snakeyaml.representer.Represent;
+import org.yaml.snakeyaml.representer.Representer;
+
+/** Serializes a CelEnvironment into a YAML file. */
+public final class CelEnvironmentYamlSerializer extends Representer {
+
+ private static DumperOptions initDumperOptions() {
+ DumperOptions options = new DumperOptions();
+ options.setIndent(2);
+ options.setPrettyFlow(true);
+ options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
+ return options;
+ }
+
+ private static final DumperOptions YAML_OPTIONS = initDumperOptions();
+
+ private static final CelEnvironmentYamlSerializer INSTANCE = new CelEnvironmentYamlSerializer();
+
+ private CelEnvironmentYamlSerializer() {
+ super(YAML_OPTIONS);
+ this.multiRepresenters.put(CelEnvironment.class, new RepresentCelEnvironment());
+ this.multiRepresenters.put(CelEnvironment.VariableDecl.class, new RepresentVariableDecl());
+ this.multiRepresenters.put(CelEnvironment.FunctionDecl.class, new RepresentFunctionDecl());
+ this.multiRepresenters.put(CelEnvironment.OverloadDecl.class, new RepresentOverloadDecl());
+ this.multiRepresenters.put(CelEnvironment.TypeDecl.class, new RepresentTypeDecl());
+ this.multiRepresenters.put(
+ CelEnvironment.ExtensionConfig.class, new RepresentExtensionConfig());
+ this.multiRepresenters.put(CelEnvironment.LibrarySubset.class, new RepresentLibrarySubset());
+ this.multiRepresenters.put(
+ CelEnvironment.LibrarySubset.FunctionSelector.class, new RepresentFunctionSelector());
+ this.multiRepresenters.put(
+ CelEnvironment.LibrarySubset.OverloadSelector.class, new RepresentOverloadSelector());
+ }
+
+ public static String toYaml(CelEnvironment environment) {
+ // Yaml is not thread-safe, so we create a new instance for each serialization.
+ Yaml yaml = new Yaml(INSTANCE, YAML_OPTIONS);
+ return yaml.dump(environment);
+ }
+
+ private final class RepresentCelEnvironment implements Represent {
+ @Override
+ public Node representData(Object data) {
+ CelEnvironment environment = (CelEnvironment) data;
+ ImmutableMap.Builder configMap = new ImmutableMap.Builder<>();
+ configMap.put("name", environment.name());
+ if (!environment.description().isEmpty()) {
+ configMap.put("description", environment.description());
+ }
+ if (!environment.container().isEmpty()) {
+ configMap.put("container", environment.container());
+ }
+ if (!environment.extensions().isEmpty()) {
+ configMap.put("extensions", environment.extensions().asList());
+ }
+ if (!environment.variables().isEmpty()) {
+ configMap.put("variables", environment.variables().asList());
+ }
+ if (!environment.functions().isEmpty()) {
+ configMap.put("functions", environment.functions().asList());
+ }
+ if (environment.standardLibrarySubset().isPresent()) {
+ configMap.put("stdlib", environment.standardLibrarySubset().get());
+ }
+ return represent(configMap.buildOrThrow());
+ }
+ }
+
+ private final class RepresentExtensionConfig implements Represent {
+ @Override
+ public Node representData(Object data) {
+ CelEnvironment.ExtensionConfig extension = (CelEnvironment.ExtensionConfig) data;
+ ImmutableMap.Builder configMap = new ImmutableMap.Builder<>();
+ configMap.put("name", extension.name());
+ if (extension.version() > 0 && extension.version() != Integer.MAX_VALUE) {
+ configMap.put("version", extension.version());
+ }
+ return represent(configMap.buildOrThrow());
+ }
+ }
+
+ private final class RepresentVariableDecl implements Represent {
+ @Override
+ public Node representData(Object data) {
+ CelEnvironment.VariableDecl variable = (CelEnvironment.VariableDecl) data;
+ ImmutableMap.Builder configMap = new ImmutableMap.Builder<>();
+ configMap.put("name", variable.name()).put("type_name", variable.type().name());
+ if (!variable.type().params().isEmpty()) {
+ configMap.put("params", variable.type().params());
+ }
+ return represent(configMap.buildOrThrow());
+ }
+ }
+
+ private final class RepresentFunctionDecl implements Represent {
+ @Override
+ public Node representData(Object data) {
+ CelEnvironment.FunctionDecl function = (CelEnvironment.FunctionDecl) data;
+ ImmutableMap.Builder configMap = new ImmutableMap.Builder<>();
+ configMap.put("name", function.name()).put("overloads", function.overloads().asList());
+ return represent(configMap.buildOrThrow());
+ }
+ }
+
+ private final class RepresentOverloadDecl implements Represent {
+ @Override
+ public Node representData(Object data) {
+ CelEnvironment.OverloadDecl overload = (CelEnvironment.OverloadDecl) data;
+ ImmutableMap.Builder configMap = new ImmutableMap.Builder<>();
+ configMap.put("id", overload.id());
+ if (overload.target().isPresent()) {
+ configMap.put("target", overload.target().get());
+ }
+ configMap.put("args", overload.arguments()).put("return", overload.returnType());
+ return represent(configMap.buildOrThrow());
+ }
+ }
+
+ private final class RepresentTypeDecl implements Represent {
+ @Override
+ public Node representData(Object data) {
+ CelEnvironment.TypeDecl type = (CelEnvironment.TypeDecl) data;
+ ImmutableMap.Builder configMap = new ImmutableMap.Builder<>();
+ configMap.put("type_name", type.name());
+ if (!type.params().isEmpty()) {
+ configMap.put("params", type.params());
+ }
+ if (type.isTypeParam()) {
+ configMap.put("is_type_param", type.isTypeParam());
+ }
+ return represent(configMap.buildOrThrow());
+ }
+ }
+
+ private final class RepresentLibrarySubset implements Represent {
+ @Override
+ public Node representData(Object data) {
+ LibrarySubset librarySubset = (LibrarySubset) data;
+ ImmutableMap.Builder configMap = new ImmutableMap.Builder<>();
+ if (librarySubset.disabled()) {
+ configMap.put("disabled", true);
+ }
+ if (librarySubset.macrosDisabled()) {
+ configMap.put("disable_macros", true);
+ }
+ if (!librarySubset.includedMacros().isEmpty()) {
+ configMap.put("include_macros", ImmutableList.sortedCopyOf(librarySubset.includedMacros()));
+ }
+ if (!librarySubset.excludedMacros().isEmpty()) {
+ configMap.put("exclude_macros", ImmutableList.sortedCopyOf(librarySubset.excludedMacros()));
+ }
+ if (!librarySubset.includedFunctions().isEmpty()) {
+ configMap.put(
+ "include_functions",
+ ImmutableList.sortedCopyOf(
+ Comparator.comparing(FunctionSelector::name), librarySubset.includedFunctions()));
+ }
+ if (!librarySubset.excludedFunctions().isEmpty()) {
+ configMap.put(
+ "exclude_functions",
+ ImmutableList.sortedCopyOf(
+ Comparator.comparing(FunctionSelector::name), librarySubset.excludedFunctions()));
+ }
+ return represent(configMap.buildOrThrow());
+ }
+ }
+
+ private final class RepresentFunctionSelector implements Represent {
+ @Override
+ public Node representData(Object data) {
+ FunctionSelector functionSelector = (FunctionSelector) data;
+ ImmutableMap.Builder configMap = new ImmutableMap.Builder<>();
+ configMap.put("name", functionSelector.name());
+ if (!functionSelector.overloads().isEmpty()) {
+ configMap.put(
+ "overloads",
+ ImmutableList.sortedCopyOf(
+ Comparator.comparing(OverloadSelector::id), functionSelector.overloads()));
+ }
+
+ return represent(configMap.buildOrThrow());
+ }
+ }
+
+ private final class RepresentOverloadSelector implements Represent {
+ @Override
+ public Node representData(Object data) {
+ OverloadSelector overloadSelector = (OverloadSelector) data;
+ return represent(ImmutableMap.of("id", overloadSelector.id()));
+ }
+ }
+}
diff --git a/bundle/src/main/java/dev/cel/bundle/CelFactory.java b/bundle/src/main/java/dev/cel/bundle/CelFactory.java
index c69c4a3a5..a2080bc25 100644
--- a/bundle/src/main/java/dev/cel/bundle/CelFactory.java
+++ b/bundle/src/main/java/dev/cel/bundle/CelFactory.java
@@ -17,8 +17,10 @@
import dev.cel.checker.CelCheckerLegacyImpl;
import dev.cel.common.CelOptions;
import dev.cel.compiler.CelCompiler;
+import dev.cel.compiler.CelCompilerImpl;
import dev.cel.parser.CelParserImpl;
import dev.cel.runtime.CelRuntime;
+import dev.cel.runtime.CelRuntimeLegacyImpl;
/** Helper class to configure the entire CEL stack in a common interface. */
public final class CelFactory {
@@ -33,7 +35,10 @@ private CelFactory() {}
* evaluation are enabled by default.
*/
public static CelBuilder standardCelBuilder() {
- return CelImpl.newBuilder(CelParserImpl.newBuilder(), CelCheckerLegacyImpl.newBuilder())
+ return CelImpl.newBuilder(
+ CelCompilerImpl.newBuilder(
+ CelParserImpl.newBuilder(), CelCheckerLegacyImpl.newBuilder()),
+ CelRuntimeLegacyImpl.newBuilder())
.setOptions(CelOptions.current().build())
.setStandardEnvironmentEnabled(true);
}
diff --git a/bundle/src/main/java/dev/cel/bundle/CelImpl.java b/bundle/src/main/java/dev/cel/bundle/CelImpl.java
index 20086b114..795e202e3 100644
--- a/bundle/src/main/java/dev/cel/bundle/CelImpl.java
+++ b/bundle/src/main/java/dev/cel/bundle/CelImpl.java
@@ -31,6 +31,7 @@
import dev.cel.checker.ProtoTypeMask;
import dev.cel.checker.TypeProvider;
import dev.cel.common.CelAbstractSyntaxTree;
+import dev.cel.common.CelContainer;
import dev.cel.common.CelFunctionDecl;
import dev.cel.common.CelOptions;
import dev.cel.common.CelSource;
@@ -39,13 +40,12 @@
import dev.cel.common.internal.EnvVisitable;
import dev.cel.common.internal.EnvVisitor;
import dev.cel.common.internal.FileDescriptorSetConverter;
+import dev.cel.common.types.CelProtoTypes;
import dev.cel.common.types.CelType;
import dev.cel.common.types.CelTypeProvider;
-import dev.cel.common.types.CelTypes;
import dev.cel.common.values.CelValueProvider;
import dev.cel.compiler.CelCompiler;
import dev.cel.compiler.CelCompilerBuilder;
-import dev.cel.compiler.CelCompilerImpl;
import dev.cel.compiler.CelCompilerLibrary;
import dev.cel.parser.CelMacro;
import dev.cel.parser.CelParserBuilder;
@@ -53,7 +53,6 @@
import dev.cel.runtime.CelEvaluationException;
import dev.cel.runtime.CelRuntime;
import dev.cel.runtime.CelRuntimeBuilder;
-import dev.cel.runtime.CelRuntimeLegacyImpl;
import dev.cel.runtime.CelRuntimeLibrary;
import java.util.Arrays;
import java.util.function.Function;
@@ -89,6 +88,11 @@ public CelValidationResult check(CelAbstractSyntaxTree ast) {
return compiler.get().check(ast);
}
+ @Override
+ public CelTypeProvider getTypeProvider() {
+ return compiler.get().getTypeProvider();
+ }
+
@Override
public CelRuntime.Program createProgram(CelAbstractSyntaxTree ast) throws CelEvaluationException {
return runtime.get().createProgram(ast);
@@ -102,6 +106,31 @@ public void accept(EnvVisitor envVisitor) {
}
}
+ @Override
+ public CelBuilder toCelBuilder() {
+ return newBuilder(toCompilerBuilder(), toRuntimeBuilder());
+ }
+
+ @Override
+ public CelParserBuilder toParserBuilder() {
+ return compiler.get().toParserBuilder();
+ }
+
+ @Override
+ public CelCheckerBuilder toCheckerBuilder() {
+ return compiler.get().toCheckerBuilder();
+ }
+
+ @Override
+ public CelCompilerBuilder toCompilerBuilder() {
+ return compiler.get().toCompilerBuilder();
+ }
+
+ @Override
+ public CelRuntimeBuilder toRuntimeBuilder() {
+ return runtime.get().toRuntimeBuilder();
+ }
+
/** Combines a prebuilt {@link CelCompiler} and {@link CelRuntime} into {@link CelImpl}. */
static CelImpl combine(CelCompiler compiler, CelRuntime runtime) {
return new CelImpl(Suppliers.memoize(() -> compiler), Suppliers.memoize(() -> runtime));
@@ -112,8 +141,9 @@ static CelImpl combine(CelCompiler compiler, CelRuntime runtime) {
*
* By default, {@link CelOptions#DEFAULT} are enabled, as is the CEL standard environment.
*/
- static CelBuilder newBuilder(CelParserBuilder parserBuilder, CelCheckerBuilder checkerBuilder) {
- return new CelImpl.Builder(parserBuilder, checkerBuilder);
+ static CelBuilder newBuilder(
+ CelCompilerBuilder compilerBuilder, CelRuntimeBuilder celRuntimeBuilder) {
+ return new CelImpl.Builder(compilerBuilder, celRuntimeBuilder);
}
/** Builder class for CelImpl instances. */
@@ -122,9 +152,9 @@ public static final class Builder implements CelBuilder {
private final CelCompilerBuilder compilerBuilder;
private final CelRuntimeBuilder runtimeBuilder;
- private Builder(CelParserBuilder parserBuilder, CelCheckerBuilder checkerBuilder) {
- this.compilerBuilder = CelCompilerImpl.newBuilder(parserBuilder, checkerBuilder);
- this.runtimeBuilder = CelRuntimeLegacyImpl.newBuilder();
+ private Builder(CelCompilerBuilder celCompilerBuilder, CelRuntimeBuilder celRuntimeBuilder) {
+ this.compilerBuilder = celCompilerBuilder;
+ this.runtimeBuilder = celRuntimeBuilder;
}
@Override
@@ -160,7 +190,12 @@ public CelBuilder addMacros(Iterable macros) {
}
@Override
- public CelBuilder setContainer(String container) {
+ public CelContainer container() {
+ return compilerBuilder.container();
+ }
+
+ @Override
+ public CelBuilder setContainer(CelContainer container) {
compilerBuilder.setContainer(container);
return this;
}
@@ -226,13 +261,13 @@ public CelBuilder addProtoTypeMasks(Iterable typeMasks) {
}
@Override
- public CelBuilder addFunctionBindings(CelRuntime.CelFunctionBinding... bindings) {
+ public CelBuilder addFunctionBindings(dev.cel.runtime.CelFunctionBinding... bindings) {
runtimeBuilder.addFunctionBindings(bindings);
return this;
}
@Override
- public CelBuilder addFunctionBindings(Iterable bindings) {
+ public CelBuilder addFunctionBindings(Iterable bindings) {
runtimeBuilder.addFunctionBindings(bindings);
return this;
}
@@ -240,7 +275,7 @@ public CelBuilder addFunctionBindings(Iterable bi
@Override
public CelBuilder setResultType(CelType resultType) {
checkNotNull(resultType);
- return setProtoResultType(CelTypes.celTypeToType(resultType));
+ return setProtoResultType(CelProtoTypes.celTypeToType(resultType));
}
@Override
diff --git a/bundle/src/main/java/dev/cel/bundle/RequiredFieldsChecker.java b/bundle/src/main/java/dev/cel/bundle/RequiredFieldsChecker.java
new file mode 100644
index 000000000..88bef2db5
--- /dev/null
+++ b/bundle/src/main/java/dev/cel/bundle/RequiredFieldsChecker.java
@@ -0,0 +1,49 @@
+// Copyright 2024 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.bundle;
+
+import static com.google.common.collect.ImmutableList.toImmutableList;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
+import java.util.Optional;
+import java.util.function.Supplier;
+
+/**
+ * Interface to be implemented on a builder that can be used to verify all required fields being
+ * set.
+ */
+interface RequiredFieldsChecker {
+
+ ImmutableList requiredFields();
+
+ default ImmutableList getMissingRequiredFieldNames() {
+ return requiredFields().stream()
+ .filter(entry -> !entry.fieldValue().get().isPresent())
+ .map(RequiredField::displayName)
+ .collect(toImmutableList());
+ }
+
+ @AutoValue
+ abstract class RequiredField {
+ abstract String displayName();
+
+ abstract Supplier> fieldValue();
+
+ static RequiredField of(String displayName, Supplier> fieldValue) {
+ return new AutoValue_RequiredFieldsChecker_RequiredField(displayName, fieldValue);
+ }
+ }
+}
diff --git a/bundle/src/test/java/dev/cel/bundle/BUILD.bazel b/bundle/src/test/java/dev/cel/bundle/BUILD.bazel
index 68ecfa5b2..a4c6f513d 100644
--- a/bundle/src/test/java/dev/cel/bundle/BUILD.bazel
+++ b/bundle/src/test/java/dev/cel/bundle/BUILD.bazel
@@ -1,3 +1,4 @@
+load("@rules_java//java:defs.bzl", "java_library")
load("//:testing.bzl", "junit4_test_suites")
package(default_applicable_licenses = [
@@ -8,38 +9,59 @@ java_library(
name = "tests",
testonly = True,
srcs = glob(["*Test.java"]),
+ resources = [
+ "//testing/environment:dump_env",
+ "//testing/environment:extended_env",
+ "//testing/environment:library_subset_env",
+ ],
deps = [
- "//:auto_value",
"//:java_truth",
"//bundle:cel",
+ "//bundle:environment",
+ "//bundle:environment_exception",
+ "//bundle:environment_exporter",
+ "//bundle:environment_yaml_parser",
+ "//checker",
"//checker:checker_legacy_environment",
"//checker:proto_type_mask",
- "//common",
+ "//common:cel_ast",
+ "//common:cel_source",
"//common:compiler_common",
+ "//common:container",
+ "//common:error_codes",
"//common:options",
"//common:proto_ast",
+ "//common:source_location",
"//common/ast",
- "//common/resources/testdata/proto2:messages_extensions_proto2_java_proto",
- "//common/resources/testdata/proto2:messages_proto2_java_proto",
- "//common/resources/testdata/proto2:test_all_types_java_proto",
+ "//common/internal:proto_time_utils",
"//common/resources/testdata/proto3:standalone_global_enum_java_proto",
- "//common/resources/testdata/proto3:test_all_types_java_proto",
"//common/testing",
"//common/types",
- "//common/types:cel_types",
+ "//common/types:cel_proto_message_types",
+ "//common/types:cel_proto_types",
"//common/types:message_type_provider",
"//common/types:type_providers",
+ "//common/values",
+ "//common/values:cel_byte_string",
"//compiler",
"//compiler:compiler_builder",
+ "//extensions",
+ "//parser",
"//parser:macro",
+ "//parser:unparser",
"//runtime",
+ "//runtime:evaluation_exception_builder",
+ "//runtime:evaluation_listener",
+ "//runtime:function_binding",
"//runtime:unknown_attributes",
- "@cel_spec//proto/cel/expr:expr_java_proto",
- "@maven//:com_google_api_grpc_proto_google_common_protos",
+ "@cel_spec//proto/cel/expr:checked_java_proto",
+ "@cel_spec//proto/cel/expr:syntax_java_proto",
+ "@cel_spec//proto/cel/expr/conformance/proto2:test_all_types_java_proto",
+ "@cel_spec//proto/cel/expr/conformance/proto3:test_all_types_java_proto",
+ "@com_google_googleapis//google/rpc/context:attribute_context_java_proto",
+ "@com_google_googleapis//google/type:type_java_proto",
"@maven//:com_google_guava_guava",
- "@maven//:com_google_guava_guava_testlib",
"@maven//:com_google_protobuf_protobuf_java",
- "@maven//:com_google_protobuf_protobuf_java_util",
"@maven//:com_google_testparameterinjector_test_parameter_injector",
"@maven//:com_google_truth_extensions_truth_proto_extension",
"@maven//:junit_junit",
diff --git a/bundle/src/test/java/dev/cel/bundle/CelEnvironmentExporterTest.java b/bundle/src/test/java/dev/cel/bundle/CelEnvironmentExporterTest.java
new file mode 100644
index 000000000..fd178857f
--- /dev/null
+++ b/bundle/src/test/java/dev/cel/bundle/CelEnvironmentExporterTest.java
@@ -0,0 +1,242 @@
+// Copyright 2025 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.bundle;
+
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.stream.Collectors.toCollection;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.io.Resources;
+import com.google.testing.junit.testparameterinjector.TestParameterInjector;
+import dev.cel.bundle.CelEnvironment.ExtensionConfig;
+import dev.cel.bundle.CelEnvironment.FunctionDecl;
+import dev.cel.bundle.CelEnvironment.LibrarySubset;
+import dev.cel.bundle.CelEnvironment.LibrarySubset.FunctionSelector;
+import dev.cel.bundle.CelEnvironment.LibrarySubset.OverloadSelector;
+import dev.cel.bundle.CelEnvironment.OverloadDecl;
+import dev.cel.bundle.CelEnvironment.TypeDecl;
+import dev.cel.bundle.CelEnvironment.VariableDecl;
+import dev.cel.common.CelFunctionDecl;
+import dev.cel.common.CelOptions;
+import dev.cel.common.CelOverloadDecl;
+import dev.cel.common.CelVarDecl;
+import dev.cel.common.types.OpaqueType;
+import dev.cel.common.types.SimpleType;
+import dev.cel.extensions.CelExtensions;
+import java.net.URL;
+import java.util.HashSet;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(TestParameterInjector.class)
+public class CelEnvironmentExporterTest {
+
+ public static final CelOptions CEL_OPTIONS =
+ CelOptions.newBuilder().enableHeterogeneousNumericComparisons(true).build();
+
+ @Test
+ public void extensions_latest() {
+ Cel cel =
+ CelFactory.standardCelBuilder()
+ .addCompilerLibraries(CelExtensions.math(CEL_OPTIONS, 2))
+ .build();
+
+ CelEnvironment celEnvironment =
+ CelEnvironmentExporter.newBuilder().addStandardExtensions(CEL_OPTIONS).build().export(cel);
+
+ assertThat(celEnvironment.extensions())
+ .containsExactly(ExtensionConfig.newBuilder().setName("math").setVersion(2).build());
+ }
+
+ @Test
+ public void extensions_earlierVersion() {
+ Cel cel =
+ CelFactory.standardCelBuilder()
+ .addCompilerLibraries(CelExtensions.math(CEL_OPTIONS, 1))
+ .build();
+
+ CelEnvironment celEnvironment =
+ CelEnvironmentExporter.newBuilder().addStandardExtensions(CEL_OPTIONS).build().export(cel);
+
+ assertThat(celEnvironment.extensions())
+ .containsExactly(ExtensionConfig.newBuilder().setName("math").setVersion(1).build());
+ }
+
+ @Test
+ public void standardLibrarySubset_favorExclusion() throws Exception {
+ URL url = Resources.getResource("environment/subset_env.yaml");
+ String yamlFileContent = Resources.toString(url, UTF_8);
+ CelEnvironment environment = CelEnvironmentYamlParser.newInstance().parse(yamlFileContent);
+
+ Cel standardCel =
+ CelFactory.standardCelBuilder()
+ .addCompilerLibraries(CelExtensions.math(CEL_OPTIONS, 2))
+ .build();
+ Cel extendedCel = environment.extend(standardCel, CEL_OPTIONS);
+
+ CelEnvironment celEnvironment =
+ CelEnvironmentExporter.newBuilder()
+ .setMaxExcludedStandardFunctions(100)
+ .setMaxExcludedStandardFunctionOverloads(100)
+ .build()
+ .export(extendedCel);
+
+ assertThat(celEnvironment.standardLibrarySubset())
+ .hasValue(
+ LibrarySubset.newBuilder()
+ .setDisabled(false)
+ .setExcludedFunctions(
+ ImmutableSet.of(
+ FunctionSelector.create(
+ "_+_", ImmutableSet.of("add_bytes", "add_list", "add_string")),
+ FunctionSelector.create("duration", ImmutableSet.of("string_to_duration")),
+ FunctionSelector.create("matches", ImmutableSet.of()),
+ FunctionSelector.create(
+ "timestamp", ImmutableSet.of("string_to_timestamp"))))
+ .setExcludedMacros(ImmutableSet.of("map", "filter"))
+ .build());
+ }
+
+ @Test
+ public void standardLibrarySubset_favorInclusion() throws Exception {
+ URL url = Resources.getResource("environment/subset_env.yaml");
+ String yamlFileContent = Resources.toString(url, UTF_8);
+ CelEnvironment environment = CelEnvironmentYamlParser.newInstance().parse(yamlFileContent);
+
+ Cel standardCel =
+ CelFactory.standardCelBuilder()
+ .addCompilerLibraries(CelExtensions.math(CEL_OPTIONS, 2))
+ .build();
+ Cel extendedCel = environment.extend(standardCel, CEL_OPTIONS);
+
+ CelEnvironment celEnvironment =
+ CelEnvironmentExporter.newBuilder()
+ .setMaxExcludedStandardFunctions(0)
+ .setMaxExcludedStandardFunctionOverloads(0)
+ .build()
+ .export(extendedCel);
+
+ LibrarySubset actual = celEnvironment.standardLibrarySubset().get();
+
+ // "matches" is fully excluded
+ assertThat(actual.includedFunctions().stream().map(FunctionSelector::name))
+ .doesNotContain("matches");
+
+ // A subset of overloads is included. Note the absence of string_to_timestamp
+ assertThat(actual.includedFunctions())
+ .contains(
+ FunctionSelector.create(
+ "timestamp", ImmutableSet.of("int64_to_timestamp", "timestamp_to_timestamp")));
+
+ Set additionOverloads =
+ actual.includedFunctions().stream()
+ .filter(fs -> fs.name().equals("_+_"))
+ .flatMap(fs -> fs.overloads().stream())
+ .map(OverloadSelector::id)
+ .collect(toCollection(HashSet::new));
+ assertThat(additionOverloads).containsNoneOf("add_bytes", "add_list", "add_string");
+
+ assertThat(actual.includedMacros()).containsNoneOf("map", "filter");
+
+ // Random-check a few standard overloads
+ assertThat(additionOverloads).containsAtLeast("add_int64", "add_uint64", "add_double");
+
+ // Random-check a couple of standard functions
+ assertThat(actual.includedFunctions())
+ .contains(FunctionSelector.create("-_", ImmutableSet.of()));
+ assertThat(actual.includedFunctions())
+ .contains(FunctionSelector.create("getDayOfYear", ImmutableSet.of()));
+ assertThat(actual.includedMacros()).containsAtLeast("all", "exists", "exists_one", "has");
+ }
+
+ @Test
+ public void customFunctions() {
+ Cel cel =
+ CelFactory.standardCelBuilder()
+ .addCompilerLibraries(CelExtensions.math(CEL_OPTIONS, 1))
+ .addFunctionDeclarations(
+ CelFunctionDecl.newFunctionDeclaration(
+ "math.isFinite",
+ CelOverloadDecl.newGlobalOverload(
+ "math_isFinite_int64", SimpleType.BOOL, SimpleType.INT)),
+ CelFunctionDecl.newFunctionDeclaration(
+ "addWeeks",
+ CelOverloadDecl.newMemberOverload(
+ "timestamp_addWeeks",
+ SimpleType.BOOL,
+ SimpleType.TIMESTAMP,
+ SimpleType.INT)))
+ .build();
+
+ CelEnvironmentExporter exporter =
+ CelEnvironmentExporter.newBuilder().addStandardExtensions(CEL_OPTIONS).build();
+ CelEnvironment celEnvironment = exporter.export(cel);
+
+ assertThat(celEnvironment.functions())
+ .containsAtLeast(
+ FunctionDecl.create(
+ "math.isFinite",
+ ImmutableSet.of(
+ OverloadDecl.newBuilder()
+ .setId("math_isFinite_int64")
+ .setArguments(ImmutableList.of(TypeDecl.create("int")))
+ .setReturnType(TypeDecl.create("bool"))
+ .build())),
+ FunctionDecl.create(
+ "addWeeks",
+ ImmutableSet.of(
+ OverloadDecl.newBuilder()
+ .setId("timestamp_addWeeks")
+ .setTarget(TypeDecl.create("google.protobuf.Timestamp"))
+ .setArguments(ImmutableList.of(TypeDecl.create("int")))
+ .setReturnType(TypeDecl.create("bool"))
+ .build())));
+
+ // Random-check some standard functions: we don't want to see them explicitly defined.
+ assertThat(
+ celEnvironment.functions().stream().map(FunctionDecl::name).collect(toImmutableList()))
+ .containsNoneOf("_+_", "math.abs", "_in_", "__not_strictly_false__");
+ }
+
+ @Test
+ public void customVariables() {
+ Cel cel =
+ CelFactory.standardCelBuilder()
+ .addVarDeclarations(
+ CelVarDecl.newVarDeclaration("x", SimpleType.INT),
+ CelVarDecl.newVarDeclaration("y", OpaqueType.create("foo.Bar")))
+ .build();
+
+ CelEnvironmentExporter exporter =
+ CelEnvironmentExporter.newBuilder().addStandardExtensions(CEL_OPTIONS).build();
+ CelEnvironment celEnvironment = exporter.export(cel);
+
+ assertThat(celEnvironment.variables())
+ .containsAtLeast(
+ VariableDecl.create("x", TypeDecl.create("int")),
+ VariableDecl.create("y", TypeDecl.create("foo.Bar")));
+
+ // Random-check some standard variables: we don't want to see them explicitly included in
+ // the CelEnvironment.
+ assertThat(
+ celEnvironment.variables().stream().map(VariableDecl::name).collect(toImmutableList()))
+ .containsNoneOf("double", "null_type");
+ }
+}
+
diff --git a/bundle/src/test/java/dev/cel/bundle/CelEnvironmentTest.java b/bundle/src/test/java/dev/cel/bundle/CelEnvironmentTest.java
new file mode 100644
index 000000000..bafa20ea6
--- /dev/null
+++ b/bundle/src/test/java/dev/cel/bundle/CelEnvironmentTest.java
@@ -0,0 +1,325 @@
+// Copyright 2025 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.bundle;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.testing.junit.testparameterinjector.TestParameterInjector;
+import dev.cel.bundle.CelEnvironment.CanonicalCelExtension;
+import dev.cel.bundle.CelEnvironment.ExtensionConfig;
+import dev.cel.bundle.CelEnvironment.LibrarySubset;
+import dev.cel.bundle.CelEnvironment.LibrarySubset.FunctionSelector;
+import dev.cel.common.CelAbstractSyntaxTree;
+import dev.cel.common.CelOptions;
+import dev.cel.common.CelValidationException;
+import dev.cel.common.CelValidationResult;
+import dev.cel.compiler.CelCompiler;
+import dev.cel.compiler.CelCompilerFactory;
+import dev.cel.parser.CelStandardMacro;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(TestParameterInjector.class)
+public class CelEnvironmentTest {
+
+ @Test
+ public void newBuilder_defaults() {
+ CelEnvironment environment = CelEnvironment.newBuilder().build();
+
+ assertThat(environment.source()).isEmpty();
+ assertThat(environment.name()).isEmpty();
+ assertThat(environment.description()).isEmpty();
+ assertThat(environment.container()).isEmpty();
+ assertThat(environment.extensions()).isEmpty();
+ assertThat(environment.variables()).isEmpty();
+ assertThat(environment.functions()).isEmpty();
+ }
+
+ @Test
+ public void extend_allExtensions() throws Exception {
+ ImmutableSet extensionConfigs =
+ ImmutableSet.of(
+ ExtensionConfig.latest("bindings"),
+ ExtensionConfig.latest("encoders"),
+ ExtensionConfig.latest("lists"),
+ ExtensionConfig.latest("math"),
+ ExtensionConfig.latest("optional"),
+ ExtensionConfig.latest("protos"),
+ ExtensionConfig.latest("sets"),
+ ExtensionConfig.latest("strings"));
+ CelEnvironment environment =
+ CelEnvironment.newBuilder().addExtensions(extensionConfigs).build();
+
+ Cel cel = environment.extend(CelFactory.standardCelBuilder().build(), CelOptions.DEFAULT);
+ CelAbstractSyntaxTree ast =
+ cel.compile(
+ "cel.bind(x, 10, math.greatest([1,x])) < int(' 11 '.trim()) &&"
+ + " optional.none().orValue(true) && [].flatten() == []")
+ .getAst();
+ boolean result = (boolean) cel.createProgram(ast).eval();
+
+ assertThat(extensionConfigs.size()).isEqualTo(CelEnvironment.CEL_EXTENSION_CONFIG_MAP.size());
+ assertThat(extensionConfigs.size()).isEqualTo(CanonicalCelExtension.values().length);
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ public void extensionVersion_specific() throws Exception {
+ CelEnvironment environment =
+ CelEnvironment.newBuilder().addExtensions(ExtensionConfig.of("math", 1)).build();
+
+ Cel cel = environment.extend(CelFactory.standardCelBuilder().build(), CelOptions.DEFAULT);
+ CelAbstractSyntaxTree ast1 = cel.compile("math.abs(-4)").getAst();
+ assertThat(cel.createProgram(ast1).eval()).isEqualTo(4);
+
+ // Version 1 of the 'math' extension does not include sqrt
+ assertThat(
+ assertThrows(
+ CelValidationException.class,
+ () -> {
+ cel.compile("math.sqrt(4)").getAst();
+ }))
+ .hasMessageThat()
+ .contains("undeclared reference to 'sqrt'");
+ }
+
+ @Test
+ public void extensionVersion_latest() throws Exception {
+ CelEnvironment environment =
+ CelEnvironment.newBuilder()
+ .addExtensions(ExtensionConfig.latest("math"))
+ .build();
+
+ Cel cel = environment.extend(CelFactory.standardCelBuilder().build(), CelOptions.DEFAULT);
+ CelAbstractSyntaxTree ast = cel.compile("math.sqrt(4)").getAst();
+ double result = (double) cel.createProgram(ast).eval();
+ assertThat(result).isEqualTo(2.0);
+ }
+
+ @Test
+ public void extensionVersion_unsupportedVersion_throws() {
+ CelEnvironment environment =
+ CelEnvironment.newBuilder().addExtensions(ExtensionConfig.of("math", -5)).build();
+
+ assertThat(
+ assertThrows(
+ CelEnvironmentException.class,
+ () -> {
+ environment.extend(CelFactory.standardCelBuilder().build(), CelOptions.DEFAULT);
+ }))
+ .hasMessageThat()
+ .contains("Unsupported 'math' extension version -5");
+ }
+
+ @Test
+ public void stdlibSubset_bothIncludeExcludeSet_throws() {
+ assertThat(
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ CelEnvironment.newBuilder()
+ .setStandardLibrarySubset(
+ LibrarySubset.newBuilder()
+ .setDisabled(false)
+ .setIncludedMacros(ImmutableSet.of("foo"))
+ .setExcludedMacros(ImmutableSet.of("bar"))
+ .build())
+ .build()))
+ .hasMessageThat()
+ .contains("cannot both include and exclude macros");
+
+ assertThat(
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ CelEnvironment.newBuilder()
+ .setStandardLibrarySubset(
+ LibrarySubset.newBuilder()
+ .setDisabled(false)
+ .setIncludedFunctions(
+ ImmutableSet.of(
+ FunctionSelector.create("foo", ImmutableSet.of())))
+ .setExcludedFunctions(
+ ImmutableSet.of(
+ FunctionSelector.create("bar", ImmutableSet.of())))
+ .build())
+ .build()))
+ .hasMessageThat()
+ .contains("cannot both include and exclude functions");
+ }
+
+ @Test
+ public void stdlibSubset_disabled() throws Exception {
+ CelEnvironment environment =
+ CelEnvironment.newBuilder()
+ .setStandardLibrarySubset(LibrarySubset.newBuilder().setDisabled(true).build())
+ .build();
+
+ CelCompiler compiler = CelCompilerFactory.standardCelCompilerBuilder().build();
+ CelCompiler extendedCompiler = environment.extend(compiler, CelOptions.DEFAULT);
+ CelValidationResult result = extendedCompiler.compile("1 != 2");
+ assertThat(result.getErrorString()).contains("undeclared reference to '_!=_'");
+ }
+
+ @Test
+ public void stdlibSubset_macrosDisabled() throws Exception {
+ CelEnvironment environment =
+ CelEnvironment.newBuilder()
+ .setStandardLibrarySubset(
+ LibrarySubset.newBuilder().setDisabled(false).setMacrosDisabled(true).build())
+ .build();
+
+ CelCompiler compiler = CelCompilerFactory.standardCelCompilerBuilder().build();
+ CelCompiler extendedCompiler = environment.extend(compiler, CelOptions.DEFAULT);
+ CelValidationResult result =
+ extendedCompiler.compile("['hello', 'world'].exists(v, v == 'hello')");
+ assertThat(result.getErrorString()).contains("undeclared reference to 'exists'");
+ }
+
+ @Test
+ public void stdlibSubset_macrosIncluded() throws Exception {
+ CelEnvironment environment =
+ CelEnvironment.newBuilder()
+ .setStandardLibrarySubset(
+ LibrarySubset.newBuilder()
+ .setDisabled(false)
+ .setIncludedMacros(ImmutableSet.of(CelStandardMacro.EXISTS.getFunction()))
+ .build())
+ .build();
+
+ CelCompiler compiler = CelCompilerFactory.standardCelCompilerBuilder().build();
+ CelCompiler extendedCompiler = environment.extend(compiler, CelOptions.DEFAULT);
+ CelValidationResult result =
+ extendedCompiler.compile("['hello', 'world'].exists(v, v == 'hello')");
+ assertThat(result.hasError()).isFalse();
+
+ result = extendedCompiler.compile("['hello', 'world'].exists_one(v, v == 'hello')");
+ assertThat(result.getErrorString()).contains("undeclared reference to 'exists_one'");
+ }
+
+ @Test
+ public void stdlibSubset_macrosExcluded() throws Exception {
+ CelEnvironment environment =
+ CelEnvironment.newBuilder()
+ .setStandardLibrarySubset(
+ LibrarySubset.newBuilder()
+ .setDisabled(false)
+ .setExcludedMacros(ImmutableSet.of(CelStandardMacro.EXISTS_ONE.getFunction()))
+ .build())
+ .build();
+
+ CelCompiler compiler = CelCompilerFactory.standardCelCompilerBuilder().build();
+ CelCompiler extendedCompiler = environment.extend(compiler, CelOptions.DEFAULT);
+ CelValidationResult result =
+ extendedCompiler.compile("['hello', 'world'].exists(v, v == 'hello')");
+ assertThat(result.hasError()).isFalse();
+
+ result = extendedCompiler.compile("['hello', 'world'].exists_one(v, v == 'hello')");
+ assertThat(result.getErrorString()).contains("undeclared reference to 'exists_one'");
+ }
+
+ @Test
+ public void stdlibSubset_functionsIncluded() throws Exception {
+ CelEnvironment environment =
+ CelEnvironment.newBuilder()
+ .setStandardLibrarySubset(
+ LibrarySubset.newBuilder()
+ .setDisabled(false)
+ .setIncludedFunctions(
+ ImmutableSet.of(
+ FunctionSelector.create("_==_", ImmutableSet.of()),
+ FunctionSelector.create("_!=_", ImmutableSet.of()),
+ FunctionSelector.create("_&&_", ImmutableSet.of())))
+ .build())
+ .build();
+
+ CelCompiler compiler = CelCompilerFactory.standardCelCompilerBuilder().build();
+ CelCompiler extendedCompiler = environment.extend(compiler, CelOptions.DEFAULT);
+ CelValidationResult result = extendedCompiler.compile("1 == 1 && 1 != 2");
+ assertThat(result.hasError()).isFalse();
+
+ result = extendedCompiler.compile("1 == 1 && 1 != 1 + 1");
+ assertThat(result.getErrorString()).contains("undeclared reference to '_+_'");
+ }
+
+ @Test
+ public void stdlibSubset_functionOverloadIncluded() throws Exception {
+ CelEnvironment environment =
+ CelEnvironment.newBuilder()
+ .setStandardLibrarySubset(
+ LibrarySubset.newBuilder()
+ .setDisabled(false)
+ .setIncludedFunctions(
+ ImmutableSet.of(
+ FunctionSelector.create("_+_", ImmutableSet.of("add_int64"))))
+ .build())
+ .build();
+
+ CelCompiler compiler = CelCompilerFactory.standardCelCompilerBuilder().build();
+ CelCompiler extendedCompiler = environment.extend(compiler, CelOptions.DEFAULT);
+ CelValidationResult result = extendedCompiler.compile("1 + 2");
+ assertThat(result.hasError()).isFalse();
+
+ result = extendedCompiler.compile("1.0 + 2.0");
+ assertThat(result.getErrorString())
+ .contains("found no matching overload for '_+_' applied to '(double, double)'");
+ }
+
+ @Test
+ public void stdlibSubset_functionsExcluded() throws Exception {
+ CelEnvironment environment =
+ CelEnvironment.newBuilder()
+ .setStandardLibrarySubset(
+ LibrarySubset.newBuilder()
+ .setDisabled(false)
+ .setExcludedFunctions(
+ ImmutableSet.of(
+ FunctionSelector.create("_+_", ImmutableSet.of())))
+ .build())
+ .build();
+
+ CelCompiler compiler = CelCompilerFactory.standardCelCompilerBuilder().build();
+ CelCompiler extendedCompiler = environment.extend(compiler, CelOptions.DEFAULT);
+ CelValidationResult result = extendedCompiler.compile("1 == 1 && 1 != 2");
+ assertThat(result.hasError()).isFalse();
+
+ result = extendedCompiler.compile("1 == 1 && 1 != 1 + 1");
+ assertThat(result.getErrorString()).contains("undeclared reference to '_+_'");
+ }
+
+ @Test
+ public void stdlibSubset_functionOverloadExcluded() throws Exception {
+ CelEnvironment environment =
+ CelEnvironment.newBuilder()
+ .setStandardLibrarySubset(
+ LibrarySubset.newBuilder()
+ .setDisabled(false)
+ .setExcludedFunctions(
+ ImmutableSet.of(
+ FunctionSelector.create("_+_", ImmutableSet.of("add_int64"))))
+ .build())
+ .build();
+
+ CelCompiler compiler = CelCompilerFactory.standardCelCompilerBuilder().build();
+ CelCompiler extendedCompiler = environment.extend(compiler, CelOptions.DEFAULT);
+ CelValidationResult result = extendedCompiler.compile("1 == 1 && 1 != 2");
+ assertThat(result.hasError()).isFalse();
+
+ result = extendedCompiler.compile("1 == 1 && 1 != 1 + 1");
+ assertThat(result.getErrorString()).contains("found no matching overload for '_+_'");
+ }
+}
diff --git a/bundle/src/test/java/dev/cel/bundle/CelEnvironmentYamlParserTest.java b/bundle/src/test/java/dev/cel/bundle/CelEnvironmentYamlParserTest.java
new file mode 100644
index 000000000..fb51f3f87
--- /dev/null
+++ b/bundle/src/test/java/dev/cel/bundle/CelEnvironmentYamlParserTest.java
@@ -0,0 +1,879 @@
+// Copyright 2025 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.bundle;
+
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.assertThrows;
+
+import com.google.common.base.Ascii;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.io.Resources;
+import com.google.rpc.context.AttributeContext;
+import com.google.testing.junit.testparameterinjector.TestParameter;
+import com.google.testing.junit.testparameterinjector.TestParameterInjector;
+import dev.cel.bundle.CelEnvironment.ExtensionConfig;
+import dev.cel.bundle.CelEnvironment.FunctionDecl;
+import dev.cel.bundle.CelEnvironment.LibrarySubset;
+import dev.cel.bundle.CelEnvironment.LibrarySubset.FunctionSelector;
+import dev.cel.bundle.CelEnvironment.OverloadDecl;
+import dev.cel.bundle.CelEnvironment.TypeDecl;
+import dev.cel.bundle.CelEnvironment.VariableDecl;
+import dev.cel.common.CelAbstractSyntaxTree;
+import dev.cel.common.CelOptions;
+import dev.cel.common.CelSource;
+import dev.cel.common.ast.CelExpr;
+import dev.cel.common.types.SimpleType;
+import dev.cel.parser.CelUnparserFactory;
+import dev.cel.runtime.CelEvaluationListener;
+import dev.cel.runtime.CelLateFunctionBindings;
+import dev.cel.runtime.CelFunctionBinding;
+import java.io.IOException;
+import java.net.URL;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicReference;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(TestParameterInjector.class)
+public final class CelEnvironmentYamlParserTest {
+
+ private static final Cel CEL_WITH_MESSAGE_TYPES =
+ CelFactory.standardCelBuilder()
+ .addMessageTypes(AttributeContext.Request.getDescriptor())
+ .build();
+
+ private static final CelEnvironmentYamlParser ENVIRONMENT_PARSER =
+ CelEnvironmentYamlParser.newInstance();
+
+ @Test
+ public void environment_setEmpty() throws Exception {
+ assertThrows(CelEnvironmentException.class, () -> ENVIRONMENT_PARSER.parse(""));
+ }
+
+ @Test
+ public void environment_setBasicProperties() throws Exception {
+ String yamlConfig = "name: hello\n" + "description: empty\n" + "container: pb.pkg\n";
+
+ CelEnvironment environment = ENVIRONMENT_PARSER.parse(yamlConfig);
+
+ assertThat(environment)
+ .isEqualTo(
+ CelEnvironment.newBuilder()
+ .setSource(environment.source().get())
+ .setName("hello")
+ .setDescription("empty")
+ .setContainer("pb.pkg")
+ .build());
+ }
+
+ @Test
+ public void environment_setExtensions() throws Exception {
+ String yamlConfig =
+ "extensions:\n"
+ + " - name: 'bindings'\n"
+ + " - name: 'encoders'\n"
+ + " - name: 'lists'\n"
+ + " - name: 'math'\n"
+ + " - name: 'optional'\n"
+ + " - name: 'protos'\n"
+ + " - name: 'sets'\n"
+ + " - name: 'strings'\n"
+ + " version: 1";
+
+ CelEnvironment environment = ENVIRONMENT_PARSER.parse(yamlConfig);
+
+ assertThat(environment)
+ .isEqualTo(
+ CelEnvironment.newBuilder()
+ .setSource(environment.source().get())
+ .addExtensions(
+ ImmutableSet.of(
+ ExtensionConfig.of("bindings"),
+ ExtensionConfig.of("encoders"),
+ ExtensionConfig.of("lists"),
+ ExtensionConfig.of("math"),
+ ExtensionConfig.of("optional"),
+ ExtensionConfig.of("protos"),
+ ExtensionConfig.of("sets"),
+ ExtensionConfig.of("strings", 1)))
+ .build());
+ assertThat(environment.extend(CEL_WITH_MESSAGE_TYPES, CelOptions.DEFAULT)).isNotNull();
+ }
+
+ @Test
+ public void environment_setExtensionVersionToLatest() throws Exception {
+ String yamlConfig =
+ "extensions:\n" //
+ + " - name: 'bindings'\n" //
+ + " version: latest";
+
+ CelEnvironment environment = ENVIRONMENT_PARSER.parse(yamlConfig);
+
+ assertThat(environment)
+ .isEqualTo(
+ CelEnvironment.newBuilder()
+ .setSource(environment.source().get())
+ .addExtensions(ImmutableSet.of(ExtensionConfig.of("bindings", Integer.MAX_VALUE)))
+ .build());
+ assertThat(environment.extend(CEL_WITH_MESSAGE_TYPES, CelOptions.DEFAULT)).isNotNull();
+ }
+
+ @Test
+ public void environment_setExtensionVersionToInvalidValue() throws Exception {
+ String yamlConfig =
+ "extensions:\n" //
+ + " - name: 'bindings'\n" //
+ + " version: invalid";
+
+ CelEnvironmentException e =
+ assertThrows(CelEnvironmentException.class, () -> ENVIRONMENT_PARSER.parse(yamlConfig));
+ assertThat(e)
+ .hasMessageThat()
+ .contains(
+ "ERROR: :3:5: Unsupported version tag: version\n"
+ + " | version: invalid\n"
+ + " | ....^");
+ }
+
+ @Test
+ public void environment_setFunctions() throws Exception {
+ String yamlConfig =
+ "functions:\n"
+ + " - name: 'coalesce'\n"
+ + " overloads:\n"
+ + " - id: 'null_coalesce_int'\n"
+ + " target:\n"
+ + " type_name: 'null_type'\n"
+ + " args:\n"
+ + " - type_name: 'int'\n"
+ + " return:\n"
+ + " type_name: 'int'\n"
+ + " - id: 'coalesce_null_int'\n"
+ + " args:\n"
+ + " - type_name: 'null_type'\n"
+ + " - type_name: 'int'\n"
+ + " return:\n"
+ + " type_name: 'int' \n"
+ + " - id: 'int_coalesce_int'\n"
+ + " target: \n"
+ + " type_name: 'int'\n"
+ + " args:\n"
+ + " - type_name: 'int'\n"
+ + " return: \n"
+ + " type_name: 'int'\n"
+ + " - id: 'optional_T_coalesce_T'\n"
+ + " target: \n"
+ + " type_name: 'optional_type'\n"
+ + " params:\n"
+ + " - type_name: 'T'\n"
+ + " is_type_param: true\n"
+ + " args:\n"
+ + " - type_name: 'T'\n"
+ + " is_type_param: true\n"
+ + " return: \n"
+ + " type_name: 'T'\n"
+ + " is_type_param: true";
+
+ CelEnvironment environment = ENVIRONMENT_PARSER.parse(yamlConfig);
+
+ assertThat(environment)
+ .isEqualTo(
+ CelEnvironment.newBuilder()
+ .setSource(environment.source().get())
+ .setFunctions(
+ ImmutableSet.of(
+ FunctionDecl.create(
+ "coalesce",
+ ImmutableSet.of(
+ OverloadDecl.newBuilder()
+ .setId("null_coalesce_int")
+ .setTarget(TypeDecl.create("null_type"))
+ .addArguments(TypeDecl.create("int"))
+ .setReturnType(TypeDecl.create("int"))
+ .build(),
+ OverloadDecl.newBuilder()
+ .setId("coalesce_null_int")
+ .addArguments(
+ TypeDecl.create("null_type"), TypeDecl.create("int"))
+ .setReturnType(TypeDecl.create("int"))
+ .build(),
+ OverloadDecl.newBuilder()
+ .setId("int_coalesce_int")
+ .setTarget(TypeDecl.create("int"))
+ .addArguments(TypeDecl.create("int"))
+ .setReturnType(TypeDecl.create("int"))
+ .build(),
+ OverloadDecl.newBuilder()
+ .setId("optional_T_coalesce_T")
+ .setTarget(
+ TypeDecl.newBuilder()
+ .setName("optional_type")
+ .addParams(
+ TypeDecl.newBuilder()
+ .setName("T")
+ .setIsTypeParam(true)
+ .build())
+ .build())
+ .addArguments(
+ TypeDecl.newBuilder()
+ .setName("T")
+ .setIsTypeParam(true)
+ .build())
+ .setReturnType(
+ TypeDecl.newBuilder()
+ .setName("T")
+ .setIsTypeParam(true)
+ .build())
+ .build()))))
+ .build());
+ assertThat(environment.extend(CEL_WITH_MESSAGE_TYPES, CelOptions.DEFAULT)).isNotNull();
+ }
+
+ @Test
+ public void environment_setListVariable() throws Exception {
+ String yamlConfig =
+ "variables:\n"
+ + "- name: 'request'\n"
+ + " type_name: 'list'\n"
+ + " params:\n"
+ + " - type_name: 'string'";
+
+ CelEnvironment environment = ENVIRONMENT_PARSER.parse(yamlConfig);
+
+ assertThat(environment)
+ .isEqualTo(
+ CelEnvironment.newBuilder()
+ .setSource(environment.source().get())
+ .setVariables(
+ ImmutableSet.of(
+ VariableDecl.create(
+ "request",
+ TypeDecl.newBuilder()
+ .setName("list")
+ .addParams(TypeDecl.create("string"))
+ .build())))
+ .build());
+ }
+
+ @Test
+ public void environment_setMapVariable() throws Exception {
+ String yamlConfig =
+ "variables:\n"
+ + "- name: 'request'\n"
+ + " type:\n"
+ + " type_name: 'map'\n"
+ + " params:\n"
+ + " - type_name: 'string'\n"
+ + " - type_name: 'dyn'";
+
+ CelEnvironment environment = ENVIRONMENT_PARSER.parse(yamlConfig);
+
+ assertThat(environment)
+ .isEqualTo(
+ CelEnvironment.newBuilder()
+ .setSource(environment.source().get())
+ .setVariables(
+ ImmutableSet.of(
+ VariableDecl.create(
+ "request",
+ TypeDecl.newBuilder()
+ .setName("map")
+ .addParams(TypeDecl.create("string"), TypeDecl.create("dyn"))
+ .build())))
+ .build());
+ assertThat(environment.extend(CEL_WITH_MESSAGE_TYPES, CelOptions.DEFAULT)).isNotNull();
+ }
+
+ @Test
+ public void environment_setMessageVariable() throws Exception {
+ String yamlConfig =
+ "variables:\n"
+ + "- name: 'request'\n"
+ + " type:\n"
+ + " type_name: 'google.rpc.context.AttributeContext.Request'";
+
+ CelEnvironment environment = ENVIRONMENT_PARSER.parse(yamlConfig);
+
+ assertThat(environment)
+ .isEqualTo(
+ CelEnvironment.newBuilder()
+ .setSource(environment.source().get())
+ .setVariables(
+ ImmutableSet.of(
+ VariableDecl.create(
+ "request",
+ TypeDecl.create("google.rpc.context.AttributeContext.Request"))))
+ .build());
+ assertThat(environment.extend(CEL_WITH_MESSAGE_TYPES, CelOptions.DEFAULT)).isNotNull();
+ }
+
+ @Test
+ public void environment_setContainer() throws Exception {
+ String yamlConfig =
+ "container: google.rpc.context\n"
+ + "variables:\n"
+ + "- name: 'request'\n"
+ + " type:\n"
+ + " type_name: 'google.rpc.context.AttributeContext.Request'";
+
+ CelEnvironment environment = ENVIRONMENT_PARSER.parse(yamlConfig);
+
+ assertThat(environment)
+ .isEqualTo(
+ CelEnvironment.newBuilder()
+ .setContainer("google.rpc.context")
+ .setSource(environment.source().get())
+ .setVariables(
+ ImmutableSet.of(
+ VariableDecl.create(
+ "request",
+ TypeDecl.create("google.rpc.context.AttributeContext.Request"))))
+ .build());
+ assertThat(environment.extend(CEL_WITH_MESSAGE_TYPES, CelOptions.DEFAULT)).isNotNull();
+ }
+
+ @Test
+ public void environment_withInlinedVariableDecl() throws Exception {
+ String yamlConfig =
+ "variables:\n"
+ + "- name: 'request'\n"
+ + " type_name: 'google.rpc.context.AttributeContext.Request'\n"
+ + "- name: 'map_var'\n"
+ + " type_name: 'map'\n"
+ + " params:\n"
+ + " - type_name: 'string'\n"
+ + " - type_name: 'string'";
+
+ CelEnvironment environment = ENVIRONMENT_PARSER.parse(yamlConfig);
+
+ assertThat(environment)
+ .isEqualTo(
+ CelEnvironment.newBuilder()
+ .setSource(environment.source().get())
+ .setVariables(
+ ImmutableSet.of(
+ VariableDecl.create(
+ "request",
+ TypeDecl.create("google.rpc.context.AttributeContext.Request")),
+ VariableDecl.create(
+ "map_var",
+ TypeDecl.newBuilder()
+ .setName("map")
+ .addParams(TypeDecl.create("string"), TypeDecl.create("string"))
+ .build())))
+ .build());
+ assertThat(environment.extend(CEL_WITH_MESSAGE_TYPES, CelOptions.DEFAULT)).isNotNull();
+ }
+
+ @Test
+ public void environment_parseErrors(@TestParameter EnvironmentParseErrorTestcase testCase) {
+ CelEnvironmentException e =
+ assertThrows(
+ CelEnvironmentException.class, () -> ENVIRONMENT_PARSER.parse(testCase.yamlConfig));
+ assertThat(e).hasMessageThat().isEqualTo(testCase.expectedErrorMessage);
+ }
+
+ @Test
+ public void environment_extendErrors(@TestParameter EnvironmentExtendErrorTestCase testCase)
+ throws Exception {
+ CelEnvironment environment = ENVIRONMENT_PARSER.parse(testCase.yamlConfig);
+
+ CelEnvironmentException e =
+ assertThrows(
+ CelEnvironmentException.class,
+ () -> environment.extend(CEL_WITH_MESSAGE_TYPES, CelOptions.DEFAULT));
+ assertThat(e).hasMessageThat().isEqualTo(testCase.expectedErrorMessage);
+ }
+
+ // Note: dangling comments in expressions below is to retain the newlines by preventing auto
+ // formatter from compressing them in a single line.
+ private enum EnvironmentParseErrorTestcase {
+ MALFORMED_YAML_DOCUMENT(
+ "a:\na",
+ "YAML document is malformed: while scanning a simple key\n"
+ + " in 'reader', line 2, column 1:\n"
+ + " a\n"
+ + " ^\n"
+ + "could not find expected ':'\n"
+ + " in 'reader', line 2, column 2:\n"
+ + " a\n"
+ + " ^\n"),
+ ILLEGAL_YAML_TYPE_CONFIG_KEY(
+ "1: test",
+ "ERROR: :1:1: Got yaml node type tag:yaml.org,2002:int, wanted type(s)"
+ + " [tag:yaml.org,2002:str !txt]\n"
+ + " | 1: test\n"
+ + " | ^"),
+ ILLEGAL_YAML_TYPE_CONFIG_VALUE(
+ "test: 1", "ERROR: :1:1: Unknown config tag: test\n" + " | test: 1\n" + " | ^"),
+ ILLEGAL_YAML_TYPE_VARIABLE_LIST(
+ "variables: 1",
+ "ERROR: :1:12: Got yaml node type tag:yaml.org,2002:int, wanted type(s)"
+ + " [tag:yaml.org,2002:seq]\n"
+ + " | variables: 1\n"
+ + " | ...........^"),
+ ILLEGAL_YAML_TYPE_VARIABLE_VALUE(
+ "variables:\n - 1",
+ "ERROR: :2:4: Got yaml node type tag:yaml.org,2002:int, wanted type(s)"
+ + " [tag:yaml.org,2002:map]\n"
+ + " | - 1\n"
+ + " | ...^"),
+ ILLEGAL_YAML_TYPE_FUNCTION_LIST(
+ "functions: 1",
+ "ERROR: :1:12: Got yaml node type tag:yaml.org,2002:int, wanted type(s)"
+ + " [tag:yaml.org,2002:seq]\n"
+ + " | functions: 1\n"
+ + " | ...........^"),
+ ILLEGAL_YAML_TYPE_FUNCTION_VALUE(
+ "functions:\n - 1",
+ "ERROR: :2:4: Got yaml node type tag:yaml.org,2002:int, wanted type(s)"
+ + " [tag:yaml.org,2002:map]\n"
+ + " | - 1\n"
+ + " | ...^"),
+ ILLEGAL_YAML_TYPE_OVERLOAD_LIST(
+ "functions:\n" //
+ + " - name: foo\n" //
+ + " overloads: 1",
+ "ERROR: :3:15: Got yaml node type tag:yaml.org,2002:int, wanted type(s)"
+ + " [tag:yaml.org,2002:seq]\n"
+ + " | overloads: 1\n"
+ + " | ..............^"),
+ ILLEGAL_YAML_TYPE_OVERLOAD_VALUE(
+ "functions:\n" //
+ + " - name: foo\n" //
+ + " overloads:\n" //
+ + " - 2",
+ "ERROR: :4:7: Got yaml node type tag:yaml.org,2002:int, wanted type(s)"
+ + " [tag:yaml.org,2002:map]\n"
+ + " | - 2\n"
+ + " | ......^"),
+ ILLEGAL_YAML_TYPE_OVERLOAD_VALUE_MAP_KEY(
+ "functions:\n" //
+ + " - name: foo\n" //
+ + " overloads:\n" //
+ + " - 2: test",
+ "ERROR: :4:9: Got yaml node type tag:yaml.org,2002:int, wanted type(s)"
+ + " [tag:yaml.org,2002:str !txt]\n"
+ + " | - 2: test\n"
+ + " | ........^\n"
+ + "ERROR: :4:9: Missing required attribute(s): id, return\n"
+ + " | - 2: test\n"
+ + " | ........^"),
+ ILLEGAL_YAML_TYPE_EXTENSION_LIST(
+ "extensions: 1",
+ "ERROR: :1:13: Got yaml node type tag:yaml.org,2002:int, wanted type(s)"
+ + " [tag:yaml.org,2002:seq]\n"
+ + " | extensions: 1\n"
+ + " | ............^"),
+ ILLEGAL_YAML_TYPE_EXTENSION_VALUE(
+ "extensions:\n - 1",
+ "ERROR: :2:4: Got yaml node type tag:yaml.org,2002:int, wanted type(s)"
+ + " [tag:yaml.org,2002:map]\n"
+ + " | - 1\n"
+ + " | ...^"),
+ ILLEGAL_YAML_TYPE_TYPE_DECL(
+ "variables:\n" //
+ + " - name: foo\n" //
+ + " type: 1",
+ "ERROR: :3:10: Got yaml node type tag:yaml.org,2002:int, wanted type(s)"
+ + " [tag:yaml.org,2002:map]\n"
+ + " | type: 1\n"
+ + " | .........^"),
+ ILLEGAL_YAML_TYPE_TYPE_VALUE(
+ "variables:\n"
+ + " - name: foo\n"
+ + " type:\n"
+ + " type_name: bar\n"
+ + " 1: hello",
+ "ERROR: :5:6: Got yaml node type tag:yaml.org,2002:int, wanted type(s)"
+ + " [tag:yaml.org,2002:str !txt]\n"
+ + " | 1: hello\n"
+ + " | .....^"),
+ ILLEGAL_YAML_TYPE_TYPE_PARAMS_LIST(
+ "variables:\n"
+ + " - name: foo\n"
+ + " type:\n"
+ + " type_name: bar\n"
+ + " params: 1",
+ "ERROR: :5:14: Got yaml node type tag:yaml.org,2002:int, wanted type(s)"
+ + " [tag:yaml.org,2002:seq]\n"
+ + " | params: 1\n"
+ + " | .............^"),
+ ILLEGAL_YAML_TYPE_WITH_INLINED_TYPE_TAGS(
+ "variables:\n"
+ + " - name: foo\n"
+ + " type_name: bar\n"
+ + " type:\n"
+ + " type_name: qux\n",
+ "ERROR: :4:4: 'type' tag cannot be used together with inlined 'type_name',"
+ + " 'is_type_param' or 'params': type\n"
+ + " | type:\n"
+ + " | ...^"),
+ ILLEGAL_YAML_INLINED_TYPE_VALUE(
+ "variables:\n" //
+ + " - name: foo\n" //
+ + " type_name: 1\n",
+ "ERROR: :3:15: Got yaml node type tag:yaml.org,2002:int, wanted type(s)"
+ + " [tag:yaml.org,2002:str !txt]\n"
+ + " | type_name: 1\n"
+ + " | ..............^"),
+ UNSUPPORTED_CONFIG_TAG(
+ "unsupported: test",
+ "ERROR: :1:1: Unknown config tag: unsupported\n"
+ + " | unsupported: test\n"
+ + " | ^"),
+ UNSUPPORTED_EXTENSION_TAG(
+ "extensions:\n" //
+ + " - name: foo\n" //
+ + " unsupported: test",
+ "ERROR: :3:5: Unsupported extension tag: unsupported\n"
+ + " | unsupported: test\n"
+ + " | ....^"),
+ UNSUPPORTED_TYPE_DECL_TAG(
+ "variables:\n"
+ + "- name: foo\n"
+ + " type:\n"
+ + " type_name: bar\n"
+ + " unsupported: hello",
+ "ERROR: :5:6: Unsupported type decl tag: unsupported\n"
+ + " | unsupported: hello\n"
+ + " | .....^"),
+ MISSING_VARIABLE_PROPERTIES(
+ "variables:\n - illegal: 2",
+ "ERROR: :2:4: Unsupported variable tag: illegal\n"
+ + " | - illegal: 2\n"
+ + " | ...^\n"
+ + "ERROR: :2:4: Missing required attribute(s): name, type\n"
+ + " | - illegal: 2\n"
+ + " | ...^"),
+ MISSING_OVERLOAD_RETURN(
+ "functions:\n"
+ + " - name: 'missing_return'\n"
+ + " overloads:\n"
+ + " - id: 'zero_arity'\n",
+ "ERROR: :4:9: Missing required attribute(s): return\n"
+ + " | - id: 'zero_arity'\n"
+ + " | ........^"),
+ MISSING_FUNCTION_NAME(
+ "functions:\n"
+ + " - overloads:\n"
+ + " - id: 'foo'\n"
+ + " return:\n"
+ + " type_name: 'string'\n",
+ "ERROR: :2:5: Missing required attribute(s): name\n"
+ + " | - overloads:\n"
+ + " | ....^"),
+ MISSING_OVERLOAD(
+ "functions:\n" + " - name: 'missing_overload'\n",
+ "ERROR: :2:5: Missing required attribute(s): overloads\n"
+ + " | - name: 'missing_overload'\n"
+ + " | ....^"),
+ MISSING_EXTENSION_NAME(
+ "extensions:\n" + "- version: 0",
+ "ERROR: :2:3: Missing required attribute(s): name\n"
+ + " | - version: 0\n"
+ + " | ..^"),
+ ILLEGAL_LIBRARY_SUBSET_TAG(
+ "name: 'test_suite_name'\n"
+ + "stdlib:\n"
+ + " unknown_tag: 'test_value'\n",
+ "ERROR: :3:3: Unsupported library subset tag: unknown_tag\n"
+ + " | unknown_tag: 'test_value'\n"
+ + " | ..^"),
+ ILLEGAL_LIBRARY_SUBSET_FUNCTION_SELECTOR_TAG(
+ "name: 'test_suite_name'\n"
+ + "stdlib:\n"
+ + " include_functions:\n"
+ + " - name: 'test_function'\n"
+ + " unknown_tag: 'test_value'\n",
+ "ERROR: :5:7: Unsupported function selector tag: unknown_tag\n"
+ + " | unknown_tag: 'test_value'\n"
+ + " | ......^"),
+ MISSING_LIBRARY_SUBSET_FUNCTION_SELECTOR_NAME(
+ "name: 'test_suite_name'\n"
+ + "stdlib:\n"
+ + " include_functions:\n"
+ + " - overloads:\n"
+ + " - id: add_bytes\n",
+ "ERROR: :4:7: Missing required attribute(s): name\n"
+ + " | - overloads:\n"
+ + " | ......^"),
+ ILLEGAL_LIBRARY_SUBSET_OVERLOAD_SELECTOR_TAG(
+ "name: 'test_suite_name'\n"
+ + "stdlib:\n"
+ + " include_functions:\n"
+ + " - name: _+_\n"
+ + " overloads:\n"
+ + " - id: test_overload\n"
+ + " unknown_tag: 'test_value'\n",
+ "ERROR: :7:11: Unsupported overload selector tag: unknown_tag\n"
+ + " | unknown_tag: 'test_value'\n"
+ + " | ..........^"),
+ ;
+
+ private final String yamlConfig;
+ private final String expectedErrorMessage;
+
+ EnvironmentParseErrorTestcase(String yamlConfig, String expectedErrorMessage) {
+ this.yamlConfig = yamlConfig;
+ this.expectedErrorMessage = expectedErrorMessage;
+ }
+ }
+
+ private enum EnvironmentExtendErrorTestCase {
+ BAD_EXTENSION("extensions:\n" + " - name: 'bad_name'", "Unrecognized extension: bad_name"),
+ BAD_TYPE(
+ "variables:\n" + "- name: 'bad_type'\n" + " type:\n" + " type_name: 'strings'",
+ "Undefined type name: strings"),
+ BAD_LIST(
+ "variables:\n" + " - name: 'bad_list'\n" + " type:\n" + " type_name: 'list'",
+ "List type has unexpected param count: 0"),
+ BAD_MAP(
+ "variables:\n"
+ + " - name: 'bad_map'\n"
+ + " type:\n"
+ + " type_name: 'map'\n"
+ + " params:\n"
+ + " - type_name: 'string'",
+ "Map type has unexpected param count: 1"),
+ BAD_LIST_TYPE_PARAM(
+ "variables:\n"
+ + " - name: 'bad_list_type_param'\n"
+ + " type:\n"
+ + " type_name: 'list'\n"
+ + " params:\n"
+ + " - type_name: 'number'",
+ "Undefined type name: number"),
+ BAD_MAP_TYPE_PARAM(
+ "variables:\n"
+ + " - name: 'bad_map_type_param'\n"
+ + " type:\n"
+ + " type_name: 'map'\n"
+ + " params:\n"
+ + " - type_name: 'string'\n"
+ + " - type_name: 'optional'",
+ "Undefined type name: optional"),
+ BAD_RETURN(
+ "functions:\n"
+ + " - name: 'bad_return'\n"
+ + " overloads:\n"
+ + " - id: 'zero_arity'\n"
+ + " return:\n"
+ + " type_name: 'mystery'",
+ "Undefined type name: mystery"),
+ BAD_OVERLOAD_TARGET(
+ "functions:\n"
+ + " - name: 'bad_target'\n"
+ + " overloads:\n"
+ + " - id: 'unary_member'\n"
+ + " target:\n"
+ + " type_name: 'unknown'\n"
+ + " return:\n"
+ + " type_name: 'null_type'",
+ "Undefined type name: unknown"),
+ BAD_OVERLOAD_ARG(
+ "functions:\n"
+ + " - name: 'bad_arg'\n"
+ + " overloads:\n"
+ + " - id: 'unary_global'\n"
+ + " args:\n"
+ + " - type_name: 'unknown'\n"
+ + " return:\n"
+ + " type_name: 'null_type'",
+ "Undefined type name: unknown"),
+ ;
+
+ private final String yamlConfig;
+ private final String expectedErrorMessage;
+
+ EnvironmentExtendErrorTestCase(String yamlConfig, String expectedErrorMessage) {
+ this.yamlConfig = yamlConfig;
+ this.expectedErrorMessage = expectedErrorMessage;
+ }
+ }
+
+ @SuppressWarnings("ImmutableEnumChecker") // Test only
+ private enum EnvironmentYamlResourceTestCase {
+ EXTENDED_ENV(
+ "environment/extended_env.yaml",
+ CelEnvironment.newBuilder()
+ .setName("extended-env")
+ .setContainer("cel.expr")
+ .addExtensions(
+ ImmutableSet.of(
+ ExtensionConfig.of("optional", 2),
+ ExtensionConfig.of("math", Integer.MAX_VALUE)))
+ .setVariables(
+ VariableDecl.newBuilder()
+ .setName("msg")
+ .setType(TypeDecl.create("cel.expr.conformance.proto3.TestAllTypes"))
+ .build())
+ .setFunctions(
+ FunctionDecl.create(
+ "isEmpty",
+ ImmutableSet.of(
+ OverloadDecl.newBuilder()
+ .setId("wrapper_string_isEmpty")
+ .setTarget(TypeDecl.create("google.protobuf.StringValue"))
+ .setReturnType(TypeDecl.create("bool"))
+ .build(),
+ OverloadDecl.newBuilder()
+ .setId("list_isEmpty")
+ .setTarget(
+ TypeDecl.newBuilder()
+ .setName("list")
+ .addParams(
+ TypeDecl.newBuilder()
+ .setName("T")
+ .setIsTypeParam(true)
+ .build())
+ .build())
+ .setReturnType(TypeDecl.create("bool"))
+ .build())))
+ .build()),
+
+ LIBRARY_SUBSET_ENV(
+ "environment/subset_env.yaml",
+ CelEnvironment.newBuilder()
+ .setName("subset-env")
+ .setStandardLibrarySubset(
+ LibrarySubset.newBuilder()
+ .setDisabled(false)
+ .setExcludedMacros(ImmutableSet.of("map", "filter"))
+ .setExcludedFunctions(
+ ImmutableSet.of(
+ FunctionSelector.create(
+ "_+_", ImmutableSet.of("add_bytes", "add_list", "add_string")),
+ FunctionSelector.create("matches", ImmutableSet.of()),
+ FunctionSelector.create(
+ "timestamp", ImmutableSet.of("string_to_timestamp")),
+ FunctionSelector.create(
+ "duration", ImmutableSet.of("string_to_duration"))))
+ .build())
+ .setVariables(
+ VariableDecl.create("x", TypeDecl.create("int")),
+ VariableDecl.create("y", TypeDecl.create("double")),
+ VariableDecl.create("z", TypeDecl.create("uint")))
+ .build()),
+ ;
+
+ private final String yamlFileContent;
+ private final CelEnvironment expectedEnvironment;
+
+ EnvironmentYamlResourceTestCase(String yamlResourcePath, CelEnvironment expectedEnvironment) {
+ try {
+ this.yamlFileContent = readFile(yamlResourcePath);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ this.expectedEnvironment = expectedEnvironment;
+ }
+ }
+
+ @Test
+ public void environment_withYamlResource(@TestParameter EnvironmentYamlResourceTestCase testCase)
+ throws Exception {
+ CelEnvironment environment = ENVIRONMENT_PARSER.parse(testCase.yamlFileContent);
+
+ // Empty out the parsed yaml source, as it's not relevant in the assertion here, and that it's
+ // only obtainable through the yaml parser.
+ environment = environment.toBuilder().setSource(Optional.empty()).build();
+ assertThat(environment).isEqualTo(testCase.expectedEnvironment);
+ }
+
+ @Test
+ public void lateBoundFunction_evaluate_callExpr() throws Exception {
+ String configSource =
+ "name: late_bound_function_config\n"
+ + "functions:\n"
+ + " - name: 'test'\n"
+ + " overloads:\n"
+ + " - id: 'test_bool'\n"
+ + " args:\n"
+ + " - type_name: 'bool'\n"
+ + " return:\n"
+ + " type_name: 'bool'";
+ CelEnvironment celEnvironment = ENVIRONMENT_PARSER.parse(configSource);
+ Cel celDetails =
+ CelFactory.standardCelBuilder()
+ .addVar("a", SimpleType.INT)
+ .addVar("b", SimpleType.INT)
+ .addVar("c", SimpleType.INT)
+ .build();
+ Cel cel = celEnvironment.extend(celDetails, CelOptions.DEFAULT);
+ CelAbstractSyntaxTree ast = cel.compile("a < 0 && b < 0 && c < 0 && test(a<0)").getAst();
+ CelLateFunctionBindings bindings =
+ CelLateFunctionBindings.from(
+ CelFunctionBinding.from("test_bool", Boolean.class, result -> result));
+
+ boolean result =
+ (boolean) cel.createProgram(ast).eval(ImmutableMap.of("a", -1, "b", -1, "c", -4), bindings);
+
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ public void lateBoundFunction_trace_callExpr_identifyFalseBranch() throws Exception {
+ AtomicReference capturedExpr = new AtomicReference<>();
+ CelEvaluationListener listener =
+ (expr, res) -> {
+ if (res instanceof Boolean && !(boolean) res && capturedExpr.get() == null) {
+ capturedExpr.set(expr);
+ }
+ };
+
+ String configSource =
+ "name: late_bound_function_config\n"
+ + "functions:\n"
+ + " - name: 'test'\n"
+ + " overloads:\n"
+ + " - id: 'test_bool'\n"
+ + " args:\n"
+ + " - type_name: 'bool'\n"
+ + " return:\n"
+ + " type_name: 'bool'";
+
+ CelEnvironment celEnvironment = ENVIRONMENT_PARSER.parse(configSource);
+ Cel celDetails =
+ CelFactory.standardCelBuilder()
+ .addVar("a", SimpleType.INT)
+ .addVar("b", SimpleType.INT)
+ .addVar("c", SimpleType.INT)
+ .build();
+ Cel cel = celEnvironment.extend(celDetails, CelOptions.DEFAULT);
+ CelAbstractSyntaxTree ast = cel.compile("a < 0 && b < 0 && c < 0 && test(a<0)").getAst();
+ CelLateFunctionBindings bindings =
+ CelLateFunctionBindings.from(
+ CelFunctionBinding.from("test_bool", Boolean.class, result -> result));
+
+ boolean result =
+ (boolean)
+ cel.createProgram(ast)
+ .trace(ImmutableMap.of("a", -1, "b", 1, "c", -4), bindings, listener);
+
+ assertThat(result).isFalse();
+ // Demonstrate that "b < 0" is what caused the expression to be false
+ CelAbstractSyntaxTree subtree =
+ CelAbstractSyntaxTree.newParsedAst(capturedExpr.get(), CelSource.newBuilder().build());
+ assertThat(CelUnparserFactory.newUnparser().unparse(subtree)).isEqualTo("b < 0");
+ }
+
+ private static String readFile(String path) throws IOException {
+ URL url = Resources.getResource(Ascii.toLowerCase(path));
+ return Resources.toString(url, UTF_8);
+ }
+}
diff --git a/bundle/src/test/java/dev/cel/bundle/CelEnvironmentYamlSerializerTest.java b/bundle/src/test/java/dev/cel/bundle/CelEnvironmentYamlSerializerTest.java
new file mode 100644
index 000000000..2bd39ad1f
--- /dev/null
+++ b/bundle/src/test/java/dev/cel/bundle/CelEnvironmentYamlSerializerTest.java
@@ -0,0 +1,174 @@
+// Copyright 2025 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.bundle;
+
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.base.Ascii;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.io.Resources;
+import dev.cel.bundle.CelEnvironment.ExtensionConfig;
+import dev.cel.bundle.CelEnvironment.FunctionDecl;
+import dev.cel.bundle.CelEnvironment.LibrarySubset;
+import dev.cel.bundle.CelEnvironment.LibrarySubset.FunctionSelector;
+import dev.cel.bundle.CelEnvironment.OverloadDecl;
+import dev.cel.bundle.CelEnvironment.TypeDecl;
+import dev.cel.bundle.CelEnvironment.VariableDecl;
+import java.io.IOException;
+import java.net.URL;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class CelEnvironmentYamlSerializerTest {
+
+ @Test
+ public void toYaml_success() throws Exception {
+ CelEnvironment environment =
+ CelEnvironment.newBuilder()
+ .setName("dump_env")
+ .setDescription("dump_env description")
+ .setContainer("test.container")
+ .addExtensions(
+ ImmutableSet.of(
+ ExtensionConfig.of("bindings"),
+ ExtensionConfig.of("encoders"),
+ ExtensionConfig.of("lists"),
+ ExtensionConfig.of("math"),
+ ExtensionConfig.of("optional"),
+ ExtensionConfig.of("protos"),
+ ExtensionConfig.of("sets"),
+ ExtensionConfig.of("strings", 1)))
+ .setVariables(
+ ImmutableSet.of(
+ VariableDecl.create(
+ "request", TypeDecl.create("google.rpc.context.AttributeContext.Request")),
+ VariableDecl.create(
+ "map_var",
+ TypeDecl.newBuilder()
+ .setName("map")
+ .addParams(TypeDecl.create("string"))
+ .addParams(TypeDecl.create("string"))
+ .build())))
+ .setFunctions(
+ ImmutableSet.of(
+ FunctionDecl.create(
+ "getOrDefault",
+ ImmutableSet.of(
+ OverloadDecl.newBuilder()
+ .setId("getOrDefault_key_value")
+ .setTarget(
+ TypeDecl.newBuilder()
+ .setName("map")
+ .addParams(
+ TypeDecl.newBuilder()
+ .setName("K")
+ .setIsTypeParam(true)
+ .build())
+ .addParams(
+ TypeDecl.newBuilder()
+ .setName("V")
+ .setIsTypeParam(true)
+ .build())
+ .build())
+ .setArguments(
+ ImmutableList.of(
+ TypeDecl.newBuilder()
+ .setName("K")
+ .setIsTypeParam(true)
+ .build(),
+ TypeDecl.newBuilder()
+ .setName("V")
+ .setIsTypeParam(true)
+ .build()))
+ .setReturnType(
+ TypeDecl.newBuilder().setName("V").setIsTypeParam(true).build())
+ .build())),
+ FunctionDecl.create(
+ "coalesce",
+ ImmutableSet.of(
+ OverloadDecl.newBuilder()
+ .setId("coalesce_null_int")
+ .setTarget(TypeDecl.create("google.protobuf.Int64Value"))
+ .setArguments(ImmutableList.of(TypeDecl.create("int")))
+ .setReturnType(TypeDecl.create("int"))
+ .build()))))
+ .setStandardLibrarySubset(
+ LibrarySubset.newBuilder()
+ .setDisabled(true)
+ .setMacrosDisabled(true)
+ .setIncludedMacros(ImmutableSet.of("has", "exists"))
+ .setIncludedFunctions(
+ ImmutableSet.of(
+ FunctionSelector.create("_!=_", ImmutableSet.of()),
+ FunctionSelector.create(
+ "_+_", ImmutableSet.of("add_bytes", "add_list"))))
+ .build())
+ .build();
+
+ String yamlOutput = CelEnvironmentYamlSerializer.toYaml(environment);
+ try {
+ String yamlFileContent = readFile("environment/dump_env.yaml");
+ // Strip the license at the beginning of the file
+ String expectedOutput = yamlFileContent.replaceAll("#.*\\n", "").replaceAll("^\\n", "");
+ assertThat(yamlOutput).isEqualTo(expectedOutput);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static String readFile(String path) throws IOException {
+ URL url = Resources.getResource(Ascii.toLowerCase(path));
+ return Resources.toString(url, UTF_8);
+ }
+
+ @Test
+ public void standardLibrary_excludeMacrosAndFunctions() throws Exception {
+ CelEnvironment environment =
+ CelEnvironment.newBuilder()
+ .setName("dump_env")
+ .setStandardLibrarySubset(
+ LibrarySubset.newBuilder()
+ .setDisabled(false)
+ .setExcludedMacros(ImmutableSet.of("has", "exists"))
+ .setExcludedFunctions(
+ ImmutableSet.of(
+ FunctionSelector.create("_!=_", ImmutableSet.of()),
+ FunctionSelector.create(
+ "_+_", ImmutableSet.of("add_bytes", "add_list"))))
+ .build())
+ .build();
+
+ String yamlOutput = CelEnvironmentYamlSerializer.toYaml(environment);
+
+ String expectedYaml =
+ "name: dump_env\n"
+ + "stdlib:\n"
+ + " exclude_macros:\n"
+ + " - exists\n"
+ + " - has\n"
+ + " exclude_functions:\n"
+ + " - name: _!=_\n"
+ + " - name: _+_\n"
+ + " overloads:\n"
+ + " - id: add_bytes\n"
+ + " - id: add_list\n";
+
+ assertThat(yamlOutput).isEqualTo(expectedYaml);
+ }
+}
diff --git a/bundle/src/test/java/dev/cel/bundle/CelImplTest.java b/bundle/src/test/java/dev/cel/bundle/CelImplTest.java
index 4cb3a75e7..2a702538a 100644
--- a/bundle/src/test/java/dev/cel/bundle/CelImplTest.java
+++ b/bundle/src/test/java/dev/cel/bundle/CelImplTest.java
@@ -16,6 +16,9 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
+import static dev.cel.common.CelFunctionDecl.newFunctionDeclaration;
+import static dev.cel.common.CelOverloadDecl.newGlobalOverload;
+import static dev.cel.common.CelOverloadDecl.newMemberOverload;
import static org.junit.Assert.assertThrows;
import dev.cel.expr.CheckedExpr;
@@ -38,36 +41,43 @@
import com.google.common.util.concurrent.MoreExecutors;
import com.google.protobuf.Any;
import com.google.protobuf.ByteString;
+import com.google.protobuf.DescriptorProtos.FileDescriptorProto;
import com.google.protobuf.DescriptorProtos.FileDescriptorSet;
+import com.google.protobuf.Descriptors.FileDescriptor;
import com.google.protobuf.Duration;
+import com.google.protobuf.Empty;
import com.google.protobuf.FieldMask;
import com.google.protobuf.Message;
-import com.google.protobuf.NullValue;
import com.google.protobuf.Struct;
+import com.google.protobuf.TextFormat;
import com.google.protobuf.Timestamp;
-import com.google.protobuf.util.Timestamps;
+import com.google.protobuf.WrappersProto;
import com.google.rpc.context.AttributeContext;
import com.google.testing.junit.testparameterinjector.TestParameter;
import com.google.testing.junit.testparameterinjector.TestParameterInjector;
import com.google.testing.junit.testparameterinjector.TestParameters;
+import dev.cel.checker.CelCheckerLegacyImpl;
import dev.cel.checker.DescriptorTypeProvider;
import dev.cel.checker.ProtoTypeMask;
import dev.cel.checker.TypeProvider;
import dev.cel.common.CelAbstractSyntaxTree;
-import dev.cel.common.CelFunctionDecl;
+import dev.cel.common.CelContainer;
+import dev.cel.common.CelErrorCode;
import dev.cel.common.CelIssue;
import dev.cel.common.CelOptions;
-import dev.cel.common.CelOverloadDecl;
import dev.cel.common.CelProtoAbstractSyntaxTree;
+import dev.cel.common.CelSourceLocation;
import dev.cel.common.CelValidationException;
import dev.cel.common.CelValidationResult;
import dev.cel.common.CelVarDecl;
import dev.cel.common.ast.CelExpr;
-import dev.cel.common.ast.CelExpr.CelCreateList;
+import dev.cel.common.ast.CelExpr.CelList;
+import dev.cel.common.internal.ProtoTimeUtils;
import dev.cel.common.testing.RepeatedTestProvider;
import dev.cel.common.types.CelKind;
+import dev.cel.common.types.CelProtoMessageTypes;
+import dev.cel.common.types.CelProtoTypes;
import dev.cel.common.types.CelType;
-import dev.cel.common.types.CelTypes;
import dev.cel.common.types.EnumType;
import dev.cel.common.types.ListType;
import dev.cel.common.types.MapType;
@@ -75,30 +85,39 @@
import dev.cel.common.types.ProtoMessageTypeProvider;
import dev.cel.common.types.SimpleType;
import dev.cel.common.types.StructTypeReference;
+import dev.cel.common.values.CelByteString;
+import dev.cel.common.values.NullValue;
import dev.cel.compiler.CelCompiler;
import dev.cel.compiler.CelCompilerFactory;
+import dev.cel.compiler.CelCompilerImpl;
+import dev.cel.expr.conformance.proto2.Proto2ExtensionScopedMessage;
+import dev.cel.expr.conformance.proto3.TestAllTypes;
+import dev.cel.parser.CelParserImpl;
import dev.cel.parser.CelStandardMacro;
import dev.cel.runtime.CelAttribute;
import dev.cel.runtime.CelAttribute.Qualifier;
import dev.cel.runtime.CelAttributePattern;
import dev.cel.runtime.CelEvaluationException;
+import dev.cel.runtime.CelEvaluationExceptionBuilder;
+import dev.cel.runtime.CelFunctionBinding;
import dev.cel.runtime.CelRuntime;
-import dev.cel.runtime.CelRuntime.CelFunctionBinding;
import dev.cel.runtime.CelRuntime.Program;
import dev.cel.runtime.CelRuntimeFactory;
+import dev.cel.runtime.CelRuntimeLegacyImpl;
import dev.cel.runtime.CelUnknownSet;
import dev.cel.runtime.CelVariableResolver;
import dev.cel.runtime.UnknownContext;
import dev.cel.testing.testdata.proto3.StandaloneGlobalEnum;
-import dev.cel.testing.testdata.proto3.TestAllTypesProto.TestAllTypes;
import java.util.ArrayList;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
-import org.jspecify.nullness.Nullable;
+import org.jspecify.annotations.Nullable;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -146,10 +165,10 @@ public final class CelImplTest {
private static final CheckedExpr CHECKED_EXPR =
CheckedExpr.newBuilder()
.setExpr(EXPR)
- .putTypeMap(1L, CelTypes.BOOL)
- .putTypeMap(2L, CelTypes.BOOL)
- .putTypeMap(3L, CelTypes.BOOL)
- .putTypeMap(4L, CelTypes.BOOL)
+ .putTypeMap(1L, CelProtoTypes.BOOL)
+ .putTypeMap(2L, CelProtoTypes.BOOL)
+ .putTypeMap(3L, CelProtoTypes.BOOL)
+ .putTypeMap(4L, CelProtoTypes.BOOL)
.putReferenceMap(2L, Reference.newBuilder().addOverloadId("logical_and").build())
.putReferenceMap(3L, Reference.newBuilder().addOverloadId("logical_not").build())
.build();
@@ -177,13 +196,13 @@ public void build_badFileDescriptorSet() {
IllegalArgumentException.class,
() ->
standardCelBuilderWithMacros()
- .setContainer("google.rpc.context.AttributeContext")
+ .setContainer(CelContainer.ofName("google.rpc.context.AttributeContext"))
.addFileTypes(
FileDescriptorSet.newBuilder()
.addFile(AttributeContext.getDescriptor().getFile().toProto())
.build())
.setProtoResultType(
- CelTypes.createMessage("google.rpc.context.AttributeContext.Resource"))
+ CelProtoTypes.createMessage("google.rpc.context.AttributeContext.Resource"))
.build());
assertThat(e).hasMessageThat().contains("file descriptor set with unresolved proto file");
}
@@ -209,7 +228,7 @@ public void check() throws Exception {
public void compile(boolean useProtoResultType) throws Exception {
CelBuilder celBuilder = standardCelBuilderWithMacros();
if (useProtoResultType) {
- celBuilder.setProtoResultType(CelTypes.BOOL);
+ celBuilder.setProtoResultType(CelProtoTypes.BOOL);
} else {
celBuilder.setResultType(SimpleType.BOOL);
}
@@ -223,7 +242,7 @@ public void compile(boolean useProtoResultType) throws Exception {
public void compile_resultTypeCheckFailure(boolean useProtoResultType) {
CelBuilder celBuilder = standardCelBuilderWithMacros();
if (useProtoResultType) {
- celBuilder.setProtoResultType(CelTypes.STRING);
+ celBuilder.setProtoResultType(CelProtoTypes.STRING);
} else {
celBuilder.setResultType(SimpleType.STRING);
}
@@ -241,13 +260,13 @@ public void compile_combinedTypeProvider() {
new ProtoMessageTypeProvider(ImmutableList.of(AttributeContext.getDescriptor()));
Cel cel =
standardCelBuilderWithMacros()
- .setContainer("google")
+ .setContainer(CelContainer.ofName("google"))
.setTypeProvider(celTypeProvider)
.addMessageTypes(com.google.type.Expr.getDescriptor())
.addProtoTypeMasks(
ImmutableList.of(ProtoTypeMask.ofAllFields("google.rpc.context.AttributeContext")))
.addVar("condition", StructTypeReference.create("google.type.Expr"))
- .setProtoResultType(CelTypes.BOOL)
+ .setProtoResultType(CelProtoTypes.BOOL)
.build();
CelValidationResult result =
cel.compile("type.Expr{expression: \"'hello'\"}.expression == condition.expression");
@@ -262,7 +281,7 @@ public void compile_customTypeProvider() {
AttributeContext.getDescriptor(), com.google.type.Expr.getDescriptor()));
Cel cel =
standardCelBuilderWithMacros()
- .setContainer("google")
+ .setContainer(CelContainer.ofName("google"))
.setTypeProvider(celTypeProvider)
.addVar("condition", StructTypeReference.create("google.type.Expr"))
.setResultType(SimpleType.BOOL)
@@ -279,7 +298,8 @@ public void compile_customTypesWithAliasingCombinedProviders() throws Exception
// However, the first type resolution from the alias to the qualified type name won't be
// sufficient as future checks will expect the resolved alias to also be a type.
TypeProvider customTypeProvider =
- aliasingProvider(ImmutableMap.of("Condition", CelTypes.createMessage("google.type.Expr")));
+ aliasingProvider(
+ ImmutableMap.of("Condition", CelProtoTypes.createMessage("google.type.Expr")));
// The registration of the aliasing TypeProvider and the google.type.Expr descriptor
// ensures that once the alias is resolved, the additional details about the Expr type
@@ -311,9 +331,9 @@ public void compile_customTypesWithAliasingSelfContainedProvider() throws Except
aliasingProvider(
ImmutableMap.of(
"Condition",
- CelTypes.createMessage("google.type.Expr"),
+ CelProtoTypes.createMessage("google.type.Expr"),
"google.type.Expr",
- CelTypes.createMessage("google.type.Expr")));
+ CelProtoTypes.createMessage("google.type.Expr")));
// The registration of the aliasing TypeProvider and the google.type.Expr descriptor
// ensures that once the alias is resolved, the additional details about the Expr type
@@ -394,6 +414,7 @@ public void program_setTypeFactoryOnAnyPackedMessage_messageConstructionSucceeds
}
@Test
+ @SuppressWarnings("unused") // testRunIndex name retained for test result readability
public void program_concurrentMessageConstruction_succeeds(
@TestParameter(valuesProvider = RepeatedTestProvider.class) int testRunIndex)
throws Exception {
@@ -401,7 +422,7 @@ public void program_concurrentMessageConstruction_succeeds(
int threadCount = 10;
Cel cel =
standardCelBuilderWithMacros()
- .setContainer("google.rpc.context.AttributeContext")
+ .setContainer(CelContainer.ofName("google.rpc.context.AttributeContext"))
.addFileTypes(
Any.getDescriptor().getFile(),
Duration.getDescriptor().getFile(),
@@ -459,7 +480,10 @@ public void compile_typeCheckFailure() {
assertThat(syntaxErrorResult.hasError()).isTrue();
assertThat(syntaxErrorResult.getErrors())
.containsExactly(
- CelIssue.formatError(1, 0, "undeclared reference to 'variable' (in container '')"));
+ CelIssue.formatError(
+ /* exprId= */ 1L,
+ CelSourceLocation.of(1, 0),
+ "undeclared reference to 'variable' (in container '')"));
assertThat(syntaxErrorResult.getErrorString())
.isEqualTo(
"ERROR: :1:1: undeclared reference to 'variable' (in container '')\n"
@@ -477,9 +501,9 @@ public void compile_withOptionalTypes() throws Exception {
CelAbstractSyntaxTree ast = cel.compile("[?a]").getAst();
- CelCreateList createList = ast.getExpr().createList();
- assertThat(createList.optionalIndices()).containsExactly(0);
- assertThat(createList.elements()).containsExactly(CelExpr.ofIdentExpr(2, "a"));
+ CelList list = ast.getExpr().list();
+ assertThat(list.optionalIndices()).containsExactly(0);
+ assertThat(list.elements()).containsExactly(CelExpr.ofIdent(2, "a"));
}
@Test
@@ -489,12 +513,14 @@ public void compile_overlappingVarsFailure() {
.addDeclarations(
Decl.newBuilder()
.setName("variable")
- .setIdent(IdentDecl.newBuilder().setType(CelTypes.STRING))
+ .setIdent(IdentDecl.newBuilder().setType(CelProtoTypes.STRING))
.build())
.addDeclarations(
Decl.newBuilder()
.setName("variable")
- .setIdent(IdentDecl.newBuilder().setType(CelTypes.createList(CelTypes.STRING)))
+ .setIdent(
+ IdentDecl.newBuilder()
+ .setType(CelProtoTypes.createList(CelProtoTypes.STRING)))
.build())
.setResultType(SimpleType.BOOL)
.build();
@@ -518,7 +544,7 @@ public void program_withVars() throws Exception {
.addDeclarations(
Decl.newBuilder()
.setName("variable")
- .setIdent(IdentDecl.newBuilder().setType(CelTypes.STRING))
+ .setIdent(IdentDecl.newBuilder().setType(CelProtoTypes.STRING))
.build())
.setResultType(SimpleType.BOOL)
.build();
@@ -534,7 +560,7 @@ public void program_withCelValue() throws Exception {
.addDeclarations(
Decl.newBuilder()
.setName("variable")
- .setIdent(IdentDecl.newBuilder().setType(CelTypes.STRING))
+ .setIdent(IdentDecl.newBuilder().setType(CelProtoTypes.STRING))
.build())
.setResultType(SimpleType.BOOL)
.build();
@@ -570,6 +596,53 @@ public void program_withProtoVars() throws Exception {
.isEqualTo(true);
}
+ @Test
+ public void program_withAllFieldsHidden_emptyMessageConstructionSuccess() throws Exception {
+ Cel cel =
+ standardCelBuilderWithMacros()
+ .addMessageTypes(AttributeContext.getDescriptor())
+ .setContainer(CelContainer.ofName("google.rpc.context.AttributeContext"))
+ .addProtoTypeMasks(
+ ProtoTypeMask.ofAllFieldsHidden("google.rpc.context.AttributeContext"))
+ .build();
+
+ assertThat(cel.createProgram(cel.compile("AttributeContext{}").getAst()).eval())
+ .isEqualTo(AttributeContext.getDefaultInstance());
+ }
+
+ @Test
+ public void compile_withAllFieldsHidden_selectHiddenField_throws() throws Exception {
+ Cel cel =
+ standardCelBuilderWithMacros()
+ .addMessageTypes(AttributeContext.getDescriptor())
+ .setContainer(CelContainer.ofName("google.rpc.context.AttributeContext"))
+ .addProtoTypeMasks(
+ ProtoTypeMask.ofAllFieldsHidden("google.rpc.context.AttributeContext"))
+ .build();
+
+ CelValidationException e =
+ assertThrows(
+ CelValidationException.class,
+ () -> cel.compile("AttributeContext{ request: AttributeContext.Request{} }").getAst());
+ assertThat(e).hasMessageThat().contains("undefined field 'request'");
+ }
+
+ @Test
+ public void compile_withAllFieldsHidden_selectHiddenFieldOnVar_throws() throws Exception {
+ Cel cel =
+ standardCelBuilderWithMacros()
+ .addMessageTypes(AttributeContext.getDescriptor())
+ .setContainer(CelContainer.ofName("google.rpc.context.AttributeContext"))
+ .addProtoTypeMasks(
+ ProtoTypeMask.ofAllFieldsHidden("google.rpc.context.AttributeContext"))
+ .addVar("attr_ctx", StructTypeReference.create("google.rpc.context.AttributeContext"))
+ .build();
+
+ CelValidationException e =
+ assertThrows(CelValidationException.class, () -> cel.compile("attr_ctx.source").getAst());
+ assertThat(e).hasMessageThat().contains("undefined field 'source'");
+ }
+
@Test
public void program_withNestedRestrictedProtoVars() throws Exception {
Cel cel =
@@ -597,11 +670,11 @@ public void program_withFunctions() throws Exception {
ImmutableList.of(
Decl.newBuilder()
.setName("one")
- .setIdent(IdentDecl.newBuilder().setType(CelTypes.BOOL))
+ .setIdent(IdentDecl.newBuilder().setType(CelProtoTypes.BOOL))
.build(),
Decl.newBuilder()
.setName("two")
- .setIdent(IdentDecl.newBuilder().setType(CelTypes.BOOL))
+ .setIdent(IdentDecl.newBuilder().setType(CelProtoTypes.BOOL))
.build(),
Decl.newBuilder()
.setName("any")
@@ -610,21 +683,21 @@ public void program_withFunctions() throws Exception {
.addOverloads(
Overload.newBuilder()
.setOverloadId("any_bool")
- .addParams(CelTypes.BOOL)
- .setResultType(CelTypes.BOOL))
+ .addParams(CelProtoTypes.BOOL)
+ .setResultType(CelProtoTypes.BOOL))
.addOverloads(
Overload.newBuilder()
.setOverloadId("any_bool_bool")
- .addParams(CelTypes.BOOL)
- .addParams(CelTypes.BOOL)
- .setResultType(CelTypes.BOOL))
+ .addParams(CelProtoTypes.BOOL)
+ .addParams(CelProtoTypes.BOOL)
+ .setResultType(CelProtoTypes.BOOL))
.addOverloads(
Overload.newBuilder()
.setOverloadId("any_bool_bool_bool")
- .addParams(CelTypes.BOOL)
- .addParams(CelTypes.BOOL)
- .addParams(CelTypes.BOOL)
- .setResultType(CelTypes.BOOL)))
+ .addParams(CelProtoTypes.BOOL)
+ .addParams(CelProtoTypes.BOOL)
+ .addParams(CelProtoTypes.BOOL)
+ .setResultType(CelProtoTypes.BOOL)))
.build()))
.addFunctionBindings(CelFunctionBinding.from("any_bool", Boolean.class, (arg) -> arg))
.addFunctionBindings(
@@ -658,7 +731,7 @@ public void program_withThrowingFunction() throws Exception {
.addOverloads(
Overload.newBuilder()
.setOverloadId("throws")
- .setResultType(CelTypes.BOOL)))
+ .setResultType(CelProtoTypes.BOOL)))
.build())
.addFunctionBindings(
CelFunctionBinding.from(
@@ -686,15 +759,16 @@ public void program_withThrowingFunctionShortcircuited() throws Exception {
.addOverloads(
Overload.newBuilder()
.setOverloadId("throws")
- .setResultType(CelTypes.BOOL)))
+ .setResultType(CelProtoTypes.BOOL)))
.build())
.addFunctionBindings(
CelFunctionBinding.from(
"throws",
ImmutableList.of(),
(args) -> {
- throw new CelEvaluationException(
- "this method always throws", new RuntimeException("reason"));
+ throw CelEvaluationExceptionBuilder.newBuilder("this method always throws")
+ .setCause(new RuntimeException("reason"))
+ .build();
}))
.setResultType(SimpleType.BOOL)
.build();
@@ -724,7 +798,7 @@ public void program_simpleStructTypeReference() throws Exception {
public void program_messageConstruction() throws Exception {
Cel cel =
standardCelBuilderWithMacros()
- .setContainer("google.type")
+ .setContainer(CelContainer.ofName("google.type"))
.addMessageTypes(com.google.type.Expr.getDescriptor())
.setResultType(StructTypeReference.create("google.type.Expr"))
.setStandardEnvironmentEnabled(false)
@@ -741,12 +815,12 @@ public void program_duplicateTypeDescriptor() throws Exception {
standardCelBuilderWithMacros()
.addMessageTypes(Timestamp.getDescriptor())
.addMessageTypes(ImmutableList.of(Timestamp.getDescriptor()))
- .setContainer("google")
+ .setContainer(CelContainer.ofName("google"))
.setResultType(SimpleType.TIMESTAMP)
.build();
CelRuntime.Program program =
cel.createProgram(cel.compile("protobuf.Timestamp{seconds: 12}").getAst());
- assertThat(program.eval()).isEqualTo(Timestamps.fromSeconds(12));
+ assertThat(program.eval()).isEqualTo(ProtoTimeUtils.fromSecondsToTimestamp(12));
}
@Test
@@ -754,12 +828,12 @@ public void program_hermeticDescriptors_wellKnownProtobuf() throws Exception {
Cel cel =
standardCelBuilderWithMacros()
.addMessageTypes(Timestamp.getDescriptor())
- .setContainer("google")
+ .setContainer(CelContainer.ofName("google"))
.setResultType(SimpleType.TIMESTAMP)
.build();
CelRuntime.Program program =
cel.createProgram(cel.compile("protobuf.Timestamp{seconds: 12}").getAst());
- assertThat(program.eval()).isEqualTo(Timestamps.fromSeconds(12));
+ assertThat(program.eval()).isEqualTo(ProtoTimeUtils.fromSecondsToTimestamp(12));
}
@Test
@@ -776,7 +850,7 @@ public void program_partialMessageTypes() throws Exception {
// defined in checked.proto. Because the `Expr` type is referenced within a message
// field of the CheckedExpr, it is available for use.
.setOptions(CelOptions.current().resolveTypeDependencies(false).build())
- .setContainer(packageName)
+ .setContainer(CelContainer.ofName(packageName))
.setResultType(StructTypeReference.create(packageName + ".Expr"))
.build();
CelRuntime.Program program = cel.createProgram(cel.compile("Expr{}").getAst());
@@ -793,7 +867,7 @@ public void program_partialMessageTypeFailure() {
// defined in checked.proto. Because the `ParsedExpr` type is not referenced, it is not
// available for use within CEL when deep type resolution is disabled.
.setOptions(CelOptions.current().resolveTypeDependencies(false).build())
- .setContainer(packageName)
+ .setContainer(CelContainer.ofName(packageName))
.setResultType(StructTypeReference.create(packageName + ".ParsedExpr"))
.build();
CelValidationException e =
@@ -812,7 +886,7 @@ public void program_deepTypeResolution() throws Exception {
// defined in checked.proto. Because deep type dependency resolution is enabled, the
// `ParsedExpr` may be used within CEL.
.setOptions(CelOptions.current().resolveTypeDependencies(true).build())
- .setContainer(packageName)
+ .setContainer(CelContainer.ofName(packageName))
.setResultType(StructTypeReference.create(packageName + ".ParsedExpr"))
.build();
CelRuntime.Program program = cel.createProgram(cel.compile("ParsedExpr{}").getAst());
@@ -826,7 +900,7 @@ public void program_deepTypeResolutionEnabledForRuntime_success() throws Excepti
CelCompilerFactory.standardCelCompilerBuilder()
.addFileTypes(ParsedExpr.getDescriptor().getFile())
.setResultType(StructTypeReference.create(packageName + ".ParsedExpr"))
- .setContainer(packageName)
+ .setContainer(CelContainer.ofName(packageName))
.build();
CelAbstractSyntaxTree ast = celCompiler.compile("ParsedExpr{}").getAst();
@@ -852,7 +926,7 @@ public void program_deepTypeResolutionDisabledForRuntime_fails() throws Exceptio
.addFileTypes(CheckedExpr.getDescriptor().getFile())
.setOptions(CelOptions.current().resolveTypeDependencies(true).build())
.setResultType(StructTypeReference.create(packageName + ".ParsedExpr"))
- .setContainer(packageName)
+ .setContainer(CelContainer.ofName(packageName))
.build();
// 'ParsedExpr' is defined in syntax.proto but the descriptor provided is from 'checked.proto'.
@@ -883,12 +957,12 @@ public void program_typeProvider() throws Exception {
standardCelBuilderWithMacros()
.setTypeProvider(
new DescriptorTypeProvider(ImmutableList.of(Timestamp.getDescriptor())))
- .setContainer("google")
+ .setContainer(CelContainer.ofName("google"))
.setResultType(SimpleType.TIMESTAMP)
.build();
CelRuntime.Program program =
cel.createProgram(cel.compile("protobuf.Timestamp{seconds: 12}").getAst());
- assertThat(program.eval()).isEqualTo(Timestamps.fromSeconds(12));
+ assertThat(program.eval()).isEqualTo(ProtoTimeUtils.fromSecondsToTimestamp(12));
}
@Test
@@ -902,7 +976,7 @@ public void program_protoActivation() throws Exception {
.setIdent(
IdentDecl.newBuilder()
.setType(
- CelTypes.createMessage(
+ CelProtoTypes.createMessage(
"google.rpc.context.AttributeContext.Resource")))
.build())
.setResultType(SimpleType.STRING)
@@ -925,7 +999,8 @@ public void program_enumTypeDirectResolution(boolean resolveTypeDependencies) th
.addFileTypes(StandaloneGlobalEnum.getDescriptor().getFile())
.setOptions(
CelOptions.current().resolveTypeDependencies(resolveTypeDependencies).build())
- .setContainer("dev.cel.testing.testdata.proto3.StandaloneGlobalEnum")
+ .setContainer(
+ CelContainer.ofName("dev.cel.testing.testdata.proto3.StandaloneGlobalEnum"))
.setResultType(SimpleType.BOOL)
.build();
@@ -946,10 +1021,13 @@ public void program_enumTypeReferenceResolution(boolean resolveTypeDependencies)
Cel cel =
standardCelBuilderWithMacros()
.setOptions(
- CelOptions.current().resolveTypeDependencies(resolveTypeDependencies).build())
+ CelOptions.current()
+ .evaluateCanonicalTypesToNativeValues(true)
+ .resolveTypeDependencies(resolveTypeDependencies)
+ .build())
.addMessageTypes(Struct.getDescriptor())
.setResultType(StructTypeReference.create("google.protobuf.NullValue"))
- .setContainer("google.protobuf")
+ .setContainer(CelContainer.ofName("google.protobuf"))
.build();
// `Value` is defined in `Struct` proto and NullValue is an enum within this `Value` struct.
@@ -964,17 +1042,20 @@ public void program_enumTypeReferenceResolution(boolean resolveTypeDependencies)
public void program_enumTypeTransitiveResolution() throws Exception {
Cel cel =
standardCelBuilderWithMacros()
- .setOptions(CelOptions.current().resolveTypeDependencies(true).build())
- .addMessageTypes(TestAllTypes.getDescriptor())
+ .setOptions(
+ CelOptions.current()
+ .evaluateCanonicalTypesToNativeValues(true)
+ .resolveTypeDependencies(true)
+ .build())
+ .addMessageTypes(Proto2ExtensionScopedMessage.getDescriptor())
.setResultType(StructTypeReference.create("google.protobuf.NullValue"))
- .setContainer("google.protobuf")
+ .setContainer(CelContainer.ofName("google.protobuf"))
.build();
// 'Value' is a struct defined as a dependency of messages_proto2.proto and 'NullValue' is an
// enum within this 'Value' struct.
// As deep type dependency is enabled, the following evaluation should work by as the
// 'NullValue' enum type is transitively discovered
-
CelRuntime.Program program =
cel.createProgram(cel.compile("Value{null_value: NullValue.NULL_VALUE}").getAst());
assertThat(program.eval()).isEqualTo(NullValue.NULL_VALUE);
@@ -999,9 +1080,9 @@ public void compile_enumTypeTransitiveResolutionFailure() {
Cel cel =
standardCelBuilderWithMacros()
.setOptions(CelOptions.current().resolveTypeDependencies(false).build())
- .addMessageTypes(TestAllTypes.getDescriptor())
+ .addMessageTypes(Proto2ExtensionScopedMessage.getDescriptor())
.setResultType(StructTypeReference.create("google.protobuf.NullValue"))
- .setContainer("google.protobuf")
+ .setContainer(CelContainer.ofName("google.protobuf"))
.build();
// 'Value' is a struct defined as a dependency of messages_proto2.proto and 'NullValue' is an
@@ -1015,6 +1096,34 @@ public void compile_enumTypeTransitiveResolutionFailure() {
assertThat(e).hasMessageThat().contains("undeclared reference to 'NullValue'");
}
+ @Test
+ public void compile_multipleInstancesOfEnumDescriptor_dedupedByFullName() throws Exception {
+ String enumTextProto =
+ "name: \"standalone_global_enum.proto\"\n"
+ + "package: \"dev.cel.testing.testdata.proto3\"\n"
+ + "enum_type {\n"
+ + " name: \"StandaloneGlobalEnum\"\n"
+ + " value {\n"
+ + " name: \"SGOO\"\n"
+ + " number: 0\n"
+ + " }\n"
+ + "}\n"
+ + "syntax: \"proto3\"\n";
+ FileDescriptorProto enumFileDescriptorProto =
+ TextFormat.parse(enumTextProto, FileDescriptorProto.class);
+ FileDescriptor enumFileDescriptor =
+ FileDescriptor.buildFrom(enumFileDescriptorProto, new FileDescriptor[] {});
+ Cel cel =
+ standardCelBuilderWithMacros()
+ .setContainer(CelContainer.ofName("dev.cel.testing.testdata"))
+ .addFileTypes(enumFileDescriptor)
+ .addFileTypes(StandaloneGlobalEnum.getDescriptor().getFile())
+ .build();
+
+ assertThat(cel.compile("dev.cel.testing.testdata.proto3.StandaloneGlobalEnum.SGOO").getAst())
+ .isNotNull();
+ }
+
@Test
public void program_customVarResolver() throws Exception {
Cel cel =
@@ -1022,7 +1131,7 @@ public void program_customVarResolver() throws Exception {
.addDeclarations(
Decl.newBuilder()
.setName("variable")
- .setIdent(IdentDecl.newBuilder().setType(CelTypes.STRING))
+ .setIdent(IdentDecl.newBuilder().setType(CelProtoTypes.STRING))
.build())
.setResultType(SimpleType.BOOL)
.build();
@@ -1045,6 +1154,15 @@ public void program_wrongTypeComprehensionThrows() throws Exception {
assertThat(e).hasMessageThat().contains("expected a list or a map");
}
+ @Test
+ public void program_stringFormatInjection_throwsEvaluationException() throws Exception {
+ Cel cel = standardCelBuilderWithMacros().build();
+ CelRuntime.Program program = cel.createProgram(cel.compile("{}['%2000222222s']").getAst());
+
+ CelEvaluationException e = assertThrows(CelEvaluationException.class, program::eval);
+ assertThat(e).hasMessageThat().contains("evaluation error");
+ }
+
@Test
public void program_emptyTypeProviderConfig() throws Exception {
Cel cel = standardCelBuilderWithMacros().build();
@@ -1056,7 +1174,7 @@ public void program_messageTypeAddedAsVarWithoutDescriptor_throwsHumanReadableEr
String packageName = CheckedExpr.getDescriptor().getFile().getPackage();
Cel cel =
standardCelBuilderWithMacros()
- .addVar("parsedExprVar", CelTypes.createMessage(ParsedExpr.getDescriptor()))
+ .addVar("parsedExprVar", CelProtoMessageTypes.createMessage(ParsedExpr.getDescriptor()))
.build();
CelValidationException exception =
assertThrows(
@@ -1235,7 +1353,7 @@ public void programAdvanceEvaluation_unknownsNamespaceSupport() throws Exception
.setOptions(CelOptions.current().enableUnknownTracking(true).build())
.addVar("com.google.a", SimpleType.BOOL)
.addVar("com.google.b", SimpleType.BOOL)
- .setContainer("com.google")
+ .setContainer(CelContainer.ofName("com.google"))
.addFunctionBindings()
.setResultType(SimpleType.BOOL)
.build();
@@ -1264,7 +1382,7 @@ public void programAdvanceEvaluation_unknownsIterativeEvalExample() throws Excep
.setOptions(CelOptions.current().enableUnknownTracking(true).build())
.addVar("com.google.a", SimpleType.BOOL)
.addVar("com.google.b", SimpleType.BOOL)
- .setContainer("com.google")
+ .setContainer(CelContainer.ofName("com.google"))
.addFunctionBindings()
.setResultType(SimpleType.BOOL)
.build();
@@ -1343,7 +1461,7 @@ public void programAdvanceEvaluation_argumentMergeErrorPriority() throws Excepti
.setResultType(
Type.newBuilder().setPrimitive(PrimitiveType.BOOL))))
.build())
- .setContainer("")
+ .setContainer(CelContainer.ofName(""))
.addFunctionBindings()
.setResultType(SimpleType.BOOL)
.build();
@@ -1386,7 +1504,7 @@ public void programAdvanceEvaluation_argumentMergeUnknowns() throws Exception {
.setResultType(
Type.newBuilder().setPrimitive(PrimitiveType.BOOL))))
.build())
- .setContainer("")
+ .setContainer(CelContainer.ofName(""))
.addFunctionBindings()
.setResultType(SimpleType.BOOL)
.build();
@@ -1413,7 +1531,7 @@ public void programAdvanceEvaluation_mapSelectUnknowns() throws Exception {
standardCelBuilderWithMacros()
.setOptions(CelOptions.current().enableUnknownTracking(true).build())
.addVar("unk", MapType.create(SimpleType.STRING, SimpleType.BOOL))
- .setContainer("")
+ .setContainer(CelContainer.ofName(""))
.addFunctionBindings()
.setResultType(SimpleType.BOOL)
.build();
@@ -1439,7 +1557,7 @@ public void programAdvanceEvaluation_mapIndexUnknowns() throws Exception {
standardCelBuilderWithMacros()
.setOptions(CelOptions.current().enableUnknownTracking(true).build())
.addVar("unk", MapType.create(SimpleType.STRING, SimpleType.BOOL))
- .setContainer("")
+ .setContainer(CelContainer.ofName(""))
.addFunctionBindings()
.setResultType(SimpleType.BOOL)
.build();
@@ -1468,7 +1586,7 @@ public void programAdvanceEvaluation_listIndexUnknowns() throws Exception {
standardCelBuilderWithMacros()
.setOptions(CelOptions.current().enableUnknownTracking(true).build())
.addVar("unk", ListType.create(SimpleType.BOOL))
- .setContainer("")
+ .setContainer(CelContainer.ofName(""))
.addFunctionBindings()
.setResultType(SimpleType.BOOL)
.build();
@@ -1497,7 +1615,7 @@ public void programAdvanceEvaluation_indexOnUnknownContainer() throws Exception
standardCelBuilderWithMacros()
.setOptions(CelOptions.current().enableUnknownTracking(true).build())
.addVar("unk", ListType.create(SimpleType.BOOL))
- .setContainer("")
+ .setContainer(CelContainer.ofName(""))
.addFunctionBindings()
.setResultType(SimpleType.BOOL)
.build();
@@ -1517,9 +1635,13 @@ public void programAdvanceEvaluation_indexOnUnknownContainer() throws Exception
public void programAdvanceEvaluation_unsupportedIndexIgnored() throws Exception {
Cel cel =
standardCelBuilderWithMacros()
- .setOptions(CelOptions.current().enableUnknownTracking(true).build())
+ .setOptions(
+ CelOptions.current()
+ .evaluateCanonicalTypesToNativeValues(true)
+ .enableUnknownTracking(true)
+ .build())
.addVar("unk", MapType.create(SimpleType.STRING, SimpleType.BOOL))
- .setContainer("")
+ .setContainer(CelContainer.ofName(""))
.addFunctionBindings()
.setResultType(SimpleType.BOOL)
.build();
@@ -1545,7 +1667,7 @@ public void programAdvanceEvaluation_unsupportedIndexIgnored() throws Exception
UnknownContext.create(
fromMap(
ImmutableMap.of(
- "unk", ImmutableMap.of(ByteString.copyFromUtf8("a"), false))),
+ "unk", ImmutableMap.of(CelByteString.copyFromUtf8("a"), false))),
ImmutableList.of())))
.isEqualTo(false);
}
@@ -1556,7 +1678,7 @@ public void programAdvanceEvaluation_listIndexMacroTracking() throws Exception {
standardCelBuilderWithMacros()
.setOptions(CelOptions.current().enableUnknownTracking(true).build())
.addVar("testList", ListType.create(SimpleType.BOOL))
- .setContainer("")
+ .setContainer(CelContainer.ofName(""))
.addFunctionBindings()
.setResultType(SimpleType.BOOL)
.build();
@@ -1589,7 +1711,7 @@ public void programAdvanceEvaluation_mapIndexMacroTracking() throws Exception {
standardCelBuilderWithMacros()
.setOptions(CelOptions.current().enableUnknownTracking(true).build())
.addVar("testMap", MapType.create(SimpleType.STRING, SimpleType.BOOL))
- .setContainer("")
+ .setContainer(CelContainer.ofName(""))
.addFunctionBindings()
.setResultType(SimpleType.BOOL)
.build();
@@ -1639,7 +1761,7 @@ public void programAdvanceEvaluation_boolOperatorMergeUnknownPriority() throws E
.addVarDeclarations(
CelVarDecl.newVarDeclaration("unk", SimpleType.BOOL),
CelVarDecl.newVarDeclaration("err", SimpleType.BOOL))
- .setContainer("com.google")
+ .setContainer(CelContainer.ofName("com.google"))
.addFunctionBindings()
.setResultType(SimpleType.BOOL)
.build();
@@ -1651,7 +1773,8 @@ public void programAdvanceEvaluation_boolOperatorMergeUnknownPriority() throws E
UnknownContext.create(
fromMap(ImmutableMap.of()),
ImmutableList.of(CelAttributePattern.fromQualifiedIdentifier("unk")))))
- .isEqualTo(CelUnknownSet.create(CelAttribute.fromQualifiedIdentifier("unk")));
+ .isEqualTo(
+ CelUnknownSet.create(CelAttribute.fromQualifiedIdentifier("unk"), ImmutableSet.of(3L)));
}
@Test
@@ -1663,7 +1786,7 @@ public void programAdvanceEvaluation_partialUnknownMapEntryPropagates() throws E
ImmutableList.of(
CelVarDecl.newVarDeclaration("partialList1", ListType.create(SimpleType.INT)),
CelVarDecl.newVarDeclaration("partialList2", ListType.create(SimpleType.INT))))
- .setContainer("com.google")
+ .setContainer(CelContainer.ofName("com.google"))
.addFunctionBindings()
.build();
CelRuntime.Program program =
@@ -1694,7 +1817,7 @@ public void programAdvanceEvaluation_partialUnknownListElementPropagates() throw
.setOptions(CelOptions.current().enableUnknownTracking(true).build())
.addVar("partialList1", ListType.create(SimpleType.INT))
.addVar("partialList2", ListType.create(SimpleType.INT))
- .setContainer("com.google")
+ .setContainer(CelContainer.ofName("com.google"))
.addFunctionBindings()
.build();
CelRuntime.Program program =
@@ -1724,13 +1847,13 @@ public void programAdvanceEvaluation_partialUnknownMessageFieldPropagates() thro
.addMessageTypes(TestAllTypes.getDescriptor())
.addVar(
"partialMessage1",
- StructTypeReference.create("dev.cel.testing.testdata.proto3.TestAllTypes"))
+ StructTypeReference.create("cel.expr.conformance.proto3.TestAllTypes"))
.addVar(
"partialMessage2",
- StructTypeReference.create("dev.cel.testing.testdata.proto3.TestAllTypes"))
+ StructTypeReference.create("cel.expr.conformance.proto3.TestAllTypes"))
.setResultType(
- StructTypeReference.create("dev.cel.testing.testdata.proto3.NestedTestAllTypes"))
- .setContainer("dev.cel.testing.testdata.proto3")
+ StructTypeReference.create("cel.expr.conformance.proto3.NestedTestAllTypes"))
+ .setContainer(CelContainer.ofName("cel.expr.conformance.proto3"))
.addFunctionBindings()
.build();
Program program =
@@ -1787,9 +1910,9 @@ public boolean isAssignableFrom(CelType other) {
CelFactory.standardCelBuilder()
.addVar("x", SimpleType.INT)
.addFunctionDeclarations(
- CelFunctionDecl.newFunctionDeclaration(
+ newFunctionDeclaration(
"print",
- CelOverloadDecl.newGlobalOverload(
+ newGlobalOverload(
"print_overload",
SimpleType.STRING,
customType))) // The overload would accept either Int or CustomType
@@ -1803,13 +1926,215 @@ public boolean isAssignableFrom(CelType other) {
assertThat(result).isEqualTo("5");
}
+ @Test
+ @SuppressWarnings("unchecked") // test only
+ public void program_functionParamWithWellKnownType() throws Exception {
+ Cel cel =
+ CelFactory.standardCelBuilder()
+ .addFunctionDeclarations(
+ newFunctionDeclaration(
+ "hasStringValue",
+ newMemberOverload(
+ "struct_hasStringValue_string_string",
+ SimpleType.BOOL,
+ StructTypeReference.create("google.protobuf.Struct"),
+ SimpleType.STRING,
+ SimpleType.STRING)))
+ .addFunctionBindings(
+ CelFunctionBinding.from(
+ "struct_hasStringValue_string_string",
+ ImmutableList.of(Map.class, String.class, String.class),
+ args -> {
+ Map map = (Map) args[0];
+ return map.containsKey(args[1]) && map.containsValue(args[2]);
+ }))
+ .build();
+ CelAbstractSyntaxTree ast = cel.compile("{'a': 'b'}.hasStringValue('a', 'b')").getAst();
+
+ boolean result = (boolean) cel.createProgram(ast).eval();
+
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ public void program_nativeTypeUnknownsEnabled_asIdentifiers() throws Exception {
+ Cel cel =
+ CelFactory.standardCelBuilder()
+ .addVar("x", SimpleType.BOOL)
+ .addVar("y", SimpleType.BOOL)
+ .setOptions(CelOptions.current().build())
+ .build();
+ CelAbstractSyntaxTree ast = cel.compile("x || y").getAst();
+
+ CelUnknownSet result = (CelUnknownSet) cel.createProgram(ast).eval();
+
+ assertThat(result.unknownExprIds()).containsExactly(1L, 3L);
+ assertThat(result.attributes()).isEmpty();
+ }
+
+ @Test
+ public void program_nativeTypeUnknownsEnabled_asCallArguments() throws Exception {
+ Cel cel =
+ CelFactory.standardCelBuilder()
+ .addVar("x", SimpleType.BOOL)
+ .addFunctionDeclarations(
+ newFunctionDeclaration(
+ "foo", newGlobalOverload("foo_bool", SimpleType.BOOL, SimpleType.BOOL)))
+ .setOptions(CelOptions.current().build())
+ .build();
+ CelAbstractSyntaxTree ast = cel.compile("foo(x)").getAst();
+
+ CelUnknownSet result = (CelUnknownSet) cel.createProgram(ast).eval();
+
+ assertThat(result.unknownExprIds()).containsExactly(2L);
+ assertThat(result.attributes()).isEmpty();
+ }
+
+ @Test
+ @TestParameters("{expression: 'string(123)'}")
+ @TestParameters("{expression: 'string(123u)'}")
+ @TestParameters("{expression: 'string(1.5)'}")
+ @TestParameters("{expression: 'string(\"foo\")'}")
+ @TestParameters("{expression: 'string(b\"foo\")'}")
+ @TestParameters("{expression: 'string(timestamp(100))'}")
+ @TestParameters("{expression: 'string(duration(\"1h\"))'}")
+ public void program_stringConversionDisabled_throws(String expression) throws Exception {
+ Cel cel =
+ CelFactory.standardCelBuilder()
+ .setOptions(
+ CelOptions.current()
+ .enableTimestampEpoch(true)
+ .enableStringConversion(false)
+ .build())
+ .build();
+ CelAbstractSyntaxTree ast = cel.compile(expression).getAst();
+
+ CelEvaluationException e =
+ assertThrows(CelEvaluationException.class, () -> cel.createProgram(ast).eval());
+ assertThat(e).hasMessageThat().contains("No matching overload for function 'string'");
+ assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.OVERLOAD_NOT_FOUND);
+ }
+
+ @Test
+ public void program_stringConcatenationDisabled_throws() throws Exception {
+ Cel cel =
+ CelFactory.standardCelBuilder()
+ .setOptions(CelOptions.current().enableStringConcatenation(false).build())
+ .build();
+ CelAbstractSyntaxTree ast = cel.compile("'foo' + 'bar'").getAst();
+
+ CelEvaluationException e =
+ assertThrows(CelEvaluationException.class, () -> cel.createProgram(ast).eval());
+ assertThat(e).hasMessageThat().contains("No matching overload for function '_+_'");
+ assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.OVERLOAD_NOT_FOUND);
+ }
+
+ @Test
+ public void program_listConcatenationDisabled_throws() throws Exception {
+ Cel cel =
+ CelFactory.standardCelBuilder()
+ .setOptions(CelOptions.current().enableListConcatenation(false).build())
+ .build();
+ CelAbstractSyntaxTree ast = cel.compile("[1] + [2]").getAst();
+
+ CelEvaluationException e =
+ assertThrows(CelEvaluationException.class, () -> cel.createProgram(ast).eval());
+ assertThat(e).hasMessageThat().contains("No matching overload for function '_+_'");
+ assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.OVERLOAD_NOT_FOUND);
+ }
+
+ @Test
+ public void program_comprehensionDisabled_throws() throws Exception {
+ Cel cel =
+ standardCelBuilderWithMacros()
+ .setOptions(CelOptions.current().enableComprehension(false).build())
+ .build();
+ CelAbstractSyntaxTree ast = cel.compile("['foo', 'bar'].map(x, x)").getAst();
+
+ CelEvaluationException e =
+ assertThrows(CelEvaluationException.class, () -> cel.createProgram(ast).eval());
+ assertThat(e).hasMessageThat().contains("Iteration budget exceeded: 0");
+ assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.ITERATION_BUDGET_EXCEEDED);
+ }
+
+ @Test
+ public void program_regexProgramSizeUnderLimit_success() throws Exception {
+ Cel cel =
+ standardCelBuilderWithMacros()
+ .setOptions(CelOptions.current().maxRegexProgramSize(7).build())
+ .build();
+ // See
+ // https://github.com/google/re2j/blob/84237cbbd0fbd637c6eb6856717c1e248daae729/javatests/com/google/re2j/PatternTest.java#L175 for program size
+ CelAbstractSyntaxTree ast = cel.compile("'foo'.matches('(a+b)')").getAst();
+
+ assertThat(cel.createProgram(ast).eval()).isEqualTo(false);
+ }
+
+ @Test
+ public void program_regexProgramSizeExceedsLimit_throws() throws Exception {
+ Cel cel =
+ standardCelBuilderWithMacros()
+ .setOptions(CelOptions.current().maxRegexProgramSize(6).build())
+ .build();
+ // See
+ // https://github.com/google/re2j/blob/84237cbbd0fbd637c6eb6856717c1e248daae729/javatests/com/google/re2j/PatternTest.java#L175 for program size
+ CelAbstractSyntaxTree ast = cel.compile("'foo'.matches('(a+b)')").getAst();
+
+ CelEvaluationException e =
+ assertThrows(CelEvaluationException.class, () -> cel.createProgram(ast).eval());
+ assertThat(e)
+ .hasMessageThat()
+ .contains(
+ "evaluation error at :13: Regex pattern exceeds allowed program size. Allowed:"
+ + " 6, Provided: 7");
+ assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.INVALID_ARGUMENT);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked") // test only
+ public void program_evaluateCanonicalTypesToNativeTypesDisabled_producesProtoValues()
+ throws Exception {
+ Cel cel =
+ standardCelBuilderWithMacros()
+ .setOptions(CelOptions.current().evaluateCanonicalTypesToNativeValues(false).build())
+ .build();
+ CelAbstractSyntaxTree ast = cel.compile("[null, {b'abc': null}]").getAst();
+ Map expectedNestedMap = new LinkedHashMap<>();
+ expectedNestedMap.put(ByteString.copyFromUtf8("abc"), com.google.protobuf.NullValue.NULL_VALUE);
+
+ List result = (List) cel.createProgram(ast).eval();
+
+ assertThat(result).containsExactly(com.google.protobuf.NullValue.NULL_VALUE, expectedNestedMap);
+ }
+
+ @Test
+ public void toBuilder_isImmutable() {
+ CelBuilder celBuilder = CelFactory.standardCelBuilder();
+ CelImpl celImpl = (CelImpl) celBuilder.build();
+
+ CelImpl.Builder newCelBuilder = (CelImpl.Builder) celImpl.toCelBuilder();
+ CelParserImpl.Builder newParserBuilder = (CelParserImpl.Builder) celImpl.toParserBuilder();
+ CelCheckerLegacyImpl.Builder newCheckerBuilder =
+ (CelCheckerLegacyImpl.Builder) celImpl.toCheckerBuilder();
+ CelCompilerImpl.Builder newCompilerBuilder =
+ (CelCompilerImpl.Builder) celImpl.toCompilerBuilder();
+ CelRuntimeLegacyImpl.Builder newRuntimeBuilder =
+ (CelRuntimeLegacyImpl.Builder) celImpl.toRuntimeBuilder();
+
+ assertThat(newCelBuilder).isNotEqualTo(celBuilder);
+ assertThat(newParserBuilder).isNotEqualTo(celImpl.toParserBuilder());
+ assertThat(newCheckerBuilder).isNotEqualTo(celImpl.toCheckerBuilder());
+ assertThat(newCompilerBuilder).isNotEqualTo(celImpl.toCompilerBuilder());
+ assertThat(newRuntimeBuilder).isNotEqualTo(celImpl.toRuntimeBuilder());
+ }
+
private static TypeProvider aliasingProvider(ImmutableMap typeAliases) {
return new TypeProvider() {
@Override
public @Nullable Type lookupType(String typeName) {
Type alias = typeAliases.get(typeName);
if (alias != null) {
- return CelTypes.create(alias);
+ return CelProtoTypes.create(alias);
}
return null;
}
@@ -1822,7 +2147,7 @@ private static TypeProvider aliasingProvider(ImmutableMap typeAlia
@Override
public TypeProvider.@Nullable FieldType lookupFieldType(Type type, String fieldName) {
if (typeAliases.containsKey(type.getMessageType())) {
- return TypeProvider.FieldType.of(CelTypes.STRING);
+ return TypeProvider.FieldType.of(CelProtoTypes.STRING);
}
return null;
}
diff --git a/cel_android_rules.bzl b/cel_android_rules.bzl
new file mode 100644
index 000000000..5a94a7ef5
--- /dev/null
+++ b/cel_android_rules.bzl
@@ -0,0 +1,61 @@
+# Copyright 2025 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.
+"""Macro to create android_library rules with CEL specific options applied."""
+
+load("@rules_android//rules:rules.bzl", "android_library", "android_local_test")
+
+DEFAULT_JAVACOPTS = [
+ "-XDstringConcat=inline", # SDK forces usage of StringConcatFactory (java 9+)
+]
+
+def cel_android_library(name, **kwargs):
+ """
+ Generates android_library target with CEL specific javacopts applied
+
+ Args:
+ name: name of the android_library target
+ **kwargs: rest of the args accepted by android_library
+ """
+
+ javacopts = kwargs.get("javacopts", [])
+ all_javacopts = DEFAULT_JAVACOPTS + javacopts
+
+ # By default, set visibility to android_allow_list, unless if overridden at the call site.
+ provided_visibility_or_default = kwargs.get("visibility", ["//:android_allow_list"])
+ filtered_kwargs = {k: v for k, v in kwargs.items() if k != "visibility"}
+
+ android_library(
+ name = name,
+ visibility = provided_visibility_or_default,
+ javacopts = all_javacopts,
+ **filtered_kwargs
+ )
+
+def cel_android_local_test(name, **kwargs):
+ """
+ Generates android_local_test target with CEL specific javacopts applied
+
+ Args:
+ name: name of the android_local_test target
+ **kwargs: rest of the args accepted by android_local_test
+ """
+
+ javacopts = kwargs.get("javacopts", [])
+ all_javacopts = DEFAULT_JAVACOPTS + javacopts
+
+ android_local_test(
+ name = name,
+ javacopts = all_javacopts,
+ **kwargs
+ )
diff --git a/checker/BUILD.bazel b/checker/BUILD.bazel
index 25fc8d739..5fa3c6c05 100644
--- a/checker/BUILD.bazel
+++ b/checker/BUILD.bazel
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
package(
default_applicable_licenses = ["//:license"],
default_visibility = ["//visibility:public"],
@@ -26,7 +28,7 @@ java_library(
java_library(
name = "type_provider_legacy_impl",
- visibility = ["//visibility:public"],
+ visibility = ["//:internal"],
exports = ["//checker/src/main/java/dev/cel/checker:type_provider_legacy_impl"],
)
@@ -37,13 +39,13 @@ java_library(
java_library(
name = "checker_legacy_environment",
- visibility = ["//visibility:public"],
+ deprecation = "See go/cel-java-migration-guide. Please use CEL-Java Fluent APIs //compiler instead",
exports = ["//checker/src/main/java/dev/cel/checker:checker_legacy_environment"],
)
java_library(
name = "type_inferencer",
- visibility = ["//visibility:public"], # Planned for use in a new type-checker.
+ visibility = ["//:internal"], # Planned for use in a new type-checker.
exports = ["//checker/src/main/java/dev/cel/checker:type_inferencer"],
)
@@ -51,3 +53,8 @@ java_library(
name = "proto_expr_visitor",
exports = ["//checker/src/main/java/dev/cel/checker:proto_expr_visitor"],
)
+
+java_library(
+ name = "standard_decl",
+ exports = ["//checker/src/main/java/dev/cel/checker:standard_decl"],
+)
diff --git a/checker/src/main/java/dev/cel/checker/BUILD.bazel b/checker/src/main/java/dev/cel/checker/BUILD.bazel
index d5832a4f8..80f6e1a8a 100644
--- a/checker/src/main/java/dev/cel/checker/BUILD.bazel
+++ b/checker/src/main/java/dev/cel/checker/BUILD.bazel
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
package(
default_applicable_licenses = [
"//:license",
@@ -28,7 +30,6 @@ CHECKER_LEGACY_ENV_SOURCES = [
"ExprChecker.java",
"ExprVisitor.java",
"InferenceContext.java",
- "Standard.java",
"TypeFormatter.java",
"TypeProvider.java",
"Types.java",
@@ -48,9 +49,9 @@ java_library(
"//common/annotations",
"//common/internal:file_descriptor_converter",
"//common/types",
- "//common/types:cel_types",
+ "//common/types:cel_proto_types",
"//common/types:type_providers",
- "@cel_spec//proto/cel/expr:expr_java_proto",
+ "@cel_spec//proto/cel/expr:checked_java_proto",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
"@maven//:com_google_protobuf_protobuf_java",
@@ -68,22 +69,29 @@ java_library(
":checker_builder",
":checker_legacy_environment",
":proto_type_mask",
+ ":standard_decl",
":type_provider_legacy_impl",
"//:auto_value",
- "//common",
+ "//common:cel_ast",
+ "//common:cel_descriptors",
+ "//common:cel_source",
"//common:compiler_common",
+ "//common:container",
"//common:options",
+ "//common:source_location",
+ "//common/annotations",
"//common/ast:expr_converter",
"//common/internal:env_visitor",
"//common/internal:errors",
"//common/types",
- "//common/types:cel_types",
+ "//common/types:cel_proto_types",
"//common/types:message_type_provider",
"//common/types:type_providers",
- "@cel_spec//proto/cel/expr:expr_java_proto",
+ "@cel_spec//proto/cel/expr:checked_java_proto",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
"@maven//:com_google_protobuf_protobuf_java",
+ "@maven//:org_jspecify_jspecify",
],
)
@@ -95,11 +103,13 @@ java_library(
deps = [
":checker_legacy_environment",
":proto_type_mask",
- "//common",
+ ":standard_decl",
+ "//common:cel_ast",
"//common:compiler_common",
+ "//common:container",
"//common:options",
"//common/types:type_providers",
- "@cel_spec//proto/cel/expr:expr_java_proto",
+ "@cel_spec//proto/cel/expr:checked_java_proto",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_protobuf_protobuf_java",
],
@@ -130,9 +140,9 @@ java_library(
"//common/annotations",
"//common/ast",
"//common/ast:expr_converter",
- "//common/types:cel_types",
+ "//common/types:cel_proto_types",
"//common/types:type_providers",
- "@cel_spec//proto/cel/expr:expr_java_proto",
+ "@cel_spec//proto/cel/expr:checked_java_proto",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
],
@@ -148,9 +158,9 @@ java_library(
"//:auto_value",
"//common/annotations",
"//common/types",
- "//common/types:cel_types",
+ "//common/types:cel_proto_types",
"//common/types:type_providers",
- "@cel_spec//proto/cel/expr:expr_java_proto",
+ "@cel_spec//proto/cel/expr:checked_java_proto",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
"@maven//:org_jspecify_jspecify",
@@ -164,9 +174,11 @@ java_library(
],
deps = [
":cel_ident_decl",
+ ":standard_decl",
"//:auto_value",
+ "//common:cel_ast",
"//common:compiler_common",
- "//common:features",
+ "//common:container",
"//common:options",
"//common:proto_ast",
"//common/annotations",
@@ -175,11 +187,13 @@ java_library(
"//common/internal:errors",
"//common/internal:file_descriptor_converter",
"//common/types",
+ "//common/types:cel_proto_types",
"//common/types:cel_types",
"//common/types:type_providers",
"//parser:macro",
"//parser:operator",
- "@cel_spec//proto/cel/expr:expr_java_proto",
+ "@cel_spec//proto/cel/expr:checked_java_proto",
+ "@cel_spec//proto/cel/expr:syntax_java_proto",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
"@maven//:com_google_protobuf_protobuf_java",
@@ -210,6 +224,26 @@ java_library(
],
deps = [
":checker_legacy_environment",
- "//common",
+ "//common:cel_ast",
+ "//common:proto_ast",
+ ],
+)
+
+java_library(
+ name = "standard_decl",
+ srcs = [
+ "CelStandardDeclarations.java",
+ ],
+ tags = [
+ ],
+ deps = [
+ ":cel_ident_decl",
+ "//common:compiler_common",
+ "//common/types",
+ "//common/types:cel_types",
+ "//common/types:type_providers",
+ "//parser:operator",
+ "@maven//:com_google_errorprone_error_prone_annotations",
+ "@maven//:com_google_guava_guava",
],
)
diff --git a/checker/src/main/java/dev/cel/checker/CelChecker.java b/checker/src/main/java/dev/cel/checker/CelChecker.java
index 5a6e9c28b..077850be1 100644
--- a/checker/src/main/java/dev/cel/checker/CelChecker.java
+++ b/checker/src/main/java/dev/cel/checker/CelChecker.java
@@ -17,6 +17,7 @@
import com.google.errorprone.annotations.Immutable;
import dev.cel.common.CelAbstractSyntaxTree;
import dev.cel.common.CelValidationResult;
+import dev.cel.common.types.CelTypeProvider;
/** Public interface for type-checking parsed CEL expressions. */
@Immutable
@@ -28,4 +29,9 @@ public interface CelChecker {
* Check validates the type-agreement of the parsed {@code CelAbstractSyntaxTree}.
*/
CelValidationResult check(CelAbstractSyntaxTree ast);
+
+ /** Returns the underlying type provider. */
+ CelTypeProvider getTypeProvider();
+
+ CelCheckerBuilder toCheckerBuilder();
}
diff --git a/checker/src/main/java/dev/cel/checker/CelCheckerBuilder.java b/checker/src/main/java/dev/cel/checker/CelCheckerBuilder.java
index 53827c387..e19cf5b70 100644
--- a/checker/src/main/java/dev/cel/checker/CelCheckerBuilder.java
+++ b/checker/src/main/java/dev/cel/checker/CelCheckerBuilder.java
@@ -21,6 +21,7 @@
import com.google.protobuf.DescriptorProtos.FileDescriptorSet;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.FileDescriptor;
+import dev.cel.common.CelContainer;
import dev.cel.common.CelFunctionDecl;
import dev.cel.common.CelOptions;
import dev.cel.common.CelVarDecl;
@@ -35,11 +36,14 @@ public interface CelCheckerBuilder {
CelCheckerBuilder setOptions(CelOptions options);
/**
- * Set the {@code container} name to use as the namespace for resolving CEL expression variables
- * and functions.
+ * Set the {@link CelContainer} to use as the namespace for resolving CEL expression variables and
+ * functions.
*/
@CanIgnoreReturnValue
- CelCheckerBuilder setContainer(String container);
+ CelCheckerBuilder setContainer(CelContainer container);
+
+ /** Retrieves the currently configured {@link CelContainer} in the builder. */
+ CelContainer container();
/** Add variable and function {@code declarations} to the CEL environment. */
@CanIgnoreReturnValue
@@ -152,6 +156,14 @@ public interface CelCheckerBuilder {
@CanIgnoreReturnValue
CelCheckerBuilder setStandardEnvironmentEnabled(boolean value);
+ /**
+ * Override the standard declarations for the type-checker. This can be used to subset the
+ * standard environment to only expose the desired declarations to the type-checker. {@link
+ * #setStandardEnvironmentEnabled(boolean)} must be set to false for this to take effect.
+ */
+ @CanIgnoreReturnValue
+ CelCheckerBuilder setStandardDeclarations(CelStandardDeclarations standardDeclarations);
+
/** Adds one or more libraries for parsing and type-checking. */
@CanIgnoreReturnValue
CelCheckerBuilder addLibraries(CelCheckerLibrary... libraries);
diff --git a/checker/src/main/java/dev/cel/checker/CelCheckerLegacyImpl.java b/checker/src/main/java/dev/cel/checker/CelCheckerLegacyImpl.java
index ccc9c2955..41d1ca073 100644
--- a/checker/src/main/java/dev/cel/checker/CelCheckerLegacyImpl.java
+++ b/checker/src/main/java/dev/cel/checker/CelCheckerLegacyImpl.java
@@ -20,6 +20,7 @@
import dev.cel.expr.Decl;
import dev.cel.expr.Type;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
@@ -30,6 +31,7 @@
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.FileDescriptor;
import dev.cel.common.CelAbstractSyntaxTree;
+import dev.cel.common.CelContainer;
import dev.cel.common.CelDescriptorUtil;
import dev.cel.common.CelFunctionDecl;
import dev.cel.common.CelIssue;
@@ -39,38 +41,54 @@
import dev.cel.common.CelSourceLocation;
import dev.cel.common.CelValidationResult;
import dev.cel.common.CelVarDecl;
+import dev.cel.common.annotations.Internal;
import dev.cel.common.ast.CelExprConverter;
import dev.cel.common.internal.EnvVisitable;
import dev.cel.common.internal.EnvVisitor;
import dev.cel.common.internal.Errors;
+import dev.cel.common.types.CelProtoTypes;
import dev.cel.common.types.CelType;
import dev.cel.common.types.CelTypeProvider;
-import dev.cel.common.types.CelTypes;
import dev.cel.common.types.ProtoMessageTypeProvider;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
+import org.jspecify.annotations.Nullable;
/**
* {@code CelChecker} implementation which uses the original CEL-Java APIs to provide a simple,
* consistent interface for type-checking.
+ *
+ *
CEL Library Internals. Do Not Use. Consumers should use {@code CelCompilerFactory} to
+ * instantiate a type-checker.
*/
@Immutable
+@Internal
public final class CelCheckerLegacyImpl implements CelChecker, EnvVisitable {
private final CelOptions celOptions;
- private final String container;
- private final ImmutableList identDeclarations;
- private final ImmutableList functionDeclarations;
+ private final CelContainer container;
+ private final ImmutableSet identDeclarations;
+ private final ImmutableSet functionDeclarations;
private final Optional expectedResultType;
@SuppressWarnings("Immutable")
private final TypeProvider typeProvider;
+ private final CelTypeProvider celTypeProvider;
private final boolean standardEnvironmentEnabled;
+ private final CelStandardDeclarations overriddenStandardDeclarations;
+
+ // This does not affect the type-checking behavior in any manner.
+ @SuppressWarnings("Immutable")
+ private final ImmutableSet checkerLibraries;
+
+ private final ImmutableSet fileDescriptors;
+ private final ImmutableSet protoTypeMasks;
+
@Override
public CelValidationResult check(CelAbstractSyntaxTree ast) {
CelSource source = ast.getSource();
@@ -88,6 +106,36 @@ public CelValidationResult check(CelAbstractSyntaxTree ast) {
return new CelValidationResult(checkedAst, ImmutableList.of());
}
+ @Override
+ public CelTypeProvider getTypeProvider() {
+ return this.celTypeProvider;
+ }
+
+ @Override
+ public CelCheckerBuilder toCheckerBuilder() {
+ CelCheckerBuilder builder =
+ new Builder()
+ .addIdentDeclarations(identDeclarations)
+ .setOptions(celOptions)
+ .setTypeProvider(celTypeProvider)
+ .setContainer(container)
+ .setStandardEnvironmentEnabled(standardEnvironmentEnabled)
+ .addFunctionDeclarations(functionDeclarations)
+ .addLibraries(checkerLibraries)
+ .addFileTypes(fileDescriptors)
+ .addProtoTypeMasks(protoTypeMasks);
+
+ if (expectedResultType.isPresent()) {
+ builder.setResultType(expectedResultType.get());
+ }
+
+ if (overriddenStandardDeclarations != null) {
+ builder.setStandardDeclarations(overriddenStandardDeclarations);
+ }
+
+ return builder;
+ }
+
@Override
public void accept(EnvVisitor envVisitor) {
Errors errors = new Errors("", "");
@@ -116,6 +164,8 @@ private Env getEnv(Errors errors) {
Env env;
if (standardEnvironmentEnabled) {
env = Env.standard(errors, typeProvider, celOptions);
+ } else if (overriddenStandardDeclarations != null) {
+ env = Env.standard(overriddenStandardDeclarations, errors, typeProvider, celOptions);
} else {
env = Env.unconfigured(errors, typeProvider, celOptions);
}
@@ -132,18 +182,19 @@ public static CelCheckerBuilder newBuilder() {
/** Builder class for the legacy {@code CelChecker} implementation. */
public static final class Builder implements CelCheckerBuilder {
- private final ImmutableList.Builder identDeclarations;
- private final ImmutableList.Builder functionDeclarations;
- private final ImmutableList.Builder protoTypeMasks;
+ private final ImmutableSet.Builder identDeclarations;
+ private final ImmutableSet.Builder functionDeclarations;
+ private final ImmutableSet.Builder protoTypeMasks;
private final ImmutableSet.Builder messageTypes;
private final ImmutableSet.Builder fileTypes;
private final ImmutableSet.Builder celCheckerLibraries;
+ private CelContainer container;
private CelOptions celOptions;
- private String container;
private CelType expectedResultType;
private TypeProvider customTypeProvider;
private CelTypeProvider celTypeProvider;
private boolean standardEnvironmentEnabled;
+ private CelStandardDeclarations standardDeclarations;
@Override
public CelCheckerBuilder setOptions(CelOptions celOptions) {
@@ -152,12 +203,17 @@ public CelCheckerBuilder setOptions(CelOptions celOptions) {
}
@Override
- public CelCheckerBuilder setContainer(String container) {
+ public CelCheckerBuilder setContainer(CelContainer container) {
checkNotNull(container);
this.container = container;
return this;
}
+ @Override
+ public CelContainer container() {
+ return this.container;
+ }
+
@Override
public CelCheckerBuilder addDeclarations(Decl... declarations) {
checkNotNull(declarations);
@@ -173,7 +229,7 @@ public CelCheckerBuilder addDeclarations(Iterable declarations) {
CelIdentDecl.Builder identBuilder =
CelIdentDecl.newBuilder()
.setName(decl.getName())
- .setType(CelTypes.typeToCelType(decl.getIdent().getType()))
+ .setType(CelProtoTypes.typeToCelType(decl.getIdent().getType()))
// Note: Setting doc and constant value exists for compatibility reason. This
// should not be set by the users.
.setDoc(decl.getIdent().getDoc());
@@ -252,7 +308,7 @@ public CelCheckerBuilder setResultType(CelType resultType) {
@Override
public CelCheckerBuilder setProtoResultType(Type resultType) {
checkNotNull(resultType);
- return setResultType(CelTypes.typeToCelType(resultType));
+ return setResultType(CelProtoTypes.typeToCelType(resultType));
}
@Override
@@ -299,7 +355,13 @@ public CelCheckerBuilder addFileTypes(FileDescriptorSet fileDescriptorSet) {
@Override
public CelCheckerBuilder setStandardEnvironmentEnabled(boolean value) {
- standardEnvironmentEnabled = value;
+ this.standardEnvironmentEnabled = value;
+ return this;
+ }
+
+ @Override
+ public CelCheckerBuilder setStandardDeclarations(CelStandardDeclarations standardDeclarations) {
+ this.standardDeclarations = checkNotNull(standardDeclarations);
return this;
}
@@ -316,11 +378,71 @@ public CelCheckerBuilder addLibraries(Iterable extends CelCheckerLibrary> libr
return this;
}
+ @CanIgnoreReturnValue
+ Builder addIdentDeclarations(ImmutableSet identDeclarations) {
+ this.identDeclarations.addAll(identDeclarations);
+ return this;
+ }
+
+ // The following getters marked @VisibleForTesting exist for testing toCheckerBuilder copies
+ // over all properties. Do not expose these to public
+ @VisibleForTesting
+ ImmutableSet.Builder functionDecls() {
+ return this.functionDeclarations;
+ }
+
+ @VisibleForTesting
+ ImmutableSet.Builder identDecls() {
+ return this.identDeclarations;
+ }
+
+ @VisibleForTesting
+ ImmutableSet.Builder protoTypeMasks() {
+ return this.protoTypeMasks;
+ }
+
+ @VisibleForTesting
+ ImmutableSet.Builder messageTypes() {
+ return this.messageTypes;
+ }
+
+ @VisibleForTesting
+ ImmutableSet.Builder fileTypes() {
+ return this.fileTypes;
+ }
+
+ @VisibleForTesting
+ ImmutableSet.Builder checkerLibraries() {
+ return this.celCheckerLibraries;
+ }
+
+ @VisibleForTesting
+ CelStandardDeclarations standardDeclarations() {
+ return this.standardDeclarations;
+ }
+
+ @VisibleForTesting
+ CelOptions options() {
+ return this.celOptions;
+ }
+
+ @VisibleForTesting
+ CelTypeProvider celTypeProvider() {
+ return this.celTypeProvider;
+ }
+
@Override
@CheckReturnValue
public CelCheckerLegacyImpl build() {
+ if (standardEnvironmentEnabled && standardDeclarations != null) {
+ throw new IllegalArgumentException(
+ "setStandardEnvironmentEnabled must be set to false to override standard"
+ + " declarations.");
+ }
+
// Add libraries, such as extensions
- celCheckerLibraries.build().forEach(celLibrary -> celLibrary.setCheckerOptions(this));
+ ImmutableSet checkerLibraries = celCheckerLibraries.build();
+ checkerLibraries.forEach(celLibrary -> celLibrary.setCheckerOptions(this));
// Configure the type provider.
ImmutableSet fileTypeSet = fileTypes.build();
@@ -348,13 +470,13 @@ public CelCheckerLegacyImpl build() {
// Configure the declaration set, and possibly alter the type provider if ProtoDecl values
// are provided as they may prevent the use of certain field selection patterns against the
// proto.
- ImmutableList identDeclarationSet = identDeclarations.build();
- ImmutableList protoTypeMaskSet = protoTypeMasks.build();
+ ImmutableSet identDeclarationSet = identDeclarations.build();
+ ImmutableSet protoTypeMaskSet = protoTypeMasks.build();
if (!protoTypeMaskSet.isEmpty()) {
ProtoTypeMaskTypeProvider protoTypeMaskTypeProvider =
new ProtoTypeMaskTypeProvider(messageTypeProvider, protoTypeMaskSet);
identDeclarationSet =
- ImmutableList.builder()
+ ImmutableSet.builder()
.addAll(identDeclarationSet)
.addAll(protoTypeMaskTypeProvider.computeDeclsFromProtoTypeMasks())
.build();
@@ -375,36 +497,51 @@ public CelCheckerLegacyImpl build() {
functionDeclarations.build(),
Optional.fromNullable(expectedResultType),
legacyProvider,
- standardEnvironmentEnabled);
+ messageTypeProvider,
+ standardEnvironmentEnabled,
+ standardDeclarations,
+ checkerLibraries,
+ fileTypeSet,
+ protoTypeMaskSet);
}
private Builder() {
this.celOptions = CelOptions.newBuilder().build();
- this.identDeclarations = ImmutableList.builder();
- this.functionDeclarations = ImmutableList.builder();
+ this.identDeclarations = ImmutableSet.builder();
+ this.functionDeclarations = ImmutableSet.builder();
this.fileTypes = ImmutableSet.builder();
this.messageTypes = ImmutableSet.builder();
- this.protoTypeMasks = ImmutableList.builder();
+ this.protoTypeMasks = ImmutableSet.builder();
this.celCheckerLibraries = ImmutableSet.builder();
- this.container = "";
+ this.container = CelContainer.ofName("");
}
}
private CelCheckerLegacyImpl(
CelOptions celOptions,
- String container,
- ImmutableList identDeclarations,
- ImmutableList functionDeclarations,
+ CelContainer container,
+ ImmutableSet identDeclarations,
+ ImmutableSet functionDeclarations,
Optional expectedResultType,
TypeProvider typeProvider,
- boolean standardEnvironmentEnabled) {
+ CelTypeProvider celTypeProvider,
+ boolean standardEnvironmentEnabled,
+ @Nullable CelStandardDeclarations overriddenStandardDeclarations,
+ ImmutableSet checkerLibraries,
+ ImmutableSet fileDescriptors,
+ ImmutableSet protoTypeMasks) {
this.celOptions = celOptions;
this.container = container;
this.identDeclarations = identDeclarations;
this.functionDeclarations = functionDeclarations;
this.expectedResultType = expectedResultType;
this.typeProvider = typeProvider;
+ this.celTypeProvider = celTypeProvider;
this.standardEnvironmentEnabled = standardEnvironmentEnabled;
+ this.overriddenStandardDeclarations = overriddenStandardDeclarations;
+ this.checkerLibraries = checkerLibraries;
+ this.fileDescriptors = fileDescriptors;
+ this.protoTypeMasks = protoTypeMasks;
}
private static ImmutableList errorsToIssues(Errors errors) {
@@ -415,7 +552,11 @@ private static ImmutableList errorsToIssues(Errors errors) {
e -> {
Errors.SourceLocation loc = errors.getPositionLocation(e.position());
CelSourceLocation newLoc = CelSourceLocation.of(loc.line(), loc.column() - 1);
- return issueBuilder.setMessage(e.rawMessage()).setSourceLocation(newLoc).build();
+ return issueBuilder
+ .setExprId(e.exprId())
+ .setMessage(e.rawMessage())
+ .setSourceLocation(newLoc)
+ .build();
})
.collect(toImmutableList());
}
diff --git a/checker/src/main/java/dev/cel/checker/CelIdentDecl.java b/checker/src/main/java/dev/cel/checker/CelIdentDecl.java
index ac71523fe..60f747b77 100644
--- a/checker/src/main/java/dev/cel/checker/CelIdentDecl.java
+++ b/checker/src/main/java/dev/cel/checker/CelIdentDecl.java
@@ -23,8 +23,8 @@
import dev.cel.common.annotations.Internal;
import dev.cel.common.ast.CelConstant;
import dev.cel.common.ast.CelExprConverter;
+import dev.cel.common.types.CelProtoTypes;
import dev.cel.common.types.CelType;
-import dev.cel.common.types.CelTypes;
import java.util.Optional;
/**
@@ -57,7 +57,7 @@ public static Decl celIdentToDecl(CelIdentDecl identDecl) {
IdentDecl.Builder identBuilder =
IdentDecl.newBuilder()
.setDoc(identDecl.doc())
- .setType(CelTypes.celTypeToType(identDecl.type()));
+ .setType(CelProtoTypes.celTypeToType(identDecl.type()));
if (identDecl.constant().isPresent()) {
identBuilder.setValue(CelExprConverter.celConstantToExprConstant(identDecl.constant().get()));
}
diff --git a/checker/src/main/java/dev/cel/checker/CelProtoExprVisitor.java b/checker/src/main/java/dev/cel/checker/CelProtoExprVisitor.java
index cd5656642..08552d56f 100644
--- a/checker/src/main/java/dev/cel/checker/CelProtoExprVisitor.java
+++ b/checker/src/main/java/dev/cel/checker/CelProtoExprVisitor.java
@@ -18,7 +18,7 @@
import dev.cel.common.CelProtoAbstractSyntaxTree;
/**
- * CEL expression visitor implementation based on the {@link com.google.api.expr.Expr} proto.
+ * CEL expression visitor implementation based on the {@link dev.cel.expr.Expr} proto.
*
* Note: Prefer using {@link dev.cel.common.ast.CelExprVisitor} if protobuf support is not
* needed.
diff --git a/checker/src/main/java/dev/cel/checker/CelStandardDeclarations.java b/checker/src/main/java/dev/cel/checker/CelStandardDeclarations.java
new file mode 100644
index 000000000..3865430ec
--- /dev/null
+++ b/checker/src/main/java/dev/cel/checker/CelStandardDeclarations.java
@@ -0,0 +1,1785 @@
+// Copyright 2024 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.checker;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.ImmutableSet.toImmutableSet;
+import static java.util.Arrays.stream;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+import com.google.errorprone.annotations.Immutable;
+import dev.cel.common.CelFunctionDecl;
+import dev.cel.common.CelOverloadDecl;
+import dev.cel.common.types.CelType;
+import dev.cel.common.types.CelTypes;
+import dev.cel.common.types.ListType;
+import dev.cel.common.types.MapType;
+import dev.cel.common.types.SimpleType;
+import dev.cel.common.types.TypeParamType;
+import dev.cel.common.types.TypeType;
+import dev.cel.parser.Operator;
+
+/**
+ * Standard declarations for CEL.
+ *
+ *
Refer to CEL
+ * Specification for comprehensive listing of functions and identifiers included in the standard
+ * environment.
+ */
+@Immutable
+public final class CelStandardDeclarations {
+ // Some shortcuts we use when building declarations.
+ private static final TypeParamType TYPE_PARAM_A = TypeParamType.create("A");
+ private static final ListType LIST_OF_A = ListType.create(TYPE_PARAM_A);
+ private static final TypeParamType TYPE_PARAM_B = TypeParamType.create("B");
+ private static final MapType MAP_OF_AB = MapType.create(TYPE_PARAM_A, TYPE_PARAM_B);
+
+ private final ImmutableSet celFunctionDecls;
+ private final ImmutableSet celIdentDecls;
+
+ /** Enumeration of Standard Functions. */
+ public enum StandardFunction {
+ // Deprecated - use {@link #IN}
+ OLD_IN(
+ true,
+ Operator.OLD_IN.getFunction(),
+ () ->
+ CelOverloadDecl.newGlobalOverload(
+ "in_list", "list membership", SimpleType.BOOL, TYPE_PARAM_A, LIST_OF_A),
+ () ->
+ CelOverloadDecl.newGlobalOverload(
+ "in_map", "map key membership", SimpleType.BOOL, TYPE_PARAM_A, MAP_OF_AB)),
+
+ // Deprecated - use {@link #NOT_STRICTLY_FALSE}
+ OLD_NOT_STRICTLY_FALSE(
+ true,
+ Operator.OLD_NOT_STRICTLY_FALSE.getFunction(),
+ () ->
+ CelOverloadDecl.newGlobalOverload(
+ "not_strictly_false",
+ "false if argument is false, true otherwise (including errors and unknowns)",
+ SimpleType.BOOL,
+ SimpleType.BOOL)),
+
+ // Internal (rewritten by macro)
+ IN(Operator.IN, Overload.InternalOperator.IN_LIST, Overload.InternalOperator.IN_MAP),
+ NOT_STRICTLY_FALSE(Operator.NOT_STRICTLY_FALSE, Overload.InternalOperator.NOT_STRICTLY_FALSE),
+ TYPE("type", Overload.InternalOperator.TYPE),
+
+ // Booleans
+ CONDITIONAL(Operator.CONDITIONAL, Overload.BooleanOperator.CONDITIONAL),
+ LOGICAL_NOT(Operator.LOGICAL_NOT, Overload.BooleanOperator.LOGICAL_NOT),
+ LOGICAL_OR(Operator.LOGICAL_OR, Overload.BooleanOperator.LOGICAL_OR),
+ LOGICAL_AND(Operator.LOGICAL_AND, Overload.BooleanOperator.LOGICAL_AND),
+
+ // Relations
+ EQUALS(Operator.EQUALS, Overload.Relation.EQUALS),
+ NOT_EQUALS(Operator.NOT_EQUALS, Overload.Relation.NOT_EQUALS),
+
+ // Arithmetic
+ ADD(
+ Operator.ADD,
+ Overload.Arithmetic.ADD_INT64,
+ Overload.Arithmetic.ADD_UINT64,
+ Overload.Arithmetic.ADD_DOUBLE,
+ Overload.Arithmetic.ADD_STRING,
+ Overload.Arithmetic.ADD_BYTES,
+ Overload.Arithmetic.ADD_LIST,
+ Overload.Arithmetic.ADD_TIMESTAMP_DURATION,
+ Overload.Arithmetic.ADD_DURATION_TIMESTAMP,
+ Overload.Arithmetic.ADD_DURATION_DURATION),
+ SUBTRACT(
+ Operator.SUBTRACT,
+ Overload.Arithmetic.SUBTRACT_INT64,
+ Overload.Arithmetic.SUBTRACT_UINT64,
+ Overload.Arithmetic.SUBTRACT_DOUBLE,
+ Overload.Arithmetic.SUBTRACT_TIMESTAMP_TIMESTAMP,
+ Overload.Arithmetic.SUBTRACT_TIMESTAMP_DURATION,
+ Overload.Arithmetic.SUBTRACT_DURATION_DURATION),
+ MULTIPLY(
+ Operator.MULTIPLY,
+ Overload.Arithmetic.MULTIPLY_INT64,
+ Overload.Arithmetic.MULTIPLY_UINT64,
+ Overload.Arithmetic.MULTIPLY_DOUBLE),
+ DIVIDE(
+ Operator.DIVIDE,
+ Overload.Arithmetic.DIVIDE_INT64,
+ Overload.Arithmetic.DIVIDE_UINT64,
+ Overload.Arithmetic.DIVIDE_DOUBLE),
+ MODULO(Operator.MODULO, Overload.Arithmetic.MODULO_INT64, Overload.Arithmetic.MODULO_UINT64),
+
+ NEGATE(Operator.NEGATE, Overload.Arithmetic.NEGATE_INT64, Overload.Arithmetic.NEGATE_DOUBLE),
+
+ // Index
+ INDEX(Operator.INDEX, Overload.Index.INDEX_LIST, Overload.Index.INDEX_MAP),
+
+ // Size
+ SIZE(
+ "size",
+ Overload.Size.SIZE_STRING,
+ Overload.Size.SIZE_BYTES,
+ Overload.Size.SIZE_LIST,
+ Overload.Size.SIZE_MAP,
+ Overload.Size.STRING_SIZE,
+ Overload.Size.BYTES_SIZE,
+ Overload.Size.LIST_SIZE,
+ Overload.Size.MAP_SIZE),
+
+ // Conversions
+ INT(
+ "int",
+ Overload.Conversions.INT64_TO_INT64,
+ Overload.Conversions.UINT64_TO_INT64,
+ Overload.Conversions.DOUBLE_TO_INT64,
+ Overload.Conversions.STRING_TO_INT64,
+ Overload.Conversions.TIMESTAMP_TO_INT64),
+ UINT(
+ "uint",
+ Overload.Conversions.UINT64_TO_UINT64,
+ Overload.Conversions.INT64_TO_UINT64,
+ Overload.Conversions.DOUBLE_TO_UINT64,
+ Overload.Conversions.STRING_TO_UINT64),
+ DOUBLE(
+ "double",
+ Overload.Conversions.DOUBLE_TO_DOUBLE,
+ Overload.Conversions.INT64_TO_DOUBLE,
+ Overload.Conversions.UINT64_TO_DOUBLE,
+ Overload.Conversions.STRING_TO_DOUBLE),
+ STRING(
+ "string",
+ Overload.Conversions.STRING_TO_STRING,
+ Overload.Conversions.INT64_TO_STRING,
+ Overload.Conversions.UINT64_TO_STRING,
+ Overload.Conversions.DOUBLE_TO_STRING,
+ Overload.Conversions.BOOL_TO_STRING,
+ Overload.Conversions.BYTES_TO_STRING,
+ Overload.Conversions.TIMESTAMP_TO_STRING,
+ Overload.Conversions.DURATION_TO_STRING),
+ BYTES("bytes", Overload.Conversions.BYTES_TO_BYTES, Overload.Conversions.STRING_TO_BYTES),
+ DYN("dyn", Overload.Conversions.TO_DYN),
+ DURATION(
+ "duration",
+ Overload.Conversions.DURATION_TO_DURATION,
+ Overload.Conversions.STRING_TO_DURATION),
+ TIMESTAMP(
+ "timestamp",
+ Overload.Conversions.STRING_TO_TIMESTAMP,
+ Overload.Conversions.TIMESTAMP_TO_TIMESTAMP,
+ Overload.Conversions.INT64_TO_TIMESTAMP),
+ BOOL("bool", Overload.Conversions.BOOL_TO_BOOL, Overload.Conversions.STRING_TO_BOOL),
+
+ // String matchers
+ MATCHES("matches", Overload.StringMatchers.MATCHES, Overload.StringMatchers.MATCHES_STRING),
+ CONTAINS("contains", Overload.StringMatchers.CONTAINS_STRING),
+ ENDS_WITH("endsWith", Overload.StringMatchers.ENDS_WITH_STRING),
+ STARTS_WITH("startsWith", Overload.StringMatchers.STARTS_WITH_STRING),
+
+ // Date/time operations
+ GET_FULL_YEAR(
+ "getFullYear",
+ Overload.DateTime.TIMESTAMP_TO_YEAR,
+ Overload.DateTime.TIMESTAMP_TO_YEAR_WITH_TZ),
+ GET_MONTH(
+ "getMonth",
+ Overload.DateTime.TIMESTAMP_TO_MONTH,
+ Overload.DateTime.TIMESTAMP_TO_MONTH_WITH_TZ),
+ GET_DAY_OF_YEAR(
+ "getDayOfYear",
+ Overload.DateTime.TIMESTAMP_TO_DAY_OF_YEAR,
+ Overload.DateTime.TIMESTAMP_TO_DAY_OF_YEAR_WITH_TZ),
+ GET_DAY_OF_MONTH(
+ "getDayOfMonth",
+ Overload.DateTime.TIMESTAMP_TO_DAY_OF_MONTH,
+ Overload.DateTime.TIMESTAMP_TO_DAY_OF_MONTH_WITH_TZ),
+ GET_DATE(
+ "getDate",
+ Overload.DateTime.TIMESTAMP_TO_DAY_OF_MONTH_1_BASED,
+ Overload.DateTime.TIMESTAMP_TO_DAY_OF_MONTH_1_BASED_WITH_TZ),
+ GET_DAY_OF_WEEK(
+ "getDayOfWeek",
+ Overload.DateTime.TIMESTAMP_TO_DAY_OF_WEEK,
+ Overload.DateTime.TIMESTAMP_TO_DAY_OF_WEEK_WITH_TZ),
+ GET_HOURS(
+ "getHours",
+ Overload.DateTime.TIMESTAMP_TO_HOURS,
+ Overload.DateTime.TIMESTAMP_TO_HOURS_WITH_TZ,
+ Overload.DateTime.DURATION_TO_HOURS),
+ GET_MINUTES(
+ "getMinutes",
+ Overload.DateTime.TIMESTAMP_TO_MINUTES,
+ Overload.DateTime.TIMESTAMP_TO_MINUTES_WITH_TZ,
+ Overload.DateTime.DURATION_TO_MINUTES),
+ GET_SECONDS(
+ "getSeconds",
+ Overload.DateTime.TIMESTAMP_TO_SECONDS,
+ Overload.DateTime.TIMESTAMP_TO_SECONDS_WITH_TZ,
+ Overload.DateTime.DURATION_TO_SECONDS),
+ GET_MILLISECONDS(
+ "getMilliseconds",
+ Overload.DateTime.TIMESTAMP_TO_MILLISECONDS,
+ Overload.DateTime.TIMESTAMP_TO_MILLISECONDS_WITH_TZ,
+ Overload.DateTime.DURATION_TO_MILLISECONDS),
+
+ // Comparisons
+ LESS(
+ Operator.LESS,
+ Overload.Comparison.LESS_BOOL,
+ Overload.Comparison.LESS_INT64,
+ Overload.Comparison.LESS_UINT64,
+ Overload.Comparison.LESS_DOUBLE,
+ Overload.Comparison.LESS_STRING,
+ Overload.Comparison.LESS_BYTES,
+ Overload.Comparison.LESS_TIMESTAMP,
+ Overload.Comparison.LESS_DURATION,
+ Overload.Comparison.LESS_INT64_UINT64,
+ Overload.Comparison.LESS_UINT64_INT64,
+ Overload.Comparison.LESS_INT64_DOUBLE,
+ Overload.Comparison.LESS_DOUBLE_INT64,
+ Overload.Comparison.LESS_UINT64_DOUBLE,
+ Overload.Comparison.LESS_DOUBLE_UINT64),
+ LESS_EQUALS(
+ Operator.LESS_EQUALS,
+ Overload.Comparison.LESS_EQUALS_BOOL,
+ Overload.Comparison.LESS_EQUALS_INT64,
+ Overload.Comparison.LESS_EQUALS_UINT64,
+ Overload.Comparison.LESS_EQUALS_DOUBLE,
+ Overload.Comparison.LESS_EQUALS_STRING,
+ Overload.Comparison.LESS_EQUALS_BYTES,
+ Overload.Comparison.LESS_EQUALS_TIMESTAMP,
+ Overload.Comparison.LESS_EQUALS_DURATION,
+ Overload.Comparison.LESS_EQUALS_INT64_UINT64,
+ Overload.Comparison.LESS_EQUALS_UINT64_INT64,
+ Overload.Comparison.LESS_EQUALS_INT64_DOUBLE,
+ Overload.Comparison.LESS_EQUALS_DOUBLE_INT64,
+ Overload.Comparison.LESS_EQUALS_UINT64_DOUBLE,
+ Overload.Comparison.LESS_EQUALS_DOUBLE_UINT64),
+ GREATER(
+ Operator.GREATER,
+ Overload.Comparison.GREATER_BOOL,
+ Overload.Comparison.GREATER_INT64,
+ Overload.Comparison.GREATER_UINT64,
+ Overload.Comparison.GREATER_DOUBLE,
+ Overload.Comparison.GREATER_STRING,
+ Overload.Comparison.GREATER_BYTES,
+ Overload.Comparison.GREATER_TIMESTAMP,
+ Overload.Comparison.GREATER_DURATION,
+ Overload.Comparison.GREATER_INT64_UINT64,
+ Overload.Comparison.GREATER_UINT64_INT64,
+ Overload.Comparison.GREATER_INT64_DOUBLE,
+ Overload.Comparison.GREATER_DOUBLE_INT64,
+ Overload.Comparison.GREATER_UINT64_DOUBLE,
+ Overload.Comparison.GREATER_DOUBLE_UINT64),
+ GREATER_EQUALS(
+ Operator.GREATER_EQUALS,
+ Overload.Comparison.GREATER_EQUALS_BOOL,
+ Overload.Comparison.GREATER_EQUALS_INT64,
+ Overload.Comparison.GREATER_EQUALS_UINT64,
+ Overload.Comparison.GREATER_EQUALS_DOUBLE,
+ Overload.Comparison.GREATER_EQUALS_STRING,
+ Overload.Comparison.GREATER_EQUALS_BYTES,
+ Overload.Comparison.GREATER_EQUALS_TIMESTAMP,
+ Overload.Comparison.GREATER_EQUALS_DURATION,
+ Overload.Comparison.GREATER_EQUALS_INT64_UINT64,
+ Overload.Comparison.GREATER_EQUALS_UINT64_INT64,
+ Overload.Comparison.GREATER_EQUALS_INT64_DOUBLE,
+ Overload.Comparison.GREATER_EQUALS_DOUBLE_INT64,
+ Overload.Comparison.GREATER_EQUALS_UINT64_DOUBLE,
+ Overload.Comparison.GREATER_EQUALS_DOUBLE_UINT64),
+ ;
+
+ private final String functionName;
+ private final CelFunctionDecl celFunctionDecl;
+ private final ImmutableSet standardOverloads;
+ private final boolean isDeprecated;
+
+ /** Container class for CEL standard function overloads. */
+ public static final class Overload {
+
+ /**
+ * Overloads for internal functions that may have been rewritten by macros (ex: @in), or those
+ * used for special purposes (comprehensions, type denotations etc).
+ */
+ public enum InternalOperator implements StandardOverload {
+ IN_LIST(
+ CelOverloadDecl.newGlobalOverload(
+ "in_list", "list membership", SimpleType.BOOL, TYPE_PARAM_A, LIST_OF_A)),
+ IN_MAP(
+ CelOverloadDecl.newGlobalOverload(
+ "in_map", "map key membership", SimpleType.BOOL, TYPE_PARAM_A, MAP_OF_AB)),
+ NOT_STRICTLY_FALSE(
+ CelOverloadDecl.newGlobalOverload(
+ "not_strictly_false",
+ "false if argument is false, true otherwise (including errors and unknowns)",
+ SimpleType.BOOL,
+ SimpleType.BOOL)),
+ TYPE(
+ CelOverloadDecl.newGlobalOverload(
+ "type", "returns type of value", TypeType.create(TYPE_PARAM_A), TYPE_PARAM_A)),
+ ;
+
+ private final CelOverloadDecl celOverloadDecl;
+
+ InternalOperator(CelOverloadDecl overloadDecl) {
+ this.celOverloadDecl = overloadDecl;
+ }
+
+ @Override
+ public CelOverloadDecl celOverloadDecl() {
+ return this.celOverloadDecl;
+ }
+ }
+
+ /** Overloads for logical operators that return a bool as a result. */
+ public enum BooleanOperator implements StandardOverload {
+ CONDITIONAL(
+ CelOverloadDecl.newGlobalOverload(
+ "conditional",
+ "conditional",
+ TYPE_PARAM_A,
+ SimpleType.BOOL,
+ TYPE_PARAM_A,
+ TYPE_PARAM_A)),
+ LOGICAL_NOT(
+ CelOverloadDecl.newGlobalOverload(
+ "logical_not", "logical not", SimpleType.BOOL, SimpleType.BOOL)),
+ LOGICAL_OR(
+ CelOverloadDecl.newGlobalOverload(
+ "logical_or", "logical or", SimpleType.BOOL, SimpleType.BOOL, SimpleType.BOOL)),
+ LOGICAL_AND(
+ CelOverloadDecl.newGlobalOverload(
+ "logical_and", "logical_and", SimpleType.BOOL, SimpleType.BOOL, SimpleType.BOOL)),
+ ;
+
+ private final CelOverloadDecl celOverloadDecl;
+
+ BooleanOperator(CelOverloadDecl overloadDecl) {
+ this.celOverloadDecl = overloadDecl;
+ }
+
+ @Override
+ public CelOverloadDecl celOverloadDecl() {
+ return this.celOverloadDecl;
+ }
+ }
+
+ /** Overloads for functions that test relations. */
+ public enum Relation implements StandardOverload {
+ EQUALS(
+ CelOverloadDecl.newGlobalOverload(
+ "equals", "equality", SimpleType.BOOL, TYPE_PARAM_A, TYPE_PARAM_A)),
+ NOT_EQUALS(
+ CelOverloadDecl.newGlobalOverload(
+ "not_equals", "inequality", SimpleType.BOOL, TYPE_PARAM_A, TYPE_PARAM_A)),
+ ;
+ private final CelOverloadDecl celOverloadDecl;
+
+ Relation(CelOverloadDecl overloadDecl) {
+ this.celOverloadDecl = overloadDecl;
+ }
+
+ @Override
+ public CelOverloadDecl celOverloadDecl() {
+ return this.celOverloadDecl;
+ }
+ }
+
+ /** Overloads for performing arithmetic operations. */
+ public enum Arithmetic implements StandardOverload {
+
+ // Add
+ ADD_STRING(
+ CelOverloadDecl.newGlobalOverload(
+ "add_string",
+ "string concatenation",
+ SimpleType.STRING,
+ SimpleType.STRING,
+ SimpleType.STRING)),
+ ADD_BYTES(
+ CelOverloadDecl.newGlobalOverload(
+ "add_bytes",
+ "bytes concatenation",
+ SimpleType.BYTES,
+ SimpleType.BYTES,
+ SimpleType.BYTES)),
+ ADD_LIST(
+ CelOverloadDecl.newGlobalOverload(
+ "add_list", "list concatenation", LIST_OF_A, LIST_OF_A, LIST_OF_A)),
+ ADD_TIMESTAMP_DURATION(
+ CelOverloadDecl.newGlobalOverload(
+ "add_timestamp_duration",
+ "arithmetic",
+ SimpleType.TIMESTAMP,
+ SimpleType.TIMESTAMP,
+ SimpleType.DURATION)),
+ ADD_DURATION_TIMESTAMP(
+ CelOverloadDecl.newGlobalOverload(
+ "add_duration_timestamp",
+ "arithmetic",
+ SimpleType.TIMESTAMP,
+ SimpleType.DURATION,
+ SimpleType.TIMESTAMP)),
+ ADD_DURATION_DURATION(
+ CelOverloadDecl.newGlobalOverload(
+ "add_duration_duration",
+ "arithmetic",
+ SimpleType.DURATION,
+ SimpleType.DURATION,
+ SimpleType.DURATION)),
+ ADD_INT64(
+ CelOverloadDecl.newGlobalOverload(
+ "add_int64", "arithmetic", SimpleType.INT, SimpleType.INT, SimpleType.INT)),
+ ADD_UINT64(
+ CelOverloadDecl.newGlobalOverload(
+ "add_uint64", "arithmetic", SimpleType.UINT, SimpleType.UINT, SimpleType.UINT)),
+ ADD_DOUBLE(
+ CelOverloadDecl.newGlobalOverload(
+ "add_double",
+ "arithmetic",
+ SimpleType.DOUBLE,
+ SimpleType.DOUBLE,
+ SimpleType.DOUBLE)),
+
+ // Subtract
+ SUBTRACT_INT64(
+ CelOverloadDecl.newGlobalOverload(
+ "subtract_int64", "arithmetic", SimpleType.INT, SimpleType.INT, SimpleType.INT)),
+ SUBTRACT_UINT64(
+ CelOverloadDecl.newGlobalOverload(
+ "subtract_uint64",
+ "arithmetic",
+ SimpleType.UINT,
+ SimpleType.UINT,
+ SimpleType.UINT)),
+ SUBTRACT_DOUBLE(
+ CelOverloadDecl.newGlobalOverload(
+ "subtract_double",
+ "arithmetic",
+ SimpleType.DOUBLE,
+ SimpleType.DOUBLE,
+ SimpleType.DOUBLE)),
+ SUBTRACT_TIMESTAMP_TIMESTAMP(
+ CelOverloadDecl.newGlobalOverload(
+ "subtract_timestamp_timestamp",
+ "arithmetic",
+ SimpleType.DURATION,
+ SimpleType.TIMESTAMP,
+ SimpleType.TIMESTAMP)),
+ SUBTRACT_TIMESTAMP_DURATION(
+ CelOverloadDecl.newGlobalOverload(
+ "subtract_timestamp_duration",
+ "arithmetic",
+ SimpleType.TIMESTAMP,
+ SimpleType.TIMESTAMP,
+ SimpleType.DURATION)),
+ SUBTRACT_DURATION_DURATION(
+ CelOverloadDecl.newGlobalOverload(
+ "subtract_duration_duration",
+ "arithmetic",
+ SimpleType.DURATION,
+ SimpleType.DURATION,
+ SimpleType.DURATION)),
+
+ // Multiply
+ MULTIPLY_INT64(
+ CelOverloadDecl.newGlobalOverload(
+ "multiply_int64", "arithmetic", SimpleType.INT, SimpleType.INT, SimpleType.INT)),
+ MULTIPLY_UINT64(
+ CelOverloadDecl.newGlobalOverload(
+ "multiply_uint64",
+ "arithmetic",
+ SimpleType.UINT,
+ SimpleType.UINT,
+ SimpleType.UINT)),
+ MULTIPLY_DOUBLE(
+ CelOverloadDecl.newGlobalOverload(
+ "multiply_double",
+ "arithmetic",
+ SimpleType.DOUBLE,
+ SimpleType.DOUBLE,
+ SimpleType.DOUBLE)),
+
+ // Divide
+ DIVIDE_INT64(
+ CelOverloadDecl.newGlobalOverload(
+ "divide_int64", "arithmetic", SimpleType.INT, SimpleType.INT, SimpleType.INT)),
+ DIVIDE_UINT64(
+ CelOverloadDecl.newGlobalOverload(
+ "divide_uint64", "arithmetic", SimpleType.UINT, SimpleType.UINT, SimpleType.UINT)),
+ DIVIDE_DOUBLE(
+ CelOverloadDecl.newGlobalOverload(
+ "divide_double",
+ "arithmetic",
+ SimpleType.DOUBLE,
+ SimpleType.DOUBLE,
+ SimpleType.DOUBLE)),
+
+ // Modulo
+ MODULO_INT64(
+ CelOverloadDecl.newGlobalOverload(
+ "modulo_int64", "arithmetic", SimpleType.INT, SimpleType.INT, SimpleType.INT)),
+ MODULO_UINT64(
+ CelOverloadDecl.newGlobalOverload(
+ "modulo_uint64", "arithmetic", SimpleType.UINT, SimpleType.UINT, SimpleType.UINT)),
+ NEGATE_INT64(
+ CelOverloadDecl.newGlobalOverload(
+ "negate_int64", "negation", SimpleType.INT, SimpleType.INT)),
+ NEGATE_DOUBLE(
+ CelOverloadDecl.newGlobalOverload(
+ "negate_double", "negation", SimpleType.DOUBLE, SimpleType.DOUBLE)),
+ ;
+ private final CelOverloadDecl celOverloadDecl;
+
+ Arithmetic(CelOverloadDecl overloadDecl) {
+ this.celOverloadDecl = overloadDecl;
+ }
+
+ @Override
+ public CelOverloadDecl celOverloadDecl() {
+ return this.celOverloadDecl;
+ }
+ }
+
+ /** Overloads for indexing a list or a map. */
+ public enum Index implements StandardOverload {
+ INDEX_LIST(
+ CelOverloadDecl.newGlobalOverload(
+ "index_list", "list indexing", TYPE_PARAM_A, LIST_OF_A, SimpleType.INT)),
+ INDEX_MAP(
+ CelOverloadDecl.newGlobalOverload(
+ "index_map", "map indexing", TYPE_PARAM_B, MAP_OF_AB, TYPE_PARAM_A)),
+ ;
+ private final CelOverloadDecl celOverloadDecl;
+
+ Index(CelOverloadDecl overloadDecl) {
+ this.celOverloadDecl = overloadDecl;
+ }
+
+ @Override
+ public CelOverloadDecl celOverloadDecl() {
+ return this.celOverloadDecl;
+ }
+ }
+
+ /** Overloads for retrieving the size of a literal or a collection. */
+ public enum Size implements StandardOverload {
+ SIZE_STRING(
+ CelOverloadDecl.newGlobalOverload(
+ "size_string", "string length", SimpleType.INT, SimpleType.STRING)),
+ SIZE_BYTES(
+ CelOverloadDecl.newGlobalOverload(
+ "size_bytes", "bytes length", SimpleType.INT, SimpleType.BYTES)),
+ SIZE_LIST(
+ CelOverloadDecl.newGlobalOverload("size_list", "list size", SimpleType.INT, LIST_OF_A)),
+ SIZE_MAP(
+ CelOverloadDecl.newGlobalOverload("size_map", "map size", SimpleType.INT, MAP_OF_AB)),
+ STRING_SIZE(
+ CelOverloadDecl.newMemberOverload(
+ "string_size", "string length", SimpleType.INT, SimpleType.STRING)),
+ BYTES_SIZE(
+ CelOverloadDecl.newMemberOverload(
+ "bytes_size", "bytes length", SimpleType.INT, SimpleType.BYTES)),
+ LIST_SIZE(
+ CelOverloadDecl.newMemberOverload("list_size", "list size", SimpleType.INT, LIST_OF_A)),
+ MAP_SIZE(
+ CelOverloadDecl.newMemberOverload("map_size", "map size", SimpleType.INT, MAP_OF_AB)),
+ ;
+
+ private final CelOverloadDecl celOverloadDecl;
+
+ Size(CelOverloadDecl overloadDecl) {
+ this.celOverloadDecl = overloadDecl;
+ }
+
+ @Override
+ public CelOverloadDecl celOverloadDecl() {
+ return this.celOverloadDecl;
+ }
+ }
+
+ /** Overloads for performing type conversions. */
+ public enum Conversions implements StandardOverload {
+ // Bool conversions
+ BOOL_TO_BOOL(
+ CelOverloadDecl.newGlobalOverload(
+ "bool_to_bool", "type conversion (identity)", SimpleType.BOOL, SimpleType.BOOL)),
+ STRING_TO_BOOL(
+ CelOverloadDecl.newGlobalOverload(
+ "string_to_bool", "type conversion", SimpleType.BOOL, SimpleType.STRING)),
+
+ // Int conversions
+ INT64_TO_INT64(
+ CelOverloadDecl.newGlobalOverload(
+ "int64_to_int64", "type conversion (identity)", SimpleType.INT, SimpleType.INT)),
+ UINT64_TO_INT64(
+ CelOverloadDecl.newGlobalOverload(
+ "uint64_to_int64", "type conversion", SimpleType.INT, SimpleType.UINT)),
+ DOUBLE_TO_INT64(
+ CelOverloadDecl.newGlobalOverload(
+ "double_to_int64", "type conversion", SimpleType.INT, SimpleType.DOUBLE)),
+ STRING_TO_INT64(
+ CelOverloadDecl.newGlobalOverload(
+ "string_to_int64", "type conversion", SimpleType.INT, SimpleType.STRING)),
+ TIMESTAMP_TO_INT64(
+ CelOverloadDecl.newGlobalOverload(
+ "timestamp_to_int64",
+ "Convert timestamp to int64 in seconds since Unix epoch.",
+ SimpleType.INT,
+ SimpleType.TIMESTAMP)),
+ // Uint conversions
+ UINT64_TO_UINT64(
+ CelOverloadDecl.newGlobalOverload(
+ "uint64_to_uint64",
+ "type conversion (identity)",
+ SimpleType.UINT,
+ SimpleType.UINT)),
+ INT64_TO_UINT64(
+ CelOverloadDecl.newGlobalOverload(
+ "int64_to_uint64", "type conversion", SimpleType.UINT, SimpleType.INT)),
+ DOUBLE_TO_UINT64(
+ CelOverloadDecl.newGlobalOverload(
+ "double_to_uint64", "type conversion", SimpleType.UINT, SimpleType.DOUBLE)),
+ STRING_TO_UINT64(
+ CelOverloadDecl.newGlobalOverload(
+ "string_to_uint64", "type conversion", SimpleType.UINT, SimpleType.STRING)),
+ // Double conversions
+ DOUBLE_TO_DOUBLE(
+ CelOverloadDecl.newGlobalOverload(
+ "double_to_double",
+ "type conversion (identity)",
+ SimpleType.DOUBLE,
+ SimpleType.DOUBLE)),
+ INT64_TO_DOUBLE(
+ CelOverloadDecl.newGlobalOverload(
+ "int64_to_double", "type conversion", SimpleType.DOUBLE, SimpleType.INT)),
+ UINT64_TO_DOUBLE(
+ CelOverloadDecl.newGlobalOverload(
+ "uint64_to_double", "type conversion", SimpleType.DOUBLE, SimpleType.UINT)),
+ STRING_TO_DOUBLE(
+ CelOverloadDecl.newGlobalOverload(
+ "string_to_double", "type conversion", SimpleType.DOUBLE, SimpleType.STRING)),
+ // String conversions
+ STRING_TO_STRING(
+ CelOverloadDecl.newGlobalOverload(
+ "string_to_string",
+ "type conversion (identity)",
+ SimpleType.STRING,
+ SimpleType.STRING)),
+ INT64_TO_STRING(
+ CelOverloadDecl.newGlobalOverload(
+ "int64_to_string", "type conversion", SimpleType.STRING, SimpleType.INT)),
+ UINT64_TO_STRING(
+ CelOverloadDecl.newGlobalOverload(
+ "uint64_to_string", "type conversion", SimpleType.STRING, SimpleType.UINT)),
+ DOUBLE_TO_STRING(
+ CelOverloadDecl.newGlobalOverload(
+ "double_to_string", "type conversion", SimpleType.STRING, SimpleType.DOUBLE)),
+ BOOL_TO_STRING(
+ CelOverloadDecl.newGlobalOverload(
+ "bool_to_string", "type conversion", SimpleType.STRING, SimpleType.BOOL)),
+ BYTES_TO_STRING(
+ CelOverloadDecl.newGlobalOverload(
+ "bytes_to_string", "type conversion", SimpleType.STRING, SimpleType.BYTES)),
+ TIMESTAMP_TO_STRING(
+ CelOverloadDecl.newGlobalOverload(
+ "timestamp_to_string", "type_conversion", SimpleType.STRING, SimpleType.TIMESTAMP)),
+ DURATION_TO_STRING(
+ CelOverloadDecl.newGlobalOverload(
+ "duration_to_string", "type_conversion", SimpleType.STRING, SimpleType.DURATION)),
+ // Bytes conversions
+ BYTES_TO_BYTES(
+ CelOverloadDecl.newGlobalOverload(
+ "bytes_to_bytes",
+ "type conversion (identity)",
+ SimpleType.BYTES,
+ SimpleType.BYTES)),
+ STRING_TO_BYTES(
+ CelOverloadDecl.newGlobalOverload(
+ "string_to_bytes", "type conversion", SimpleType.BYTES, SimpleType.STRING)),
+ // Duration conversions
+ DURATION_TO_DURATION(
+ CelOverloadDecl.newGlobalOverload(
+ "duration_to_duration",
+ "type conversion (identity)",
+ SimpleType.DURATION,
+ SimpleType.DURATION)),
+ STRING_TO_DURATION(
+ CelOverloadDecl.newGlobalOverload(
+ "string_to_duration",
+ "type conversion, duration should be end with \"s\", which stands for seconds",
+ SimpleType.DURATION,
+ SimpleType.STRING)),
+ // Timestamp conversions
+ STRING_TO_TIMESTAMP(
+ CelOverloadDecl.newGlobalOverload(
+ "string_to_timestamp",
+ "Type conversion of strings to timestamps according to RFC3339. Example:"
+ + " \"1972-01-01T10:00:20.021-05:00\".",
+ SimpleType.TIMESTAMP,
+ SimpleType.STRING)),
+ TIMESTAMP_TO_TIMESTAMP(
+ CelOverloadDecl.newGlobalOverload(
+ "timestamp_to_timestamp",
+ "type conversion (identity)",
+ SimpleType.TIMESTAMP,
+ SimpleType.TIMESTAMP)),
+ INT64_TO_TIMESTAMP(
+ CelOverloadDecl.newGlobalOverload(
+ "int64_to_timestamp",
+ "Type conversion of integers as Unix epoch seconds to timestamps.",
+ SimpleType.TIMESTAMP,
+ SimpleType.INT)),
+
+ // Dyn conversions
+ TO_DYN(
+ CelOverloadDecl.newGlobalOverload(
+ "to_dyn", "type conversion", SimpleType.DYN, TYPE_PARAM_A)),
+ ;
+ private final CelOverloadDecl celOverloadDecl;
+
+ Conversions(CelOverloadDecl overloadDecl) {
+ this.celOverloadDecl = overloadDecl;
+ }
+
+ @Override
+ public CelOverloadDecl celOverloadDecl() {
+ return this.celOverloadDecl;
+ }
+ }
+
+ /**
+ * Overloads for functions performing string matching, such as regular expressions or contains
+ * check.
+ */
+ public enum StringMatchers implements StandardOverload {
+ MATCHES(
+ CelOverloadDecl.newGlobalOverload(
+ "matches",
+ "matches first argument against regular expression in second argument",
+ SimpleType.BOOL,
+ SimpleType.STRING,
+ SimpleType.STRING)),
+
+ MATCHES_STRING(
+ CelOverloadDecl.newMemberOverload(
+ "matches_string",
+ "matches the self argument against regular expression in first argument",
+ SimpleType.BOOL,
+ SimpleType.STRING,
+ SimpleType.STRING)),
+
+ CONTAINS_STRING(
+ CelOverloadDecl.newMemberOverload(
+ "contains_string",
+ "tests whether the string operand contains the substring",
+ SimpleType.BOOL,
+ SimpleType.STRING,
+ SimpleType.STRING)),
+
+ ENDS_WITH_STRING(
+ CelOverloadDecl.newMemberOverload(
+ "ends_with_string",
+ "tests whether the string operand ends with the suffix argument",
+ SimpleType.BOOL,
+ SimpleType.STRING,
+ SimpleType.STRING)),
+
+ STARTS_WITH_STRING(
+ CelOverloadDecl.newMemberOverload(
+ "starts_with_string",
+ "tests whether the string operand starts with the prefix argument",
+ SimpleType.BOOL,
+ SimpleType.STRING,
+ SimpleType.STRING)),
+ ;
+ private final CelOverloadDecl celOverloadDecl;
+
+ StringMatchers(CelOverloadDecl overloadDecl) {
+ this.celOverloadDecl = overloadDecl;
+ }
+
+ @Override
+ public CelOverloadDecl celOverloadDecl() {
+ return this.celOverloadDecl;
+ }
+ }
+
+ /** Overloads for functions performing date/time operations. */
+ public enum DateTime implements StandardOverload {
+ TIMESTAMP_TO_YEAR(
+ CelOverloadDecl.newMemberOverload(
+ "timestamp_to_year",
+ "get year from the date in UTC",
+ SimpleType.INT,
+ SimpleType.TIMESTAMP)),
+ TIMESTAMP_TO_YEAR_WITH_TZ(
+ CelOverloadDecl.newMemberOverload(
+ "timestamp_to_year_with_tz",
+ "get year from the date with timezone",
+ SimpleType.INT,
+ SimpleType.TIMESTAMP,
+ SimpleType.STRING)),
+
+ TIMESTAMP_TO_MONTH(
+ CelOverloadDecl.newMemberOverload(
+ "timestamp_to_month",
+ "get month from the date in UTC, 0-11",
+ SimpleType.INT,
+ SimpleType.TIMESTAMP)),
+
+ TIMESTAMP_TO_MONTH_WITH_TZ(
+ CelOverloadDecl.newMemberOverload(
+ "timestamp_to_month_with_tz",
+ "get month from the date with timezone, 0-11",
+ SimpleType.INT,
+ SimpleType.TIMESTAMP,
+ SimpleType.STRING)),
+
+ TIMESTAMP_TO_DAY_OF_YEAR(
+ CelOverloadDecl.newMemberOverload(
+ "timestamp_to_day_of_year",
+ "get day of year from the date in UTC, zero-based indexing",
+ SimpleType.INT,
+ SimpleType.TIMESTAMP)),
+
+ TIMESTAMP_TO_DAY_OF_YEAR_WITH_TZ(
+ CelOverloadDecl.newMemberOverload(
+ "timestamp_to_day_of_year_with_tz",
+ "get day of year from the date with timezone, zero-based indexing",
+ SimpleType.INT,
+ SimpleType.TIMESTAMP,
+ SimpleType.STRING)),
+
+ TIMESTAMP_TO_DAY_OF_MONTH(
+ CelOverloadDecl.newMemberOverload(
+ "timestamp_to_day_of_month",
+ "get day of month from the date in UTC, zero-based indexing",
+ SimpleType.INT,
+ SimpleType.TIMESTAMP)),
+
+ TIMESTAMP_TO_DAY_OF_MONTH_WITH_TZ(
+ CelOverloadDecl.newMemberOverload(
+ "timestamp_to_day_of_month_with_tz",
+ "get day of month from the date with timezone, zero-based indexing",
+ SimpleType.INT,
+ SimpleType.TIMESTAMP,
+ SimpleType.STRING)),
+
+ TIMESTAMP_TO_DAY_OF_MONTH_1_BASED(
+ CelOverloadDecl.newMemberOverload(
+ "timestamp_to_day_of_month_1_based",
+ "get day of month from the date in UTC, one-based indexing",
+ SimpleType.INT,
+ SimpleType.TIMESTAMP)),
+ TIMESTAMP_TO_DAY_OF_MONTH_1_BASED_WITH_TZ(
+ CelOverloadDecl.newMemberOverload(
+ "timestamp_to_day_of_month_1_based_with_tz",
+ "get day of month from the date with timezone, one-based indexing",
+ SimpleType.INT,
+ SimpleType.TIMESTAMP,
+ SimpleType.STRING)),
+
+ TIMESTAMP_TO_DAY_OF_WEEK(
+ CelOverloadDecl.newMemberOverload(
+ "timestamp_to_day_of_week",
+ "get day of week from the date in UTC, zero-based, zero for Sunday",
+ SimpleType.INT,
+ SimpleType.TIMESTAMP)),
+
+ TIMESTAMP_TO_DAY_OF_WEEK_WITH_TZ(
+ CelOverloadDecl.newMemberOverload(
+ "timestamp_to_day_of_week_with_tz",
+ "get day of week from the date with timezone, zero-based, zero for Sunday",
+ SimpleType.INT,
+ SimpleType.TIMESTAMP,
+ SimpleType.STRING)),
+
+ TIMESTAMP_TO_HOURS(
+ CelOverloadDecl.newMemberOverload(
+ "timestamp_to_hours",
+ "get hours from the date in UTC, 0-23",
+ SimpleType.INT,
+ SimpleType.TIMESTAMP)),
+
+ TIMESTAMP_TO_HOURS_WITH_TZ(
+ CelOverloadDecl.newMemberOverload(
+ "timestamp_to_hours_with_tz",
+ "get hours from the date with timezone, 0-23",
+ SimpleType.INT,
+ SimpleType.TIMESTAMP,
+ SimpleType.STRING)),
+
+ DURATION_TO_HOURS(
+ CelOverloadDecl.newMemberOverload(
+ "duration_to_hours",
+ "get hours from duration",
+ SimpleType.INT,
+ SimpleType.DURATION)),
+ TIMESTAMP_TO_MINUTES(
+ CelOverloadDecl.newMemberOverload(
+ "timestamp_to_minutes",
+ "get minutes from the date in UTC, 0-59",
+ SimpleType.INT,
+ SimpleType.TIMESTAMP)),
+
+ TIMESTAMP_TO_MINUTES_WITH_TZ(
+ CelOverloadDecl.newMemberOverload(
+ "timestamp_to_minutes_with_tz",
+ "get minutes from the date with timezone, 0-59",
+ SimpleType.INT,
+ SimpleType.TIMESTAMP,
+ SimpleType.STRING)),
+
+ DURATION_TO_MINUTES(
+ CelOverloadDecl.newMemberOverload(
+ "duration_to_minutes",
+ "get minutes from duration",
+ SimpleType.INT,
+ SimpleType.DURATION)),
+ TIMESTAMP_TO_SECONDS(
+ CelOverloadDecl.newMemberOverload(
+ "timestamp_to_seconds",
+ "get seconds from the date in UTC, 0-59",
+ SimpleType.INT,
+ SimpleType.TIMESTAMP)),
+
+ TIMESTAMP_TO_SECONDS_WITH_TZ(
+ CelOverloadDecl.newMemberOverload(
+ "timestamp_to_seconds_with_tz",
+ "get seconds from the date with timezone, 0-59",
+ SimpleType.INT,
+ SimpleType.TIMESTAMP,
+ SimpleType.STRING)),
+
+ DURATION_TO_SECONDS(
+ CelOverloadDecl.newMemberOverload(
+ "duration_to_seconds",
+ "get seconds from duration",
+ SimpleType.INT,
+ SimpleType.DURATION)),
+ TIMESTAMP_TO_MILLISECONDS(
+ CelOverloadDecl.newMemberOverload(
+ "timestamp_to_milliseconds",
+ "get milliseconds from the date in UTC, 0-999",
+ SimpleType.INT,
+ SimpleType.TIMESTAMP)),
+
+ TIMESTAMP_TO_MILLISECONDS_WITH_TZ(
+ CelOverloadDecl.newMemberOverload(
+ "timestamp_to_milliseconds_with_tz",
+ "get milliseconds from the date with timezone, 0-999",
+ SimpleType.INT,
+ SimpleType.TIMESTAMP,
+ SimpleType.STRING)),
+
+ DURATION_TO_MILLISECONDS(
+ CelOverloadDecl.newMemberOverload(
+ "duration_to_milliseconds",
+ "milliseconds from duration, 0-999",
+ SimpleType.INT,
+ SimpleType.DURATION)),
+ ;
+ private final CelOverloadDecl celOverloadDecl;
+
+ DateTime(CelOverloadDecl overloadDecl) {
+ this.celOverloadDecl = overloadDecl;
+ }
+
+ @Override
+ public CelOverloadDecl celOverloadDecl() {
+ return this.celOverloadDecl;
+ }
+ }
+
+ /** Overloads for performing numeric comparisons. */
+ public enum Comparison implements StandardOverload {
+ // Less
+ LESS_BOOL(
+ CelOverloadDecl.newGlobalOverload(
+ "less_bool", "ordering", SimpleType.BOOL, SimpleType.BOOL, SimpleType.BOOL),
+ false),
+ LESS_INT64(
+ CelOverloadDecl.newGlobalOverload(
+ "less_int64", "ordering", SimpleType.BOOL, SimpleType.INT, SimpleType.INT),
+ false),
+ LESS_UINT64(
+ CelOverloadDecl.newGlobalOverload(
+ "less_uint64", "ordering", SimpleType.BOOL, SimpleType.UINT, SimpleType.UINT),
+ false),
+ LESS_DOUBLE(
+ CelOverloadDecl.newGlobalOverload(
+ "less_double", "ordering", SimpleType.BOOL, SimpleType.DOUBLE, SimpleType.DOUBLE),
+ false),
+ LESS_STRING(
+ CelOverloadDecl.newGlobalOverload(
+ "less_string", "ordering", SimpleType.BOOL, SimpleType.STRING, SimpleType.STRING),
+ false),
+ LESS_BYTES(
+ CelOverloadDecl.newGlobalOverload(
+ "less_bytes", "ordering", SimpleType.BOOL, SimpleType.BYTES, SimpleType.BYTES),
+ false),
+ LESS_TIMESTAMP(
+ CelOverloadDecl.newGlobalOverload(
+ "less_timestamp",
+ "ordering",
+ SimpleType.BOOL,
+ SimpleType.TIMESTAMP,
+ SimpleType.TIMESTAMP),
+ false),
+ LESS_DURATION(
+ CelOverloadDecl.newGlobalOverload(
+ "less_duration",
+ "ordering",
+ SimpleType.BOOL,
+ SimpleType.DURATION,
+ SimpleType.DURATION),
+ false),
+ LESS_INT64_UINT64(
+ CelOverloadDecl.newGlobalOverload(
+ "less_int64_uint64",
+ "Compare a signed integer value to an unsigned integer value",
+ SimpleType.BOOL,
+ SimpleType.INT,
+ SimpleType.UINT),
+ true),
+ LESS_UINT64_INT64(
+ CelOverloadDecl.newGlobalOverload(
+ "less_uint64_int64",
+ "Compare an unsigned integer value to a signed integer value",
+ SimpleType.BOOL,
+ SimpleType.UINT,
+ SimpleType.INT),
+ true),
+ LESS_INT64_DOUBLE(
+ CelOverloadDecl.newGlobalOverload(
+ "less_int64_double",
+ "Compare a signed integer value to a double value, coalesces the integer to a"
+ + " double",
+ SimpleType.BOOL,
+ SimpleType.INT,
+ SimpleType.DOUBLE),
+ true),
+ LESS_DOUBLE_INT64(
+ CelOverloadDecl.newGlobalOverload(
+ "less_double_int64",
+ "Compare a double value to a signed integer value, coalesces the integer to a"
+ + " double",
+ SimpleType.BOOL,
+ SimpleType.DOUBLE,
+ SimpleType.INT),
+ true),
+ LESS_UINT64_DOUBLE(
+ CelOverloadDecl.newGlobalOverload(
+ "less_uint64_double",
+ "Compare an unsigned integer value to a double value, coalesces the unsigned"
+ + " integer to a double",
+ SimpleType.BOOL,
+ SimpleType.UINT,
+ SimpleType.DOUBLE),
+ true),
+ LESS_DOUBLE_UINT64(
+ CelOverloadDecl.newGlobalOverload(
+ "less_double_uint64",
+ "Compare a double value to an unsigned integer value, coalesces the unsigned"
+ + " integer to a double",
+ SimpleType.BOOL,
+ SimpleType.DOUBLE,
+ SimpleType.UINT),
+ true),
+ // Less Equals
+ LESS_EQUALS_BOOL(
+ Comparison.LESS_BOOL.celOverloadDecl.toBuilder()
+ .setOverloadId(
+ Comparison.LESS_BOOL
+ .celOverloadDecl
+ .overloadId()
+ .replace("less", "less_equals"))
+ .build(),
+ false),
+ LESS_EQUALS_INT64(
+ Comparison.LESS_INT64.celOverloadDecl.toBuilder()
+ .setOverloadId(
+ Comparison.LESS_INT64
+ .celOverloadDecl
+ .overloadId()
+ .replace("less", "less_equals"))
+ .build(),
+ false),
+ LESS_EQUALS_UINT64(
+ Comparison.LESS_UINT64.celOverloadDecl.toBuilder()
+ .setOverloadId(
+ Comparison.LESS_UINT64
+ .celOverloadDecl
+ .overloadId()
+ .replace("less", "less_equals"))
+ .build(),
+ false),
+ LESS_EQUALS_DOUBLE(
+ Comparison.LESS_DOUBLE.celOverloadDecl.toBuilder()
+ .setOverloadId(
+ Comparison.LESS_DOUBLE
+ .celOverloadDecl
+ .overloadId()
+ .replace("less", "less_equals"))
+ .build(),
+ false),
+ LESS_EQUALS_STRING(
+ Comparison.LESS_STRING.celOverloadDecl.toBuilder()
+ .setOverloadId(
+ Comparison.LESS_STRING
+ .celOverloadDecl
+ .overloadId()
+ .replace("less", "less_equals"))
+ .build(),
+ false),
+ LESS_EQUALS_BYTES(
+ Comparison.LESS_BYTES.celOverloadDecl.toBuilder()
+ .setOverloadId(
+ Comparison.LESS_BYTES
+ .celOverloadDecl
+ .overloadId()
+ .replace("less", "less_equals"))
+ .build(),
+ false),
+ LESS_EQUALS_TIMESTAMP(
+ Comparison.LESS_TIMESTAMP.celOverloadDecl.toBuilder()
+ .setOverloadId(
+ Comparison.LESS_TIMESTAMP
+ .celOverloadDecl
+ .overloadId()
+ .replace("less", "less_equals"))
+ .build(),
+ false),
+ LESS_EQUALS_DURATION(
+ Comparison.LESS_DURATION.celOverloadDecl.toBuilder()
+ .setOverloadId(
+ Comparison.LESS_DURATION
+ .celOverloadDecl
+ .overloadId()
+ .replace("less", "less_equals"))
+ .build(),
+ false),
+ LESS_EQUALS_INT64_UINT64(
+ Comparison.LESS_INT64_UINT64.celOverloadDecl.toBuilder()
+ .setOverloadId(
+ Comparison.LESS_INT64_UINT64
+ .celOverloadDecl
+ .overloadId()
+ .replace("less", "less_equals"))
+ .build(),
+ true),
+ LESS_EQUALS_UINT64_INT64(
+ Comparison.LESS_UINT64_INT64.celOverloadDecl.toBuilder()
+ .setOverloadId(
+ Comparison.LESS_UINT64_INT64
+ .celOverloadDecl
+ .overloadId()
+ .replace("less", "less_equals"))
+ .build(),
+ true),
+ LESS_EQUALS_INT64_DOUBLE(
+ Comparison.LESS_INT64_DOUBLE.celOverloadDecl.toBuilder()
+ .setOverloadId(
+ Comparison.LESS_INT64_DOUBLE
+ .celOverloadDecl
+ .overloadId()
+ .replace("less", "less_equals"))
+ .build(),
+ true),
+ LESS_EQUALS_DOUBLE_INT64(
+ Comparison.LESS_DOUBLE_INT64.celOverloadDecl.toBuilder()
+ .setOverloadId(
+ Comparison.LESS_DOUBLE_INT64
+ .celOverloadDecl
+ .overloadId()
+ .replace("less", "less_equals"))
+ .build(),
+ true),
+ LESS_EQUALS_UINT64_DOUBLE(
+ Comparison.LESS_UINT64_DOUBLE.celOverloadDecl.toBuilder()
+ .setOverloadId(
+ Comparison.LESS_UINT64_DOUBLE
+ .celOverloadDecl
+ .overloadId()
+ .replace("less", "less_equals"))
+ .build(),
+ true),
+ LESS_EQUALS_DOUBLE_UINT64(
+ Comparison.LESS_DOUBLE_UINT64.celOverloadDecl.toBuilder()
+ .setOverloadId(
+ Comparison.LESS_DOUBLE_UINT64
+ .celOverloadDecl
+ .overloadId()
+ .replace("less", "less_equals"))
+ .build(),
+ true),
+
+ // Greater
+ GREATER_BOOL(
+ CelOverloadDecl.newGlobalOverload(
+ "greater_bool", "ordering", SimpleType.BOOL, SimpleType.BOOL, SimpleType.BOOL),
+ false),
+ GREATER_INT64(
+ CelOverloadDecl.newGlobalOverload(
+ "greater_int64", "ordering", SimpleType.BOOL, SimpleType.INT, SimpleType.INT),
+ false),
+ GREATER_UINT64(
+ CelOverloadDecl.newGlobalOverload(
+ "greater_uint64", "ordering", SimpleType.BOOL, SimpleType.UINT, SimpleType.UINT),
+ false),
+ GREATER_DOUBLE(
+ CelOverloadDecl.newGlobalOverload(
+ "greater_double",
+ "ordering",
+ SimpleType.BOOL,
+ SimpleType.DOUBLE,
+ SimpleType.DOUBLE),
+ false),
+ GREATER_STRING(
+ CelOverloadDecl.newGlobalOverload(
+ "greater_string",
+ "ordering",
+ SimpleType.BOOL,
+ SimpleType.STRING,
+ SimpleType.STRING),
+ false),
+ GREATER_BYTES(
+ CelOverloadDecl.newGlobalOverload(
+ "greater_bytes", "ordering", SimpleType.BOOL, SimpleType.BYTES, SimpleType.BYTES),
+ false),
+ GREATER_TIMESTAMP(
+ CelOverloadDecl.newGlobalOverload(
+ "greater_timestamp",
+ "ordering",
+ SimpleType.BOOL,
+ SimpleType.TIMESTAMP,
+ SimpleType.TIMESTAMP),
+ false),
+ GREATER_DURATION(
+ CelOverloadDecl.newGlobalOverload(
+ "greater_duration",
+ "ordering",
+ SimpleType.BOOL,
+ SimpleType.DURATION,
+ SimpleType.DURATION),
+ false),
+ GREATER_INT64_UINT64(
+ CelOverloadDecl.newGlobalOverload(
+ "greater_int64_uint64",
+ "Compare a signed integer value to an unsigned integer value",
+ SimpleType.BOOL,
+ SimpleType.INT,
+ SimpleType.UINT),
+ true),
+ GREATER_UINT64_INT64(
+ CelOverloadDecl.newGlobalOverload(
+ "greater_uint64_int64",
+ "Compare an unsigned integer value to a signed integer value",
+ SimpleType.BOOL,
+ SimpleType.UINT,
+ SimpleType.INT),
+ true),
+ GREATER_INT64_DOUBLE(
+ CelOverloadDecl.newGlobalOverload(
+ "greater_int64_double",
+ "Compare a signed integer value to a double value, coalesces the integer to a"
+ + " double",
+ SimpleType.BOOL,
+ SimpleType.INT,
+ SimpleType.DOUBLE),
+ true),
+ GREATER_DOUBLE_INT64(
+ CelOverloadDecl.newGlobalOverload(
+ "greater_double_int64",
+ "Compare a double value to a signed integer value, coalesces the integer to a"
+ + " double",
+ SimpleType.BOOL,
+ SimpleType.DOUBLE,
+ SimpleType.INT),
+ true),
+ GREATER_UINT64_DOUBLE(
+ CelOverloadDecl.newGlobalOverload(
+ "greater_uint64_double",
+ "Compare an unsigned integer value to a double value, coalesces the unsigned"
+ + " integer to a double",
+ SimpleType.BOOL,
+ SimpleType.UINT,
+ SimpleType.DOUBLE),
+ true),
+ GREATER_DOUBLE_UINT64(
+ CelOverloadDecl.newGlobalOverload(
+ "greater_double_uint64",
+ "Compare a double value to an unsigned integer value, coalesces the unsigned"
+ + " integer to a double",
+ SimpleType.BOOL,
+ SimpleType.DOUBLE,
+ SimpleType.UINT),
+ true),
+
+ // Greater Equals
+ GREATER_EQUALS_BOOL(
+ Comparison.LESS_BOOL.celOverloadDecl.toBuilder()
+ .setOverloadId(
+ Comparison.LESS_BOOL
+ .celOverloadDecl
+ .overloadId()
+ .replace("less", "greater_equals"))
+ .build(),
+ false),
+ GREATER_EQUALS_INT64(
+ Comparison.LESS_INT64.celOverloadDecl.toBuilder()
+ .setOverloadId(
+ Comparison.LESS_INT64
+ .celOverloadDecl
+ .overloadId()
+ .replace("less", "greater_equals"))
+ .build(),
+ false),
+ GREATER_EQUALS_UINT64(
+ Comparison.LESS_UINT64.celOverloadDecl.toBuilder()
+ .setOverloadId(
+ Comparison.LESS_UINT64
+ .celOverloadDecl
+ .overloadId()
+ .replace("less", "greater_equals"))
+ .build(),
+ false),
+ GREATER_EQUALS_DOUBLE(
+ Comparison.LESS_DOUBLE.celOverloadDecl.toBuilder()
+ .setOverloadId(
+ Comparison.LESS_DOUBLE
+ .celOverloadDecl
+ .overloadId()
+ .replace("less", "greater_equals"))
+ .build(),
+ false),
+ GREATER_EQUALS_STRING(
+ Comparison.LESS_STRING.celOverloadDecl.toBuilder()
+ .setOverloadId(
+ Comparison.LESS_STRING
+ .celOverloadDecl
+ .overloadId()
+ .replace("less", "greater_equals"))
+ .build(),
+ false),
+ GREATER_EQUALS_BYTES(
+ Comparison.LESS_BYTES.celOverloadDecl.toBuilder()
+ .setOverloadId(
+ Comparison.LESS_BYTES
+ .celOverloadDecl
+ .overloadId()
+ .replace("less", "greater_equals"))
+ .build(),
+ false),
+ GREATER_EQUALS_TIMESTAMP(
+ Comparison.LESS_TIMESTAMP.celOverloadDecl.toBuilder()
+ .setOverloadId(
+ Comparison.LESS_TIMESTAMP
+ .celOverloadDecl
+ .overloadId()
+ .replace("less", "greater_equals"))
+ .build(),
+ false),
+ GREATER_EQUALS_DURATION(
+ Comparison.LESS_DURATION.celOverloadDecl.toBuilder()
+ .setOverloadId(
+ Comparison.LESS_DURATION
+ .celOverloadDecl
+ .overloadId()
+ .replace("less", "greater_equals"))
+ .build(),
+ false),
+ GREATER_EQUALS_INT64_UINT64(
+ Comparison.LESS_INT64_UINT64.celOverloadDecl.toBuilder()
+ .setOverloadId(
+ Comparison.LESS_INT64_UINT64
+ .celOverloadDecl
+ .overloadId()
+ .replace("less", "greater_equals"))
+ .build(),
+ true),
+ GREATER_EQUALS_UINT64_INT64(
+ Comparison.LESS_UINT64_INT64.celOverloadDecl.toBuilder()
+ .setOverloadId(
+ Comparison.LESS_UINT64_INT64
+ .celOverloadDecl
+ .overloadId()
+ .replace("less", "greater_equals"))
+ .build(),
+ true),
+ GREATER_EQUALS_INT64_DOUBLE(
+ Comparison.LESS_INT64_DOUBLE.celOverloadDecl.toBuilder()
+ .setOverloadId(
+ Comparison.LESS_INT64_DOUBLE
+ .celOverloadDecl
+ .overloadId()
+ .replace("less", "greater_equals"))
+ .build(),
+ true),
+ GREATER_EQUALS_DOUBLE_INT64(
+ Comparison.LESS_DOUBLE_INT64.celOverloadDecl.toBuilder()
+ .setOverloadId(
+ Comparison.LESS_DOUBLE_INT64
+ .celOverloadDecl
+ .overloadId()
+ .replace("less", "greater_equals"))
+ .build(),
+ true),
+ GREATER_EQUALS_UINT64_DOUBLE(
+ Comparison.LESS_UINT64_DOUBLE.celOverloadDecl.toBuilder()
+ .setOverloadId(
+ Comparison.LESS_UINT64_DOUBLE
+ .celOverloadDecl
+ .overloadId()
+ .replace("less", "greater_equals"))
+ .build(),
+ true),
+ GREATER_EQUALS_DOUBLE_UINT64(
+ Comparison.LESS_DOUBLE_UINT64.celOverloadDecl.toBuilder()
+ .setOverloadId(
+ Comparison.LESS_DOUBLE_UINT64
+ .celOverloadDecl
+ .overloadId()
+ .replace("less", "greater_equals"))
+ .build(),
+ true),
+ ;
+
+ private final CelOverloadDecl celOverloadDecl;
+ private final boolean isHeterogeneousComparison;
+
+ Comparison(CelOverloadDecl overloadDecl, boolean isHeterogeneousComparison) {
+ this.celOverloadDecl = overloadDecl;
+ this.isHeterogeneousComparison = isHeterogeneousComparison;
+ }
+
+ public boolean isHeterogeneousComparison() {
+ return isHeterogeneousComparison;
+ }
+
+ @Override
+ public CelOverloadDecl celOverloadDecl() {
+ return this.celOverloadDecl;
+ }
+ }
+
+ private Overload() {}
+ }
+
+ /** Gets the declaration for this standard function. */
+ private CelFunctionDecl withOverloads(Iterable overloads) {
+ return newCelFunctionDecl(functionName, ImmutableSet.copyOf(overloads));
+ }
+
+ public CelFunctionDecl functionDecl() {
+ return celFunctionDecl;
+ }
+
+ String functionName() {
+ return functionName;
+ }
+
+ boolean isDeprecated() {
+ return isDeprecated;
+ }
+
+ StandardFunction(Operator operator, StandardOverload... overloads) {
+ this(false, operator.getFunction(), overloads);
+ }
+
+ StandardFunction(String functionName, StandardOverload... overloads) {
+ this(false, functionName, overloads);
+ }
+
+ StandardFunction(boolean isDeprecated, String functionName, StandardOverload... overloads) {
+ this.isDeprecated = isDeprecated;
+ this.functionName = functionName;
+ this.standardOverloads = ImmutableSet.copyOf(overloads);
+ this.celFunctionDecl = newCelFunctionDecl(functionName, this.standardOverloads);
+ }
+
+ private static CelFunctionDecl newCelFunctionDecl(
+ String functionName, ImmutableSet overloads) {
+ return CelFunctionDecl.newFunctionDeclaration(
+ functionName,
+ overloads.stream().map(StandardOverload::celOverloadDecl).collect(toImmutableSet()));
+ }
+ }
+
+ /** Standard CEL identifiers. */
+ public enum StandardIdentifier {
+ INT(newStandardIdentDecl(SimpleType.INT)),
+ UINT(newStandardIdentDecl(SimpleType.UINT)),
+ BOOL(newStandardIdentDecl(SimpleType.BOOL)),
+ DOUBLE(newStandardIdentDecl(SimpleType.DOUBLE)),
+ BYTES(newStandardIdentDecl(SimpleType.BYTES)),
+ STRING(newStandardIdentDecl(SimpleType.STRING)),
+ DYN(newStandardIdentDecl(SimpleType.DYN)),
+ TYPE(newStandardIdentDecl("type", SimpleType.DYN)),
+ NULL_TYPE(newStandardIdentDecl("null_type", SimpleType.NULL_TYPE)),
+ LIST(newStandardIdentDecl("list", ListType.create(SimpleType.DYN))),
+ MAP(newStandardIdentDecl("map", MapType.create(SimpleType.DYN, SimpleType.DYN))),
+ ;
+
+ private static CelIdentDecl newStandardIdentDecl(CelType celType) {
+ return newStandardIdentDecl(CelTypes.format(celType), celType);
+ }
+
+ private static CelIdentDecl newStandardIdentDecl(String identName, CelType celType) {
+ return CelIdentDecl.newBuilder()
+ .setName(identName)
+ .setType(TypeType.create(celType))
+ .setDoc("type denotation")
+ .build();
+ }
+
+ private final CelIdentDecl identDecl;
+
+ public CelIdentDecl identDecl() {
+ return identDecl;
+ }
+
+ StandardIdentifier(CelIdentDecl identDecl) {
+ this.identDecl = identDecl;
+ }
+ }
+
+ /** General interface for defining a standard function overload. */
+ @Immutable
+ public interface StandardOverload {
+ CelOverloadDecl celOverloadDecl();
+ }
+
+ /** Set of all standard function names. */
+ public static ImmutableSet getAllFunctionNames() {
+ return stream(StandardFunction.values())
+ .filter(f -> !f.isDeprecated)
+ .map(f -> f.functionName)
+ .collect(toImmutableSet());
+ }
+
+ /** Builder for constructing the set of standard function/identifiers. */
+ public static final class Builder {
+
+ private ImmutableSet includeFunctions;
+ private ImmutableSet excludeFunctions;
+ private FunctionFilter functionFilter;
+
+ private ImmutableSet includeIdentifiers;
+ private ImmutableSet excludeIdentifiers;
+ private IdentifierFilter identifierFilter;
+
+ @CanIgnoreReturnValue
+ public Builder excludeFunctions(StandardFunction... functions) {
+ return excludeFunctions(ImmutableSet.copyOf(functions));
+ }
+
+ @CanIgnoreReturnValue
+ public Builder excludeFunctions(Iterable functions) {
+ this.excludeFunctions = checkNotNull(ImmutableSet.copyOf(functions));
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder includeFunctions(StandardFunction... functions) {
+ return includeFunctions(ImmutableSet.copyOf(functions));
+ }
+
+ @CanIgnoreReturnValue
+ public Builder includeFunctions(Iterable functions) {
+ this.includeFunctions = checkNotNull(ImmutableSet.copyOf(functions));
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder filterFunctions(FunctionFilter functionFilter) {
+ this.functionFilter = functionFilter;
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder excludeIdentifiers(StandardIdentifier... identifiers) {
+ return excludeIdentifiers(ImmutableSet.copyOf(identifiers));
+ }
+
+ @CanIgnoreReturnValue
+ public Builder excludeIdentifiers(Iterable identifiers) {
+ this.excludeIdentifiers = checkNotNull(ImmutableSet.copyOf(identifiers));
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder includeIdentifiers(StandardIdentifier... identifiers) {
+ return includeIdentifiers(ImmutableSet.copyOf(identifiers));
+ }
+
+ @CanIgnoreReturnValue
+ public Builder includeIdentifiers(Iterable identifiers) {
+ this.includeIdentifiers = checkNotNull(ImmutableSet.copyOf(identifiers));
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder filterIdentifiers(IdentifierFilter identifierFilter) {
+ this.identifierFilter = identifierFilter;
+ return this;
+ }
+
+ private static void assertOneSettingIsSet(
+ boolean a, boolean b, boolean c, String errorMessage) {
+ int count = 0;
+ if (a) {
+ count++;
+ }
+ if (b) {
+ count++;
+ }
+ if (c) {
+ count++;
+ }
+
+ if (count > 1) {
+ throw new IllegalArgumentException(errorMessage);
+ }
+ }
+
+ public CelStandardDeclarations build() {
+ boolean hasIncludeFunctions = !this.includeFunctions.isEmpty();
+ boolean hasExcludeFunctions = !this.excludeFunctions.isEmpty();
+ boolean hasFilterFunction = this.functionFilter != null;
+ assertOneSettingIsSet(
+ hasIncludeFunctions,
+ hasExcludeFunctions,
+ hasFilterFunction,
+ "You may only populate one of the following builder methods: includeFunctions,"
+ + " excludeFunctions or filterFunctions");
+ boolean hasIncludeIdentifiers = !this.includeIdentifiers.isEmpty();
+ boolean hasExcludeIdentifiers = !this.excludeIdentifiers.isEmpty();
+ boolean hasIdentifierFilter = this.identifierFilter != null;
+ assertOneSettingIsSet(
+ hasIncludeIdentifiers,
+ hasExcludeIdentifiers,
+ hasIdentifierFilter,
+ "You may only populate one of the following builder methods: includeIdentifiers,"
+ + " excludeIdentifiers or filterIdentifiers");
+
+ ImmutableSet.Builder functionDeclBuilder = ImmutableSet.builder();
+ for (StandardFunction standardFunction : StandardFunction.values()) {
+ if (hasIncludeFunctions) {
+ if (this.includeFunctions.contains(standardFunction)) {
+ functionDeclBuilder.add(standardFunction.celFunctionDecl);
+ }
+ continue;
+ }
+ if (hasExcludeFunctions) {
+ if (!this.excludeFunctions.contains(standardFunction)) {
+ functionDeclBuilder.add(standardFunction.celFunctionDecl);
+ }
+ continue;
+ }
+ if (hasFilterFunction) {
+ ImmutableSet.Builder overloadBuilder = ImmutableSet.builder();
+ for (StandardOverload standardOverload : standardFunction.standardOverloads) {
+ boolean includeOverload = functionFilter.include(standardFunction, standardOverload);
+ if (includeOverload) {
+ overloadBuilder.add(standardOverload);
+ }
+ }
+
+ ImmutableSet overloads = overloadBuilder.build();
+ if (!overloads.isEmpty()) {
+ functionDeclBuilder.add(standardFunction.withOverloads(overloadBuilder.build()));
+ }
+ continue;
+ }
+
+ functionDeclBuilder.add(standardFunction.celFunctionDecl);
+ }
+
+ ImmutableSet.Builder identBuilder = ImmutableSet.builder();
+ for (StandardIdentifier standardIdentifier : StandardIdentifier.values()) {
+ if (hasIncludeIdentifiers) {
+ if (this.includeIdentifiers.contains(standardIdentifier)) {
+ identBuilder.add(standardIdentifier.identDecl);
+ }
+ continue;
+ }
+
+ if (hasExcludeIdentifiers) {
+ if (!this.excludeIdentifiers.contains(standardIdentifier)) {
+ identBuilder.add(standardIdentifier.identDecl);
+ }
+ continue;
+ }
+
+ if (hasIdentifierFilter) {
+ boolean includeIdent = identifierFilter.include(standardIdentifier);
+ if (includeIdent) {
+ identBuilder.add(standardIdentifier.identDecl);
+ }
+ continue;
+ }
+
+ identBuilder.add(standardIdentifier.identDecl);
+ }
+
+ return new CelStandardDeclarations(functionDeclBuilder.build(), identBuilder.build());
+ }
+
+ private Builder() {
+ this.includeFunctions = ImmutableSet.of();
+ this.excludeFunctions = ImmutableSet.of();
+ this.includeIdentifiers = ImmutableSet.of();
+ this.excludeIdentifiers = ImmutableSet.of();
+ }
+
+ /**
+ * Functional interface for filtering standard functions. Returning true in the callback will
+ * include the function in the environment.
+ */
+ @FunctionalInterface
+ public interface FunctionFilter {
+ boolean include(StandardFunction standardFunction, StandardOverload standardOverload);
+ }
+
+ /**
+ * Functional interface for filtering standard identifiers. Returning true in the callback will
+ * include the identifier in the environment.
+ */
+ @FunctionalInterface
+ public interface IdentifierFilter {
+ boolean include(StandardIdentifier standardIdentifier);
+ }
+ }
+
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ ImmutableSet functionDecls() {
+ return celFunctionDecls;
+ }
+
+ ImmutableSet identifierDecls() {
+ return celIdentDecls;
+ }
+
+ private CelStandardDeclarations(
+ ImmutableSet celFunctionDecls, ImmutableSet celIdentDecls) {
+ this.celFunctionDecls = celFunctionDecls;
+ this.celIdentDecls = celIdentDecls;
+ }
+}
diff --git a/checker/src/main/java/dev/cel/checker/DescriptorTypeProvider.java b/checker/src/main/java/dev/cel/checker/DescriptorTypeProvider.java
index e2fe17351..b5f849d50 100644
--- a/checker/src/main/java/dev/cel/checker/DescriptorTypeProvider.java
+++ b/checker/src/main/java/dev/cel/checker/DescriptorTypeProvider.java
@@ -41,7 +41,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
-import org.jspecify.nullness.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* The {@code DescriptorTypeProvider} provides type information for one or more {@link Descriptor}
diff --git a/checker/src/main/java/dev/cel/checker/Env.java b/checker/src/main/java/dev/cel/checker/Env.java
index 17d563dc8..d5692d48c 100644
--- a/checker/src/main/java/dev/cel/checker/Env.java
+++ b/checker/src/main/java/dev/cel/checker/Env.java
@@ -19,6 +19,7 @@
import dev.cel.expr.Decl.FunctionDecl.Overload;
import dev.cel.expr.Expr;
import dev.cel.expr.Type;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -27,10 +28,12 @@
import com.google.common.collect.Lists;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.errorprone.annotations.CheckReturnValue;
+import dev.cel.checker.CelStandardDeclarations.StandardFunction.Overload.Comparison;
+import dev.cel.checker.CelStandardDeclarations.StandardFunction.Overload.Conversions;
+import dev.cel.common.CelContainer;
import dev.cel.common.CelFunctionDecl;
import dev.cel.common.CelOptions;
import dev.cel.common.CelOverloadDecl;
-import dev.cel.common.ExprFeatures;
import dev.cel.common.annotations.Internal;
import dev.cel.common.ast.CelConstant;
import dev.cel.common.ast.CelExpr;
@@ -38,6 +41,7 @@
import dev.cel.common.ast.CelReference;
import dev.cel.common.internal.Errors;
import dev.cel.common.types.CelKind;
+import dev.cel.common.types.CelProtoTypes;
import dev.cel.common.types.CelType;
import dev.cel.common.types.CelTypes;
import dev.cel.common.types.SimpleType;
@@ -48,7 +52,7 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
-import org.jspecify.nullness.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* Environment used during checking of expressions. Provides name resolution and error reporting.
@@ -94,6 +98,12 @@ public class Env {
/** CEL Feature flags. */
private final CelOptions celOptions;
+ private static final CelOptions LEGACY_TYPE_CHECKER_OPTIONS =
+ CelOptions.newBuilder()
+ .disableCelStandardEquality(false)
+ .enableNamespacedDeclarations(false)
+ .build();
+
private Env(
Errors errors, TypeProvider typeProvider, DeclGroup declGroup, CelOptions celOptions) {
this.celOptions = celOptions;
@@ -103,125 +113,112 @@ private Env(
}
/**
- * Creates an unconfigured {@code Env} value without the standard CEL types, functions, and
- * operators with a reference to the feature flags enabled in the environment.
- *
- * @deprecated use {@code unconfigured} with {@code CelOptions} instead.
- */
- @Deprecated
- public static Env unconfigured(Errors errors, ExprFeatures... exprFeatures) {
- return unconfigured(errors, new DescriptorTypeProvider(), ImmutableSet.copyOf(exprFeatures));
- }
-
- /**
- * Creates an unconfigured {@code Env} value without the standard CEL types, functions, and
- * operators using a custom {@code typeProvider}.
- *
- * @deprecated use {@code unconfigured} with {@code CelOptions} instead.
- */
- @Deprecated
- public static Env unconfigured(
- Errors errors, TypeProvider typeProvider, ExprFeatures... exprFeatures) {
- return unconfigured(errors, typeProvider, ImmutableSet.copyOf(exprFeatures));
- }
-
- /**
- * Creates an unconfigured {@code Env} value without the standard CEL types, functions, and
- * operators using a custom {@code typeProvider}. The set of enabled {@code exprFeatures} is also
- * provided.
- *
- * @deprecated use {@code unconfigured} with {@code CelOptions} instead.
+ * @deprecated Do not use. This exists for compatibility reasons. Migrate to CEL-Java fluent APIs.
+ * See {@code CelCompilerFactory}.
*/
@Deprecated
- public static Env unconfigured(
- Errors errors, TypeProvider typeProvider, ImmutableSet exprFeatures) {
- return unconfigured(errors, typeProvider, CelOptions.fromExprFeatures(exprFeatures));
+ public static Env unconfigured(Errors errors) {
+ return unconfigured(errors, LEGACY_TYPE_CHECKER_OPTIONS);
}
/**
* Creates an unconfigured {@code Env} value without the standard CEL types, functions, and
* operators with a reference to the configured {@code celOptions}.
*/
- public static Env unconfigured(Errors errors, CelOptions celOptions) {
+ @VisibleForTesting
+ static Env unconfigured(Errors errors, CelOptions celOptions) {
return unconfigured(errors, new DescriptorTypeProvider(), celOptions);
}
/**
* Creates an unconfigured {@code Env} value without the standard CEL types, functions, and
- * operators using a custom {@code typeProvider}. The {@code CelOptions} are provided as well.
+ * operators using a custom {@code typeProvider}.
+ *
+ * @deprecated Do not use. This exists for compatibility reasons. Migrate to CEL-Java fluent APIs.
+ * See {@code CelCompilerFactory}.
*/
+ @Deprecated
public static Env unconfigured(Errors errors, TypeProvider typeProvider, CelOptions celOptions) {
return new Env(errors, typeProvider, new DeclGroup(), celOptions);
}
/**
- * Creates an {@code Env} value configured with the standard types, functions, and operators with
- * a reference to the set of {@code exprFeatures} enabled in the environment.
- *
- * Note: standard declarations are configured in an isolated scope, and may be shadowed by
- * subsequent declarations.
- *
- * @deprecated use {@code standard} with {@code CelOptions} instead.
+ * @deprecated Do not use. This exists for compatibility reasons. Migrate to CEL-Java fluent APIs.
+ * See {@code CelCompilerFactory}.
*/
@Deprecated
- public static Env standard(Errors errors, ExprFeatures... exprFeatures) {
- return standard(errors, new DescriptorTypeProvider(), exprFeatures);
+ public static Env standard(Errors errors) {
+ return standard(errors, new DescriptorTypeProvider());
}
/**
- * Creates an {@code Env} value configured with the standard types, functions, and operators,
- * configured with a custom {@code typeProvider}.
- *
- *
Note: standard declarations are configured in an isolated scope, and may be shadowed by
- * subsequent declarations with the same signature.
- *
- * @deprecated use {@code standard} with {@code CelOptions} instead.
+ * @deprecated Do not use. This exists for compatibility reasons. Migrate to CEL-Java fluent APIs.
+ * See {@code CelCompilerFactory}.
*/
@Deprecated
- public static Env standard(
- Errors errors, TypeProvider typeProvider, ExprFeatures... exprFeatures) {
- return standard(errors, typeProvider, ImmutableSet.copyOf(exprFeatures));
+ public static Env standard(Errors errors, TypeProvider typeProvider) {
+ return standard(errors, typeProvider, LEGACY_TYPE_CHECKER_OPTIONS);
}
/**
* Creates an {@code Env} value configured with the standard types, functions, and operators,
- * configured with a custom {@code typeProvider} and a reference to the set of {@code
- * exprFeatures} enabled in the environment.
+ * configured with a custom {@code typeProvider} and a reference to the {@code celOptions} to use
+ * within the environment.
*
*
Note: standard declarations are configured in an isolated scope, and may be shadowed by
* subsequent declarations with the same signature.
*
- * @deprecated use {@code standard} with {@code CelOptions} instead.
+ * @deprecated Do not use. This exists for compatibility reasons. Migrate to CEL-Java fluent APIs.
+ * See {@code CelCompilerFactory}.
*/
@Deprecated
- public static Env standard(
- Errors errors, TypeProvider typeProvider, ImmutableSet exprFeatures) {
- return standard(errors, typeProvider, CelOptions.fromExprFeatures(exprFeatures));
- }
-
- /**
- * Creates an {@code Env} value configured with the standard types, functions, and operators and a
- * reference to the configured {@code celOptions}.
- *
- * Note: standard declarations are configured in an isolated scope, and may be shadowed by
- * subsequent declarations with the same signature.
- */
- public static Env standard(Errors errors, CelOptions celOptions) {
- return standard(errors, new DescriptorTypeProvider(), celOptions);
+ public static Env standard(Errors errors, TypeProvider typeProvider, CelOptions celOptions) {
+ CelStandardDeclarations celStandardDeclaration =
+ CelStandardDeclarations.newBuilder()
+ .filterFunctions(
+ (function, overload) -> {
+ switch (function) {
+ case INT:
+ if (!celOptions.enableUnsignedLongs()
+ && overload.equals(Conversions.INT64_TO_INT64)) {
+ return false;
+ }
+ break;
+ case TIMESTAMP:
+ // TODO: Remove this flag guard once the feature has been
+ // auto-enabled.
+ if (!celOptions.enableTimestampEpoch()
+ && overload.equals(Conversions.INT64_TO_TIMESTAMP)) {
+ return false;
+ }
+ break;
+ default:
+ if (!celOptions.enableHeterogeneousNumericComparisons()
+ && overload instanceof Comparison) {
+ Comparison comparison = (Comparison) overload;
+ if (comparison.isHeterogeneousComparison()) {
+ return false;
+ }
+ }
+ break;
+ }
+ return true;
+ })
+ .build();
+
+ return standard(celStandardDeclaration, errors, typeProvider, celOptions);
}
- /**
- * Creates an {@code Env} value configured with the standard types, functions, and operators,
- * configured with a custom {@code typeProvider} and a reference to the {@code celOptions} to use
- * within the environment.
- *
- *
Note: standard declarations are configured in an isolated scope, and may be shadowed by
- * subsequent declarations with the same signature.
- */
- public static Env standard(Errors errors, TypeProvider typeProvider, CelOptions celOptions) {
+ public static Env standard(
+ CelStandardDeclarations celStandardDeclaration,
+ Errors errors,
+ TypeProvider typeProvider,
+ CelOptions celOptions) {
Env env = Env.unconfigured(errors, typeProvider, celOptions);
// Isolate the standard declarations into their own scope for forward compatibility.
- Standard.add(env);
+ celStandardDeclaration.functionDecls().forEach(env::add);
+ celStandardDeclaration.identifierDecls().forEach(env::add);
+
env.enterScope();
return env;
}
@@ -296,7 +293,7 @@ public Map getTypeMap() {
@Deprecated
public Type getType(Expr expr) {
Preconditions.checkNotNull(expr);
- return CelTypes.celTypeToType(getType(CelExprConverter.fromExpr(expr)));
+ return CelProtoTypes.celTypeToType(getType(CelExprConverter.fromExpr(expr)));
}
/**
@@ -349,7 +346,7 @@ public Env add(Decl decl) {
CelIdentDecl.Builder identBuilder =
CelIdentDecl.newBuilder()
.setName(decl.getName())
- .setType(CelTypes.typeToCelType(decl.getIdent().getType()))
+ .setType(CelProtoTypes.typeToCelType(decl.getIdent().getType()))
// Note: Setting doc and constant value exists for compatibility reason. This should
// not be set by the users.
.setDoc(decl.getIdent().getDoc());
@@ -394,15 +391,17 @@ public Env add(CelIdentDecl celIdentDecl) {
@CanIgnoreReturnValue
@Deprecated
public Env add(String name, Type type) {
- return add(CelIdentDecl.newIdentDeclaration(name, CelTypes.typeToCelType(type)));
+ return add(CelIdentDecl.newIdentDeclaration(name, CelProtoTypes.typeToCelType(type)));
}
/**
+ * Note: Used by legacy type-checker users
+ *
* @deprecated Use {@link #tryLookupCelFunction} instead.
*/
@Deprecated
public @Nullable Decl tryLookupFunction(String container, String name) {
- CelFunctionDecl decl = tryLookupCelFunction(container, name);
+ CelFunctionDecl decl = tryLookupCelFunction(CelContainer.ofName(container), name);
if (decl == null) {
return null;
}
@@ -420,8 +419,8 @@ public Env add(String name, Type type) {
*
* Returns {@code null} if the function cannot be found.
*/
- public @Nullable CelFunctionDecl tryLookupCelFunction(String container, String name) {
- for (String cand : qualifiedTypeNameCandidates(container, name)) {
+ public @Nullable CelFunctionDecl tryLookupCelFunction(CelContainer container, String name) {
+ for (String cand : container.resolveCandidateNames(name)) {
// First determine whether we know this name already.
CelFunctionDecl decl = findFunctionDecl(cand);
if (decl != null) {
@@ -435,7 +434,7 @@ public Env add(String name, Type type) {
* @deprecated Use {@link #tryLookupCelIdent} instead.
*/
@Deprecated
- public @Nullable Decl tryLookupIdent(String container, String name) {
+ public @Nullable Decl tryLookupIdent(CelContainer container, String name) {
CelIdentDecl decl = tryLookupCelIdent(container, name);
if (decl == null) {
return null;
@@ -454,8 +453,8 @@ public Env add(String name, Type type) {
*
*
Returns {@code null} if the function cannot be found.
*/
- public @Nullable CelIdentDecl tryLookupCelIdent(String container, String name) {
- for (String cand : qualifiedTypeNameCandidates(container, name)) {
+ public @Nullable CelIdentDecl tryLookupCelIdent(CelContainer container, String name) {
+ for (String cand : container.resolveCandidateNames(name)) {
// First determine whether we know this name already.
CelIdentDecl decl = findIdentDecl(cand);
if (decl != null) {
@@ -493,10 +492,15 @@ public Env add(String name, Type type) {
* Lookup a name like {@link #tryLookupCelIdent}, but report an error if the name is not found and
* return the {@link #ERROR_IDENT_DECL}.
*/
- public CelIdentDecl lookupIdent(int position, String inContainer, String name) {
- CelIdentDecl result = tryLookupCelIdent(inContainer, name);
+ public CelIdentDecl lookupIdent(long exprId, int position, CelContainer container, String name) {
+ CelIdentDecl result = tryLookupCelIdent(container, name);
if (result == null) {
- reportError(position, "undeclared reference to '%s' (in container '%s')", name, inContainer);
+ reportError(
+ exprId,
+ position,
+ "undeclared reference to '%s' (in container '%s')",
+ name,
+ container.name());
return ERROR_IDENT_DECL;
}
return result;
@@ -506,18 +510,34 @@ public CelIdentDecl lookupIdent(int position, String inContainer, String name) {
* Lookup a name like {@link #tryLookupCelFunction} but report an error if the name is not found
* and return the {@link #ERROR_FUNCTION_DECL}.
*/
- public CelFunctionDecl lookupFunction(int position, String inContainer, String name) {
- CelFunctionDecl result = tryLookupCelFunction(inContainer, name);
+ public CelFunctionDecl lookupFunction(
+ long exprId, int position, CelContainer container, String name) {
+ CelFunctionDecl result = tryLookupCelFunction(container, name);
if (result == null) {
- reportError(position, "undeclared reference to '%s' (in container '%s')", name, inContainer);
+ reportError(
+ exprId,
+ position,
+ "undeclared reference to '%s' (in container '%s')",
+ name,
+ container.name());
return ERROR_FUNCTION_DECL;
}
return result;
}
- /** Reports an error. */
+ /**
+ * Note: Used by legacy type-checker users
+ *
+ * @deprecated Use {@link #reportError(long, int, String, Object...) instead.}
+ */
+ @Deprecated
public void reportError(int position, String message, Object... args) {
- errors.reportError(position, message, args);
+ reportError(0L, position, message, args);
+ }
+
+ /** Reports an error. */
+ public void reportError(long exprId, int position, String message, Object... args) {
+ errors.reportError(exprId, position, message, args);
}
boolean enableCompileTimeOverloadResolution() {
@@ -532,14 +552,6 @@ boolean enableNamespacedDeclarations() {
return celOptions.enableNamespacedDeclarations();
}
- boolean enableHeterogeneousNumericComparisons() {
- return celOptions.enableHeterogeneousNumericComparisons();
- }
-
- boolean enableTimestampEpoch() {
- return celOptions.enableTimestampEpoch();
- }
-
/** Add an identifier {@code decl} to the environment. */
@CanIgnoreReturnValue
private Env addIdent(CelIdentDecl celIdentDecl) {
@@ -548,7 +560,8 @@ private Env addIdent(CelIdentDecl celIdentDecl) {
getDeclGroup().putIdent(celIdentDecl);
} else {
reportError(
- 0,
+ /* exprId= */ 0,
+ /* position= */ 0,
"overlapping declaration name '%s' (type '%s' cannot be distinguished from '%s')",
celIdentDecl.name(),
CelTypes.format(current.type()),
@@ -594,7 +607,8 @@ private void addOverload(CelFunctionDecl.Builder builder, CelOverloadDecl overlo
|| Types.isAssignable(emptySubs, existingTypeErased, overloadTypeErased) != null;
if (overlap && existing.isInstanceFunction() == overload.isInstanceFunction()) {
reportError(
- 0,
+ /* exprId= */ 0,
+ /* position= */ 0,
"overlapping overload for name '%s' (type '%s' cannot be distinguished from '%s')",
builder.name(),
CelTypes.format(existingFunction),
@@ -609,7 +623,8 @@ private void addOverload(CelFunctionDecl.Builder builder, CelOverloadDecl overlo
&& macro.getDefinition().isReceiverStyle() == overload.isInstanceFunction()
&& macro.getDefinition().getArgumentCount() == overload.parameterTypes().size()) {
reportError(
- 0,
+ /* exprId= */ 0,
+ /* position= */ 0,
"overload for name '%s' with %s argument(s) overlaps with predefined macro",
builder.name(),
macro.getDefinition().getArgumentCount());
@@ -674,30 +689,6 @@ private void addOverload(CelFunctionDecl.Builder builder, CelOverloadDecl overlo
.build();
}
- /**
- * Returns the candidates for name resolution of a name within a container(e.g. package, message,
- * enum, service elements) context following PB (== C++) conventions. Iterates those names which
- * shadow other names first; recognizes and removes a leading '.' for overriding shadowing. Given
- * a container name {@code a.b.c.M.N} and a type name {@code R.s}, this will deliver in order
- * {@code a.b.c.M.N.R.s, a.b.c.M.R.s, a.b.c.R.s, a.b.R.s, a.R.s, R.s}.
- */
- private static ImmutableList qualifiedTypeNameCandidates(
- String container, String typeName) {
- // This function is a copy of //j/c/g/api/tools/model/SymbolTable#nameCandidates.
- if (typeName.startsWith(".")) {
- return ImmutableList.of(typeName.substring(1));
- }
- if (container.isEmpty()) {
- return ImmutableList.of(typeName);
- } else {
- int i = container.lastIndexOf('.');
- return ImmutableList.builder()
- .add(container + "." + typeName)
- .addAll(qualifiedTypeNameCandidates(i >= 0 ? container.substring(0, i) : "", typeName))
- .build();
- }
- }
-
/**
* A helper class for constructing identifier declarations.
*
@@ -716,7 +707,7 @@ public IdentBuilder(String name) {
@CanIgnoreReturnValue
public IdentBuilder type(Type value) {
Preconditions.checkNotNull(value);
- builder.setType(CelTypes.typeToCelType(Preconditions.checkNotNull(value)));
+ builder.setType(CelProtoTypes.typeToCelType(Preconditions.checkNotNull(value)));
return this;
}
@@ -798,12 +789,12 @@ public FunctionBuilder add(String id, Type resultType, Type... argTypes) {
public FunctionBuilder add(String id, Type resultType, Iterable argTypes) {
ImmutableList.Builder argumentBuilder = new ImmutableList.Builder<>();
for (Type type : argTypes) {
- argumentBuilder.add(CelTypes.typeToCelType(type));
+ argumentBuilder.add(CelProtoTypes.typeToCelType(type));
}
this.overloads.add(
CelOverloadDecl.newBuilder()
.setOverloadId(id)
- .setResultType(CelTypes.typeToCelType(resultType))
+ .setResultType(CelProtoTypes.typeToCelType(resultType))
.addParameterTypes(argumentBuilder.build())
.setIsInstanceFunction(isInstance)
.build());
@@ -823,12 +814,12 @@ public FunctionBuilder add(
String id, List typeParams, Type resultType, Iterable argTypes) {
ImmutableList.Builder argumentBuilder = new ImmutableList.Builder<>();
for (Type type : argTypes) {
- argumentBuilder.add(CelTypes.typeToCelType(type));
+ argumentBuilder.add(CelProtoTypes.typeToCelType(type));
}
this.overloads.add(
CelOverloadDecl.newBuilder()
.setOverloadId(id)
- .setResultType(CelTypes.typeToCelType(resultType))
+ .setResultType(CelProtoTypes.typeToCelType(resultType))
.addParameterTypes(argumentBuilder.build())
.setIsInstanceFunction(isInstance)
.build());
@@ -958,7 +949,7 @@ private static CelFunctionDecl sanitizeFunction(CelFunctionDecl func) {
}
CelFunctionDecl.Builder funcBuilder = func.toBuilder();
- ImmutableList.Builder overloadsBuilder = new ImmutableList.Builder<>();
+ ImmutableSet.Builder overloadsBuilder = new ImmutableSet.Builder<>();
for (CelOverloadDecl overloadDecl : funcBuilder.overloads()) {
CelOverloadDecl.Builder overloadBuilder = overloadDecl.toBuilder();
CelType resultType = overloadBuilder.build().resultType();
diff --git a/checker/src/main/java/dev/cel/checker/ExprChecker.java b/checker/src/main/java/dev/cel/checker/ExprChecker.java
index 5c0c21ffd..c2a2929b1 100644
--- a/checker/src/main/java/dev/cel/checker/ExprChecker.java
+++ b/checker/src/main/java/dev/cel/checker/ExprChecker.java
@@ -22,11 +22,12 @@
import com.google.auto.value.AutoValue;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
-import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
import com.google.errorprone.annotations.CheckReturnValue;
import dev.cel.common.CelAbstractSyntaxTree;
+import dev.cel.common.CelContainer;
import dev.cel.common.CelFunctionDecl;
import dev.cel.common.CelOverloadDecl;
import dev.cel.common.CelProtoAbstractSyntaxTree;
@@ -35,6 +36,7 @@
import dev.cel.common.ast.CelExpr;
import dev.cel.common.ast.CelReference;
import dev.cel.common.types.CelKind;
+import dev.cel.common.types.CelProtoTypes;
import dev.cel.common.types.CelType;
import dev.cel.common.types.CelTypes;
import dev.cel.common.types.ListType;
@@ -47,7 +49,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import org.jspecify.nullness.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* The expression type checker.
@@ -61,8 +63,7 @@
public final class ExprChecker {
/**
- * Checks the parsed expression within the given environment and returns a checked expression.
- * Conditions for type checking and the result are described in checked.proto.
+ * Deprecated type-check API.
*
* @deprecated Do not use. CEL-Java users should leverage the Fluent APIs instead. See {@code
* CelCompilerFactory}.
@@ -74,10 +75,7 @@ public static CheckedExpr check(Env env, String inContainer, ParsedExpr parsedEx
}
/**
- * Type-checks the parsed expression within the given environment and returns a checked
- * expression. If an expected result type was given, then it verifies that that type matches the
- * actual result type. Conditions for type checking and the constructed {@code CheckedExpr} are
- * described in checked.proto.
+ * Deprecated type-check API.
*
* @deprecated Do not use. CEL-Java users should leverage the Fluent APIs instead. See {@code
* CelCompilerFactory}.
@@ -88,11 +86,14 @@ public static CheckedExpr typecheck(
Env env, String inContainer, ParsedExpr parsedExpr, Optional expectedResultType) {
Optional type =
expectedResultType.isPresent()
- ? Optional.of(CelTypes.typeToCelType(expectedResultType.get()))
+ ? Optional.of(CelProtoTypes.typeToCelType(expectedResultType.get()))
: Optional.absent();
CelAbstractSyntaxTree ast =
typecheck(
- env, inContainer, CelProtoAbstractSyntaxTree.fromParsedExpr(parsedExpr).getAst(), type);
+ env,
+ CelContainer.ofName(inContainer),
+ CelProtoAbstractSyntaxTree.fromParsedExpr(parsedExpr).getAst(),
+ type);
if (ast.isChecked()) {
return CelProtoAbstractSyntaxTree.fromCelAst(ast).toCheckedExpr();
@@ -116,14 +117,14 @@ public static CheckedExpr typecheck(
@Internal
public static CelAbstractSyntaxTree typecheck(
Env env,
- String inContainer,
+ CelContainer container,
CelAbstractSyntaxTree ast,
Optional expectedResultType) {
env.resetTypeAndRefMaps();
final ExprChecker checker =
new ExprChecker(
env,
- inContainer,
+ container,
ast.getSource().getPositionsMap(),
new InferenceContext(),
env.enableCompileTimeOverloadResolution(),
@@ -143,7 +144,7 @@ public static CelAbstractSyntaxTree typecheck(
private final Env env;
private final TypeProvider typeProvider;
- private final String inContainer;
+ private final CelContainer container;
private final Map positionMap;
private final InferenceContext inferenceContext;
private final boolean compileTimeOverloadResolution;
@@ -152,17 +153,17 @@ public static CelAbstractSyntaxTree typecheck(
private ExprChecker(
Env env,
- String inContainer,
+ CelContainer container,
Map positionMap,
InferenceContext inferenceContext,
boolean compileTimeOverloadResolution,
boolean homogeneousLiterals,
boolean namespacedDeclarations) {
- this.env = Preconditions.checkNotNull(env);
+ this.env = checkNotNull(env);
this.typeProvider = env.getTypeProvider();
- this.positionMap = Preconditions.checkNotNull(positionMap);
- this.inContainer = Preconditions.checkNotNull(inContainer);
- this.inferenceContext = Preconditions.checkNotNull(inferenceContext);
+ this.positionMap = checkNotNull(positionMap);
+ this.container = checkNotNull(container);
+ this.inferenceContext = checkNotNull(inferenceContext);
this.compileTimeOverloadResolution = compileTimeOverloadResolution;
this.homogeneousLiterals = homogeneousLiterals;
this.namespacedDeclarations = namespacedDeclarations;
@@ -180,12 +181,12 @@ public CelExpr visit(CelExpr expr) {
return visit(expr, expr.select());
case CALL:
return visit(expr, expr.call());
- case CREATE_LIST:
- return visit(expr, expr.createList());
- case CREATE_STRUCT:
- return visit(expr, expr.createStruct());
- case CREATE_MAP:
- return visit(expr, expr.createMap());
+ case LIST:
+ return visit(expr, expr.list());
+ case STRUCT:
+ return visit(expr, expr.struct());
+ case MAP:
+ return visit(expr, expr.map());
case COMPREHENSION:
return visit(expr, expr.comprehension());
default:
@@ -231,7 +232,7 @@ private CelExpr visit(CelExpr expr, CelConstant constant) {
@CheckReturnValue
private CelExpr visit(CelExpr expr, CelExpr.CelIdent ident) {
- CelIdentDecl decl = env.lookupIdent(getPosition(expr), inContainer, ident.name());
+ CelIdentDecl decl = env.lookupIdent(expr.id(), getPosition(expr), container, ident.name());
checkNotNull(decl);
if (decl.equals(Env.ERROR_IDENT_DECL)) {
// error reported
@@ -253,10 +254,10 @@ private CelExpr visit(CelExpr expr, CelExpr.CelSelect select) {
// Before traversing down the tree, try to interpret as qualified name.
String qname = asQualifiedName(expr);
if (qname != null) {
- CelIdentDecl decl = env.tryLookupCelIdent(inContainer, qname);
+ CelIdentDecl decl = env.tryLookupCelIdent(container, qname);
if (decl != null) {
if (select.testOnly()) {
- env.reportError(getPosition(expr), "expression does not select a field");
+ env.reportError(expr.id(), getPosition(expr), "expression does not select a field");
env.setType(expr, SimpleType.BOOL);
} else {
if (namespacedDeclarations) {
@@ -307,8 +308,8 @@ private CelExpr visit(CelExpr expr, CelExpr.CelCall call) {
if (!call.target().isPresent()) {
// Regular static call with simple name.
- CelFunctionDecl decl = env.lookupFunction(position, inContainer, call.function());
- resolution = resolveOverload(position, decl, null, call.args());
+ CelFunctionDecl decl = env.lookupFunction(expr.id(), position, container, call.function());
+ resolution = resolveOverload(expr.id(), position, decl, null, call.args());
if (!decl.name().equals(call.function())) {
if (namespacedDeclarations) {
@@ -320,9 +321,9 @@ private CelExpr visit(CelExpr expr, CelExpr.CelCall call) {
// Check whether the target is actually a qualified name for a static function.
String qualifiedName = asQualifiedName(call.target().get());
CelFunctionDecl decl =
- env.tryLookupCelFunction(inContainer, qualifiedName + "." + call.function());
+ env.tryLookupCelFunction(container, qualifiedName + "." + call.function());
if (decl != null) {
- resolution = resolveOverload(position, decl, null, call.args());
+ resolution = resolveOverload(expr.id(), position, decl, null, call.args());
if (namespacedDeclarations) {
// The function name is namespaced and so preserving the target operand would
@@ -341,8 +342,9 @@ private CelExpr visit(CelExpr expr, CelExpr.CelCall call) {
}
resolution =
resolveOverload(
+ expr.id(),
position,
- env.lookupFunction(getPosition(expr), inContainer, call.function()),
+ env.lookupFunction(expr.id(), getPosition(expr), container, call.function()),
target,
call.args());
}
@@ -355,21 +357,25 @@ private CelExpr visit(CelExpr expr, CelExpr.CelCall call) {
}
@CheckReturnValue
- private CelExpr visit(CelExpr expr, CelExpr.CelCreateStruct createStruct) {
+ private CelExpr visit(CelExpr expr, CelExpr.CelStruct struct) {
// Determine the type of the message.
CelType messageType = SimpleType.ERROR;
- CelIdentDecl decl = env.lookupIdent(getPosition(expr), inContainer, createStruct.messageName());
+ CelIdentDecl decl =
+ env.lookupIdent(expr.id(), getPosition(expr), container, struct.messageName());
env.setRef(expr, CelReference.newBuilder().setName(decl.name()).build());
CelType type = decl.type();
if (type.kind() != CelKind.ERROR) {
if (type.kind() != CelKind.TYPE) {
// expected type of types
- env.reportError(getPosition(expr), "'%s' is not a type", CelTypes.format(type));
+ env.reportError(expr.id(), getPosition(expr), "'%s' is not a type", CelTypes.format(type));
} else {
messageType = ((TypeType) type).type();
if (messageType.kind() != CelKind.STRUCT) {
env.reportError(
- getPosition(expr), "'%s' is not a message type", CelTypes.format(messageType));
+ expr.id(),
+ getPosition(expr),
+ "'%s' is not a message type",
+ CelTypes.format(messageType));
messageType = SimpleType.ERROR;
}
}
@@ -383,26 +389,31 @@ private CelExpr visit(CelExpr expr, CelExpr.CelCreateStruct createStruct) {
}
// Check the field initializers.
- ImmutableList entriesList = createStruct.entries();
+ ImmutableList entriesList = struct.entries();
for (int i = 0; i < entriesList.size(); i++) {
- CelExpr.CelCreateStruct.Entry entry = entriesList.get(i);
+ CelExpr.CelStruct.Entry entry = entriesList.get(i);
CelExpr visitedValueExpr = visit(entry.value());
if (namespacedDeclarations && !visitedValueExpr.equals(entry.value())) {
// Subtree has been rewritten. Replace the struct value.
expr = replaceStructEntryValueSubtree(expr, visitedValueExpr, i);
}
- CelType fieldType = getFieldType(getPosition(entry), messageType, entry.fieldKey()).celType();
+ CelType fieldType =
+ getFieldType(entry.id(), getPosition(entry), messageType, entry.fieldKey()).celType();
CelType valueType = env.getType(visitedValueExpr);
if (entry.optionalEntry()) {
if (valueType instanceof OptionalType) {
valueType = unwrapOptional(valueType);
} else {
assertIsAssignable(
- getPosition(visitedValueExpr), valueType, OptionalType.create(valueType));
+ visitedValueExpr.id(),
+ getPosition(visitedValueExpr),
+ valueType,
+ OptionalType.create(valueType));
}
}
if (!inferenceContext.isAssignable(fieldType, valueType)) {
env.reportError(
+ expr.id(),
getPosition(entry),
"expected type of field '%s' is '%s' but provided type is '%s'",
entry.fieldKey(),
@@ -414,19 +425,23 @@ private CelExpr visit(CelExpr expr, CelExpr.CelCreateStruct createStruct) {
}
@CheckReturnValue
- private CelExpr visit(CelExpr expr, CelExpr.CelCreateMap createMap) {
+ private CelExpr visit(CelExpr expr, CelExpr.CelMap map) {
CelType mapKeyType = null;
CelType mapValueType = null;
- ImmutableList