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//	(path.to.MyMessage).list_field[5].map_field["hello"] = {hello: "world"}
103func (p Values) String() string {
104	n := p.Len()
105	if n == 0 {
106		return ""
107	}
108
109	// Determine the field descriptor associated with the last step.
110	var fd protoreflect.FieldDescriptor
111	last := p.Index(-1)
112	switch last.Step.kind {
113	case FieldAccessStep:
114		fd = last.Step.FieldDescriptor()
115	case MapIndexStep, ListIndexStep:
116		fd = p.Index(-2).Step.FieldDescriptor()
117	}
118
119	// Format the full path with the last value.
120	return fmt.Sprintf("%v = %v", p.Path[:n], msgfmt.FormatValue(last.Value, fd))
121}
122