![JAR search and dependency download from the Maven repository](/logo.png)
dev.cel.checker.CelCheckerLegacyImpl Maven / Gradle / Ivy
Show all versions of validators Show documentation
// Copyright 2022 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.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
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;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.errorprone.annotations.CheckReturnValue;
import com.google.errorprone.annotations.Immutable;
import com.google.protobuf.DescriptorProtos.FileDescriptorSet;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.FileDescriptor;
import dev.cel.common.CelAbstractSyntaxTree;
import dev.cel.common.CelDescriptorUtil;
import dev.cel.common.CelFunctionDecl;
import dev.cel.common.CelIssue;
import dev.cel.common.CelOptions;
import dev.cel.common.CelOverloadDecl;
import dev.cel.common.CelSource;
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.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;
/**
* {@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 ImmutableSet identDeclarations;
private final ImmutableSet functionDeclarations;
private final Optional expectedResultType;
@SuppressWarnings("Immutable")
private final TypeProvider typeProvider;
private final boolean standardEnvironmentEnabled;
// Builder is mutable by design. APIs must make defensive copies in and out of this class.
@SuppressWarnings("Immutable")
private final Builder checkerBuilder;
@Override
public CelValidationResult check(CelAbstractSyntaxTree ast) {
CelSource source = ast.getSource();
Errors errors = new Errors(source.getDescription(), source.getContent().toString());
Env env = getEnv(errors);
if (errors.getErrorCount() > 0) {
return new CelValidationResult(source, errorsToIssues(errors));
}
CelAbstractSyntaxTree checkedAst =
ExprChecker.typecheck(env, container, ast, expectedResultType);
if (errors.getErrorCount() > 0) {
return new CelValidationResult(source, errorsToIssues(errors));
}
return new CelValidationResult(checkedAst, ImmutableList.of());
}
@Override
public CelCheckerBuilder toCheckerBuilder() {
return new Builder(checkerBuilder);
}
@Override
public void accept(EnvVisitor envVisitor) {
Errors errors = new Errors("", "");
Env env = getEnv(errors);
for (int i = env.scopeDepth(); i >= 0; i--) {
Env.DeclGroup declGroup = env.getDeclGroup(i);
SortedSet names = new TreeSet<>();
names.addAll(declGroup.getIdents().keySet());
names.addAll(declGroup.getFunctions().keySet());
for (String name : names) {
CelIdentDecl ident = declGroup.getIdent(name);
CelFunctionDecl func = declGroup.getFunction(name);
List decls = new ArrayList<>();
if (ident != null) {
decls.add(CelIdentDecl.celIdentToDecl(ident));
}
if (func != null) {
decls.add(CelFunctionDecl.celFunctionDeclToDecl(func));
}
envVisitor.visitDecl(name, decls);
}
}
}
private Env getEnv(Errors errors) {
Env env;
if (standardEnvironmentEnabled) {
env = Env.standard(errors, typeProvider, celOptions);
} else {
env = Env.unconfigured(errors, typeProvider, celOptions);
}
identDeclarations.forEach(env::add);
functionDeclarations.forEach(env::add);
return env;
}
/** Create a new builder to construct a {@code CelChecker} instance. */
public static CelCheckerBuilder newBuilder() {
return new Builder();
}
/** Builder class for the legacy {@code CelChecker} implementation. */
public static final class Builder implements CelCheckerBuilder {
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 CelOptions celOptions;
private String container;
private CelType expectedResultType;
private TypeProvider customTypeProvider;
private CelTypeProvider celTypeProvider;
private boolean standardEnvironmentEnabled;
@Override
public CelCheckerBuilder setOptions(CelOptions celOptions) {
this.celOptions = checkNotNull(celOptions);
return this;
}
@Override
public CelCheckerBuilder setContainer(String container) {
checkNotNull(container);
this.container = container;
return this;
}
@Override
public CelCheckerBuilder addDeclarations(Decl... declarations) {
checkNotNull(declarations);
return addDeclarations(Arrays.asList(declarations));
}
@Override
public CelCheckerBuilder addDeclarations(Iterable declarations) {
checkNotNull(declarations);
for (Decl decl : declarations) {
switch (decl.getDeclKindCase()) {
case IDENT:
CelIdentDecl.Builder identBuilder =
CelIdentDecl.newBuilder()
.setName(decl.getName())
.setType(CelTypes.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());
if (decl.getIdent().hasValue()) {
identBuilder.setConstant(
CelExprConverter.exprConstantToCelConstant(decl.getIdent().getValue()));
}
this.identDeclarations.add(identBuilder.build());
break;
case FUNCTION:
addFunctionDeclarations(
CelFunctionDecl.newFunctionDeclaration(
decl.getName(),
decl.getFunction().getOverloadsList().stream()
.map(CelOverloadDecl::overloadToCelOverload)
.collect(toImmutableList())));
break;
default:
throw new IllegalArgumentException("unexpected decl kind: " + decl.getDeclKindCase());
}
}
return this;
}
@Override
@CanIgnoreReturnValue
public CelCheckerBuilder addFunctionDeclarations(CelFunctionDecl... celFunctionDecls) {
checkNotNull(celFunctionDecls);
return addFunctionDeclarations(Arrays.asList(celFunctionDecls));
}
@Override
public CelCheckerBuilder addFunctionDeclarations(Iterable celFunctionDecls) {
checkNotNull(celFunctionDecls);
this.functionDeclarations.addAll(celFunctionDecls);
return this;
}
@Override
public CelCheckerBuilder addVarDeclarations(CelVarDecl... celVarDecls) {
checkNotNull(celVarDecls);
return addVarDeclarations(Arrays.asList(celVarDecls));
}
@Override
public CelCheckerBuilder addVarDeclarations(Iterable celVarDecls) {
checkNotNull(celVarDecls);
for (CelVarDecl celVarDecl : celVarDecls) {
this.identDeclarations.add(
CelIdentDecl.newIdentDeclaration(celVarDecl.name(), celVarDecl.type()));
}
return this;
}
@Override
public CelCheckerBuilder addProtoTypeMasks(ProtoTypeMask... typeMasks) {
checkNotNull(typeMasks);
return addProtoTypeMasks(Arrays.asList(typeMasks));
}
@Override
public CelCheckerBuilder addProtoTypeMasks(Iterable typeMasks) {
checkNotNull(typeMasks);
protoTypeMasks.addAll(typeMasks);
return this;
}
@Override
public CelCheckerBuilder setResultType(CelType resultType) {
checkNotNull(resultType);
this.expectedResultType = resultType;
return this;
}
@Override
public CelCheckerBuilder setProtoResultType(Type resultType) {
checkNotNull(resultType);
return setResultType(CelTypes.typeToCelType(resultType));
}
@Override
@Deprecated
public CelCheckerBuilder setTypeProvider(TypeProvider typeProvider) {
this.customTypeProvider = typeProvider;
return this;
}
@Override
public CelCheckerBuilder setTypeProvider(CelTypeProvider celTypeProvider) {
this.celTypeProvider = celTypeProvider;
return this;
}
@Override
public CelCheckerBuilder addMessageTypes(Descriptor... descriptors) {
return addMessageTypes(Arrays.asList(checkNotNull(descriptors)));
}
@Override
public CelCheckerBuilder addMessageTypes(Iterable descriptors) {
this.messageTypes.addAll(checkNotNull(descriptors));
return this;
}
@Override
public CelCheckerBuilder addFileTypes(FileDescriptor... fileDescriptors) {
return addFileTypes(Arrays.asList(checkNotNull(fileDescriptors)));
}
@Override
public CelCheckerBuilder addFileTypes(Iterable fileDescriptors) {
this.fileTypes.addAll(checkNotNull(fileDescriptors));
return this;
}
@Override
public CelCheckerBuilder addFileTypes(FileDescriptorSet fileDescriptorSet) {
checkNotNull(fileDescriptorSet);
return addFileTypes(
CelDescriptorUtil.getFileDescriptorsFromFileDescriptorSet(fileDescriptorSet));
}
@Override
public CelCheckerBuilder setStandardEnvironmentEnabled(boolean value) {
standardEnvironmentEnabled = value;
return this;
}
@Override
public CelCheckerBuilder addLibraries(CelCheckerLibrary... libraries) {
checkNotNull(libraries);
return this.addLibraries(Arrays.asList(libraries));
}
@Override
public CelCheckerBuilder addLibraries(Iterable extends CelCheckerLibrary> libraries) {
checkNotNull(libraries);
this.celCheckerLibraries.addAll(libraries);
return this;
}
// The following getters exist for asserting immutability for collections held by this builder,
// and shouldn't be exposed to the public.
@VisibleForTesting
ImmutableSet.Builder getFunctionDecls() {
return this.functionDeclarations;
}
@VisibleForTesting
ImmutableSet.Builder getIdentDecls() {
return this.identDeclarations;
}
@VisibleForTesting
ImmutableSet.Builder getProtoTypeMasks() {
return this.protoTypeMasks;
}
@VisibleForTesting
ImmutableSet.Builder getMessageTypes() {
return this.messageTypes;
}
@VisibleForTesting
ImmutableSet.Builder getFileTypes() {
return this.fileTypes;
}
@VisibleForTesting
ImmutableSet.Builder getCheckerLibraries() {
return this.celCheckerLibraries;
}
@Override
@CheckReturnValue
public CelCheckerLegacyImpl build() {
// Add libraries, such as extensions
celCheckerLibraries.build().forEach(celLibrary -> celLibrary.setCheckerOptions(this));
// Configure the type provider.
ImmutableSet fileTypeSet = fileTypes.build();
ImmutableSet messageTypeSet = messageTypes.build();
if (!messageTypeSet.isEmpty()) {
fileTypeSet =
new ImmutableSet.Builder()
.addAll(fileTypeSet)
.addAll(messageTypeSet.stream().map(Descriptor::getFile).collect(toImmutableSet()))
.build();
}
CelTypeProvider messageTypeProvider =
new ProtoMessageTypeProvider(
CelDescriptorUtil.getAllDescriptorsFromFileDescriptor(
fileTypeSet, celOptions.resolveTypeDependencies()));
if (celTypeProvider != null && fileTypeSet.isEmpty()) {
messageTypeProvider = celTypeProvider;
} else if (celTypeProvider != null) {
messageTypeProvider =
new CelTypeProvider.CombinedCelTypeProvider(
ImmutableList.of(celTypeProvider, messageTypeProvider));
}
// 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.
ImmutableSet identDeclarationSet = identDeclarations.build();
ImmutableSet protoTypeMaskSet = protoTypeMasks.build();
if (!protoTypeMaskSet.isEmpty()) {
ProtoTypeMaskTypeProvider protoTypeMaskTypeProvider =
new ProtoTypeMaskTypeProvider(messageTypeProvider, protoTypeMaskSet);
identDeclarationSet =
ImmutableSet.builder()
.addAll(identDeclarationSet)
.addAll(protoTypeMaskTypeProvider.computeDeclsFromProtoTypeMasks())
.build();
messageTypeProvider = protoTypeMaskTypeProvider;
}
TypeProvider legacyProvider = new TypeProviderLegacyImpl(messageTypeProvider);
if (customTypeProvider != null) {
legacyProvider =
new TypeProvider.CombinedTypeProvider(
ImmutableList.of(customTypeProvider, legacyProvider));
}
return new CelCheckerLegacyImpl(
celOptions,
container,
identDeclarationSet,
functionDeclarations.build(),
Optional.fromNullable(expectedResultType),
legacyProvider,
standardEnvironmentEnabled,
this);
}
private Builder() {
this.celOptions = CelOptions.newBuilder().build();
this.identDeclarations = ImmutableSet.builder();
this.functionDeclarations = ImmutableSet.builder();
this.fileTypes = ImmutableSet.builder();
this.messageTypes = ImmutableSet.builder();
this.protoTypeMasks = ImmutableSet.builder();
this.celCheckerLibraries = ImmutableSet.builder();
this.container = "";
}
private Builder(Builder builder) {
// The following properties are either immutable or simple primitives, thus can be assigned
// directly.
this.celOptions = builder.celOptions;
this.celTypeProvider = builder.celTypeProvider;
this.container = builder.container;
this.customTypeProvider = builder.customTypeProvider;
this.expectedResultType = builder.expectedResultType;
this.standardEnvironmentEnabled = builder.standardEnvironmentEnabled;
// The following needs to be deep copied as they are collection builders
this.functionDeclarations = deepCopy(builder.functionDeclarations);
this.identDeclarations = deepCopy(builder.identDeclarations);
this.fileTypes = deepCopy(builder.fileTypes);
this.messageTypes = deepCopy(builder.messageTypes);
this.protoTypeMasks = deepCopy(builder.protoTypeMasks);
this.celCheckerLibraries = deepCopy(builder.celCheckerLibraries);
}
private static ImmutableSet.Builder deepCopy(ImmutableSet.Builder builderToCopy) {
ImmutableSet.Builder newBuilder = ImmutableSet.builder();
newBuilder.addAll(builderToCopy.build());
return newBuilder;
}
}
private CelCheckerLegacyImpl(
CelOptions celOptions,
String container,
ImmutableSet identDeclarations,
ImmutableSet functionDeclarations,
Optional expectedResultType,
TypeProvider typeProvider,
boolean standardEnvironmentEnabled,
Builder checkerBuilder) {
this.celOptions = celOptions;
this.container = container;
this.identDeclarations = identDeclarations;
this.functionDeclarations = functionDeclarations;
this.expectedResultType = expectedResultType;
this.typeProvider = typeProvider;
this.standardEnvironmentEnabled = standardEnvironmentEnabled;
this.checkerBuilder = new Builder(checkerBuilder);
}
private static ImmutableList errorsToIssues(Errors errors) {
ImmutableList errorList = errors.getErrors();
CelIssue.Builder issueBuilder = CelIssue.newBuilder().setSeverity(CelIssue.Severity.ERROR);
return errorList.stream()
.map(
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();
})
.collect(toImmutableList());
}
}