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. 7package protopath 8 9import ( 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. 28type 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. 37func (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. 49func (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 Path and Values must be identical. 59// The first step must be a Root step and 60// the first value must be a concrete message value. 61type 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. 68func (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. 79func (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"} 104func (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