blob: 01a6e9ea4f3a46c09aac24dfea23bb56c0b020c6 [file] [log] [blame]
khenaidoo0927c722021-12-15 16:49:32 -05001package desc
2
3import (
4 "errors"
5 "fmt"
6 "strings"
7
Abhay Kumar40252eb2025-10-13 13:25:53 +00008 "google.golang.org/protobuf/reflect/protodesc"
9 "google.golang.org/protobuf/reflect/protoreflect"
10 "google.golang.org/protobuf/reflect/protoregistry"
11 "google.golang.org/protobuf/types/descriptorpb"
khenaidoo0927c722021-12-15 16:49:32 -050012
13 "github.com/jhump/protoreflect/desc/internal"
14 intn "github.com/jhump/protoreflect/internal"
15)
16
17// CreateFileDescriptor instantiates a new file descriptor for the given descriptor proto.
18// The file's direct dependencies must be provided. If the given dependencies do not include
19// all of the file's dependencies or if the contents of the descriptors are internally
20// inconsistent (e.g. contain unresolvable symbols) then an error is returned.
Abhay Kumar40252eb2025-10-13 13:25:53 +000021func CreateFileDescriptor(fd *descriptorpb.FileDescriptorProto, deps ...*FileDescriptor) (*FileDescriptor, error) {
khenaidoo0927c722021-12-15 16:49:32 -050022 return createFileDescriptor(fd, deps, nil)
23}
24
Abhay Kumar40252eb2025-10-13 13:25:53 +000025type descResolver struct {
26 files []*FileDescriptor
27 importResolver *ImportResolver
28 fromPath string
29}
30
31func (r *descResolver) FindFileByPath(path string) (protoreflect.FileDescriptor, error) {
32 resolvedPath := r.importResolver.ResolveImport(r.fromPath, path)
33 d := r.findFileByPath(resolvedPath)
34 if d != nil {
35 return d, nil
36 }
37 if resolvedPath != path {
38 d := r.findFileByPath(path)
39 if d != nil {
40 return d, nil
41 }
42 }
43 return nil, protoregistry.NotFound
44}
45
46func (r *descResolver) findFileByPath(path string) protoreflect.FileDescriptor {
47 for _, fd := range r.files {
48 if fd.GetName() == path {
49 return fd.UnwrapFile()
50 }
51 }
52 return nil
53}
54
55func (r *descResolver) FindDescriptorByName(n protoreflect.FullName) (protoreflect.Descriptor, error) {
56 for _, fd := range r.files {
57 d := fd.FindSymbol(string(n))
58 if d != nil {
59 return d.(DescriptorWrapper).Unwrap(), nil
60 }
61 }
62 return nil, protoregistry.NotFound
63}
64
65func createFileDescriptor(fd *descriptorpb.FileDescriptorProto, deps []*FileDescriptor, r *ImportResolver) (*FileDescriptor, error) {
66 dr := &descResolver{files: deps, importResolver: r, fromPath: fd.GetName()}
67 d, err := protodesc.NewFile(fd, dr)
68 if err != nil {
69 return nil, err
70 }
71
72 // make sure cache has dependencies populated
73 cache := mapCache{}
74 for _, dep := range deps {
75 fd, err := dr.FindFileByPath(dep.GetName())
76 if err != nil {
77 return nil, err
78 }
79 cache.put(fd, dep)
80 }
81
82 return convertFile(d, fd, cache)
83}
84
85func convertFile(d protoreflect.FileDescriptor, fd *descriptorpb.FileDescriptorProto, cache descriptorCache) (*FileDescriptor, error) {
khenaidoo0927c722021-12-15 16:49:32 -050086 ret := &FileDescriptor{
Abhay Kumar40252eb2025-10-13 13:25:53 +000087 wrapped: d,
khenaidoo0927c722021-12-15 16:49:32 -050088 proto: fd,
89 symbols: map[string]Descriptor{},
90 fieldIndex: map[string]map[int32]*FieldDescriptor{},
91 }
Abhay Kumar40252eb2025-10-13 13:25:53 +000092 cache.put(d, ret)
khenaidoo0927c722021-12-15 16:49:32 -050093
94 // populate references to file descriptor dependencies
khenaidoo0927c722021-12-15 16:49:32 -050095 ret.deps = make([]*FileDescriptor, len(fd.GetDependency()))
Abhay Kumar40252eb2025-10-13 13:25:53 +000096 for i := 0; i < d.Imports().Len(); i++ {
97 f := d.Imports().Get(i).FileDescriptor
98 if c, err := wrapFile(f, cache); err != nil {
99 return nil, err
100 } else {
101 ret.deps[i] = c
khenaidoo0927c722021-12-15 16:49:32 -0500102 }
103 }
104 ret.publicDeps = make([]*FileDescriptor, len(fd.GetPublicDependency()))
105 for i, pd := range fd.GetPublicDependency() {
106 ret.publicDeps[i] = ret.deps[pd]
107 }
108 ret.weakDeps = make([]*FileDescriptor, len(fd.GetWeakDependency()))
109 for i, wd := range fd.GetWeakDependency() {
110 ret.weakDeps[i] = ret.deps[wd]
111 }
khenaidoo0927c722021-12-15 16:49:32 -0500112
113 // populate all tables of child descriptors
Abhay Kumar40252eb2025-10-13 13:25:53 +0000114 path := make([]int32, 1, 8)
115 path[0] = internal.File_messagesTag
116 for i := 0; i < d.Messages().Len(); i++ {
117 src := d.Messages().Get(i)
118 srcProto := fd.GetMessageType()[src.Index()]
119 md := createMessageDescriptor(ret, ret, src, srcProto, ret.symbols, cache, append(path, int32(i)))
120 ret.symbols[string(src.FullName())] = md
khenaidoo0927c722021-12-15 16:49:32 -0500121 ret.messages = append(ret.messages, md)
122 }
Abhay Kumar40252eb2025-10-13 13:25:53 +0000123 path[0] = internal.File_enumsTag
124 for i := 0; i < d.Enums().Len(); i++ {
125 src := d.Enums().Get(i)
126 srcProto := fd.GetEnumType()[src.Index()]
127 ed := createEnumDescriptor(ret, ret, src, srcProto, ret.symbols, cache, append(path, int32(i)))
128 ret.symbols[string(src.FullName())] = ed
khenaidoo0927c722021-12-15 16:49:32 -0500129 ret.enums = append(ret.enums, ed)
130 }
Abhay Kumar40252eb2025-10-13 13:25:53 +0000131 path[0] = internal.File_extensionsTag
132 for i := 0; i < d.Extensions().Len(); i++ {
133 src := d.Extensions().Get(i)
134 srcProto := fd.GetExtension()[src.Index()]
135 exd := createFieldDescriptor(ret, ret, src, srcProto, cache, append(path, int32(i)))
136 ret.symbols[string(src.FullName())] = exd
khenaidoo0927c722021-12-15 16:49:32 -0500137 ret.extensions = append(ret.extensions, exd)
138 }
Abhay Kumar40252eb2025-10-13 13:25:53 +0000139 path[0] = internal.File_servicesTag
140 for i := 0; i < d.Services().Len(); i++ {
141 src := d.Services().Get(i)
142 srcProto := fd.GetService()[src.Index()]
143 sd := createServiceDescriptor(ret, src, srcProto, ret.symbols, append(path, int32(i)))
144 ret.symbols[string(src.FullName())] = sd
khenaidoo0927c722021-12-15 16:49:32 -0500145 ret.services = append(ret.services, sd)
146 }
147
148 ret.sourceInfo = internal.CreateSourceInfoMap(fd)
149 ret.sourceInfoRecomputeFunc = ret.recomputeSourceInfo
150
151 // now we can resolve all type references and source code info
Abhay Kumar40252eb2025-10-13 13:25:53 +0000152 for _, md := range ret.messages {
153 if err := md.resolve(cache); err != nil {
khenaidoo0927c722021-12-15 16:49:32 -0500154 return nil, err
155 }
156 }
khenaidoo0927c722021-12-15 16:49:32 -0500157 path[0] = internal.File_extensionsTag
Abhay Kumar40252eb2025-10-13 13:25:53 +0000158 for _, exd := range ret.extensions {
159 if err := exd.resolve(cache); err != nil {
khenaidoo0927c722021-12-15 16:49:32 -0500160 return nil, err
161 }
162 }
163 path[0] = internal.File_servicesTag
Abhay Kumar40252eb2025-10-13 13:25:53 +0000164 for _, sd := range ret.services {
165 if err := sd.resolve(cache); err != nil {
khenaidoo0927c722021-12-15 16:49:32 -0500166 return nil, err
167 }
168 }
169
170 return ret, nil
171}
172
173// CreateFileDescriptors constructs a set of descriptors, one for each of the
174// given descriptor protos. The given set of descriptor protos must include all
175// transitive dependencies for every file.
Abhay Kumar40252eb2025-10-13 13:25:53 +0000176func CreateFileDescriptors(fds []*descriptorpb.FileDescriptorProto) (map[string]*FileDescriptor, error) {
khenaidoo0927c722021-12-15 16:49:32 -0500177 return createFileDescriptors(fds, nil)
178}
179
Abhay Kumar40252eb2025-10-13 13:25:53 +0000180func createFileDescriptors(fds []*descriptorpb.FileDescriptorProto, r *ImportResolver) (map[string]*FileDescriptor, error) {
khenaidoo0927c722021-12-15 16:49:32 -0500181 if len(fds) == 0 {
182 return nil, nil
183 }
Abhay Kumar40252eb2025-10-13 13:25:53 +0000184 files := map[string]*descriptorpb.FileDescriptorProto{}
khenaidoo0927c722021-12-15 16:49:32 -0500185 resolved := map[string]*FileDescriptor{}
186 var name string
187 for _, fd := range fds {
188 name = fd.GetName()
189 files[name] = fd
190 }
191 for _, fd := range fds {
192 _, err := createFromSet(fd.GetName(), r, nil, files, resolved)
193 if err != nil {
194 return nil, err
195 }
196 }
197 return resolved, nil
198}
199
200// ToFileDescriptorSet creates a FileDescriptorSet proto that contains all of the given
201// file descriptors and their transitive dependencies. The files are topologically sorted
202// so that a file will always appear after its dependencies.
Abhay Kumar40252eb2025-10-13 13:25:53 +0000203func ToFileDescriptorSet(fds ...*FileDescriptor) *descriptorpb.FileDescriptorSet {
204 var fdps []*descriptorpb.FileDescriptorProto
khenaidoo0927c722021-12-15 16:49:32 -0500205 addAllFiles(fds, &fdps, map[string]struct{}{})
Abhay Kumar40252eb2025-10-13 13:25:53 +0000206 return &descriptorpb.FileDescriptorSet{File: fdps}
khenaidoo0927c722021-12-15 16:49:32 -0500207}
208
Abhay Kumar40252eb2025-10-13 13:25:53 +0000209func addAllFiles(src []*FileDescriptor, results *[]*descriptorpb.FileDescriptorProto, seen map[string]struct{}) {
khenaidoo0927c722021-12-15 16:49:32 -0500210 for _, fd := range src {
211 if _, ok := seen[fd.GetName()]; ok {
212 continue
213 }
214 seen[fd.GetName()] = struct{}{}
215 addAllFiles(fd.GetDependencies(), results, seen)
216 *results = append(*results, fd.AsFileDescriptorProto())
217 }
218}
219
220// CreateFileDescriptorFromSet creates a descriptor from the given file descriptor set. The
221// set's *last* file will be the returned descriptor. The set's remaining files must comprise
222// the full set of transitive dependencies of that last file. This is the same format and
223// order used by protoc when emitting a FileDescriptorSet file with an invocation like so:
Abhay Kumar40252eb2025-10-13 13:25:53 +0000224//
225// protoc --descriptor_set_out=./test.protoset --include_imports -I. test.proto
226func CreateFileDescriptorFromSet(fds *descriptorpb.FileDescriptorSet) (*FileDescriptor, error) {
khenaidoo0927c722021-12-15 16:49:32 -0500227 return createFileDescriptorFromSet(fds, nil)
228}
229
Abhay Kumar40252eb2025-10-13 13:25:53 +0000230func createFileDescriptorFromSet(fds *descriptorpb.FileDescriptorSet, r *ImportResolver) (*FileDescriptor, error) {
khenaidoo0927c722021-12-15 16:49:32 -0500231 result, err := createFileDescriptorsFromSet(fds, r)
232 if err != nil {
233 return nil, err
234 }
235 files := fds.GetFile()
236 lastFilename := files[len(files)-1].GetName()
237 return result[lastFilename], nil
238}
239
240// CreateFileDescriptorsFromSet creates file descriptors from the given file descriptor set.
241// The returned map includes all files in the set, keyed b name. The set must include the
242// full set of transitive dependencies for all files therein or else a link error will occur
243// and be returned instead of the slice of descriptors. This is the same format used by
244// protoc when a FileDescriptorSet file with an invocation like so:
Abhay Kumar40252eb2025-10-13 13:25:53 +0000245//
246// protoc --descriptor_set_out=./test.protoset --include_imports -I. test.proto
247func CreateFileDescriptorsFromSet(fds *descriptorpb.FileDescriptorSet) (map[string]*FileDescriptor, error) {
khenaidoo0927c722021-12-15 16:49:32 -0500248 return createFileDescriptorsFromSet(fds, nil)
249}
250
Abhay Kumar40252eb2025-10-13 13:25:53 +0000251func createFileDescriptorsFromSet(fds *descriptorpb.FileDescriptorSet, r *ImportResolver) (map[string]*FileDescriptor, error) {
khenaidoo0927c722021-12-15 16:49:32 -0500252 files := fds.GetFile()
253 if len(files) == 0 {
254 return nil, errors.New("file descriptor set is empty")
255 }
256 return createFileDescriptors(files, r)
257}
258
259// createFromSet creates a descriptor for the given filename. It recursively
260// creates descriptors for the given file's dependencies.
Abhay Kumar40252eb2025-10-13 13:25:53 +0000261func createFromSet(filename string, r *ImportResolver, seen []string, files map[string]*descriptorpb.FileDescriptorProto, resolved map[string]*FileDescriptor) (*FileDescriptor, error) {
khenaidoo0927c722021-12-15 16:49:32 -0500262 for _, s := range seen {
263 if filename == s {
264 return nil, fmt.Errorf("cycle in imports: %s", strings.Join(append(seen, filename), " -> "))
265 }
266 }
267 seen = append(seen, filename)
268
269 if d, ok := resolved[filename]; ok {
270 return d, nil
271 }
272 fdp := files[filename]
273 if fdp == nil {
274 return nil, intn.ErrNoSuchFile(filename)
275 }
276 deps := make([]*FileDescriptor, len(fdp.GetDependency()))
277 for i, depName := range fdp.GetDependency() {
278 resolvedDep := r.ResolveImport(filename, depName)
279 dep, err := createFromSet(resolvedDep, r, seen, files, resolved)
280 if _, ok := err.(intn.ErrNoSuchFile); ok && resolvedDep != depName {
281 dep, err = createFromSet(depName, r, seen, files, resolved)
282 }
283 if err != nil {
284 return nil, err
285 }
286 deps[i] = dep
287 }
288 d, err := createFileDescriptor(fdp, deps, r)
289 if err != nil {
290 return nil, err
291 }
292 resolved[filename] = d
293 return d, nil
294}