Skip to content

Commit

Permalink
add 'oasdiff checks' (#381)
Browse files Browse the repository at this point in the history
  • Loading branch information
Reuven Harrison authored Sep 19, 2023
1 parent 2eb2513 commit 34d3dbf
Show file tree
Hide file tree
Showing 8 changed files with 101 additions and 35 deletions.
12 changes: 6 additions & 6 deletions internal/breaking_changes.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func getBreakingChangesCmd() *cobra.Command {
Short: "Display breaking changes",
Long: `Display breaking changes between base and revision specs.
Base and revision can be a path to a file or a URL.
In 'composed' mode, base and revision can be a glob and oasdiff will compare mathcing endpoints between the two sets of files.
In 'composed' mode, base and revision can be a glob and oasdiff will compare matching endpoints between the two sets of files.
`,
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
Expand All @@ -43,8 +43,8 @@ In 'composed' mode, base and revision can be a glob and oasdiff will compare mat
}

cmd.PersistentFlags().BoolVarP(&flags.composed, "composed", "c", false, "work in 'composed' mode, compare paths in all specs matching base and revision globs")
cmd.PersistentFlags().VarP(newEnumValue([]string{FormatYAML, FormatJSON, FormatText}, FormatText, &flags.format), "format", "f", "output format: yaml, json, or text")
cmd.PersistentFlags().VarP(newEnumSliceValue(diff.ExcludeDiffOptions, nil, &flags.excludeElements), "exclude-elements", "e", "comma-separated list of elements to exclude")
enumWithOptions(&cmd, newEnumValue([]string{FormatYAML, FormatJSON, FormatText}, FormatText, &flags.format), "format", "f", "output format")
enumWithOptions(&cmd, newEnumSliceValue(diff.ExcludeDiffOptions, nil, &flags.excludeElements), "exclude-elements", "e", "comma-separated list of elements to exclude")
cmd.PersistentFlags().StringVarP(&flags.matchPath, "match-path", "p", "", "include only paths that match this regular expression")
cmd.PersistentFlags().StringVarP(&flags.filterExtension, "filter-extension", "", "", "exclude paths and operations with an OpenAPI Extension matching this regular expression")
cmd.PersistentFlags().IntVarP(&flags.circularReferenceCounter, "max-circular-dep", "", 5, "maximum allowed number of circular dependencies between objects in OpenAPI specs")
Expand All @@ -53,11 +53,11 @@ In 'composed' mode, base and revision can be a glob and oasdiff will compare mat
cmd.PersistentFlags().StringVarP(&flags.stripPrefixBase, "strip-prefix-base", "", "", "strip this prefix from paths in base-spec before comparison")
cmd.PersistentFlags().StringVarP(&flags.stripPrefixRevision, "strip-prefix-revision", "", "", "strip this prefix from paths in revised-spec before comparison")
cmd.PersistentFlags().BoolVarP(&flags.includePathParams, "include-path-params", "", false, "include path parameter names in endpoint matching")
cmd.PersistentFlags().VarP(newEnumValue([]string{LevelErr, LevelWarn}, "", &flags.failOn), "fail-on", "o", "exit with return code 1 when output includes errors with this level or higher")
cmd.PersistentFlags().VarP(newEnumValue([]string{LangEn, LangRu}, LangDefault, &flags.lang), "lang", "l", "language for localized output")
enumWithOptions(&cmd, newEnumValue([]string{LevelErr, LevelWarn}, "", &flags.failOn), "fail-on", "o", "exit with return code 1 when output includes errors with this level or higher")
enumWithOptions(&cmd, newEnumValue([]string{LangEn, LangRu}, LangDefault, &flags.lang), "lang", "l", "language for localized output")
cmd.PersistentFlags().StringVarP(&flags.errIgnoreFile, "err-ignore", "", "", "configuration file for ignoring errors")
cmd.PersistentFlags().StringVarP(&flags.warnIgnoreFile, "warn-ignore", "", "", "configuration file for ignoring warnings")
cmd.PersistentFlags().VarP(newEnumSliceValue(checker.GetOptionalChecks(), nil, &flags.includeChecks), "include-checks", "i", "comma-separated list of optional checks")
enumWithOptions(&cmd, newEnumSliceValue(checker.GetOptionalChecks(), nil, &flags.includeChecks), "include-checks", "i", "comma-separated list of optional checks (run 'oasdiff checks' to see options)")
cmd.PersistentFlags().IntVarP(&flags.deprecationDaysBeta, "deprecation-days-beta", "", checker.BetaDeprecationDays, "min days required between deprecating a beta resource and removing it")
cmd.PersistentFlags().IntVarP(&flags.deprecationDaysStable, "deprecation-days-stable", "", checker.StableDeprecationDays, "min days required between deprecating a stable resource and removing it")

Expand Down
13 changes: 9 additions & 4 deletions internal/changelog.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ In 'composed' mode, base and revision can be a glob and oasdiff will compare mat
}

