github.com/phpstudyer/protoreflect@v1.7.2/desc/builder/builder.go (about)

     1  package builder
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"reflect"
     7  
     8  	"github.com/golang/protobuf/proto"
     9  	dpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
    10  
    11  	"github.com/phpstudyer/protoreflect/desc"
    12  	"github.com/phpstudyer/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 *dpb.SourceCodeInfo_Location) {
   129  	c.LeadingDetachedComments = loc.GetLeadingDetachedComments()
   130  	c.LeadingComment = loc.GetLeadingComments()
   131  	c.TrailingComment = loc.GetTrailingComments()
   132  }
   133  
   134  func addCommentsTo(sourceInfo *dpb.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, &dpb.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  }