// Copyright 2020-2024 Buf Technologies, Inc. // // 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 // // http://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 protoutil contains useful functions for interacting with descriptors. // For now these include only functions for efficiently converting descriptors // produced by the compiler to descriptor protos and functions for resolving // "features" (a core concept of Protobuf Editions). // // Despite the fact that descriptor protos are mutable, calling code should NOT // mutate any of the protos returned from this package. For efficiency, some // values returned from this package may reference internal state of a compiler // result, and mutating the proto could corrupt or invalidate parts of that // result. package protoutil import ( "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protodesc" "google.golang.org/protobuf/reflect/protoreflect" "google.golang.org/protobuf/types/descriptorpb" ) // DescriptorProtoWrapper is a protoreflect.Descriptor that wraps an // underlying descriptor proto. It provides the same interface as // Descriptor but with one extra operation, to efficiently query for // the underlying descriptor proto. // // Descriptors that implement this will also implement another method // whose specified return type is the concrete type returned by the // AsProto method. The name of this method varies by the type of this // descriptor: // // Descriptor Type Other Method Name // ---------------------+------------------------------------ // FileDescriptor | FileDescriptorProto() // MessageDescriptor | MessageDescriptorProto() // FieldDescriptor | FieldDescriptorProto() // OneofDescriptor | OneofDescriptorProto() // EnumDescriptor | EnumDescriptorProto() // EnumValueDescriptor | EnumValueDescriptorProto() // ServiceDescriptor | ServiceDescriptorProto() // MethodDescriptor | MethodDescriptorProto() // // For example, a DescriptorProtoWrapper that implements FileDescriptor // returns a *descriptorpb.FileDescriptorProto value from its AsProto // method and also provides a method with the following signature: // // FileDescriptorProto() *descriptorpb.FileDescriptorProto type DescriptorProtoWrapper interface { protoreflect.Descriptor // AsProto returns the underlying descriptor proto. The concrete // type of the proto message depends on the type of this // descriptor: // Descriptor Type Proto Message Type // ---------------------+------------------------------------ // FileDescriptor | *descriptorpb.FileDescriptorProto // MessageDescriptor | *descriptorpb.DescriptorProto // FieldDescriptor | *descriptorpb.FieldDescriptorProto // OneofDescriptor | *descriptorpb.OneofDescriptorProto // EnumDescriptor | *descriptorpb.EnumDescriptorProto // EnumValueDescriptor | *descriptorpb.EnumValueDescriptorProto // ServiceDescriptor | *descriptorpb.ServiceDescriptorProto // MethodDescriptor | *descriptorpb.MethodDescriptorProto AsProto() proto.Message } // ProtoFromDescriptor extracts a descriptor proto from the given "rich" // descriptor. For descriptors generated by the compiler, this is an // inexpensive and non-lossy operation. Descriptors from other sources // however may be expensive (to re-create a proto) and even lossy. func ProtoFromDescriptor(d protoreflect.Descriptor) proto.Message { switch d := d.(type) { case protoreflect.FileDescriptor: return ProtoFromFileDescriptor(d) case protoreflect.MessageDescriptor: return ProtoFromMessageDescriptor(d) case protoreflect.FieldDescriptor: return ProtoFromFieldDescriptor(d) case protoreflect.OneofDescriptor: return ProtoFromOneofDescriptor(d) case protoreflect.EnumDescriptor: return ProtoFromEnumDescriptor(d) case protoreflect.EnumValueDescriptor: return ProtoFromEnumValueDescriptor(d) case protoreflect.ServiceDescriptor: return ProtoFromServiceDescriptor(d) case protoreflect.MethodDescriptor: return ProtoFromMethodDescriptor(d) default: // WTF?? if res, ok := d.(DescriptorProtoWrapper); ok { return res.AsProto() } return nil } } // ProtoFromFileDescriptor extracts a descriptor proto from the given "rich" // descriptor. For file descriptors generated by the compiler, this is an // inexpensive and non-lossy operation. File descriptors from other sources // however may be expensive (to re-create a proto) and even lossy. func ProtoFromFileDescriptor(d protoreflect.FileDescriptor) *descriptorpb.FileDescriptorProto { if imp, ok := d.(protoreflect.FileImport); ok { d = imp.FileDescriptor } type canProto interface { FileDescriptorProto() *descriptorpb.FileDescriptorProto } if res, ok := d.(canProto); ok { return res.FileDescriptorProto() } if res, ok := d.(DescriptorProtoWrapper); ok { if fd, ok := res.AsProto().(*descriptorpb.FileDescriptorProto); ok { return fd } } return protodesc.ToFileDescriptorProto(d) } // ProtoFromMessageDescriptor extracts a descriptor proto from the given "rich" // descriptor. For message descriptors generated by the compiler, this is an // inexpensive and non-lossy operation. Message descriptors from other sources // however may be expensive (to re-create a proto) and even lossy. func ProtoFromMessageDescriptor(d protoreflect.MessageDescriptor) *descriptorpb.DescriptorProto { type canProto interface { MessageDescriptorProto() *descriptorpb.DescriptorProto } if res, ok := d.(canProto); ok { return res.MessageDescriptorProto() } if res, ok := d.(DescriptorProtoWrapper); ok { if md, ok := res.AsProto().(*descriptorpb.DescriptorProto); ok { return md } } return protodesc.ToDescriptorProto(d) } // ProtoFromFieldDescriptor extracts a descriptor proto from the given "rich" // descriptor. For field descriptors generated by the compiler, this is an // inexpensive and non-lossy operation. Field descriptors from other sources // however may be expensive (to re-create a proto) and even lossy. func ProtoFromFieldDescriptor(d protoreflect.FieldDescriptor) *descriptorpb.FieldDescriptorProto { type canProto interface { FieldDescriptorProto() *descriptorpb.FieldDescriptorProto } if res, ok := d.(canProto); ok { return res.FieldDescriptorProto() } if res, ok := d.(DescriptorProtoWrapper); ok { if fd, ok := res.AsProto().(*descriptorpb.FieldDescriptorProto); ok { return fd } } return protodesc.ToFieldDescriptorProto(d) } // ProtoFromOneofDescriptor extracts a descriptor proto from the given "rich" // descriptor. For oneof descriptors generated by the compiler, this is an // inexpensive and non-lossy operation. Oneof descriptors from other sources // however may be expensive (to re-create a proto) and even lossy. func ProtoFromOneofDescriptor(d protoreflect.OneofDescriptor) *descriptorpb.OneofDescriptorProto { type canProto interface { OneofDescriptorProto() *descriptorpb.OneofDescriptorProto } if res, ok := d.(canProto); ok { return res.OneofDescriptorProto() } if res, ok := d.(DescriptorProtoWrapper); ok { if ood, ok := res.AsProto().(*descriptorpb.OneofDescriptorProto); ok { return ood } } return protodesc.ToOneofDescriptorProto(d) } // ProtoFromEnumDescriptor extracts a descriptor proto from the given "rich" // descriptor. For enum descriptors generated by the compiler, this is an // inexpensive and non-lossy operation. Enum descriptors from other sources // however may be expensive (to re-create a proto) and even lossy. func ProtoFromEnumDescriptor(d protoreflect.EnumDescriptor) *descriptorpb.EnumDescriptorProto { type canProto interface { EnumDescriptorProto() *descriptorpb.EnumDescriptorProto } if res, ok := d.(canProto); ok { return res.EnumDescriptorProto() } if res, ok := d.(DescriptorProtoWrapper); ok { if ed, ok := res.AsProto().(*descriptorpb.EnumDescriptorProto); ok { return ed } } return protodesc.ToEnumDescriptorProto(d) } // ProtoFromEnumValueDescriptor extracts a descriptor proto from the given "rich" // descriptor. For enum value descriptors generated by the compiler, this is an // inexpensive and non-lossy operation. Enum value descriptors from other sources // however may be expensive (to re-create a proto) and even lossy. func ProtoFromEnumValueDescriptor(d protoreflect.EnumValueDescriptor) *descriptorpb.EnumValueDescriptorProto { type canProto interface { EnumValueDescriptorProto() *descriptorpb.EnumValueDescriptorProto } if res, ok := d.(canProto); ok { return res.EnumValueDescriptorProto() } if res, ok := d.(DescriptorProtoWrapper); ok { if ed, ok := res.AsProto().(*descriptorpb.EnumValueDescriptorProto); ok { return ed } } return protodesc.ToEnumValueDescriptorProto(d) } // ProtoFromServiceDescriptor extracts a descriptor proto from the given "rich" // descriptor. For service descriptors generated by the compiler, this is an // inexpensive and non-lossy operation. Service descriptors from other sources // however may be expensive (to re-create a proto) and even lossy. func ProtoFromServiceDescriptor(d protoreflect.ServiceDescriptor) *descriptorpb.ServiceDescriptorProto { type canProto interface { ServiceDescriptorProto() *descriptorpb.ServiceDescriptorProto } if res, ok := d.(canProto); ok { return res.ServiceDescriptorProto() } if res, ok := d.(DescriptorProtoWrapper); ok { if sd, ok := res.AsProto().(*descriptorpb.ServiceDescriptorProto); ok { return sd } } return protodesc.ToServiceDescriptorProto(d) } // ProtoFromMethodDescriptor extracts a descriptor proto from the given "rich" // descriptor. For method descriptors generated by the compiler, this is an // inexpensive and non-lossy operation. Method descriptors from other sources // however may be expensive (to re-create a proto) and even lossy. func ProtoFromMethodDescriptor(d protoreflect.MethodDescriptor) *descriptorpb.MethodDescriptorProto { type canProto interface { MethodDescriptorProto() *descriptorpb.MethodDescriptorProto } if res, ok := d.(canProto); ok { return res.MethodDescriptorProto() } if res, ok := d.(DescriptorProtoWrapper); ok { if md, ok := res.AsProto().(*descriptorpb.MethodDescriptorProto); ok { return md } } return protodesc.ToMethodDescriptorProto(d) }