1*1c12ee1eSDan Willemsen// Copyright 2020 The Go Authors. All rights reserved. 2*1c12ee1eSDan Willemsen// Use of this source code is governed by a BSD-style 3*1c12ee1eSDan Willemsen// license that can be found in the LICENSE file. 4*1c12ee1eSDan Willemsen 5*1c12ee1eSDan Willemsen// Package protopath provides functionality for 6*1c12ee1eSDan Willemsen// representing a sequence of protobuf reflection operations on a message. 7*1c12ee1eSDan Willemsenpackage protopath 8*1c12ee1eSDan Willemsen 9*1c12ee1eSDan Willemsenimport ( 10*1c12ee1eSDan Willemsen "fmt" 11*1c12ee1eSDan Willemsen 12*1c12ee1eSDan Willemsen "google.golang.org/protobuf/internal/msgfmt" 13*1c12ee1eSDan Willemsen "google.golang.org/protobuf/reflect/protoreflect" 14*1c12ee1eSDan Willemsen) 15*1c12ee1eSDan Willemsen 16*1c12ee1eSDan Willemsen// NOTE: The Path and Values are separate types here since there are use cases 17*1c12ee1eSDan Willemsen// where you would like to "address" some value in a message with just the path 18*1c12ee1eSDan Willemsen// and don't have the value information available. 19*1c12ee1eSDan Willemsen// 20*1c12ee1eSDan Willemsen// This is different from how "github.com/google/go-cmp/cmp".Path operates, 21*1c12ee1eSDan Willemsen// which combines both path and value information together. 22*1c12ee1eSDan Willemsen// Since the cmp package itself is the only one ever constructing a cmp.Path, 23*1c12ee1eSDan Willemsen// it will always have the value available. 24*1c12ee1eSDan Willemsen 25*1c12ee1eSDan Willemsen// Path is a sequence of protobuf reflection steps applied to some root 26*1c12ee1eSDan Willemsen// protobuf message value to arrive at the current value. 27*1c12ee1eSDan Willemsen// The first step must be a Root step. 28*1c12ee1eSDan Willemsentype Path []Step 29*1c12ee1eSDan Willemsen 30*1c12ee1eSDan Willemsen// TODO: Provide a Parse function that parses something similar to or 31*1c12ee1eSDan Willemsen// perhaps identical to the output of Path.String. 32*1c12ee1eSDan Willemsen 33*1c12ee1eSDan Willemsen// Index returns the ith step in the path and supports negative indexing. 34*1c12ee1eSDan Willemsen// A negative index starts counting from the tail of the Path such that -1 35*1c12ee1eSDan Willemsen// refers to the last step, -2 refers to the second-to-last step, and so on. 36*1c12ee1eSDan Willemsen// It returns a zero Step value if the index is out-of-bounds. 37*1c12ee1eSDan Willemsenfunc (p Path) Index(i int) Step { 38*1c12ee1eSDan Willemsen if i < 0 { 39*1c12ee1eSDan Willemsen i = len(p) + i 40*1c12ee1eSDan Willemsen } 41*1c12ee1eSDan Willemsen if i < 0 || i >= len(p) { 42*1c12ee1eSDan Willemsen return Step{} 43*1c12ee1eSDan Willemsen } 44*1c12ee1eSDan Willemsen return p[i] 45*1c12ee1eSDan Willemsen} 46*1c12ee1eSDan Willemsen 47*1c12ee1eSDan Willemsen// String returns a structured representation of the path 48*1c12ee1eSDan Willemsen// by concatenating the string representation of every path step. 49*1c12ee1eSDan Willemsenfunc (p Path) String() string { 50*1c12ee1eSDan Willemsen var b []byte 51*1c12ee1eSDan Willemsen for _, s := range p { 52*1c12ee1eSDan Willemsen b = s.appendString(b) 53*1c12ee1eSDan Willemsen } 54*1c12ee1eSDan Willemsen return string(b) 55*1c12ee1eSDan Willemsen} 56*1c12ee1eSDan Willemsen 57*1c12ee1eSDan Willemsen// Values is a Path paired with a sequence of values at each step. 58*1c12ee1eSDan Willemsen// The lengths of Path and Values must be identical. 59*1c12ee1eSDan Willemsen// The first step must be a Root step and 60*1c12ee1eSDan Willemsen// the first value must be a concrete message value. 61*1c12ee1eSDan Willemsentype Values struct { 62*1c12ee1eSDan Willemsen Path Path 63*1c12ee1eSDan Willemsen Values []protoreflect.Value 64*1c12ee1eSDan Willemsen} 65*1c12ee1eSDan Willemsen 66*1c12ee1eSDan Willemsen// Len reports the length of the path and values. 67*1c12ee1eSDan Willemsen// If the path and values have differing length, it returns the minimum length. 68*1c12ee1eSDan Willemsenfunc (p Values) Len() int { 69*1c12ee1eSDan Willemsen n := len(p.Path) 70*1c12ee1eSDan Willemsen if n > len(p.Values) { 71*1c12ee1eSDan Willemsen n = len(p.Values) 72*1c12ee1eSDan Willemsen } 73*1c12ee1eSDan Willemsen return n 74*1c12ee1eSDan Willemsen} 75*1c12ee1eSDan Willemsen 76*1c12ee1eSDan Willemsen// Index returns the ith step and value and supports negative indexing. 77*1c12ee1eSDan Willemsen// A negative index starts counting from the tail of the Values such that -1 78*1c12ee1eSDan Willemsen// refers to the last pair, -2 refers to the second-to-last pair, and so on. 79*1c12ee1eSDan Willemsenfunc (p Values) Index(i int) (out struct { 80*1c12ee1eSDan Willemsen Step Step 81*1c12ee1eSDan Willemsen Value protoreflect.Value 82*1c12ee1eSDan Willemsen}) { 83*1c12ee1eSDan Willemsen // NOTE: This returns a single struct instead of two return values so that 84*1c12ee1eSDan Willemsen // callers can make use of the the value in an expression: 85*1c12ee1eSDan Willemsen // vs.Index(i).Value.Interface() 86*1c12ee1eSDan Willemsen n := p.Len() 87*1c12ee1eSDan Willemsen if i < 0 { 88*1c12ee1eSDan Willemsen i = n + i 89*1c12ee1eSDan Willemsen } 90*1c12ee1eSDan Willemsen if i < 0 || i >= n { 91*1c12ee1eSDan Willemsen return out 92*1c12ee1eSDan Willemsen } 93*1c12ee1eSDan Willemsen out.Step = p.Path[i] 94*1c12ee1eSDan Willemsen out.Value = p.Values[i] 95*1c12ee1eSDan Willemsen return out 96*1c12ee1eSDan Willemsen} 97*1c12ee1eSDan Willemsen 98*1c12ee1eSDan Willemsen// String returns a humanly readable representation of the path and last value. 99*1c12ee1eSDan Willemsen// Do not depend on the output being stable. 100*1c12ee1eSDan Willemsen// 101*1c12ee1eSDan Willemsen// For example: 102*1c12ee1eSDan Willemsen// 103*1c12ee1eSDan Willemsen// (path.to.MyMessage).list_field[5].map_field["hello"] = {hello: "world"} 104*1c12ee1eSDan Willemsenfunc (p Values) String() string { 105*1c12ee1eSDan Willemsen n := p.Len() 106*1c12ee1eSDan Willemsen if n == 0 { 107*1c12ee1eSDan Willemsen return "" 108*1c12ee1eSDan Willemsen } 109*1c12ee1eSDan Willemsen 110*1c12ee1eSDan Willemsen // Determine the field descriptor associated with the last step. 111*1c12ee1eSDan Willemsen var fd protoreflect.FieldDescriptor 112*1c12ee1eSDan Willemsen last := p.Index(-1) 113*1c12ee1eSDan Willemsen switch last.Step.kind { 114*1c12ee1eSDan Willemsen case FieldAccessStep: 115*1c12ee1eSDan Willemsen fd = last.Step.FieldDescriptor() 116*1c12ee1eSDan Willemsen case MapIndexStep, ListIndexStep: 117*1c12ee1eSDan Willemsen fd = p.Index(-2).Step.FieldDescriptor() 118*1c12ee1eSDan Willemsen } 119*1c12ee1eSDan Willemsen 120*1c12ee1eSDan Willemsen // Format the full path with the last value. 121*1c12ee1eSDan Willemsen return fmt.Sprintf("%v = %v", p.Path[:n], msgfmt.FormatValue(last.Value, fd)) 122*1c12ee1eSDan Willemsen} 123