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