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  }