path.go 3.6 KB

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