github.com/ffalor/go-swagger@v0.0.0-20231011000038-9f25265ac351/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 // WarningChangeCount Calculates the warning change count 64 func (sd SpecDifferences) WarningChangeCount() int { 65 count := 0 66 for _, eachDiff := range sd { 67 if eachDiff.Compatibility == Warning { 68 count++ 69 } 70 } 71 return count 72 } 73 74 // FilterIgnores returns a copy of the list without the items in the specified ignore list 75 func (sd SpecDifferences) FilterIgnores(ignores SpecDifferences) SpecDifferences { 76 newDiffs := SpecDifferences{} 77 for _, eachDiff := range sd { 78 if !ignores.Contains(eachDiff) { 79 newDiffs = newDiffs.addDiff(eachDiff) 80 } 81 } 82 return newDiffs 83 } 84 85 // Contains Returns true if the item contains the specified item 86 func (sd SpecDifferences) Contains(diff SpecDifference) bool { 87 for _, eachDiff := range sd { 88 if eachDiff.Matches(diff) { 89 return true 90 } 91 } 92 return false 93 } 94 95 // String std string renderer 96 func (sd SpecDifference) String() string { 97 isResponse := sd.DifferenceLocation.Response > 0 98 hasMethod := len(sd.DifferenceLocation.Method) > 0 99 hasURL := len(sd.DifferenceLocation.URL) > 0 100 101 prefix := "" 102 direction := "" 103 104 if hasMethod { 105 if hasURL { 106 prefix = fmt.Sprintf("%s:%s", sd.DifferenceLocation.URL, sd.DifferenceLocation.Method) 107 } 108 if isResponse { 109 prefix += fmt.Sprintf(" -> %d", sd.DifferenceLocation.Response) 110 direction = "Response" 111 } else { 112 direction = "Request" 113 } 114 } else { 115 prefix = sd.DifferenceLocation.URL 116 } 117 118 paramOrPropertyLocation := "" 119 if sd.DifferenceLocation.Node != nil { 120 paramOrPropertyLocation = sd.DifferenceLocation.Node.String() 121 } 122 optionalInfo := "" 123 if sd.DiffInfo != "" { 124 optionalInfo = sd.DiffInfo 125 } 126 127 items := []string{} 128 for _, item := range []string{prefix, direction, paramOrPropertyLocation, sd.Code.Description(), optionalInfo} { 129 if item != "" { 130 items = append(items, item) 131 } 132 } 133 return strings.Join(items, " - ") 134 // return fmt.Sprintf("%s%s%s - %s%s", prefix, direction, paramOrPropertyLocation, sd.Code.Description(), optionalInfo) 135 } 136 137 func (sd SpecDifferences) addDiff(diff SpecDifference) SpecDifferences { 138 context := Request 139 if diff.DifferenceLocation.Response > 0 { 140 context = Response 141 } 142 diff.Compatibility = getCompatibilityForChange(diff.Code, context) 143 144 return append(sd, diff) 145 } 146 147 // ReportCompatibility lists and spec 148 func (sd *SpecDifferences) ReportCompatibility() (io.Reader, error, error) { 149 var out bytes.Buffer 150 breakingCount := sd.BreakingChangeCount() 151 if breakingCount > 0 { 152 if len(*sd) != breakingCount { 153 fmt.Fprintln(&out, "") 154 } 155 fmt.Fprintln(&out, "BREAKING CHANGES:\n=================") 156 _, _ = out.ReadFrom(sd.reportChanges(Breaking)) 157 msg := fmt.Sprintf("compatibility test FAILED: %d breaking changes detected", breakingCount) 158 fmt.Fprintln(&out, msg) 159 return &out, nil, errors.New(msg) 160 } 161 fmt.Fprintf(&out, "compatibility test OK. No breaking changes identified.\n") 162 return &out, nil, nil 163 } 164 165 func (sd SpecDifferences) reportChanges(compat Compatibility) io.Reader { 166 toReportList := []string{} 167 var out bytes.Buffer 168 169 for _, diff := range sd { 170 if diff.Compatibility == compat { 171 toReportList = append(toReportList, diff.String()) 172 } 173 } 174 175 sort.Slice(toReportList, func(i, j int) bool { 176 return toReportList[i] < toReportList[j] 177 }) 178 179 for _, eachDiff := range toReportList { 180 fmt.Fprintln(&out, eachDiff) 181 } 182 return &out 183 } 184 185 // ReportAllDiffs lists all the diffs between two specs 186 func (sd SpecDifferences) ReportAllDiffs(fmtJSON bool) (io.Reader, error, error) { 187 if fmtJSON { 188 b, err := JSONMarshal(sd) 189 if err != nil { 190 return nil, fmt.Errorf("couldn't print results: %v", err), nil 191 } 192 out, err := prettyprint(b) 193 return out, err, nil 194 } 195 numDiffs := len(sd) 196 if numDiffs == 0 { 197 return bytes.NewBuffer([]byte("No changes identified\n")), nil, nil 198 } 199 200 var out bytes.Buffer 201 if numDiffs != sd.BreakingChangeCount() { 202 fmt.Fprintln(&out, "NON-BREAKING CHANGES:\n=====================") 203 _, _ = out.ReadFrom(sd.reportChanges(NonBreaking)) 204 if sd.WarningChangeCount() > 0 { 205 fmt.Fprintln(&out, "\nNON-BREAKING CHANGES WITH WARNING:\n==================================") 206 _, _ = out.ReadFrom(sd.reportChanges(Warning)) 207 } 208 } 209 210 more, err, warn := sd.ReportCompatibility() 211 if err != nil { 212 return nil, err, warn 213 } 214 _, _ = out.ReadFrom(more) 215 return &out, nil, warn 216 }