1package builder
2
3import (
4	"fmt"
5	"sort"
6
7	"github.com/golang/protobuf/proto"
8	dpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
9
10	"github.com/jhump/protoreflect/desc"
11	"github.com/jhump/protoreflect/desc/internal"
12)
13
14// EnumBuilder is a builder used to construct a desc.EnumDescriptor.
15//
16// To create a new EnumBuilder, use NewEnum.
17type EnumBuilder struct {
18	baseBuilder
19
20	Options        *dpb.EnumOptions
21	ReservedRanges []*dpb.EnumDescriptorProto_EnumReservedRange
22	ReservedNames  []string
23
24	values  []*EnumValueBuilder
25	symbols map[string]*EnumValueBuilder
26}
27
28// NewEnum creates a new EnumBuilder for an enum with the given name. Since the
29// new message has no parent element, it also has no package name (e.g. it is in
30// the unnamed package, until it is assigned to a file builder that defines a
31// package name).
32func NewEnum(name string) *EnumBuilder {
33	return &EnumBuilder{
34		baseBuilder: baseBuilderWithName(name),
35		symbols:     map[string]*EnumValueBuilder{},
36	}
37}
38
39// FromEnum returns an EnumBuilder that is effectively a copy of the given
40// descriptor.
41//
42// Note that it is not just the given enum that is copied but its entire file.
43// So the caller can get the parent element of the returned builder and the
44// result would be a builder that is effectively a copy of the enum descriptor's
45// parent.
46//
47// This means that enum builders created from descriptors do not need to be
48// explicitly assigned to a file in order to preserve the original enum's
49// package name.
50func FromEnum(ed *desc.EnumDescriptor) (*EnumBuilder, error) {
51	if fb, err := FromFile(ed.GetFile()); err != nil {
52		return nil, err
53	} else if eb, ok := fb.findFullyQualifiedElement(ed.GetFullyQualifiedName()).(*EnumBuilder); ok {
54		return eb, nil
55	} else {
56		return nil, fmt.Errorf("could not find enum %s after converting file %q to builder", ed.GetFullyQualifiedName(), ed.GetFile().GetName())
57	}
58}
59
60func fromEnum(ed *desc.EnumDescriptor, localEnums map[*desc.EnumDescriptor]*EnumBuilder) (*EnumBuilder, error) {
61	eb := NewEnum(ed.GetName())
62	eb.Options = ed.GetEnumOptions()
63	eb.ReservedRanges = ed.AsEnumDescriptorProto().GetReservedRange()
64	eb.ReservedNames = ed.AsEnumDescriptorProto().GetReservedName()
65	setComments(&eb.comments, ed.GetSourceInfo())
66
67	localEnums[ed] = eb
68
69	for _, evd := range ed.GetValues() {
70		if evb, err := fromEnumValue(evd); err != nil {
71			return nil, err
72		} else if err := eb.TryAddValue(evb); err != nil {
73			return nil, err
74		}
75	}
76
77	return eb, nil
78}
79
80// SetName changes this enum's name, returning the enum builder for method
81// chaining. If the given new name is not valid (e.g. TrySetName would have
82// returned an error) then this method will panic.
83func (eb *EnumBuilder) SetName(newName string) *EnumBuilder {
84	if err := eb.TrySetName(newName); err != nil {
85		panic(err)
86	}
87	return eb
88}
89
90// TrySetName changes this enum's name. It will return an error if the given new
91// name is not a valid protobuf identifier or if the parent builder already has
92// an element with the given name.
93func (eb *EnumBuilder) TrySetName(newName string) error {
94	return eb.baseBuilder.setName(eb, newName)
95}
96
97// SetComments sets the comments associated with the enum. This method returns
98// the enum builder, for method chaining.
99func (eb *EnumBuilder) SetComments(c Comments) *EnumBuilder {
100	eb.comments = c
101	return eb
102}
103
104// GetChildren returns any builders assigned to this enum builder. These will be
105// the enum's values.
106func (eb *EnumBuilder) GetChildren() []Builder {
107	var ch []Builder
108	for _, evb := range eb.values {
109		ch = append(ch, evb)
110	}
111	return ch
112}
113
114func (eb *EnumBuilder) findChild(name string) Builder {
115	return eb.symbols[name]
116}
117
118func (eb *EnumBuilder) removeChild(b Builder) {
119	if p, ok := b.GetParent().(*EnumBuilder); !ok || p != eb {
120		return
121	}
122	eb.values = deleteBuilder(b.GetName(), eb.values).([]*EnumValueBuilder)
123	delete(eb.symbols, b.GetName())
124	b.setParent(nil)
125}
126
127func (eb *EnumBuilder) renamedChild(b Builder, oldName string) error {
128	if p, ok := b.GetParent().(*EnumBuilder); !ok || p != eb {
129		return nil
130	}
131
132	if err := eb.addSymbol(b.(*EnumValueBuilder)); err != nil {
133		return err
134	}
135	delete(eb.symbols, oldName)
136	return nil
137}
138
139func (eb *EnumBuilder) addSymbol(b *EnumValueBuilder) error {
140	if _, ok := eb.symbols[b.GetName()]; ok {
141		return fmt.Errorf("enum %s already contains value named %q", GetFullyQualifiedName(eb), b.GetName())
142	}
143	eb.symbols[b.GetName()] = b
144	return nil
145}
146
147// SetOptions sets the enum options for this enum and returns the enum, for
148// method chaining.
149func (eb *EnumBuilder) SetOptions(options *dpb.EnumOptions) *EnumBuilder {
150	eb.Options = options
151	return eb
152}
153
154// GetValue returns the enum value with the given name. If no such value exists
155// in the enum, nil is returned.
156func (eb *EnumBuilder) GetValue(name string) *EnumValueBuilder {
157	return eb.symbols[name]
158}
159
160// RemoveValue removes the enum value with the given name. If no such value
161// exists in the enum, this is a no-op. This returns the enum builder, for
162// method chaining.
163func (eb *EnumBuilder) RemoveValue(name string) *EnumBuilder {
164	eb.TryRemoveValue(name)
165	return eb
166}
167
168// TryRemoveValue removes the enum value with the given name and returns false
169// if the enum has no such value.
170func (eb *EnumBuilder) TryRemoveValue(name string) bool {
171	if evb, ok := eb.symbols[name]; ok {
172		eb.removeChild(evb)
173		return true
174	}
175	return false
176}
177
178// AddValue adds the given enum value to this enum. If an error prevents the
179// value from being added, this method panics. This returns the enum builder,
180// for method chaining.
181func (eb *EnumBuilder) AddValue(evb *EnumValueBuilder) *EnumBuilder {
182	if err := eb.TryAddValue(evb); err != nil {
183		panic(err)
184	}
185	return eb
186}
187
188// TryAddValue adds the given enum value to this enum, returning any error that
189// prevents the value from being added (such as a name collision with another
190// value already added to the enum).
191func (eb *EnumBuilder) TryAddValue(evb *EnumValueBuilder) error {
192	if err := eb.addSymbol(evb); err != nil {
193		return err
194	}
195	Unlink(evb)
196	evb.setParent(eb)
197	eb.values = append(eb.values, evb)
198	return nil
199}
200
201// AddReservedRange adds the given reserved range to this message. The range is
202// inclusive of both the start and end, just like defining a range in proto IDL
203// source. This returns the message, for method chaining.
204func (eb *EnumBuilder) AddReservedRange(start, end int32) *EnumBuilder {
205	rr := &dpb.EnumDescriptorProto_EnumReservedRange{
206		Start: proto.Int32(start),
207		End:   proto.Int32(end),
208	}
209	eb.ReservedRanges = append(eb.ReservedRanges, rr)
210	return eb
211}
212
213// SetReservedRanges replaces all of this enum's reserved ranges with the
214// given slice of ranges. This returns the enum, for method chaining.
215func (eb *EnumBuilder) SetReservedRanges(ranges []*dpb.EnumDescriptorProto_EnumReservedRange) *EnumBuilder {
216	eb.ReservedRanges = ranges
217	return eb
218}
219
220// AddReservedName adds the given name to the list of reserved value names for
221// this enum. This returns the enum, for method chaining.
222func (eb *EnumBuilder) AddReservedName(name string) *EnumBuilder {
223	eb.ReservedNames = append(eb.ReservedNames, name)
224	return eb
225}
226
227// SetReservedNames replaces all of this enum's reserved value names with the
228// given slice of names. This returns the enum, for method chaining.
229func (eb *EnumBuilder) SetReservedNames(names []string) *EnumBuilder {
230	eb.ReservedNames = names
231	return eb
232}
233
234func (eb *EnumBuilder) buildProto(path []int32, sourceInfo *dpb.SourceCodeInfo) (*dpb.EnumDescriptorProto, error) {
235	addCommentsTo(sourceInfo, path, &eb.comments)
236
237	var needNumbersAssigned []*dpb.EnumValueDescriptorProto
238	values := make([]*dpb.EnumValueDescriptorProto, 0, len(eb.values))
239	for _, evb := range eb.values {
240		path := append(path, internal.Enum_valuesTag, int32(len(values)))
241		evp, err := evb.buildProto(path, sourceInfo)
242		if err != nil {
243			return nil, err
244		}
245		values = append(values, evp)
246		if !evb.numberSet {
247			needNumbersAssigned = append(needNumbersAssigned, evp)
248		}
249	}
250
251	if len(needNumbersAssigned) > 0 {
252		tags := make([]int, len(values)-len(needNumbersAssigned))
253		for i, ev := range values {
254			tag := ev.GetNumber()
255			if tag != 0 {
256				tags[i] = int(tag)
257			}
258		}
259		sort.Ints(tags)
260		t := 0
261		ti := sort.Search(len(tags), func(i int) bool {
262			return tags[i] >= 0
263		})
264		if ti < len(tags) {
265			tags = tags[ti:]
266		}
267		for len(needNumbersAssigned) > 0 {
268			for len(tags) > 0 && t == tags[0] {
269				t++
270				tags = tags[1:]
271			}
272			needNumbersAssigned[0].Number = proto.Int32(int32(t))
273			needNumbersAssigned = needNumbersAssigned[1:]
274			t++
275		}
276	}
277
278	return &dpb.EnumDescriptorProto{
279		Name:          proto.String(eb.name),
280		Options:       eb.Options,
281		Value:         values,
282		ReservedRange: eb.ReservedRanges,
283		ReservedName:  eb.ReservedNames,
284	}, nil
285}
286
287// Build constructs an enum descriptor based on the contents of this enum
288// builder. If there are any problems constructing the descriptor, including
289// resolving symbols referenced by the builder or failing to meet certain
290// validation rules, an error is returned.
291func (eb *EnumBuilder) Build() (*desc.EnumDescriptor, error) {
292	ed, err := eb.BuildDescriptor()
293	if err != nil {
294		return nil, err
295	}
296	return ed.(*desc.EnumDescriptor), nil
297}
298
299// BuildDescriptor constructs an enum descriptor based on the contents of this
300// enum builder. Most usages will prefer Build() instead, whose return type
301// is a concrete descriptor type. This method is present to satisfy the Builder
302// interface.
303func (eb *EnumBuilder) BuildDescriptor() (desc.Descriptor, error) {
304	return doBuild(eb, BuilderOptions{})
305}
306
307// EnumValueBuilder is a builder used to construct a desc.EnumValueDescriptor.
308// A enum value builder *must* be added to an enum before calling its Build()
309// method.
310//
311// To create a new EnumValueBuilder, use NewEnumValue.
312type EnumValueBuilder struct {
313	baseBuilder
314
315	number    int32
316	numberSet bool
317	Options   *dpb.EnumValueOptions
318}
319
320// NewEnumValue creates a new EnumValueBuilder for an enum value with the given
321// name. The return value's numeric value will not be set, which means it will
322// be auto-assigned when the descriptor is built, unless explicitly set with a
323// call to SetNumber.
324func NewEnumValue(name string) *EnumValueBuilder {
325	return &EnumValueBuilder{baseBuilder: baseBuilderWithName(name)}
326}
327
328// FromEnumValue returns an EnumValueBuilder that is effectively a copy of the
329// given descriptor.
330//
331// Note that it is not just the given enum value that is copied but its entire
332// file. So the caller can get the parent element of the returned builder and
333// the result would be a builder that is effectively a copy of the enum value
334// descriptor's parent enum.
335//
336// This means that enum value builders created from descriptors do not need to
337// be explicitly assigned to a file in order to preserve the original enum
338// value's package name.
339func FromEnumValue(evd *desc.EnumValueDescriptor) (*EnumValueBuilder, error) {
340	if fb, err := FromFile(evd.GetFile()); err != nil {
341		return nil, err
342	} else if evb, ok := fb.findFullyQualifiedElement(evd.GetFullyQualifiedName()).(*EnumValueBuilder); ok {
343		return evb, nil
344	} else {
345		return nil, fmt.Errorf("could not find enum value %s after converting file %q to builder", evd.GetFullyQualifiedName(), evd.GetFile().GetName())
346	}
347}
348
349func fromEnumValue(evd *desc.EnumValueDescriptor) (*EnumValueBuilder, error) {
350	evb := NewEnumValue(evd.GetName())
351	evb.Options = evd.GetEnumValueOptions()
352	evb.number = evd.GetNumber()
353	evb.numberSet = true
354	setComments(&evb.comments, evd.GetSourceInfo())
355
356	return evb, nil
357}
358
359// SetName changes this enum value's name, returning the enum value builder for
360// method chaining. If the given new name is not valid (e.g. TrySetName would
361// have returned an error) then this method will panic.
362func (evb *EnumValueBuilder) SetName(newName string) *EnumValueBuilder {
363	if err := evb.TrySetName(newName); err != nil {
364		panic(err)
365	}
366	return evb
367}
368
369// TrySetName changes this enum value's name. It will return an error if the
370// given new name is not a valid protobuf identifier or if the parent enum
371// builder already has an enum value with the given name.
372func (evb *EnumValueBuilder) TrySetName(newName string) error {
373	return evb.baseBuilder.setName(evb, newName)
374}
375
376// SetComments sets the comments associated with the enum value. This method
377// returns the enum value builder, for method chaining.
378func (evb *EnumValueBuilder) SetComments(c Comments) *EnumValueBuilder {
379	evb.comments = c
380	return evb
381}
382
383// GetChildren returns nil, since enum values cannot have child elements. It is
384// present to satisfy the Builder interface.
385func (evb *EnumValueBuilder) GetChildren() []Builder {
386	// enum values do not have children
387	return nil
388}
389
390func (evb *EnumValueBuilder) findChild(name string) Builder {
391	// enum values do not have children
392	return nil
393}
394
395func (evb *EnumValueBuilder) removeChild(b Builder) {
396	// enum values do not have children
397}
398
399func (evb *EnumValueBuilder) renamedChild(b Builder, oldName string) error {
400	// enum values do not have children
401	return nil
402}
403
404// SetOptions sets the enum value options for this enum value and returns the
405// enum value, for method chaining.
406func (evb *EnumValueBuilder) SetOptions(options *dpb.EnumValueOptions) *EnumValueBuilder {
407	evb.Options = options
408	return evb
409}
410
411// GetNumber returns the enum value's numeric value. If the number has not been
412// set this returns zero.
413func (evb *EnumValueBuilder) GetNumber() int32 {
414	return evb.number
415}
416
417// HasNumber returns whether or not the enum value's numeric value has been set.
418// If it has not been set, it is auto-assigned when the descriptor is built.
419func (evb *EnumValueBuilder) HasNumber() bool {
420	return evb.numberSet
421}
422
423// ClearNumber clears this enum value's numeric value and then returns the enum
424// value builder, for method chaining. After being cleared, the number will be
425// auto-assigned when the descriptor is built, unless explicitly set by a
426// subsequent call to SetNumber.
427func (evb *EnumValueBuilder) ClearNumber() *EnumValueBuilder {
428	evb.number = 0
429	evb.numberSet = false
430	return evb
431}
432
433// SetNumber changes the numeric value for this enum value and then returns the
434// enum value, for method chaining.
435func (evb *EnumValueBuilder) SetNumber(number int32) *EnumValueBuilder {
436	evb.number = number
437	evb.numberSet = true
438	return evb
439}
440
441func (evb *EnumValueBuilder) buildProto(path []int32, sourceInfo *dpb.SourceCodeInfo) (*dpb.EnumValueDescriptorProto, error) {
442	addCommentsTo(sourceInfo, path, &evb.comments)
443
444	return &dpb.EnumValueDescriptorProto{
445		Name:    proto.String(evb.name),
446		Number:  proto.Int32(evb.number),
447		Options: evb.Options,
448	}, nil
449}
450
451// Build constructs an enum value descriptor based on the contents of this enum
452// value builder. If there are any problems constructing the descriptor,
453// including resolving symbols referenced by the builder or failing to meet
454// certain validation rules, an error is returned.
455func (evb *EnumValueBuilder) Build() (*desc.EnumValueDescriptor, error) {
456	evd, err := evb.BuildDescriptor()
457	if err != nil {
458		return nil, err
459	}
460	return evd.(*desc.EnumValueDescriptor), nil
461}
462
463// BuildDescriptor constructs an enum value descriptor based on the contents of
464// this enum value builder. Most usages will prefer Build() instead, whose
465// return type is a concrete descriptor type. This method is present to satisfy
466// the Builder interface.
467func (evb *EnumValueBuilder) BuildDescriptor() (desc.Descriptor, error) {
468	return doBuild(evb, BuilderOptions{})
469}
470