dev.cel.common.values.ProtoCelValueConverter Maven / Gradle / Ivy
Show all versions of runtime Show documentation
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package dev.cel.common.values;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static com.google.common.math.LongMath.checkedAdd;
import static com.google.common.math.LongMath.checkedSubtract;
import com.google.common.base.Preconditions;
import com.google.errorprone.annotations.Immutable;
import com.google.protobuf.Any;
import com.google.protobuf.BoolValue;
import com.google.protobuf.ByteString;
import com.google.protobuf.Descriptors.EnumValueDescriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.DoubleValue;
import com.google.protobuf.DynamicMessage;
import com.google.protobuf.FloatValue;
import com.google.protobuf.Int32Value;
import com.google.protobuf.Int64Value;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.MapEntry;
import com.google.protobuf.Message;
import com.google.protobuf.MessageOrBuilder;
import com.google.protobuf.StringValue;
import com.google.protobuf.Struct;
import com.google.protobuf.Timestamp;
import com.google.protobuf.UInt32Value;
import com.google.protobuf.UInt64Value;
import com.google.protobuf.Value;
import com.google.protobuf.util.Durations;
import com.google.protobuf.util.Timestamps;
import dev.cel.common.CelOptions;
import dev.cel.common.annotations.Internal;
import dev.cel.common.internal.CelDescriptorPool;
import dev.cel.common.internal.DynamicProto;
import dev.cel.common.internal.WellKnownProto;
import dev.cel.common.types.CelTypes;
import java.time.Duration;
import java.time.Instant;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* {@code CelValueConverter} handles bidirectional conversion between native Java and protobuf
* objects to {@link CelValue}.
*
* Protobuf semantics take precedence for conversion. For example, CEL's TimestampValue will be
* converted into Protobuf's Timestamp instead of java.time.Instant.
*
*
CEL Library Internals. Do Not Use.
*/
@Immutable
@Internal
public final class ProtoCelValueConverter extends CelValueConverter {
private final CelDescriptorPool celDescriptorPool;
private final DynamicProto dynamicProto;
/** Constructs a new instance of ProtoCelValueConverter. */
public static ProtoCelValueConverter newInstance(
CelOptions celOptions, CelDescriptorPool celDescriptorPool, DynamicProto dynamicProto) {
return new ProtoCelValueConverter(celOptions, celDescriptorPool, dynamicProto);
}
/**
* Adapts a {@link CelValue} to a native Java object. The CelValue is adapted into protobuf object
* when an equivalent exists.
*/
@Override
public Object fromCelValueToJavaObject(CelValue celValue) {
Preconditions.checkNotNull(celValue);
if (celValue instanceof TimestampValue) {
return TimeUtils.toProtoTimestamp(((TimestampValue) celValue).value());
} else if (celValue instanceof DurationValue) {
return TimeUtils.toProtoDuration(((DurationValue) celValue).value());
} else if (celValue instanceof BytesValue) {
return ByteString.copyFrom(((BytesValue) celValue).value().toByteArray());
} else if (NullValue.NULL_VALUE.equals(celValue)) {
return com.google.protobuf.NullValue.NULL_VALUE;
}
return super.fromCelValueToJavaObject(celValue);
}
/** Adapts a Protobuf message into a {@link CelValue}. */
public CelValue fromProtoMessageToCelValue(MessageOrBuilder message) {
Preconditions.checkNotNull(message);
// Attempt to convert the proto from a dynamic message into a concrete message if possible.
if (message instanceof DynamicMessage) {
message = dynamicProto.maybeAdaptDynamicMessage((DynamicMessage) message);
}
WellKnownProto wellKnownProto =
WellKnownProto.getByDescriptorName(message.getDescriptorForType().getFullName());
if (wellKnownProto == null) {
return ProtoMessageValue.create((Message) message, celDescriptorPool, this);
}
switch (wellKnownProto) {
case ANY_VALUE:
Message unpackedMessage;
try {
unpackedMessage = dynamicProto.unpack((Any) message);
} catch (InvalidProtocolBufferException e) {
throw new IllegalStateException(
"Unpacking failed for message: " + message.getDescriptorForType().getFullName(), e);
}
return fromProtoMessageToCelValue(unpackedMessage);
case JSON_VALUE:
return adaptJsonValueToCelValue((Value) message);
case JSON_STRUCT_VALUE:
return adaptJsonStructToCelValue((Struct) message);
case JSON_LIST_VALUE:
return adaptJsonListToCelValue((com.google.protobuf.ListValue) message);
case DURATION_VALUE:
return DurationValue.create(
TimeUtils.toJavaDuration((com.google.protobuf.Duration) message));
case TIMESTAMP_VALUE:
return TimestampValue.create(TimeUtils.toJavaInstant((Timestamp) message));
case BOOL_VALUE:
return fromJavaPrimitiveToCelValue(((BoolValue) message).getValue());
case BYTES_VALUE:
return fromJavaPrimitiveToCelValue(
((com.google.protobuf.BytesValue) message).getValue().toByteArray());
case DOUBLE_VALUE:
return fromJavaPrimitiveToCelValue(((DoubleValue) message).getValue());
case FLOAT_VALUE:
return fromJavaPrimitiveToCelValue(((FloatValue) message).getValue());
case INT32_VALUE:
return fromJavaPrimitiveToCelValue(((Int32Value) message).getValue());
case INT64_VALUE:
return fromJavaPrimitiveToCelValue(((Int64Value) message).getValue());
case STRING_VALUE:
return fromJavaPrimitiveToCelValue(((StringValue) message).getValue());
case UINT32_VALUE:
return UintValue.create(
((UInt32Value) message).getValue(), celOptions.enableUnsignedLongs());
case UINT64_VALUE:
return UintValue.create(
((UInt64Value) message).getValue(), celOptions.enableUnsignedLongs());
}
throw new UnsupportedOperationException(
"Unsupported message to CelValue conversion - " + message);
}
/**
* Adapts a plain old Java Object to a {@link CelValue}. Protobuf semantics take precedence for
* conversion.
*/
@Override
public CelValue fromJavaObjectToCelValue(Object value) {
Preconditions.checkNotNull(value);
if (value instanceof Message) {
return fromProtoMessageToCelValue((Message) value);
} else if (value instanceof Message.Builder) {
Message.Builder msgBuilder = (Message.Builder) value;
return fromProtoMessageToCelValue(msgBuilder.build());
} else if (value instanceof ByteString) {
return BytesValue.create(CelByteString.of(((ByteString) value).toByteArray()));
} else if (value instanceof com.google.protobuf.NullValue) {
return NullValue.NULL_VALUE;
} else if (value instanceof EnumValueDescriptor) {
// (b/178627883) Strongly typed enum is not supported yet
return IntValue.create(((EnumValueDescriptor) value).getNumber());
}
return super.fromJavaObjectToCelValue(value);
}
/** Adapts the protobuf message field into {@link CelValue}. */
@SuppressWarnings({"unchecked", "rawtypes"})
public CelValue fromProtoMessageFieldToCelValue(
Message message, FieldDescriptor fieldDescriptor) {
Preconditions.checkNotNull(message);
Preconditions.checkNotNull(fieldDescriptor);
Object result = message.getField(fieldDescriptor);
switch (fieldDescriptor.getType()) {
case MESSAGE:
if (CelTypes.isWrapperType(fieldDescriptor.getMessageType().getFullName())
&& !message.hasField(fieldDescriptor)) {
// Special semantics for wrapper types per CEL specification. These all convert into null
// instead of the default value.
return NullValue.NULL_VALUE;
} else if (fieldDescriptor.isMapField()) {
Map