1package builder
2
3import (
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/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.
21type 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.
96type 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.
113func (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.
122type Comments struct {
123	LeadingDetachedComments []string
124	LeadingComment          string
125	TrailingComment         string
126}
127
128func setComments(c *Comments, loc *dpb.SourceCodeInfo_Location) {
129	c.LeadingDetachedComments = loc.GetLeadingDetachedComments()
130	c.LeadingComment = loc.GetLeadingComments()
131	c.TrailingComment = loc.GetTrailingComments()
132}
133
134func 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).
239type baseBuilder struct {
240	name     string
241	parent   Builder
242	comments Comments
243}
244
245func baseBuilderWithName(name string) baseBuilder {
246	if err := checkName(name); err != nil {
247		panic(err)
248	}
249	return baseBuilder{name: name}
250}
251
252func 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.
268func (b *baseBuilder) GetName() string {
269	return b.name
270}
271
272func (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.
300func (b *baseBuilder) GetParent() Builder {
301	return b.parent
302}
303
304func (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.
314func (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.
327func (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.
333func 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
344func 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.
371func 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.
379func 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.
387func 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.
401func 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}
413