1// Copyright 2020 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package protopath
6
7import (
8	"fmt"
9	"strconv"
10	"strings"
11
12	"google.golang.org/protobuf/internal/encoding/text"
13	"google.golang.org/protobuf/reflect/protoreflect"
14)
15
16// StepKind identifies the kind of step operation.
17// Each kind of step corresponds with some protobuf reflection operation.
18type StepKind int
19
20const (
21	invalidStep StepKind = iota
22	// RootStep identifies a step as the Root step operation.
23	RootStep
24	// FieldAccessStep identifies a step as the FieldAccess step operation.
25	FieldAccessStep
26	// UnknownAccessStep identifies a step as the UnknownAccess step operation.
27	UnknownAccessStep
28	// ListIndexStep identifies a step as the ListIndex step operation.
29	ListIndexStep
30	// MapIndexStep identifies a step as the MapIndex step operation.
31	MapIndexStep
32	// AnyExpandStep identifies a step as the AnyExpand step operation.
33	AnyExpandStep
34)
35
36func (k StepKind) String() string {
37	switch k {
38	case invalidStep:
39		return "<invalid>"
40	case RootStep:
41		return "Root"
42	case FieldAccessStep:
43		return "FieldAccess"
44	case UnknownAccessStep:
45		return "UnknownAccess"
46	case ListIndexStep:
47		return "ListIndex"
48	case MapIndexStep:
49		return "MapIndex"
50	case AnyExpandStep:
51		return "AnyExpand"
52	default:
53		return fmt.Sprintf("<unknown:%d>", k)
54	}
55}
56
57// Step is a union where only one step operation may be specified at a time.
58// The different kinds of steps are specified by the constants defined for
59// the StepKind type.
60type Step struct {
61	kind StepKind
62	desc protoreflect.Descriptor
63	key  protoreflect.Value
64}
65
66// Root indicates the root message that a path is relative to.
67// It should always (and only ever) be the first step in a path.
68func Root(md protoreflect.MessageDescriptor) Step {
69	if md == nil {
70		panic("nil message descriptor")
71	}
72	return Step{kind: RootStep, desc: md}
73}
74
75// FieldAccess describes access of a field within a message.
76// Extension field accesses are also represented using a FieldAccess and
77// must be provided with a protoreflect.FieldDescriptor
78//
79// Within the context of Values,
80// the type of the previous step value is always a message, and
81// the type of the current step value is determined by the field descriptor.
82func FieldAccess(fd protoreflect.FieldDescriptor) Step {
83	if fd == nil {
84		panic("nil field descriptor")
85	} else if _, ok := fd.(protoreflect.ExtensionTypeDescriptor); !ok && fd.IsExtension() {
86		panic(fmt.Sprintf("extension field %q must implement protoreflect.ExtensionTypeDescriptor", fd.FullName()))
87	}
88	return Step{kind: FieldAccessStep, desc: fd}
89}
90
91// UnknownAccess describes access to the unknown fields within a message.
92//
93// Within the context of Values,
94// the type of the previous step value is always a message, and
95// the type of the current step value is always a bytes type.
96func UnknownAccess() Step {
97	return Step{kind: UnknownAccessStep}
98}
99
100// ListIndex describes index of an element within a list.
101//
102// Within the context of Values,
103// the type of the previous, previous step value is always a message,
104// the type of the previous step value is always a list, and
105// the type of the current step value is determined by the field descriptor.
106func ListIndex(i int) Step {
107	if i < 0 {
108		panic(fmt.Sprintf("invalid list index: %v", i))
109	}
110	return Step{kind: ListIndexStep, key: protoreflect.ValueOfInt64(int64(i))}
111}
112
113// MapIndex describes index of an entry within a map.
114// The key type is determined by field descriptor that the map belongs to.
115//
116// Within the context of Values,
117// the type of the previous previous step value is always a message,
118// the type of the previous step value is always a map, and
119// the type of the current step value is determined by the field descriptor.
120func MapIndex(k protoreflect.MapKey) Step {
121	if !k.IsValid() {
122		panic("invalid map index")
123	}
124	return Step{kind: MapIndexStep, key: k.Value()}
125}
126
127// AnyExpand describes expansion of a google.protobuf.Any message into
128// a structured representation of the underlying message.
129//
130// Within the context of Values,
131// the type of the previous step value is always a google.protobuf.Any message, and
132// the type of the current step value is always a message.
133func AnyExpand(md protoreflect.MessageDescriptor) Step {
134	if md == nil {
135		panic("nil message descriptor")
136	}
137	return Step{kind: AnyExpandStep, desc: md}
138}
139
140// MessageDescriptor returns the message descriptor for Root or AnyExpand steps,
141// otherwise it returns nil.
142func (s Step) MessageDescriptor() protoreflect.MessageDescriptor {
143	switch s.kind {
144	case RootStep, AnyExpandStep:
145		return s.desc.(protoreflect.MessageDescriptor)
146	default:
147		return nil
148	}
149}
150
151// FieldDescriptor returns the field descriptor for FieldAccess steps,
152// otherwise it returns nil.
153func (s Step) FieldDescriptor() protoreflect.FieldDescriptor {
154	switch s.kind {
155	case FieldAccessStep:
156		return s.desc.(protoreflect.FieldDescriptor)
157	default:
158		return nil
159	}
160}
161
162// ListIndex returns the list index for ListIndex steps,
163// otherwise it returns 0.
164func (s Step) ListIndex() int {
165	switch s.kind {
166	case ListIndexStep:
167		return int(s.key.Int())
168	default:
169		return 0
170	}
171}
172
173// MapIndex returns the map key for MapIndex steps,
174// otherwise it returns an invalid map key.
175func (s Step) MapIndex() protoreflect.MapKey {
176	switch s.kind {
177	case MapIndexStep:
178		return s.key.MapKey()
179	default:
180		return protoreflect.MapKey{}
181	}
182}
183
184// Kind reports which kind of step this is.
185func (s Step) Kind() StepKind {
186	return s.kind
187}
188
189func (s Step) String() string {
190	return string(s.appendString(nil))
191}
192
193func (s Step) appendString(b []byte) []byte {
194	switch s.kind {
195	case RootStep:
196		b = append(b, '(')
197		b = append(b, s.desc.FullName()...)
198		b = append(b, ')')
199	case FieldAccessStep:
200		b = append(b, '.')
201		if fd := s.desc.(protoreflect.FieldDescriptor); fd.IsExtension() {
202			b = append(b, '(')
203			b = append(b, strings.Trim(fd.TextName(), "[]")...)
204			b = append(b, ')')
205		} else {
206			b = append(b, fd.TextName()...)
207		}
208	case UnknownAccessStep:
209		b = append(b, '.')
210		b = append(b, '?')
211	case ListIndexStep:
212		b = append(b, '[')
213		b = strconv.AppendInt(b, s.key.Int(), 10)
214		b = append(b, ']')
215	case MapIndexStep:
216		b = append(b, '[')
217		switch k := s.key.Interface().(type) {
218		case bool:
219			b = strconv.AppendBool(b, bool(k)) // e.g., "true" or "false"
220		case int32:
221			b = strconv.AppendInt(b, int64(k), 10) // e.g., "-32"
222		case int64:
223			b = strconv.AppendInt(b, int64(k), 10) // e.g., "-64"
224		case uint32:
225			b = strconv.AppendUint(b, uint64(k), 10) // e.g., "32"
226		case uint64:
227			b = strconv.AppendUint(b, uint64(k), 10) // e.g., "64"
228		case string:
229			b = text.AppendString(b, k) // e.g., `"hello, world"`
230		}
231		b = append(b, ']')
232	case AnyExpandStep:
233		b = append(b, '.')
234		b = append(b, '(')
235		b = append(b, s.desc.FullName()...)
236		b = append(b, ')')
237	default:
238		b = append(b, "<invalid>"...)
239	}
240	return b
241}
242