protobuf-go/reflect/protopath/path.go

123 lines
3.6 KiB
Go
Raw Normal View History

reflect: add protopath and protorange packages The protopath package provides a means to programmatically represent a sequence of protobuf reflection operations. The protorange package traverses through a message and calls a user-provided function as it iterates. This feature sets the groundwork for the often requested feature of being able to exclude certain fields when merging or serializing. package protopath type Path []Step type Step struct{ ... } func Root(protoreflect.MessageDescriptor) Step func FieldAccess(protoreflect.FieldDescriptor) Step func UnknownAccess() Step func ListIndex(int) Step func MapIndex(protoreflect.MapKey) Step func AnyExpand(protoreflect.MessageDescriptor) Step func (Step) Kind() StepKind func (Step) FieldDescriptor() protoreflect.FieldDescriptor func (Step) MessageDescriptor() protoreflect.MessageDescriptor func (Step) ListIndex() int func (Step) MapIndex() protoreflect.MapKey func (Step) String() string type StepKind int const RootStep StepKind const FieldAccessStep StepKind const UnknownAccessStep StepKind const ListIndexStep StepKind const MapIndexStep StepKind const AnyExpandStep StepKind type Values struct { Path Path Values []protoreflect.Value } func (Values) Index(int) (out struct{ ... }) func (Values) Len() int func (Values) String() string package protorange var Break error var Terminate error func Range(protoreflect.Message, func(protopath.Values) error) error type Options struct { Stable bool Resolver interface { ... } } func (Options) Range(m protoreflect.Message, push, pop func(protopath.Values) error) error Change-Id: I29cbd5142fe169d78367d54a95d37801888b64f4 Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/236540 Trust: Joe Tsai <joetsai@digital-static.net> Reviewed-by: Damien Neil <dneil@google.com>
2020-05-12 00:47:32 +00:00
// 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 provides functionality for
// representing a sequence of protobuf reflection operations on a message.
package protopath
import (
"fmt"
"google.golang.org/protobuf/internal/msgfmt"
"google.golang.org/protobuf/reflect/protoreflect"
)
// NOTE: The Path and Values are separate types here since there are use cases
// where you would like to "address" some value in a message with just the path
// and don't have the value information available.
//
// This is different from how "github.com/google/go-cmp/cmp".Path operates,
// which combines both path and value information together.
// Since the cmp package itself is the only one ever constructing a cmp.Path,
// it will always have the value available.
// Path is a sequence of protobuf reflection steps applied to some root
// protobuf message value to arrive at the current value.
// The first step must be a Root step.
type Path []Step
// TODO: Provide a Parse function that parses something similar to or
// perhaps identical to the output of Path.String.
// Index returns the ith step in the path and supports negative indexing.
// A negative index starts counting from the tail of the Path such that -1
// refers to the last step, -2 refers to the second-to-last step, and so on.
// It returns a zero Step value if the index is out-of-bounds.
func (p Path) Index(i int) Step {
if i < 0 {
i = len(p) + i
}
if i < 0 || i >= len(p) {
return Step{}
}
return p[i]
}
// String returns a structured representation of the path
// by concatenating the string representation of every path step.
func (p Path) String() string {
var b []byte
for _, s := range p {
b = s.appendString(b)
}
return string(b)
}
// Values is a Path paired with a sequence of values at each step.
// The lengths of Path and Values must be identical.
// The first step must be a Root step and
// the first value must be a concrete message value.
type Values struct {
Path Path
Values []protoreflect.Value
}
// Len reports the length of the path and values.
// If the path and values have differing length, it returns the minimum length.
func (p Values) Len() int {
n := len(p.Path)
if n > len(p.Values) {
n = len(p.Values)
}
return n
}
// Index returns the ith step and value and supports negative indexing.
// A negative index starts counting from the tail of the Values such that -1
// refers to the last pair, -2 refers to the second-to-last pair, and so on.
func (p Values) Index(i int) (out struct {
Step Step
Value protoreflect.Value
}) {
// NOTE: This returns a single struct instead of two return values so that
// callers can make use of the the value in an expression:
// vs.Index(i).Value.Interface()
n := p.Len()
if i < 0 {
i = n + i
}
if i < 0 || i >= n {
return out
}
out.Step = p.Path[i]
out.Value = p.Values[i]
return out
}
// String returns a humanly readable representation of the path and last value.
// Do not depend on the output being stable.
//
// For example:
//
reflect: add protopath and protorange packages The protopath package provides a means to programmatically represent a sequence of protobuf reflection operations. The protorange package traverses through a message and calls a user-provided function as it iterates. This feature sets the groundwork for the often requested feature of being able to exclude certain fields when merging or serializing. package protopath type Path []Step type Step struct{ ... } func Root(protoreflect.MessageDescriptor) Step func FieldAccess(protoreflect.FieldDescriptor) Step func UnknownAccess() Step func ListIndex(int) Step func MapIndex(protoreflect.MapKey) Step func AnyExpand(protoreflect.MessageDescriptor) Step func (Step) Kind() StepKind func (Step) FieldDescriptor() protoreflect.FieldDescriptor func (Step) MessageDescriptor() protoreflect.MessageDescriptor func (Step) ListIndex() int func (Step) MapIndex() protoreflect.MapKey func (Step) String() string type StepKind int const RootStep StepKind const FieldAccessStep StepKind const UnknownAccessStep StepKind const ListIndexStep StepKind const MapIndexStep StepKind const AnyExpandStep StepKind type Values struct { Path Path Values []protoreflect.Value } func (Values) Index(int) (out struct{ ... }) func (Values) Len() int func (Values) String() string package protorange var Break error var Terminate error func Range(protoreflect.Message, func(protopath.Values) error) error type Options struct { Stable bool Resolver interface { ... } } func (Options) Range(m protoreflect.Message, push, pop func(protopath.Values) error) error Change-Id: I29cbd5142fe169d78367d54a95d37801888b64f4 Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/236540 Trust: Joe Tsai <joetsai@digital-static.net> Reviewed-by: Damien Neil <dneil@google.com>
2020-05-12 00:47:32 +00:00
// (path.to.MyMessage).list_field[5].map_field["hello"] = {hello: "world"}
func (p Values) String() string {
n := p.Len()
if n == 0 {
return ""
}
// Determine the field descriptor associated with the last step.
var fd protoreflect.FieldDescriptor
last := p.Index(-1)
switch last.Step.kind {
case FieldAccessStep:
fd = last.Step.FieldDescriptor()
case MapIndexStep, ListIndexStep:
fd = p.Index(-2).Step.FieldDescriptor()
}
// Format the full path with the last value.
return fmt.Sprintf("%v = %v", p.Path[:n], msgfmt.FormatValue(last.Value, fd))
}