cmd.PersistentFlags().BoolVarP(&flags.composed, "composed", "c", false, "work in 'composed' mode, compare paths in all specs matching base and revision globs")
cmd.PersistentFlags().VarP(newEnumValue([]string{FormatYAML, FormatJSON, FormatText}, FormatText, &flags.format), "format", "f", "output format: yaml, json, or text")
cmd.PersistentFlags().VarP(newEnumSliceValue(diff.ExcludeDiffOptions, nil, &flags.excludeElements), "exclude-elements", "e", "comma-separated list of elements to exclude")
enumWithOptions(&cmd, newEnumValue([]string{FormatYAML, FormatJSON, FormatText}, FormatText, &flags.format), "format", "f", "output format")
enumWithOptions(&cmd, newEnumSliceValue(diff.ExcludeDiffOptions, nil, &flags.excludeElements), "exclude-elements", "e", "comma-separated list of elements to exclude")
cmd.PersistentFlags().StringVarP(&flags.matchPath, "match-path", "p", "", "include only paths that match this regular expression")
cmd.PersistentFlags().StringVarP(&flags.filterExtension, "filter-extension", "", "", "exclude paths and operations with an OpenAPI Extension matching this regular expression")
cmd.PersistentFlags().IntVarP(&flags.circularReferenceCounter, "max-circular-dep", "", 5, "maximum allowed number of circular dependencies between objects in OpenAPI specs")
Expand All @@ -55,15 +55,20 @@ In 'composed' mode, base and revision can be a glob and oasdiff will compare mat
cmd.PersistentFlags().StringVarP(&flags.stripPrefixBase, "strip-prefix-base", "", "", "strip this prefix from paths in base-spec before comparison")
cmd.PersistentFlags().StringVarP(&flags.stripPrefixRevision, "strip-prefix-revision", "", "", "strip this prefix from paths in revised-spec before comparison")
cmd.PersistentFlags().BoolVarP(&flags.includePathParams, "include-path-params", "", false, "include path parameter names in endpoint matching")
cmd.PersistentFlags().VarP(newEnumValue([]string{LangEn, LangRu}, LangDefault, &flags.lang), "lang", "l", "language for localized output")
enumWithOptions(&cmd, newEnumValue([]string{LangEn, LangRu}, LangDefault, &flags.lang), "lang", "l", "language for localized output")
cmd.PersistentFlags().StringVarP(&flags.errIgnoreFile, "err-ignore", "", "", "configuration file for ignoring errors")
cmd.PersistentFlags().StringVarP(&flags.warnIgnoreFile, "warn-ignore", "", "", "configuration file for ignoring warnings")
cmd.PersistentFlags().VarP(newEnumSliceValue(checker.GetOptionalChecks(), nil, &flags.includeChecks), "include-checks", "i", "comma-separated list of optional checks")
cmd.PersistentFlags().VarP(newEnumSliceValue(checker.GetOptionalChecks(), nil, &flags.includeChecks), "include-checks", "i", "comma-separated list of optional checks (run 'oasdiff checks' to see options)")
cmd.PersistentFlags().IntVarP(&flags.deprecationDaysBeta, "deprecation-days-beta", "", checker.BetaDeprecationDays, "min days required between deprecating a beta resource and removing it")
cmd.PersistentFlags().IntVarP(&flags.deprecationDaysStable, "deprecation-days-stable", "", checker.StableDeprecationDays, "min days required between deprecating a stable resource and removing it")

return &cmd
}

