knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/kmp/reporters.go (about) 1 /* 2 Copyright 2019 The Knative Authors 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package kmp 18 19 import ( 20 "fmt" 21 "reflect" 22 "sort" 23 "strings" 24 25 "github.com/google/go-cmp/cmp" 26 ) 27 28 // fieldListReporter implements the cmp.Reporter interface. It keeps 29 // track of the field names that differ between two structs and reports 30 // them through the Fields() function. 31 type fieldListReporter struct { 32 path cmp.Path 33 fieldNames []string 34 } 35 36 // PushStep implements the cmp.Reporter. 37 func (r *fieldListReporter) PushStep(ps cmp.PathStep) { 38 r.path = append(r.path, ps) 39 } 40 41 // fieldName returns a readable name for the field. If the field has JSON annotations it 42 // returns the JSON key. If the field does not have JSON annotations or the JSON annotation 43 // marks the field as ignored it returns the field's go name 44 func (r *fieldListReporter) fieldName() string { 45 if len(r.path) < 2 { 46 return r.path.Index(0).String() 47 } 48 fieldName := strings.TrimPrefix(r.path.Index(1).String(), ".") 49 // Prefer JSON name to fieldName if it exists 50 structField, exists := r.path.Index(0).Type().FieldByName(fieldName) 51 if exists { 52 tag := structField.Tag.Get("json") 53 if tag != "" && tag != "-" { 54 return strings.SplitN(tag, ",", 2)[0] 55 } 56 } 57 58 return fieldName 59 } 60 61 // Report implements the cmp.Reporter. 62 func (r *fieldListReporter) Report(rs cmp.Result) { 63 if rs.Equal() { 64 return 65 } 66 name := r.fieldName() 67 // Only append elements we don't already have. 68 for _, v := range r.fieldNames { 69 if name == v { 70 return 71 } 72 } 73 r.fieldNames = append(r.fieldNames, name) 74 } 75 76 // PopStep implements cmp.Reporter. 77 func (r *fieldListReporter) PopStep() { 78 r.path = r.path[:len(r.path)-1] 79 } 80 81 // Fields returns the field names that differed between the two 82 // objects after calling cmp.Equal with the FieldListReporter. Field names 83 // are returned in alphabetical order. 84 func (r *fieldListReporter) Fields() []string { 85 sort.Strings(r.fieldNames) 86 return r.fieldNames 87 } 88 89 // shortDiffReporter implements the cmp.Reporter interface. It reports 90 // on fields which have diffing values in a short zero-context, unified diff 91 // format. 92 type shortDiffReporter struct { 93 path cmp.Path 94 diffs []string 95 err error 96 } 97 98 // PushStep implements the cmp.Reporter. 99 func (r *shortDiffReporter) PushStep(ps cmp.PathStep) { 100 r.path = append(r.path, ps) 101 } 102 103 // Report implements the cmp.Reporter. 104 func (r *shortDiffReporter) Report(rs cmp.Result) { 105 if rs.Equal() { 106 return 107 } 108 cur := r.path.Last() 109 vx, vy := cur.Values() 110 t := cur.Type() 111 // Prefix struct values with the types to add clarity in output 112 if !vx.IsValid() && !vy.IsValid() { 113 r.err = fmt.Errorf("unable to diff %+v and %+v on path %#v", vx, vy, r.path) 114 return 115 } 116 diff := fmt.Sprintf("%#v:\n", r.path) 117 if vx.IsValid() { 118 diff += r.diffString("-", t, vx) 119 } 120 if vy.IsValid() { 121 diff += r.diffString("+", t, vy) 122 } 123 r.diffs = append(r.diffs, diff) 124 } 125 126 func (r *shortDiffReporter) diffString(diffType string, t reflect.Type, v reflect.Value) string { 127 if t.Kind() == reflect.Struct { 128 return fmt.Sprintf("\t%s: %+v: \"%+v\"\n", diffType, t, v) 129 } 130 return fmt.Sprintf("\t%s: \"%+v\"\n", diffType, v) 131 } 132 133 // PopStep implements the cmp.Reporter. 134 func (r *shortDiffReporter) PopStep() { 135 r.path = r.path[:len(r.path)-1] 136 } 137 138 // Diff returns the generated short diff for this object. 139 // cmp.Equal should be called before this method. 140 func (r *shortDiffReporter) Diff() (string, error) { 141 if r.err != nil { 142 return "", r.err 143 } 144 return strings.Join(r.diffs, ""), nil 145 }