github.com/rzurga/go-swagger@v0.28.1-0.20211109195225-5d1f453ffa3a/cmd/swagger/commands/diff/spec_difference.go (about) 1 package diff 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "io" 8 "sort" 9 "strings" 10 ) 11 12 // SpecDifference encapsulates the details of an individual diff in part of a spec 13 type SpecDifference struct { 14 DifferenceLocation DifferenceLocation `json:"location"` 15 Code SpecChangeCode `json:"code"` 16 Compatibility Compatibility `json:"compatibility"` 17 DiffInfo string `json:"info,omitempty"` 18 } 19 20 // SpecDifferences list of differences 21 type SpecDifferences []SpecDifference 22 23 // Matches returns true if the diff matches another 24 func (sd SpecDifference) Matches(other SpecDifference) bool { 25 return sd.Code == other.Code && 26 sd.Compatibility == other.Compatibility && 27 sd.DiffInfo == other.DiffInfo && 28 equalLocations(sd.DifferenceLocation, other.DifferenceLocation) 29 } 30 31 func equalLocations(a, b DifferenceLocation) bool { 32 return a.Method == b.Method && 33 a.Response == b.Response && 34 a.URL == b.URL && 35 equalNodes(a.Node, b.Node) 36 } 37 38 func equalNodes(a, b *Node) bool { 39 if a == nil && b == nil { 40 return true 41 } 42 if a == nil || b == nil { 43 return false 44 } 45 return a.Field == b.Field && 46 a.IsArray == b.IsArray && 47 a.TypeName == b.TypeName && 48 equalNodes(a.ChildNode, b.ChildNode) 49 50 } 51 52 // BreakingChangeCount Calculates the breaking change count 53 func (sd SpecDifferences) BreakingChangeCount() int { 54 count := 0 55 for _, eachDiff := range sd { 56 if eachDiff.Compatibility == Breaking { 57 count++ 58 } 59 } 60 return count 61 } 62 63 // FilterIgnores returns a copy of the list without the items in the specified ignore list 64 func (sd SpecDifferences) FilterIgnores(ignores SpecDifferences) SpecDifferences { 65 newDiffs := SpecDifferences{} 66 for _, eachDiff := range sd { 67 if !ignores.Contains(eachDiff) { 68 newDiffs = newDiffs.addDiff(eachDiff) 69 } 70 } 71 return newDiffs 72 } 73 74 // Contains Returns true if the item contains the specified item 75 func (sd SpecDifferences) Contains(diff SpecDifference) bool { 76 for _, eachDiff := range sd { 77 if eachDiff.Matches(diff) { 78 return true 79 } 80 } 81 return false 82 } 83 84 // String std string renderer 85 func (sd SpecDifference) String() string { 86 isResponse := sd.DifferenceLocation.Response > 0 87 hasMethod := len(sd.DifferenceLocation.Method) > 0 88 hasURL := len(sd.DifferenceLocation.URL) > 0 89 90 prefix := "" 91 direction := "" 92 93 if hasMethod { 94 if hasURL { 95 prefix = fmt.Sprintf("%s:%s", sd.DifferenceLocation.URL, sd.DifferenceLocation.Method) 96 } 97 if isResponse { 98 prefix += fmt.Sprintf(" -> %d", sd.DifferenceLocation.Response) 99 direction = "Response" 100 } else { 101 direction = "Request" 102 } 103 } else { 104 prefix = sd.DifferenceLocation.URL 105 } 106 107 paramOrPropertyLocation := "" 108 if sd.DifferenceLocation.Node != nil { 109 paramOrPropertyLocation = sd.DifferenceLocation.Node.String() 110 } 111 optionalInfo := "" 112 if sd.DiffInfo != "" { 113 optionalInfo = sd.DiffInfo 114 } 115 116 items := []string{} 117 for _, item := range []string{prefix, direction, paramOrPropertyLocation, sd.Code.Description(), optionalInfo} { 118 if item != "" { 119 items = append(items, item) 120 } 121 } 122 return strings.Join(items, " - ") 123 // return fmt.Sprintf("%s%s%s - %s%s", prefix, direction, paramOrPropertyLocation, sd.Code.Description(), optionalInfo) 124 } 125 126 func (sd SpecDifferences) addDiff(diff SpecDifference) SpecDifferences { 127 context := Request 128 if diff.DifferenceLocation.Response > 0 { 129 context = Response 130 } 131 diff.Compatibility = getCompatibilityForChange(diff.Code, context) 132 133 return append(sd, diff) 134 } 135 136 // ReportCompatibility lists and spec 137 func (sd *SpecDifferences) ReportCompatibility() (io.Reader, error, error) { 138 var out bytes.Buffer 139 breakingCount := sd.BreakingChangeCount() 140 if breakingCount > 0 { 141 fmt.Fprintln(&out, "\nBREAKING CHANGES:\n=================") 142 _, _ = out.ReadFrom(sd.reportChanges(Breaking)) 143 msg := fmt.Sprintf("compatibility test FAILED: %d breaking changes detected", breakingCount) 144 fmt.Fprintln(&out, msg) 145 return &out, nil, errors.New(msg) 146 } 147 fmt.Fprintf(&out, "compatibility test OK. No breaking changes identified.") 148 return &out, nil, nil 149 } 150 151 func (sd SpecDifferences) reportChanges(compat Compatibility) io.Reader { 152 toReportList := []string{} 153 var out bytes.Buffer 154 155 for _, diff := range sd { 156 if diff.Compatibility == compat { 157 toReportList = append(toReportList, diff.String()) 158 } 159 } 160 161 sort.Slice(toReportList, func(i, j int) bool { 162 return toReportList[i] < toReportList[j] 163 }) 164 165 for _, eachDiff := range toReportList { 166 fmt.Fprintln(&out, eachDiff) 167 } 168 return &out 169 } 170 171 // ReportAllDiffs lists all the diffs between two specs 172 func (sd SpecDifferences) ReportAllDiffs(fmtJSON bool) (io.Reader, error, error) { 173 if fmtJSON { 174 b, err := JSONMarshal(sd) 175 if err != nil { 176 return nil, fmt.Errorf("couldn't print results: %v", err), nil 177 } 178 out, err := prettyprint(b) 179 return out, err, nil 180 } 181 numDiffs := len(sd) 182 if numDiffs == 0 { 183 return bytes.NewBuffer([]byte("No changes identified")), nil, nil 184 } 185 186 var out bytes.Buffer 187 if numDiffs != sd.BreakingChangeCount() { 188 fmt.Fprintln(&out, "NON-BREAKING CHANGES:\n=====================") 189 _, _ = out.ReadFrom(sd.reportChanges(NonBreaking)) 190 } 191 192 more, err, warn := sd.ReportCompatibility() 193 if err != nil { 194 return nil, err, warn 195 } 196 _, _ = out.ReadFrom(more) 197 return &out, nil, warn 198 }