// 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 [Values.Path] and [Values.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: // // (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)) }