blob: 5a9d22cb696805a53a0d68a9fe5d59dcab39b86b [file] [log] [blame]
// Copyright 2022 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package reflectutil
import (
"fmt"
"strings"
"go.chromium.org/luci/common/errors"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
)
// PathItem is a single step in a Path.
type PathItem interface {
fmt.Stringer
// Retrieve applies this PathItem to the given Value.
//
// If it fails, returns a Value where v.IsValid() == false.
Retrieve(protoreflect.Value) protoreflect.Value
}
// PathField is a PathItem which indicates field access in a Message.
type PathField struct{ protoreflect.FieldDescriptor }
var _ PathItem = PathField{}
// Retrieve implements PathItem
func (f PathField) Retrieve(v protoreflect.Value) (ret protoreflect.Value) {
if msg, ok := v.Interface().(protoreflect.Message); ok {
ret = msg.Get(f.FieldDescriptor)
}
return
}
// String implements fmt.Stringer.
//
// This makes the most sense when used with Path.String(); this formats as
// `.proto_field_name`..
func (f PathField) String() string {
return fmt.Sprintf(".%s", f.FieldDescriptor.Name())
}
// PathListIdx is a PathItem which indicates the indexing of
// a repeated field.
type PathListIdx int
var _ PathItem = PathListIdx(0)
// Retrieve implements PathItem
func (i PathListIdx) Retrieve(v protoreflect.Value) (ret protoreflect.Value) {
if lst, ok := v.Interface().(protoreflect.List); ok {
ret = lst.Get(int(i))
}
return
}
// String implements fmt.Stringer.
//
// This makes the most sense when used with Path.String(); this formats as
// `[index_value]`.
func (i PathListIdx) String() string {
return fmt.Sprintf("[%d]", i)
}
// PathMapKey is a PathItem which indicates the indexing of
// a map field.
type PathMapKey protoreflect.MapKey
var _ PathItem = PathMapKey{}
// Retrieve implements PathItem
func (k PathMapKey) Retrieve(v protoreflect.Value) (ret protoreflect.Value) {
if m, ok := v.Interface().(protoreflect.Map); ok {
ret = m.Get(protoreflect.MapKey(k))
}
return
}
// String implements fmt.Stringer.
//
// This makes the most sense when used with Path.String(); this formats as
// `[map_key]` for integer/bool types, and `["map_key"]` for string types.
func (k PathMapKey) String() string {
mk := protoreflect.MapKey(k)
if strk, ok := mk.Interface().(string); ok {
return fmt.Sprintf("[%q]", strk)
}
return fmt.Sprintf("[%s]", mk.String())
}
// MustMakePathItem will return a PathItem from `item` or panic.
//
// `item` can be a:
// - int (PathListIdx)
// - protoreflect.FieldDescriptor (PathField)
// - protoreflect.MapKey (PathMapKey)
func MustMakePathItem(item any) PathItem {
switch x := item.(type) {
case int:
return PathListIdx(x)
case protoreflect.FieldDescriptor:
return PathField{x}
case protoreflect.MapKey:
return PathMapKey(x)
}
panic(errors.Reason("unknown proto path item: %T", item).Err())
}
// Path is a series of tokens to indicate some (possibly nested) field
// within a Message.
type Path []PathItem
// String implements fmt.Stringer.
//
// Formats the Path in a way that makes sense to humans.
func (p Path) String() string {
bld := strings.Builder{}
for _, itm := range p {
bld.WriteString(itm.String())
}
return bld.String()
}
// Retrieve applies this Path to a proto.Message, returning the Value
// indicated.
//
// If the Path doesn't match a path in `msg` (i.e. the Path came from
// a different type of Message), returns an invalid Value.
func (p Path) Retrieve(msg proto.Message) protoreflect.Value {
v := protoreflect.ValueOf(msg.ProtoReflect())
for _, itm := range p {
v = itm.Retrieve(v)
}
return v
}