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  }