func enumWithOptions(cmd *cobra.Command, value enumVal, name, shorthand, usage string) {
cmd.PersistentFlags().VarP(value, name, shorthand, usage+": "+value.listOf())
}

func runChangelog(flags *ChangelogFlags, stdout io.Writer) (bool, *ReturnError) {
return getChangelog(flags, stdout, checker.INFO, getChangelogTitle)
}
Expand Down
40 changes: 40 additions & 0 deletions internal/checks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package internal

import (
"io"

"github.com/spf13/cobra"
"github.com/tufin/oasdiff/checker"
)

func getChecksCmd() *cobra.Command {

cmd := cobra.Command{
Use: "checks [flags]",
Short: "Display optional checks",
Long: `Display optional checks that can be passed to 'breaking' and 'changelog' with the 'include-checks' flag`,
Args: cobra.NoArgs,
ValidArgsFunction: cobra.NoFileCompletions, // see https://github.com/spf13/cobra/issues/1969
RunE: func(cmd *cobra.Command, args []string) error {

// by now flags have been parsed successfully so we don't need to show usage on any errors
cmd.Root().SilenceUsage = true

if err := runChecks(cmd.OutOrStdout()); err != nil {
setReturnValue(cmd, err.Code)
return err
}

return nil
},
}

return &cmd
}

func runChecks(stdout io.Writer) *ReturnError {
if err := printYAML(stdout, checker.GetOptionalChecks()); err != nil {
return getErrFailedPrint("optional checks YAML", err)
}
return nil
}
4 changes: 2 additions & 2 deletions internal/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func getDiffCmd() *cobra.Command {
Short: "Generate a diff report",
Long: `Generate a diff report between base and revision specs.
Base and revision can be a path to a file or a URL.
In 'composed' mode, base and revision can be a glob and oasdiff will compare mathcing endpoints between the two sets of files.
In 'composed' mode, base and revision can be a glob and oasdiff will compare matching endpoints between the two sets of files.
`,
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
Expand All @@ -46,7 +46,7 @@ In 'composed' mode, base and revision can be a glob and oasdiff will compare mat
}

cmd.PersistentFlags().BoolVarP(&flags.composed, "composed", "c", false, "work in 'composed' mode, compare paths in all specs matching base and revision globs")
cmd.PersistentFlags().VarP(newEnumValue([]string{FormatYAML, FormatJSON, FormatText, FormatHTML}, FormatYAML, &flags.format), "format", "f", "output format: yaml, json, text or html")
enumWithOptions(&cmd, newEnumValue([]string{FormatYAML, FormatJSON, FormatText, FormatHTML}, FormatYAML, &flags.format), "format", "f", "output format")
cmd.PersistentFlags().VarP(newEnumSliceValue(diff.ExcludeDiffOptions, nil, &flags.excludeElements), "exclude-elements", "e", "comma-separated list of elements to exclude")
cmd.PersistentFlags().StringVarP(&flags.matchPath, "match-path", "p", "", "include only paths that match this regular expression")
cmd.PersistentFlags().StringVarP(&flags.filterExtension, "filter-extension", "", "", "exclude paths and operations with an OpenAPI Extension matching this regular expression")
Expand Down
33 changes: 20 additions & 13 deletions internal/enum.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ import (
"golang.org/x/exp/slices"
)

type enumVal interface {
Set(s string) error
String() string
Type() string
listOf() string
}

// enumValue is like stringValue with allowed values
type enumValue struct {
value *string
Expand All @@ -32,24 +39,24 @@ func (v *enumValue) Set(s string) error {
*v.value = s
return nil
}
return fmt.Errorf("must be %s", listOf(v.allowedValues))
return fmt.Errorf("%s is not one of the allowed values: %s", s, v.listOf())
}

