github.com/jhump/protoreflect@v1.16.0/desc/builder/builder.go (about) 1 package builder 2 3 import ( 4 "bytes" 5 "fmt" 6 "reflect" 7 8 "google.golang.org/protobuf/proto" 9 "google.golang.org/protobuf/types/descriptorpb" 10 11 "github.com/jhump/protoreflect/desc" 12 "github.com/jhump/protoreflect/dynamic" 13 ) 14 15 // Builder is the core interface implemented by all descriptor builders. It 16 // exposes some basic information about the descriptor hierarchy's structure. 17 // 18 // All Builders also have a Build() method, but that is not part of this 19 // interface because its return type varies with the type of descriptor that 20 // is built. 21 type Builder interface { 22 // GetName returns this element's name. The name returned is a simple name, 23 // not a qualified name. 24 GetName() string 25 26 // TrySetName attempts to set this element's name. If the rename cannot 27 // proceed (e.g. this element's parent already has an element with that 28 // name) then an error is returned. 29 // 30 // All builders also have a method named SetName that panics on error and 31 // returns the builder itself (for method chaining). But that isn't defined 32 // on this interface because its return type varies with the type of the 33 // descriptor builder. 34 TrySetName(newName string) error 35 36 // GetParent returns this element's parent element. It returns nil if there 37 // is no parent element. File builders never have parent elements. 38 GetParent() Builder 39 40 // GetFile returns this element's file. This returns nil if the element has 41 // not yet been assigned to a file. 42 GetFile() *FileBuilder 43 44 // GetChildren returns all of this element's child elements. A file will 45 // return all of its top-level messages, enums, extensions, and services. A 46 // message will return all of its fields as well as nested messages, enums, 47 // and extensions, etc. Children will generally be grouped by type and, 48 // within a group, in the same order as the children were added to their 49 // parent. 50 GetChildren() []Builder 51 52 // GetComments returns the comments for this element. If the element has no 53 // comments then the returned struct will have all empty fields. Comments 54 // can be added to the element by setting fields of the returned struct. 55 // 56 // All builders also have a SetComments method that modifies the comments 57 // and returns the builder itself (for method chaining). But that isn't 58 // defined on this interface because its return type varies with the type of 59 // the descriptor builder. 60 GetComments() *Comments 61 62 // BuildDescriptor is a generic form of the Build method. Its declared 63 // return type is general so that it can be included in this interface and 64 // implemented by all concrete builder types. 65 // 66 // If the builder includes references to custom options, only those known to 67 // the calling program (i.e. linked in and registered with the proto 68 // package) can be correctly interpreted. If the builder references other 69 // custom options, use BuilderOptions.Build instead. 70 BuildDescriptor() (desc.Descriptor, error) 71 72 // findChild returns the child builder with the given name or nil if this 73 // builder has no such child. 74 findChild(string) Builder 75 76 // removeChild removes the given child builder from this element. If the 77 // given element is not a child, it should do nothing. 78 // 79 // NOTE: It is this method's responsibility to call child.setParent(nil) 80 // after removing references to the child from this element. 81 removeChild(Builder) 82 83 // renamedChild updates references by-name references to the given child and 84 // validates its name. The given string is the child's old name. If the 85 // rename can proceed, no error should be returned and any by-name 86 // references to the old name should be removed. 87 renamedChild(Builder, string) error 88 89 // setParent simply updates the up-link (from child to parent) so that the 90 // this element's parent is up-to-date. It does NOT try to remove references 91 // from the parent to this child. (See doc for removeChild(Builder)). 92 setParent(Builder) 93 } 94 95 // BuilderOptions includes additional options to use when building descriptors. 96 type BuilderOptions struct { 97 // This registry provides definitions for custom options. If a builder 98 // refers to an option that is not known by this registry, it can still be 99 // interpreted if the extension is "known" to the calling program (i.e. 100 // linked in and registered with the proto package). 101 Extensions *dynamic.ExtensionRegistry 102 103 // If this option is true, then all options referred to in builders must 104 // be interpreted. That means that if an option is present that is neither 105 // recognized by Extenions nor known to the calling program, trying to build 106 // the descriptor will fail. 107 RequireInterpretedOptions bool 108 } 109 110 // Build processes the given builder into a descriptor using these options. 111 // Using the builder's Build() or BuildDescriptor() method is equivalent to 112 // building with a zero-value BuilderOptions. 113 func (opts BuilderOptions) Build(b Builder) (desc.Descriptor, error) { 114 return doBuild(b, opts) 115 } 116 117 // Comments represents the various comments that might be associated with a 118 // descriptor. These are equivalent to the various kinds of comments found in a 119 // *dpb.SourceCodeInfo_Location struct that protoc associates with elements in 120 // the parsed proto source file. This can be used to create or preserve comments 121 // (including documentation) for elements. 122 type Comments struct { 123 LeadingDetachedComments []string 124 LeadingComment string 125 TrailingComment string 126 } 127 128 func setComments(c *Comments, loc *descriptorpb.SourceCodeInfo_Location) { 129 c.LeadingDetachedComments = loc.GetLeadingDetachedComments() 130 c.LeadingComment = loc.GetLeadingComments() 131 c.TrailingComment = loc.GetTrailingComments() 132 } 133 134 func addCommentsTo(sourceInfo *descriptorpb.SourceCodeInfo, path []int32, c *Comments) { 135 var lead, trail *string 136 if c.LeadingComment != "" { 137 lead = proto.String(c.LeadingComment) 138 } 139 if c.TrailingComment != "" { 140 trail = proto.String(c.TrailingComment) 141 } 142 143 // we need defensive copies of the slices 144 p := make([]int32, len(path)) 145 copy(p, path) 146 147 var detached []string 148 if len(c.LeadingDetachedComments) > 0 { 149 detached = make([]string, len(c.LeadingDetachedComments)) 150 copy(detached, c.LeadingDetachedComments) 151 } 152 153 sourceInfo.Location = append(sourceInfo.Location, &descriptorpb.SourceCodeInfo_Location{ 154 LeadingDetachedComments: detached, 155 LeadingComments: lead, 156 TrailingComments: trail, 157 Path: p, 158 Span: []int32{0, 0, 0}, 159 }) 160 } 161 162 /* NB: There are a few flows that need to maintain strong referential integrity 163 * and perform symbol and/or number uniqueness checks. The way these flows are 164 * implemented is described below. The actions generally involve two different 165 * components: making local changes to an element and making corresponding 166 * and/or related changes in a parent element. Below describes the separation of 167 * responsibilities between the two. 168 * 169 * 170 * RENAMING AN ELEMENT 171 * 172 * Renaming an element is initiated via Builder.TrySetName. Implementations 173 * should do the following: 174 * 1. Validate the new name using any local constraints and naming rules. 175 * 2. If there are child elements whose names should be kept in sync in some 176 * way, rename them. 177 * 3. Invoke baseBuilder.setName. This changes this element's name and then 178 * invokes Builder.renamedChild(child, oldName) to update any by-name 179 * references from the parent to the child. 180 * 4. If step #3 failed, any other element names that were changed to keep 181 * them in sync (from step #2) should be reverted. 182 * 183 * A key part of this flow is how parents react to child elements being renamed. 184 * This is done in Builder.renamedChild. Implementations should do the 185 * following: 186 * 1. Validate the name using any local constraints. (Often there are no new 187 * constraints and any checks already done by Builder.TrySetName should 188 * suffice.) 189 * 2. If the parent element should be renamed to keep it in sync with the 190 * child's name, rename it. 191 * 3. Register references to the element using the new name. A possible cause 192 * of error in this step is a uniqueness constraint, e.g. the element's new 193 * name collides with a sibling element's name. 194 * 4. If step #3 failed and this element name was changed to keep it in sync 195 * (from step #2), it should be reverted. 196 * 5. Finally, remove references to the element for the old name. This step 197 * should always succeed. 198 * 199 * Changing the tag number for a non-extension field has a similar flow since it 200 * is also checked for uniqueness, to make sure the new tag number does not 201 * conflict with another existing field. 202 * 203 * Note that TrySetName and renamedChild methods both can return an error, which 204 * should indicate why the element could not be renamed (e.g. name is invalid, 205 * new name conflicts with existing sibling names, etc). 206 * 207 * 208 * MOVING/REMOVING AN ELEMENT 209 * 210 * When an element is added to a new parent but is already assigned to a parent, 211 * it is "moved" to the new parent. This is done via "Add" methods on the parent 212 * entity (for example, MessageBuilder.AddField). Implementations of such a 213 * method should do the following: 214 * 1. Register references to the element. A possible cause of failure in this 215 * step is that the new element collides with an existing child. 216 * 2. Use the Unlink function to remove the element from any existing parent. 217 * 3. Use Builder.setParent to link the child to its parent. 218 * 219 * The Unlink function, which removes an element from its parent if it has a 220 * parent, relies on the parent's Builder.removeChild method. Implementations of 221 * that method should do the following: 222 * 1. Check that the element is actually a child. If not, return without doing 223 * anything. 224 * 2. Remove all references to the child. 225 * 3. Finally, this method must call Builder.setParent(nil) to clear the 226 * element's up-link so it no longer refers to the old parent. 227 * 228 * The "Add" methods typically have a "Try" form which can return an error. This 229 * could happen if the new child is not legal to add (including, for example, 230 * that its name collides with an existing child element). 231 * 232 * The removeChild and setParent methods, on the other hand, cannot return an 233 * error and thus must always succeed. 234 */ 235 236 // baseBuilder is a struct that can be embedded into each Builder implementation 237 // and provides a kernel of builder-wiring support (to reduce boiler-plate in 238 // each implementation). 239 type baseBuilder struct { 240 name string 241 parent Builder 242 comments Comments 243 } 244 245 func baseBuilderWithName(name string) baseBuilder { 246 if err := checkName(name); err != nil { 247 panic(err) 248 } 249 return baseBuilder{name: name} 250 } 251 252 func checkName(name string) error { 253 for i, ch := range name { 254 if i == 0 { 255 if ch != '_' && (ch < 'a' || ch > 'z') && (ch < 'A' || ch > 'Z') { 256 return fmt.Errorf("name %q is invalid; It must start with an underscore or letter", name) 257 } 258 } else { 259 if ch != '_' && (ch < '0' || ch > '9') && (ch < 'a' || ch > 'z') && (ch < 'A' || ch > 'Z') { 260 return fmt.Errorf("name %q contains invalid character %q; only underscores, letters, and numbers are allowed", name, string(ch)) 261 } 262 } 263 } 264 return nil 265 } 266 267 // GetName returns the name of the element that will be built by this builder. 268 func (b *baseBuilder) GetName() string { 269 return b.name 270 } 271 272 func (b *baseBuilder) setName(fullBuilder Builder, newName string) error { 273 if newName == b.name { 274 return nil // no change 275 } 276 if err := checkName(newName); err != nil { 277 return err 278 } 279 oldName := b.name 280 b.name = newName 281 if b.parent != nil { 282 if err := b.parent.renamedChild(fullBuilder, oldName); err != nil { 283 // revert the rename on error 284 b.name = oldName 285 return err 286 } 287 } 288 return nil 289 } 290 291 // GetParent returns the parent builder to which this builder has been added. If 292 // the builder has not been added to another, this returns nil. 293 // 294 // The parents of message builders will be file builders or other message 295 // builders. Same for the parents of extension field builders and enum builders. 296 // One-of builders and non-extension field builders will return a message 297 // builder. Method builders' parents are service builders; enum value builders' 298 // parents are enum builders. Finally, service builders will always return file 299 // builders as their parent. 300 func (b *baseBuilder) GetParent() Builder { 301 return b.parent 302 } 303 304 func (b *baseBuilder) setParent(newParent Builder) { 305 b.parent = newParent 306 } 307 308 // GetFile returns the file to which this builder is assigned. This examines the 309 // builder's parent, and its parent, and so on, until it reaches a file builder 310 // or nil. 311 // 312 // If the builder is not assigned to a file (even transitively), this method 313 // returns nil. 314 func (b *baseBuilder) GetFile() *FileBuilder { 315 p := b.parent 316 for p != nil { 317 if fb, ok := p.(*FileBuilder); ok { 318 return fb 319 } 320 p = p.GetParent() 321 } 322 return nil 323 } 324 325 // GetComments returns comments associated with the element that will be built 326 // by this builder. 327 func (b *baseBuilder) GetComments() *Comments { 328 return &b.comments 329 } 330 331 // doBuild is a helper for implementing the Build() method that each builder 332 // exposes. It is used for all builders except for the root FileBuilder type. 333 func doBuild(b Builder, opts BuilderOptions) (desc.Descriptor, error) { 334 fd, err := newResolver(opts).resolveElement(b, nil) 335 if err != nil { 336 return nil, err 337 } 338 if _, ok := b.(*FileBuilder); ok { 339 return fd, nil 340 } 341 return fd.FindSymbol(GetFullyQualifiedName(b)), nil 342 } 343 344 func getFullyQualifiedName(b Builder, buf *bytes.Buffer) { 345 if fb, ok := b.(*FileBuilder); ok { 346 buf.WriteString(fb.Package) 347 } else if b != nil { 348 p := b.GetParent() 349 if _, ok := p.(*FieldBuilder); ok { 350 // field can be the parent of a message (if it's 351 // the field's map entry or group type), but its 352 // name is not part of message's fqn; so skip 353 p = p.GetParent() 354 } 355 if _, ok := p.(*OneOfBuilder); ok { 356 // one-of can be the parent of a field, but its 357 // name is not part of field's fqn; so skip 358 p = p.GetParent() 359 } 360 getFullyQualifiedName(p, buf) 361 if buf.Len() > 0 { 362 buf.WriteByte('.') 363 } 364 buf.WriteString(b.GetName()) 365 } 366 } 367 368 // GetFullyQualifiedName returns the given builder's fully-qualified name. This 369 // name is based on the parent elements the builder may be linked to, which 370 // provide context like package and (optional) enclosing message names. 371 func GetFullyQualifiedName(b Builder) string { 372 var buf bytes.Buffer 373 getFullyQualifiedName(b, &buf) 374 return buf.String() 375 } 376 377 // Unlink removes the given builder from its parent. The parent will no longer 378 // refer to the builder and vice versa. 379 func Unlink(b Builder) { 380 if p := b.GetParent(); p != nil { 381 p.removeChild(b) 382 } 383 } 384 385 // getRoot navigates up the hierarchy to find the root builder for the given 386 // instance. 387 func getRoot(b Builder) Builder { 388 for { 389 p := b.GetParent() 390 if p == nil { 391 return b 392 } 393 b = p 394 } 395 } 396 397 // deleteBuilder will delete a descriptor builder with the given name from the 398 // given slice. The slice's elements can be any builder type. The parameter has 399 // type interface{} so it can accept []*MessageBuilder or []*FieldBuilder, for 400 // example. It returns a value of the same type with the named builder omitted. 401 func deleteBuilder(name string, descs interface{}) interface{} { 402 rv := reflect.ValueOf(descs) 403 for i := 0; i < rv.Len(); i++ { 404 c := rv.Index(i).Interface().(Builder) 405 if c.GetName() == name { 406 head := rv.Slice(0, i) 407 tail := rv.Slice(i+1, rv.Len()) 408 return reflect.AppendSlice(head, tail).Interface() 409 } 410 } 411 return descs 412 }