1515package dev .cel .common .internal ;
1616
1717import static com .google .common .base .Preconditions .checkNotNull ;
18+ import static com .google .common .collect .ImmutableMap .toImmutableMap ;
19+ import static java .util .Arrays .stream ;
1820
21+ import com .google .auto .value .AutoBuilder ;
22+ import com .google .common .collect .ImmutableCollection ;
23+ import com .google .common .collect .ImmutableMap ;
24+ import com .google .common .collect .ImmutableMultimap ;
1925import com .google .errorprone .annotations .CheckReturnValue ;
2026import com .google .errorprone .annotations .Immutable ;
2127import com .google .protobuf .Any ;
2228import com .google .protobuf .ByteString ;
29+ import com .google .protobuf .Descriptors .Descriptor ;
30+ import com .google .protobuf .Descriptors .FieldDescriptor ;
2331import com .google .protobuf .DynamicMessage ;
2432import com .google .protobuf .InvalidProtocolBufferException ;
2533import com .google .protobuf .Message ;
34+ import dev .cel .common .CelDescriptorUtil ;
35+ import dev .cel .common .CelDescriptors ;
2636import dev .cel .common .annotations .Internal ;
37+ import dev .cel .common .types .CelTypes ;
38+ import java .util .Map .Entry ;
2739import java .util .Optional ;
40+ import org .jspecify .nullness .Nullable ;
2841
2942/**
3043 * The {@code DynamicProto} class supports the conversion of {@link Any} values to concrete {@code
3649@ CheckReturnValue
3750@ Internal
3851public final class DynamicProto {
52+
53+ private static final ImmutableMap <String , Descriptor > WELL_KNOWN_DESCRIPTORS =
54+ stream (ProtoAdapter .WellKnownProto .values ())
55+ .collect (toImmutableMap (d -> d .typeName (), d -> d .descriptor ()));
56+
57+ private final ImmutableMap <String , Descriptor > dynamicDescriptors ;
58+ private final ImmutableMultimap <String , FieldDescriptor > dynamicExtensionDescriptors ;
3959 private final ProtoMessageFactory protoMessageFactory ;
4060
41- public static DynamicProto create (ProtoMessageFactory protoMessageFactory ) {
42- return new DynamicProto (protoMessageFactory );
61+ /** {@code ProtoMessageFactory} provides a method to create a protobuf builder objects by name. */
62+ @ Immutable
63+ @ FunctionalInterface
64+ public interface ProtoMessageFactory {
65+ Message .@ Nullable Builder newBuilder (String messageName );
4366 }
4467
45- DynamicProto (ProtoMessageFactory protoMessageFactory ) {
46- this .protoMessageFactory = checkNotNull (protoMessageFactory );
68+ /** Builder for configuring the {@link DynamicProto}. */
69+ @ AutoBuilder (ofClass = DynamicProto .class )
70+ public abstract static class Builder {
71+
72+ /** Sets {@link CelDescriptors} to unpack any message types. */
73+ public abstract Builder setDynamicDescriptors (CelDescriptors celDescriptors );
74+
75+ /** Sets a custom type factory to unpack any message types. */
76+ public abstract Builder setProtoMessageFactory (ProtoMessageFactory factory );
77+
78+ /** Builds a new instance of {@link DynamicProto} */
79+ @ CheckReturnValue
80+ public abstract DynamicProto build ();
4781 }
4882
49- /** Attempts to unpack an Any message. */
50- public Optional <Message > maybeUnpackAny (Message msg ) {
51- try {
52- Any any =
53- msg instanceof Any
54- ? (Any ) msg
55- : Any .parseFrom (
56- msg .toByteString (),
57- protoMessageFactory .getDescriptorPool ().getExtensionRegistry ());
58-
59- return Optional .of (unpack (any ));
60- } catch (InvalidProtocolBufferException e ) {
61- return Optional .empty ();
62- }
83+ public static Builder newBuilder () {
84+ return new AutoBuilder_DynamicProto_Builder ()
85+ .setDynamicDescriptors (CelDescriptors .builder ().build ())
86+ .setProtoMessageFactory ((typeName ) -> null );
87+ }
88+
89+ DynamicProto (
90+ CelDescriptors dynamicDescriptors ,
91+ ProtoMessageFactory protoMessageFactory ) {
92+ ImmutableMap <String , Descriptor > messageTypeDescriptorMap =
93+ CelDescriptorUtil .descriptorCollectionToMap (dynamicDescriptors .messageTypeDescriptors ());
94+ ImmutableMap <String , Descriptor > filteredDescriptors =
95+ messageTypeDescriptorMap .entrySet ().stream ()
96+ .filter (e -> !WELL_KNOWN_DESCRIPTORS .containsKey (e .getKey ()))
97+ .collect (toImmutableMap (Entry ::getKey , Entry ::getValue ));
98+ this .dynamicDescriptors =
99+ ImmutableMap .<String , Descriptor >builder ()
100+ .putAll (WELL_KNOWN_DESCRIPTORS )
101+ .putAll (filteredDescriptors )
102+ .buildOrThrow ();
103+ this .dynamicExtensionDescriptors = checkNotNull (dynamicDescriptors .extensionDescriptors ());
104+ this .protoMessageFactory = checkNotNull (protoMessageFactory );
63105 }
64106
65107 /**
@@ -78,8 +120,7 @@ public Message unpack(Any any) throws InvalidProtocolBufferException {
78120 String .format ("malformed type URL: %s" , any .getTypeUrl ())));
79121
80122 Message .Builder builder =
81- protoMessageFactory
82- .newBuilder (messageTypeName )
123+ newMessageBuilder (messageTypeName )
83124 .orElseThrow (
84125 () ->
85126 new InvalidProtocolBufferException (
@@ -95,7 +136,7 @@ public Message unpack(Any any) throws InvalidProtocolBufferException {
95136 */
96137 public Message maybeAdaptDynamicMessage (DynamicMessage input ) {
97138 Optional <Message .Builder > maybeBuilder =
98- protoMessageFactory . newBuilder (input .getDescriptorForType ().getFullName ());
139+ newMessageBuilder (input .getDescriptorForType ().getFullName ());
99140 if (!maybeBuilder .isPresent () || maybeBuilder .get () instanceof DynamicMessage .Builder ) {
100141 // Just return the same input if:
101142 // 1. We didn't get a builder back because there's no descriptor (nothing we can do)
@@ -107,6 +148,61 @@ public Message maybeAdaptDynamicMessage(DynamicMessage input) {
107148 return merge (maybeBuilder .get (), input .toByteString ());
108149 }
109150
151+ /**
152+ * This method instantiates a builder for the given {@code typeName} assuming one is configured
153+ * within the descriptor set provided to the {@code DynamicProto} constructor.
154+ *
155+ * <p>When the {@code useLinkedTypes} flag is set, the {@code Message.Builder} returned will be
156+ * the concrete builder instance linked into the binary if it is present; otherwise, the result
157+ * will be a {@code DynamicMessageBuilder}.
158+ */
159+ public Optional <Message .Builder > newMessageBuilder (String typeName ) {
160+ if (!CelTypes .isWellKnownType (typeName )) {
161+ // Check if the message factory can produce a concrete message via custom type factory
162+ // first.
163+ Message .Builder builder = protoMessageFactory .newBuilder (typeName );
164+ if (builder != null ) {
165+ return Optional .of (builder );
166+ }
167+ }
168+
169+ Optional <Descriptor > descriptor = maybeGetDescriptor (typeName );
170+ if (!descriptor .isPresent ()) {
171+ return Optional .empty ();
172+ }
173+ // If the descriptor that's resolved does not match the descriptor instance in the message
174+ // factory, the call to fetch the prototype will return null, and a dynamic proto message
175+ // should be used as a fallback.
176+ Optional <Message > message =
177+ DefaultInstanceMessageFactory .getInstance ().getPrototype (descriptor .get ());
178+ if (message .isPresent ()) {
179+ return Optional .of (message .get ().toBuilder ());
180+ }
181+
182+ // Fallback to a dynamic proto instance.
183+ return Optional .of (DynamicMessage .newBuilder (descriptor .get ()));
184+ }
185+
186+ private Optional <Descriptor > maybeGetDescriptor (String typeName ) {
187+
188+ Descriptor descriptor = ProtoRegistryProvider .getTypeRegistry ().find (typeName );
189+ return Optional .ofNullable (descriptor != null ? descriptor : dynamicDescriptors .get (typeName ));
190+ }
191+
192+ /** Gets the corresponding field descriptor for an extension field on a message. */
193+ public Optional <FieldDescriptor > maybeGetExtensionDescriptor (
194+ Descriptor containingDescriptor , String fieldName ) {
195+
196+ String typeName = containingDescriptor .getFullName ();
197+ ImmutableCollection <FieldDescriptor > fieldDescriptors =
198+ dynamicExtensionDescriptors .get (typeName );
199+ if (fieldDescriptors .isEmpty ()) {
200+ return Optional .empty ();
201+ }
202+
203+ return fieldDescriptors .stream ().filter (d -> d .getFullName ().equals (fieldName )).findFirst ();
204+ }
205+
110206 /**
111207 * Merge takes in a Message builder and merges another message bytes into the builder. Some
112208 * example usages are:
@@ -118,9 +214,7 @@ public Message maybeAdaptDynamicMessage(DynamicMessage input) {
118214 */
119215 private Message merge (Message .Builder builder , ByteString inputBytes ) {
120216 try {
121- return builder
122- .mergeFrom (inputBytes , protoMessageFactory .getDescriptorPool ().getExtensionRegistry ())
123- .build ();
217+ return builder .mergeFrom (inputBytes , ProtoRegistryProvider .getExtensionRegistry ()).build ();
124218 } catch (InvalidProtocolBufferException e ) {
125219 throw new AssertionError ("Failed to merge input message into the message builder" , e );
126220 }
0 commit comments