github.com/jhump/protoreflect@v1.16.0/desc/builder/file.go (about) 1 package builder 2 3 import ( 4 "fmt" 5 "sort" 6 "strings" 7 "sync/atomic" 8 9 "google.golang.org/protobuf/proto" 10 "google.golang.org/protobuf/types/descriptorpb" 11 12 "github.com/jhump/protoreflect/desc" 13 "github.com/jhump/protoreflect/desc/internal" 14 "github.com/jhump/protoreflect/dynamic" 15 ) 16 17 var uniqueFileCounter uint64 18 19 func uniqueFileName() string { 20 i := atomic.AddUint64(&uniqueFileCounter, 1) 21 return fmt.Sprintf("{generated-file-%04x}.proto", i) 22 } 23 24 func makeUnique(name string, existingNames map[string]struct{}) string { 25 i := 1 26 n := name 27 for { 28 if _, ok := existingNames[n]; !ok { 29 return n 30 } 31 n = fmt.Sprintf("%s(%d)", name, i) 32 i++ 33 } 34 } 35 36 // FileBuilder is a builder used to construct a desc.FileDescriptor. This is the 37 // root of the hierarchy. All other descriptors belong to a file, and thus all 38 // other builders also belong to a file. 39 // 40 // If a builder is *not* associated with a file, the resulting descriptor will 41 // be associated with a synthesized file that contains only the built descriptor 42 // and its ancestors. This means that such descriptors will have no associated 43 // package name. 44 // 45 // To create a new FileBuilder, use NewFile. 46 type FileBuilder struct { 47 name string 48 49 // IsProto3 indicates that the file's syntax is "proto3". If this is 50 // false the file either uses syntax "proto2" (when Edition is zero) 51 // or uses editions (when Edition is non-zero). 52 IsProto3 bool 53 54 // Edition indicates which edition this file uses. It may only be 55 // set when IsProto3 is false. 56 Edition descriptorpb.Edition 57 58 Package string 59 Options *descriptorpb.FileOptions 60 61 comments Comments 62 SyntaxComments Comments 63 PackageComments Comments 64 65 messages []*MessageBuilder 66 extensions []*FieldBuilder 67 enums []*EnumBuilder 68 services []*ServiceBuilder 69 symbols map[string]Builder 70 71 origExts *dynamic.ExtensionRegistry 72 explicitDeps map[*FileBuilder]struct{} 73 explicitImports map[*desc.FileDescriptor]struct{} 74 } 75 76 // NewFile creates a new FileBuilder for a file with the given name. The 77 // name can be blank, which indicates a unique name should be generated for it. 78 func NewFile(name string) *FileBuilder { 79 return &FileBuilder{ 80 name: name, 81 symbols: map[string]Builder{}, 82 } 83 } 84 85 // FromFile returns a FileBuilder that is effectively a copy of the given 86 // descriptor. Note that builders do not retain full source code info, even if 87 // the given descriptor included it. Instead, comments are extracted from the 88 // given descriptor's source info (if present) and, when built, the resulting 89 // descriptor will have just the comment info (no location information). 90 func FromFile(fd *desc.FileDescriptor) (*FileBuilder, error) { 91 fb := NewFile(fd.GetName()) 92 fb.IsProto3 = fd.IsProto3() 93 fb.Edition = fd.Edition() 94 fb.Package = fd.GetPackage() 95 fb.Options = fd.GetFileOptions() 96 setComments(&fb.comments, fd.GetSourceInfo()) 97 98 // find syntax and package comments, too 99 for _, loc := range fd.AsFileDescriptorProto().GetSourceCodeInfo().GetLocation() { 100 if len(loc.Path) == 1 { 101 if loc.Path[0] == internal.File_syntaxTag || loc.Path[0] == internal.File_editionTag { 102 setComments(&fb.SyntaxComments, loc) 103 } else if loc.Path[0] == internal.File_packageTag { 104 setComments(&fb.PackageComments, loc) 105 } 106 } 107 } 108 109 // add imports explicitly 110 for _, dep := range fd.GetDependencies() { 111 fb.AddImportedDependency(dep) 112 fb.addExtensionsFromImport(dep) 113 } 114 115 localMessages := map[*desc.MessageDescriptor]*MessageBuilder{} 116 localEnums := map[*desc.EnumDescriptor]*EnumBuilder{} 117 118 for _, md := range fd.GetMessageTypes() { 119 if mb, err := fromMessage(md, localMessages, localEnums); err != nil { 120 return nil, err 121 } else if err := fb.TryAddMessage(mb); err != nil { 122 return nil, err 123 } 124 } 125 for _, ed := range fd.GetEnumTypes() { 126 if eb, err := fromEnum(ed, localEnums); err != nil { 127 return nil, err 128 } else if err := fb.TryAddEnum(eb); err != nil { 129 return nil, err 130 } 131 } 132 for _, exd := range fd.GetExtensions() { 133 if exb, err := fromField(exd); err != nil { 134 return nil, err 135 } else if err := fb.TryAddExtension(exb); err != nil { 136 return nil, err 137 } 138 } 139 for _, sd := range fd.GetServices() { 140 if sb, err := fromService(sd); err != nil { 141 return nil, err 142 } else if err := fb.TryAddService(sb); err != nil { 143 return nil, err 144 } 145 } 146 147 // we've converted everything, so now we update all foreign type references 148 // to be local type references if possible 149 for _, mb := range fb.messages { 150 updateLocalRefsInMessage(mb, localMessages, localEnums) 151 } 152 for _, exb := range fb.extensions { 153 updateLocalRefsInField(exb, localMessages, localEnums) 154 } 155 for _, sb := range fb.services { 156 for _, mtb := range sb.methods { 157 updateLocalRefsInRpcType(mtb.ReqType, localMessages) 158 updateLocalRefsInRpcType(mtb.RespType, localMessages) 159 } 160 } 161 162 return fb, nil 163 } 164 165 func updateLocalRefsInMessage(mb *MessageBuilder, localMessages map[*desc.MessageDescriptor]*MessageBuilder, localEnums map[*desc.EnumDescriptor]*EnumBuilder) { 166 for _, b := range mb.fieldsAndOneOfs { 167 if flb, ok := b.(*FieldBuilder); ok { 168 updateLocalRefsInField(flb, localMessages, localEnums) 169 } else { 170 oob := b.(*OneOfBuilder) 171 for _, flb := range oob.choices { 172 updateLocalRefsInField(flb, localMessages, localEnums) 173 } 174 } 175 } 176 for _, nmb := range mb.nestedMessages { 177 updateLocalRefsInMessage(nmb, localMessages, localEnums) 178 } 179 for _, exb := range mb.nestedExtensions { 180 updateLocalRefsInField(exb, localMessages, localEnums) 181 } 182 } 183 184 func updateLocalRefsInField(flb *FieldBuilder, localMessages map[*desc.MessageDescriptor]*MessageBuilder, localEnums map[*desc.EnumDescriptor]*EnumBuilder) { 185 if flb.fieldType.foreignMsgType != nil { 186 if mb, ok := localMessages[flb.fieldType.foreignMsgType]; ok { 187 flb.fieldType.foreignMsgType = nil 188 flb.fieldType.localMsgType = mb 189 } 190 } 191 if flb.fieldType.foreignEnumType != nil { 192 if eb, ok := localEnums[flb.fieldType.foreignEnumType]; ok { 193 flb.fieldType.foreignEnumType = nil 194 flb.fieldType.localEnumType = eb 195 } 196 } 197 if flb.foreignExtendee != nil { 198 if mb, ok := localMessages[flb.foreignExtendee]; ok { 199 flb.foreignExtendee = nil 200 flb.localExtendee = mb 201 } 202 } 203 if flb.msgType != nil { 204 updateLocalRefsInMessage(flb.msgType, localMessages, localEnums) 205 } 206 } 207 208 func updateLocalRefsInRpcType(rpcType *RpcType, localMessages map[*desc.MessageDescriptor]*MessageBuilder) { 209 if rpcType.foreignType != nil { 210 if mb, ok := localMessages[rpcType.foreignType]; ok { 211 rpcType.foreignType = nil 212 rpcType.localType = mb 213 } 214 } 215 } 216 217 // GetName returns the name of the file. It may include relative path 218 // information, too. 219 func (fb *FileBuilder) GetName() string { 220 return fb.name 221 } 222 223 // SetName changes this file's name, returning the file builder for method 224 // chaining. 225 func (fb *FileBuilder) SetName(newName string) *FileBuilder { 226 fb.name = newName 227 return fb 228 } 229 230 // TrySetName changes this file's name. It always returns nil since renaming 231 // a file cannot fail. (It is specified to return error to satisfy the Builder 232 // interface.) 233 func (fb *FileBuilder) TrySetName(newName string) error { 234 fb.name = newName 235 return nil 236 } 237 238 // GetParent always returns nil since files are the roots of builder 239 // hierarchies. 240 func (fb *FileBuilder) GetParent() Builder { 241 return nil 242 } 243 244 func (fb *FileBuilder) setParent(parent Builder) { 245 if parent != nil { 246 panic("files cannot have parent elements") 247 } 248 } 249 250 // GetComments returns comments associated with the file itself and not any 251 // particular element therein. (Note that such a comment will not be rendered by 252 // the protoprint package.) 253 func (fb *FileBuilder) GetComments() *Comments { 254 return &fb.comments 255 } 256 257 // SetComments sets the comments associated with the file itself, not any 258 // particular element therein. (Note that such a comment will not be rendered by 259 // the protoprint package.) This method returns the file, for method chaining. 260 func (fb *FileBuilder) SetComments(c Comments) *FileBuilder { 261 fb.comments = c 262 return fb 263 } 264 265 // SetSyntaxComments sets the comments associated with the syntax declaration 266 // element (which, if present, is required to be the first element in a proto 267 // file). This method returns the file, for method chaining. 268 func (fb *FileBuilder) SetSyntaxComments(c Comments) *FileBuilder { 269 fb.SyntaxComments = c 270 return fb 271 } 272 273 // SetPackageComments sets the comments associated with the package declaration 274 // element. (This comment will not be rendered if the file's declared package is 275 // empty.) This method returns the file, for method chaining. 276 func (fb *FileBuilder) SetPackageComments(c Comments) *FileBuilder { 277 fb.PackageComments = c 278 return fb 279 } 280 281 // GetFile implements the Builder interface and always returns this file. 282 func (fb *FileBuilder) GetFile() *FileBuilder { 283 return fb 284 } 285 286 // GetChildren returns builders for all nested elements, including all top-level 287 // messages, enums, extensions, and services. 288 func (fb *FileBuilder) GetChildren() []Builder { 289 var ch []Builder 290 for _, mb := range fb.messages { 291 ch = append(ch, mb) 292 } 293 for _, exb := range fb.extensions { 294 ch = append(ch, exb) 295 } 296 for _, eb := range fb.enums { 297 ch = append(ch, eb) 298 } 299 for _, sb := range fb.services { 300 ch = append(ch, sb) 301 } 302 return ch 303 } 304 305 func (fb *FileBuilder) findChild(name string) Builder { 306 return fb.symbols[name] 307 } 308 309 func (fb *FileBuilder) removeChild(b Builder) { 310 if p, ok := b.GetParent().(*FileBuilder); !ok || p != fb { 311 return 312 } 313 314 switch b.(type) { 315 case *MessageBuilder: 316 fb.messages = deleteBuilder(b.GetName(), fb.messages).([]*MessageBuilder) 317 case *FieldBuilder: 318 fb.extensions = deleteBuilder(b.GetName(), fb.extensions).([]*FieldBuilder) 319 case *EnumBuilder: 320 fb.enums = deleteBuilder(b.GetName(), fb.enums).([]*EnumBuilder) 321 case *ServiceBuilder: 322 fb.services = deleteBuilder(b.GetName(), fb.services).([]*ServiceBuilder) 323 } 324 delete(fb.symbols, b.GetName()) 325 b.setParent(nil) 326 } 327 328 func (fb *FileBuilder) renamedChild(b Builder, oldName string) error { 329 if p, ok := b.GetParent().(*FileBuilder); !ok || p != fb { 330 return nil 331 } 332 333 if err := fb.addSymbol(b); err != nil { 334 return err 335 } 336 delete(fb.symbols, oldName) 337 return nil 338 } 339 340 func (fb *FileBuilder) addSymbol(b Builder) error { 341 if ex, ok := fb.symbols[b.GetName()]; ok { 342 return fmt.Errorf("file %q already contains element (%T) named %q", fb.GetName(), ex, b.GetName()) 343 } 344 fb.symbols[b.GetName()] = b 345 return nil 346 } 347 348 func (fb *FileBuilder) findFullyQualifiedElement(fqn string) Builder { 349 if fb.Package != "" { 350 if !strings.HasPrefix(fqn, fb.Package+".") { 351 return nil 352 } 353 fqn = fqn[len(fb.Package)+1:] 354 } 355 names := strings.Split(fqn, ".") 356 var b Builder = fb 357 for b != nil && len(names) > 0 { 358 b = b.findChild(names[0]) 359 names = names[1:] 360 } 361 return b 362 } 363 364 // GetMessage returns the top-level message with the given name. If no such 365 // message exists in the file, nil is returned. 366 func (fb *FileBuilder) GetMessage(name string) *MessageBuilder { 367 b := fb.symbols[name] 368 if mb, ok := b.(*MessageBuilder); ok { 369 return mb 370 } else { 371 return nil 372 } 373 } 374 375 // RemoveMessage removes the top-level message with the given name. If no such 376 // message exists in the file, this is a no-op. This returns the file builder, 377 // for method chaining. 378 func (fb *FileBuilder) RemoveMessage(name string) *FileBuilder { 379 fb.TryRemoveMessage(name) 380 return fb 381 } 382 383 // TryRemoveMessage removes the top-level message with the given name and 384 // returns false if the file has no such message. 385 func (fb *FileBuilder) TryRemoveMessage(name string) bool { 386 b := fb.symbols[name] 387 if mb, ok := b.(*MessageBuilder); ok { 388 fb.removeChild(mb) 389 return true 390 } 391 return false 392 } 393 394 // AddMessage adds the given message to this file. If an error prevents the 395 // message from being added, this method panics. This returns the file builder, 396 // for method chaining. 397 func (fb *FileBuilder) AddMessage(mb *MessageBuilder) *FileBuilder { 398 if err := fb.TryAddMessage(mb); err != nil { 399 panic(err) 400 } 401 return fb 402 } 403 404 // TryAddMessage adds the given message to this file, returning any error that 405 // prevents the message from being added (such as a name collision with another 406 // element already added to the file). 407 func (fb *FileBuilder) TryAddMessage(mb *MessageBuilder) error { 408 if err := fb.addSymbol(mb); err != nil { 409 return err 410 } 411 Unlink(mb) 412 mb.setParent(fb) 413 fb.messages = append(fb.messages, mb) 414 return nil 415 } 416 417 // GetExtension returns the top-level extension with the given name. If no such 418 // extension exists in the file, nil is returned. 419 func (fb *FileBuilder) GetExtension(name string) *FieldBuilder { 420 b := fb.symbols[name] 421 if exb, ok := b.(*FieldBuilder); ok { 422 return exb 423 } else { 424 return nil 425 } 426 } 427 428 // RemoveExtension removes the top-level extension with the given name. If no 429 // such extension exists in the file, this is a no-op. This returns the file 430 // builder, for method chaining. 431 func (fb *FileBuilder) RemoveExtension(name string) *FileBuilder { 432 fb.TryRemoveExtension(name) 433 return fb 434 } 435 436 // TryRemoveExtension removes the top-level extension with the given name and 437 // returns false if the file has no such extension. 438 func (fb *FileBuilder) TryRemoveExtension(name string) bool { 439 b := fb.symbols[name] 440 if exb, ok := b.(*FieldBuilder); ok { 441 fb.removeChild(exb) 442 return true 443 } 444 return false 445 } 446 447 // AddExtension adds the given extension to this file. If an error prevents the 448 // extension from being added, this method panics. This returns the file 449 // builder, for method chaining. 450 func (fb *FileBuilder) AddExtension(exb *FieldBuilder) *FileBuilder { 451 if err := fb.TryAddExtension(exb); err != nil { 452 panic(err) 453 } 454 return fb 455 } 456 457 // TryAddExtension adds the given extension to this file, returning any error 458 // that prevents the extension from being added (such as a name collision with 459 // another element already added to the file). 460 func (fb *FileBuilder) TryAddExtension(exb *FieldBuilder) error { 461 if !exb.IsExtension() { 462 return fmt.Errorf("field %s is not an extension", exb.GetName()) 463 } 464 if err := fb.addSymbol(exb); err != nil { 465 return err 466 } 467 Unlink(exb) 468 exb.setParent(fb) 469 fb.extensions = append(fb.extensions, exb) 470 return nil 471 } 472 473 // GetEnum returns the top-level enum with the given name. If no such enum 474 // exists in the file, nil is returned. 475 func (fb *FileBuilder) GetEnum(name string) *EnumBuilder { 476 b := fb.symbols[name] 477 if eb, ok := b.(*EnumBuilder); ok { 478 return eb 479 } else { 480 return nil 481 } 482 } 483 484 // RemoveEnum removes the top-level enum with the given name. If no such enum 485 // exists in the file, this is a no-op. This returns the file builder, for 486 // method chaining. 487 func (fb *FileBuilder) RemoveEnum(name string) *FileBuilder { 488 fb.TryRemoveEnum(name) 489 return fb 490 } 491 492 // TryRemoveEnum removes the top-level enum with the given name and returns 493 // false if the file has no such enum. 494 func (fb *FileBuilder) TryRemoveEnum(name string) bool { 495 b := fb.symbols[name] 496 if eb, ok := b.(*EnumBuilder); ok { 497 fb.removeChild(eb) 498 return true 499 } 500 return false 501 } 502 503 // AddEnum adds the given enum to this file. If an error prevents the enum from 504 // being added, this method panics. This returns the file builder, for method 505 // chaining. 506 func (fb *FileBuilder) AddEnum(eb *EnumBuilder) *FileBuilder { 507 if err := fb.TryAddEnum(eb); err != nil { 508 panic(err) 509 } 510 return fb 511 } 512 513 // TryAddEnum adds the given enum to this file, returning any error that 514 // prevents the enum from being added (such as a name collision with another 515 // element already added to the file). 516 func (fb *FileBuilder) TryAddEnum(eb *EnumBuilder) error { 517 if err := fb.addSymbol(eb); err != nil { 518 return err 519 } 520 Unlink(eb) 521 eb.setParent(fb) 522 fb.enums = append(fb.enums, eb) 523 return nil 524 } 525 526 // GetService returns the top-level service with the given name. If no such 527 // service exists in the file, nil is returned. 528 func (fb *FileBuilder) GetService(name string) *ServiceBuilder { 529 b := fb.symbols[name] 530 if sb, ok := b.(*ServiceBuilder); ok { 531 return sb 532 } else { 533 return nil 534 } 535 } 536 537 // RemoveService removes the top-level service with the given name. If no such 538 // service exists in the file, this is a no-op. This returns the file builder, 539 // for method chaining. 540 func (fb *FileBuilder) RemoveService(name string) *FileBuilder { 541 fb.TryRemoveService(name) 542 return fb 543 } 544 545 // TryRemoveService removes the top-level service with the given name and 546 // returns false if the file has no such service. 547 func (fb *FileBuilder) TryRemoveService(name string) bool { 548 b := fb.symbols[name] 549 if sb, ok := b.(*ServiceBuilder); ok { 550 fb.removeChild(sb) 551 return true 552 } 553 return false 554 } 555 556 // AddService adds the given service to this file. If an error prevents the 557 // service from being added, this method panics. This returns the file builder, 558 // for method chaining. 559 func (fb *FileBuilder) AddService(sb *ServiceBuilder) *FileBuilder { 560 if err := fb.TryAddService(sb); err != nil { 561 panic(err) 562 } 563 return fb 564 } 565 566 // TryAddService adds the given service to this file, returning any error that 567 // prevents the service from being added (such as a name collision with another 568 // element already added to the file). 569 func (fb *FileBuilder) TryAddService(sb *ServiceBuilder) error { 570 if err := fb.addSymbol(sb); err != nil { 571 return err 572 } 573 Unlink(sb) 574 sb.setParent(fb) 575 fb.services = append(fb.services, sb) 576 return nil 577 } 578 579 func (fb *FileBuilder) addExtensionsFromImport(dep *desc.FileDescriptor) { 580 if fb.origExts == nil { 581 fb.origExts = &dynamic.ExtensionRegistry{} 582 } 583 fb.origExts.AddExtensionsFromFile(dep) 584 // we also add any extensions from this dependency's "public" imports since 585 // they are also visible to the importing file 586 for _, publicDep := range dep.GetPublicDependencies() { 587 fb.addExtensionsFromImport(publicDep) 588 } 589 } 590 591 // AddDependency adds the given file as an explicit import. Normally, 592 // dependencies can be inferred during the build process by finding the files 593 // for all referenced types (such as message and enum types used in this file). 594 // However, this does not work for custom options, which must be known in order 595 // to be interpretable. And they aren't known unless an explicit import is added 596 // for the file that contains the custom options. 597 // 598 // Knowledge of custom options can also be provided by using BuilderOptions with 599 // an ExtensionRegistry, when building the file. 600 func (fb *FileBuilder) AddDependency(dep *FileBuilder) *FileBuilder { 601 if fb.explicitDeps == nil { 602 fb.explicitDeps = map[*FileBuilder]struct{}{} 603 } 604 fb.explicitDeps[dep] = struct{}{} 605 return fb 606 } 607 608 // AddImportedDependency adds the given file as an explicit import. Normally, 609 // dependencies can be inferred during the build process by finding the files 610 // for all referenced types (such as message and enum types used in this file). 611 // However, this does not work for custom options, which must be known in order 612 // to be interpretable. And they aren't known unless an explicit import is added 613 // for the file that contains the custom options. 614 // 615 // Knowledge of custom options can also be provided by using BuilderOptions with 616 // an ExtensionRegistry, when building the file. 617 func (fb *FileBuilder) AddImportedDependency(dep *desc.FileDescriptor) *FileBuilder { 618 if fb.explicitImports == nil { 619 fb.explicitImports = map[*desc.FileDescriptor]struct{}{} 620 } 621 fb.explicitImports[dep] = struct{}{} 622 return fb 623 } 624 625 // PruneUnusedDependencies removes all imports that are not actually used in the 626 // file. Note that this undoes any calls to AddDependency or AddImportedDependency 627 // which means that custom options may be missing from the resulting built 628 // descriptor unless BuilderOptions are used that include an ExtensionRegistry with 629 // knowledge of all custom options. 630 // 631 // When FromFile is used to create a FileBuilder from an existing descriptor, all 632 // imports are usually preserved in any subsequent built descriptor. But this method 633 // can be used to remove imports from the original file, like if mutations are made 634 // to the file's contents such that not all imports are needed anymore. When FromFile 635 // is used, any custom options present in the original descriptor will be correctly 636 // retained. If the file is mutated such that new custom options are added to the file, 637 // they may be missing unless AddImportedDependency is called after pruning OR 638 // BuilderOptions are used that include an ExtensionRegistry with knowledge of the 639 // new custom options. 640 func (fb *FileBuilder) PruneUnusedDependencies() *FileBuilder { 641 fb.explicitImports = nil 642 fb.explicitDeps = nil 643 return fb 644 } 645 646 // SetOptions sets the file options for this file and returns the file, for 647 // method chaining. 648 func (fb *FileBuilder) SetOptions(options *descriptorpb.FileOptions) *FileBuilder { 649 fb.Options = options 650 return fb 651 } 652 653 // SetPackageName sets the name of the package for this file and returns the 654 // file, for method chaining. 655 func (fb *FileBuilder) SetPackageName(pkg string) *FileBuilder { 656 fb.Package = pkg 657 return fb 658 } 659 660 // SetProto3 sets whether this file is declared to use "proto3" syntax or not 661 // and returns the file, for method chaining. If this is called with a value 662 // of false, then "proto2" syntax is assumed. To instead set the file to use 663 // editions, call SetEdition. 664 func (fb *FileBuilder) SetProto3(isProto3 bool) *FileBuilder { 665 fb.IsProto3 = isProto3 666 fb.Edition = 0 667 return fb 668 } 669 670 // SetEdition sets the edition that this file uses and returns the file, for 671 // method chaining. This supports the use of the EDITION_PROTO2 and EDITION_PROTO3 672 // values to actually set the value as "proto2" or "proto3" syntax respectively. 673 // If a value less than EDITION_PROTO2 is provided, the invalid value is ignored 674 // and the file is instead set to use "proto2" syntax. 675 func (fb *FileBuilder) SetEdition(edition descriptorpb.Edition) *FileBuilder { 676 if edition <= descriptorpb.Edition_EDITION_PROTO2 { 677 fb.IsProto3 = false 678 fb.Edition = 0 679 } else if edition == descriptorpb.Edition_EDITION_PROTO3 { 680 fb.IsProto3 = true 681 fb.Edition = 0 682 } else { 683 fb.IsProto3 = false 684 fb.Edition = edition 685 } 686 return fb 687 } 688 689 func (fb *FileBuilder) buildProto(deps []*desc.FileDescriptor) (*descriptorpb.FileDescriptorProto, error) { 690 name := fb.name 691 if name == "" { 692 name = uniqueFileName() 693 } 694 var syntax *string 695 var edition *descriptorpb.Edition 696 if fb.IsProto3 { 697 syntax = proto.String("proto3") 698 } else if fb.Edition > 0 { 699 if fb.Edition < descriptorpb.Edition_EDITION_2023 || 700 fb.Edition >= descriptorpb.Edition_EDITION_MAX || 701 descriptorpb.Edition_name[int32(fb.Edition)] == "" || 702 strings.HasSuffix(fb.Edition.String(), "_TEST_ONLY") { 703 // Not a valid edition! 704 return nil, fmt.Errorf("builder contains unknown or invalid edition: %v", fb.Edition) 705 } 706 syntax = proto.String("editions") 707 edition = fb.Edition.Enum() 708 } 709 var pkg *string 710 if fb.Package != "" { 711 pkg = proto.String(fb.Package) 712 } 713 714 path := make([]int32, 0, 10) 715 sourceInfo := descriptorpb.SourceCodeInfo{} 716 addCommentsTo(&sourceInfo, path, &fb.comments) 717 addCommentsTo(&sourceInfo, append(path, internal.File_syntaxTag), &fb.SyntaxComments) 718 addCommentsTo(&sourceInfo, append(path, internal.File_packageTag), &fb.PackageComments) 719 720 imports := make([]string, 0, len(deps)) 721 for _, dep := range deps { 722 imports = append(imports, dep.GetName()) 723 } 724 sort.Strings(imports) 725 726 messages := make([]*descriptorpb.DescriptorProto, 0, len(fb.messages)) 727 for _, mb := range fb.messages { 728 path := append(path, internal.File_messagesTag, int32(len(messages))) 729 if md, err := mb.buildProto(path, &sourceInfo); err != nil { 730 return nil, err 731 } else { 732 messages = append(messages, md) 733 } 734 } 735 736 enums := make([]*descriptorpb.EnumDescriptorProto, 0, len(fb.enums)) 737 for _, eb := range fb.enums { 738 path := append(path, internal.File_enumsTag, int32(len(enums))) 739 if ed, err := eb.buildProto(path, &sourceInfo); err != nil { 740 return nil, err 741 } else { 742 enums = append(enums, ed) 743 } 744 } 745 746 extensions := make([]*descriptorpb.FieldDescriptorProto, 0, len(fb.extensions)) 747 for _, exb := range fb.extensions { 748 path := append(path, internal.File_extensionsTag, int32(len(extensions))) 749 if exd, err := exb.buildProto(path, &sourceInfo, isExtendeeMessageSet(exb)); err != nil { 750 return nil, err 751 } else { 752 extensions = append(extensions, exd) 753 } 754 } 755 756 services := make([]*descriptorpb.ServiceDescriptorProto, 0, len(fb.services)) 757 for _, sb := range fb.services { 758 path := append(path, internal.File_servicesTag, int32(len(services))) 759 if sd, err := sb.buildProto(path, &sourceInfo); err != nil { 760 return nil, err 761 } else { 762 services = append(services, sd) 763 } 764 } 765 766 return &descriptorpb.FileDescriptorProto{ 767 Name: proto.String(name), 768 Package: pkg, 769 Dependency: imports, 770 Options: fb.Options, 771 Syntax: syntax, 772 Edition: edition, 773 MessageType: messages, 774 EnumType: enums, 775 Extension: extensions, 776 Service: services, 777 SourceCodeInfo: &sourceInfo, 778 }, nil 779 } 780 781 func isExtendeeMessageSet(flb *FieldBuilder) bool { 782 if flb.localExtendee != nil { 783 return flb.localExtendee.Options.GetMessageSetWireFormat() 784 } 785 return flb.foreignExtendee.GetMessageOptions().GetMessageSetWireFormat() 786 } 787 788 // Build constructs a file descriptor based on the contents of this file 789 // builder. If there are any problems constructing the descriptor, including 790 // resolving symbols referenced by the builder or failing to meet certain 791 // validation rules, an error is returned. 792 func (fb *FileBuilder) Build() (*desc.FileDescriptor, error) { 793 fd, err := fb.BuildDescriptor() 794 if err != nil { 795 return nil, err 796 } 797 return fd.(*desc.FileDescriptor), nil 798 } 799 800 // BuildDescriptor constructs a file descriptor based on the contents of this 801 // file builder. Most usages will prefer Build() instead, whose return type is a 802 // concrete descriptor type. This method is present to satisfy the Builder 803 // interface. 804 func (fb *FileBuilder) BuildDescriptor() (desc.Descriptor, error) { 805 return doBuild(fb, BuilderOptions{}) 806 }