github.com/jhump/protoreflect@v1.16.0/desc/convert.go (about) 1 package desc 2 3 import ( 4 "errors" 5 "fmt" 6 "strings" 7 8 "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" 12 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. 21 func CreateFileDescriptor(fd *descriptorpb.FileDescriptorProto, deps ...*FileDescriptor) (*FileDescriptor, error) { 22 return createFileDescriptor(fd, deps, nil) 23 } 24 25 type descResolver struct { 26 files []*FileDescriptor 27 importResolver *ImportResolver 28 fromPath string 29 } 30 31 func (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 46 func (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 55 func (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 65 func 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 85 func convertFile(d protoreflect.FileDescriptor, fd *descriptorpb.FileDescriptorProto, cache descriptorCache) (*FileDescriptor, error) { 86 ret := &FileDescriptor{ 87 wrapped: d, 88 proto: fd, 89 symbols: map[string]Descriptor{}, 90 fieldIndex: map[string]map[int32]*FieldDescriptor{}, 91 } 92 cache.put(d, ret) 93 94 // populate references to file descriptor dependencies 95 ret.deps = make([]*FileDescriptor, len(fd.GetDependency())) 96 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 102 } 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 } 112 113 // populate all tables of child descriptors 114 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 121 ret.messages = append(ret.messages, md) 122 } 123 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 129 ret.enums = append(ret.enums, ed) 130 } 131 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 137 ret.extensions = append(ret.extensions, exd) 138 } 139 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 145 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 152 for _, md := range ret.messages { 153 if err := md.resolve(cache); err != nil { 154 return nil, err 155 } 156 } 157 path[0] = internal.File_extensionsTag 158 for _, exd := range ret.extensions { 159 if err := exd.resolve(cache); err != nil { 160 return nil, err 161 } 162 } 163 path[0] = internal.File_servicesTag 164 for _, sd := range ret.services { 165 if err := sd.resolve(cache); err != nil { 166 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. 176 func CreateFileDescriptors(fds []*descriptorpb.FileDescriptorProto) (map[string]*FileDescriptor, error) { 177 return createFileDescriptors(fds, nil) 178 } 179 180 func createFileDescriptors(fds []*descriptorpb.FileDescriptorProto, r *ImportResolver) (map[string]*FileDescriptor, error) { 181 if len(fds) == 0 { 182 return nil, nil 183 } 184 files := map[string]*descriptorpb.FileDescriptorProto{} 185 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. 203 func ToFileDescriptorSet(fds ...*FileDescriptor) *descriptorpb.FileDescriptorSet { 204 var fdps []*descriptorpb.FileDescriptorProto 205 addAllFiles(fds, &fdps, map[string]struct{}{}) 206 return &descriptorpb.FileDescriptorSet{File: fdps} 207 } 208 209 func addAllFiles(src []*FileDescriptor, results *[]*descriptorpb.FileDescriptorProto, seen map[string]struct{}) { 210 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: 224 // 225 // protoc --descriptor_set_out=./test.protoset --include_imports -I. test.proto 226 func CreateFileDescriptorFromSet(fds *descriptorpb.FileDescriptorSet) (*FileDescriptor, error) { 227 return createFileDescriptorFromSet(fds, nil) 228 } 229 230 func createFileDescriptorFromSet(fds *descriptorpb.FileDescriptorSet, r *ImportResolver) (*FileDescriptor, error) { 231 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: 245 // 246 // protoc --descriptor_set_out=./test.protoset --include_imports -I. test.proto 247 func CreateFileDescriptorsFromSet(fds *descriptorpb.FileDescriptorSet) (map[string]*FileDescriptor, error) { 248 return createFileDescriptorsFromSet(fds, nil) 249 } 250 251 func createFileDescriptorsFromSet(fds *descriptorpb.FileDescriptorSet, r *ImportResolver) (map[string]*FileDescriptor, error) { 252 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. 261 func createFromSet(filename string, r *ImportResolver, seen []string, files map[string]*descriptorpb.FileDescriptorProto, resolved map[string]*FileDescriptor) (*FileDescriptor, error) { 262 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 }