func listOf(options []string) string {
l := len(options)
if l == 0 {
return "empty"
}
if l == 1 {
return options[0]
}
if l == 2 {
return options[0] + " or " + options[1]
func (v *enumValue) listOf() string {
l := len(v.allowedValues)
switch l {
case 0:
return "no options available"
case 1:
return v.allowedValues[0]
case 2:
return v.allowedValues[0] + " or " + v.allowedValues[1]
default:
return strings.Join(v.allowedValues[:l-1], ", ") + ", or " + v.allowedValues[l-1]
}
return strings.Join(options[:l-1], ", ") + ", or " + options[l-1]
}

// Type is only used in help text
func (f *enumValue) Type() string {
func (v *enumValue) Type() string {
return "string"
}
28 changes: 21 additions & 7 deletions internal/enum_slice.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,32 +50,46 @@ func writeAsCSV(vals []string) (string, error) {
return strings.TrimSuffix(b.String(), "\n"), nil
}

func checkAllowedValues(values []string, allowed []string) error {
if notAllowed := utils.StringList(values).ToStringSet().Minus(utils.StringList(allowed).ToStringSet()); !notAllowed.Empty() {
func (s *enumSliceValue) checkAllowedValues(values []string) error {
if notAllowed := utils.StringList(values).ToStringSet().Minus(utils.StringList(s.allowedValues).ToStringSet()); !notAllowed.Empty() {
verb := "are"
if len(notAllowed) == 1 {
verb = "is"
}
// TODO: find a better way to document the options
return fmt.Errorf("%s %s not one of the allowed values: %s", strings.Join(notAllowed.ToStringList(), ","), verb, strings.Join(allowed, ","))
return fmt.Errorf("%s %s not one of the allowed values: %s", strings.Join(notAllowed.ToStringList(), ","), verb, s.listOf())
}
return nil
}

func (s *enumSliceValue) listOf() string {
l := len(s.allowedValues)
switch l {
case 0:
return "no options available"
case 1:
return s.allowedValues[0]
case 2:
return s.allowedValues[0] + " or " + s.allowedValues[1]
default:
return strings.Join(s.allowedValues[:l-1], ", ") + ", or " + s.allowedValues[l-1]
}
}

func (s *enumSliceValue) Set(val string) error {
v, err := readAsCSV(val)
value, err := readAsCSV(val)
if err != nil {
return err
}

if err := checkAllowedValues(v, s.allowedValues); err != nil {
if err := s.checkAllowedValues(value); err != nil {
return err
}

if !s.changed {
*s.value = v
*s.value = value
} else {
*s.value = append(*s.value, v...)
*s.value = append(*s.value, value...)
}
s.changed = true
return nil
Expand Down
1 change: 1 addition & 0 deletions internal/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ func Run(args []string, stdout io.Writer, stderr io.Writer) int {
getSummaryCmd(),
getBreakingChangesCmd(),
getChangelogCmd(),
getChecksCmd(),
)

return strategy()(rootCmd)
Expand Down
5 changes: 2 additions & 3 deletions internal/summary.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ func getSummaryCmd() *cobra.Command {
Short: "Generate a diff summary",
Long: `Display a summary of changes between base and revision specs.
Base and revision can be a path to a file or a URL.
In 'composed' mode, base and revision can be a glob and oasdiff will compare mathcing endpoints between the two sets of files.
In 'composed' mode, base and revision can be a glob and oasdiff will compare matching endpoints between the two sets of files.
`,
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
Expand All @@ -42,7 +42,7 @@ In 'composed' mode, base and revision can be a glob and oasdiff will compare mat
}

cmd.PersistentFlags().BoolVarP(&flags.composed, "composed", "c", false, "work in 'composed' mode, compare paths in all specs matching base and revision globs")
cmd.PersistentFlags().VarP(newEnumValue([]string{FormatYAML, FormatJSON}, FormatYAML, &flags.format), "format", "f", "output format: yaml or json")
enumWithOptions(&cmd, newEnumValue([]string{FormatYAML, FormatJSON}, FormatYAML, &flags.format), "format", "f", "output format")
cmd.PersistentFlags().VarP(newEnumSliceValue(diff.ExcludeDiffOptions, nil, &flags.excludeElements), "exclude-elements", "e", "comma-separated list of elements to exclude")
cmd.PersistentFlags().StringVarP(&flags.matchPath, "match-path", "p", "", "include only paths that match this regular expression")
cmd.PersistentFlags().StringVarP(&flags.filterExtension, "filter-extension", "", "", "exclude paths and operations with an OpenAPI Extension matching this regular expression")
Expand Down Expand Up @@ -89,5 +89,4 @@ func outputSummary(stdout io.Writer, diffReport *diff.Diff, format string) *Retu
}

return nil

}

0 comments on commit 34d3dbf

Please sign in to comment.