blob: 95ae85c5b1dbb1aa25715b1e09f2ea2739583936 [file] [log] [blame]
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package protopath
import (
"fmt"
"strconv"
"strings"
"google.golang.org/protobuf/internal/encoding/text"
"google.golang.org/protobuf/reflect/protoreflect"
)
// StepKind identifies the kind of step operation.
// Each kind of step corresponds with some protobuf reflection operation.
type StepKind int
const (
invalidStep StepKind = iota
// RootStep identifies a step as the Root step operation.
RootStep
// FieldAccessStep identifies a step as the FieldAccess step operation.
FieldAccessStep
// UnknownAccessStep identifies a step as the UnknownAccess step operation.
UnknownAccessStep
// ListIndexStep identifies a step as the ListIndex step operation.
ListIndexStep
// MapIndexStep identifies a step as the MapIndex step operation.
MapIndexStep
// AnyExpandStep identifies a step as the AnyExpand step operation.
AnyExpandStep
)
func (k StepKind) String() string {
switch k {
case invalidStep:
return "<invalid>"
case RootStep:
return "Root"
case FieldAccessStep:
return "FieldAccess"
case UnknownAccessStep:
return "UnknownAccess"
case ListIndexStep:
return "ListIndex"
case MapIndexStep:
return "MapIndex"
case AnyExpandStep:
return "AnyExpand"
default:
return fmt.Sprintf("<unknown:%d>", k)
}
}
// Step is a union where only one step operation may be specified at a time.
// The different kinds of steps are specified by the constants defined for
// the StepKind type.
type Step struct {
kind StepKind
desc protoreflect.Descriptor
key protoreflect.Value
}
// Root indicates the root message that a path is relative to.
// It should always (and only ever) be the first step in a path.
func Root(md protoreflect.MessageDescriptor) Step {
if md == nil {
panic("nil message descriptor")
}
return Step{kind: RootStep, desc: md}
}
// FieldAccess describes access of a field within a message.
// Extension field accesses are also represented using a FieldAccess and
// must be provided with a protoreflect.FieldDescriptor
//
// Within the context of Values,
// the type of the previous step value is always a message, and
// the type of the current step value is determined by the field descriptor.
func FieldAccess(fd protoreflect.FieldDescriptor) Step {
if fd == nil {
panic("nil field descriptor")
} else if _, ok := fd.(protoreflect.ExtensionTypeDescriptor); !ok && fd.IsExtension() {
panic(fmt.Sprintf("extension field %q must implement protoreflect.ExtensionTypeDescriptor", fd.FullName()))
}
return Step{kind: FieldAccessStep, desc: fd}
}
// UnknownAccess describes access to the unknown fields within a message.
//
// Within the context of Values,
// the type of the previous step value is always a message, and
// the type of the current step value is always a bytes type.
func UnknownAccess() Step {
return Step{kind: UnknownAccessStep}
}
// ListIndex describes index of an element within a list.
//
// Within the context of Values,
// the type of the previous, previous step value is always a message,
// the type of the previous step value is always a list, and
// the type of the current step value is determined by the field descriptor.
func ListIndex(i int) Step {
if i < 0 {
panic(fmt.Sprintf("invalid list index: %v", i))
}
return Step{kind: ListIndexStep, key: protoreflect.ValueOfInt64(int64(i))}
}
// MapIndex describes index of an entry within a map.
// The key type is determined by field descriptor that the map belongs to.
//
// Within the context of Values,
// the type of the previous previous step value is always a message,
// the type of the previous step value is always a map, and
// the type of the current step value is determined by the field descriptor.
func MapIndex(k protoreflect.MapKey) Step {
if !k.IsValid() {
panic("invalid map index")
}
return Step{kind: MapIndexStep, key: k.Value()}
}
// AnyExpand describes expansion of a google.protobuf.Any message into
// a structured representation of the underlying message.
//
// Within the context of Values,
// the type of the previous step value is always a google.protobuf.Any message, and
// the type of the current step value is always a message.
func AnyExpand(md protoreflect.MessageDescriptor) Step {
if md == nil {
panic("nil message descriptor")
}
return Step{kind: AnyExpandStep, desc: md}
}
// MessageDescriptor returns the message descriptor for Root or AnyExpand steps,
// otherwise it returns nil.
func (s Step) MessageDescriptor() protoreflect.MessageDescriptor {
switch s.kind {
case RootStep, AnyExpandStep:
return s.desc.(protoreflect.MessageDescriptor)
default:
return nil
}
}
// FieldDescriptor returns the field descriptor for FieldAccess steps,
// otherwise it returns nil.
func (s Step) FieldDescriptor() protoreflect.FieldDescriptor {
switch s.kind {
case FieldAccessStep:
return s.desc.(protoreflect.FieldDescriptor)
default:
return nil
}
}
// ListIndex returns the list index for ListIndex steps,
// otherwise it returns 0.
func (s Step) ListIndex() int {
switch s.kind {
case ListIndexStep:
return int(s.key.Int())
default:
return 0
}
}
// MapIndex returns the map key for MapIndex steps,
// otherwise it returns an invalid map key.
func (s Step) MapIndex() protoreflect.MapKey {
switch s.kind {
case MapIndexStep:
return s.key.MapKey()
default:
return protoreflect.MapKey{}
}
}
// Kind reports which kind of step this is.
func (s Step) Kind() StepKind {
return s.kind
}
func (s Step) String() string {
return string(s.appendString(nil))
}
func (s Step) appendString(b []byte) []byte {
switch s.kind {
case RootStep:
b = append(b, '(')
b = append(b, s.desc.FullName()...)
b = append(b, ')')
case FieldAccessStep:
b = append(b, '.')
if fd := s.desc.(protoreflect.FieldDescriptor); fd.IsExtension() {
b = append(b, '(')
b = append(b, strings.Trim(fd.TextName(), "[]")...)
b = append(b, ')')
} else {
b = append(b, fd.TextName()...)
}
case UnknownAccessStep:
b = append(b, '.')
b = append(b, '?')
case ListIndexStep:
b = append(b, '[')
b = strconv.AppendInt(b, s.key.Int(), 10)
b = append(b, ']')
case MapIndexStep:
b = append(b, '[')
switch k := s.key.Interface().(type) {
case bool:
b = strconv.AppendBool(b, bool(k)) // e.g., "true" or "false"
case int32:
b = strconv.AppendInt(b, int64(k), 10) // e.g., "-32"
case int64:
b = strconv.AppendInt(b, int64(k), 10) // e.g., "-64"
case uint32:
b = strconv.AppendUint(b, uint64(k), 10) // e.g., "32"
case uint64:
b = strconv.AppendUint(b, uint64(k), 10) // e.g., "64"
case string:
b = text.AppendString(b, k) // e.g., `"hello, world"`
}
b = append(b, ']')
case AnyExpandStep:
b = append(b, '.')
b = append(b, '(')
b = append(b, s.desc.FullName()...)
b = append(b, ')')
default:
b = append(b, "<invalid>"...)
}
return b
}