Skip to content

Commit

Permalink
read specs from stdin (#396)
Browse files Browse the repository at this point in the history
  • Loading branch information
Reuven Harrison authored Oct 10, 2023
1 parent 11f146e commit 3618a31
Show file tree
Hide file tree
Showing 18 changed files with 380 additions and 170 deletions.
2 changes: 1 addition & 1 deletion checker/checker_deprecation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
)

func open(file string) (*load.SpecInfo, error) {
return load.LoadSpecInfoFromFile(openapi3.NewLoader(), file)
return load.LoadSpecInfo(openapi3.NewLoader(), load.GetSource(file))
}

func getDeprecationFile(file string) string {
Expand Down
4 changes: 2 additions & 2 deletions diff/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,13 @@ func ExampleGetPathsDiff() {
loader := openapi3.NewLoader()
loader.IsExternalRefsAllowed = true

s1, err := load.LoadSpecInfo(loader, "../data/openapi-test1.yaml")
s1, err := load.LoadSpecInfo(loader, load.GetSource("../data/openapi-test1.yaml"))
if err != nil {
fmt.Fprintf(os.Stderr, "failed to load spec with %v", err)
return
}

s2, err := load.LoadSpecInfo(loader, "../data/openapi-test3.yaml")
s2, err := load.LoadSpecInfo(loader, load.GetSource("../data/openapi-test3.yaml"))
if err != nil {
fmt.Fprintf(os.Stderr, "failed to load spec with %v", err)
return
Expand Down
32 changes: 5 additions & 27 deletions internal/breaking_changes.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,31 +15,9 @@ func getBreakingChangesCmd() *cobra.Command {
cmd := cobra.Command{
Use: "breaking base revision [flags]",
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 matching endpoints between the two sets of files.
`,
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {

flags.base = args[0]
flags.revision = args[1]

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

failEmpty, err := runBreakingChanges(&flags, cmd.OutOrStdout())
if err != nil {
setReturnValue(cmd, err.Code)
return err
}

if failEmpty {
setReturnValue(cmd, 1)
}

return nil
},
Long: "Display breaking changes between base and revision specs." + specHelp,
Args: getParseArgs(&flags),
RunE: getRun(&flags, runBreakingChanges),
}

cmd.PersistentFlags().BoolVarP(&flags.composed, "composed", "c", false, "work in 'composed' mode, compare paths in all specs matching base and revision globs")
Expand All @@ -58,14 +36,14 @@ In 'composed' mode, base and revision can be a glob and oasdiff will compare mat
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")
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().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 runBreakingChanges(flags *ChangelogFlags, stdout io.Writer) (bool, *ReturnError) {
func runBreakingChanges(flags Flags, stdout io.Writer) (bool, *ReturnError) {
return getChangelog(flags, stdout, checker.WARN, getBreakingChangesTitle)
}

Expand Down
48 changes: 13 additions & 35 deletions internal/changelog.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,31 +17,9 @@ func getChangelogCmd() *cobra.Command {
cmd := cobra.Command{
Use: "changelog base revision [flags]",
Short: "Display changelog",
Long: `Display a changelog 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.
`,
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {

flags.base = args[0]
flags.revision = args[1]

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

failEmpty, err := runChangelog(&flags, cmd.OutOrStdout())
if err != nil {
setReturnValue(cmd, err.Code)
return err
}

if failEmpty {
setReturnValue(cmd, 1)
}

return nil
},
Long: "Display a changelog between base and revision specs." + specHelp,
Args: getParseArgs(&flags),
RunE: getRun(&flags, runChangelog),
}

cmd.PersistentFlags().BoolVarP(&flags.composed, "composed", "c", false, "work in 'composed' mode, compare paths in all specs matching base and revision globs")
Expand Down Expand Up @@ -70,38 +48,38 @@ func enumWithOptions(cmd *cobra.Command, value enumVal, name, shorthand, usage s
cmd.PersistentFlags().VarP(value, name, shorthand, usage+": "+value.listOf())
}

func runChangelog(flags *ChangelogFlags, stdout io.Writer) (bool, *ReturnError) {
func runChangelog(flags Flags, stdout io.Writer) (bool, *ReturnError) {
return getChangelog(flags, stdout, checker.INFO, getChangelogTitle)
}

func getChangelog(flags *ChangelogFlags, stdout io.Writer, level checker.Level, getOutputTitle GetOutputTitle) (bool, *ReturnError) {
func getChangelog(flags Flags, stdout io.Writer, level checker.Level, getOutputTitle GetOutputTitle) (bool, *ReturnError) {

openapi3.CircularReferenceCounter = flags.circularReferenceCounter
openapi3.CircularReferenceCounter = flags.getCircularReferenceCounter()

diffReport, operationsSources, err := calcDiff(flags)
if err != nil {
return false, err
}

bcConfig := checker.GetAllChecks(flags.includeChecks, flags.deprecationDaysBeta, flags.deprecationDaysStable)
bcConfig.Localize = checker.NewLocalizer(flags.lang, LangDefault)
bcConfig := checker.GetAllChecks(flags.getIncludeChecks(), flags.getDeprecationDaysBeta(), flags.getDeprecationDaysStable())
bcConfig.Localize = checker.NewLocalizer(flags.getLang(), LangDefault)

errs, returnErr := filterIgnored(
checker.CheckBackwardCompatibilityUntilLevel(bcConfig, diffReport, operationsSources, level),
flags.warnIgnoreFile, flags.errIgnoreFile)
flags.getWarnIgnoreFile(), flags.getErrIgnoreFile())

if returnErr != nil {
return false, returnErr
}

if returnErr := outputChangelog(bcConfig, flags.format, stdout, errs, getOutputTitle); returnErr != nil {
if returnErr := outputChangelog(bcConfig, flags.getFormat(), stdout, errs, getOutputTitle); returnErr != nil {
return false, returnErr
}

if flags.failOn != "" {
level, err := checker.NewLevel(flags.failOn)
if flags.getFailOn() != "" {
level, err := checker.NewLevel(flags.getFailOn())
if err != nil {
return false, getErrInvalidFlags(fmt.Errorf("invalid fail-on value %s", flags.failOn))
return false, getErrInvalidFlags(fmt.Errorf("invalid fail-on value %s", flags.getFailOn()))
}
return errs.HasLevelOrHigher(level), nil
}
Expand Down
61 changes: 57 additions & 4 deletions internal/changelog_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ package internal

import (
"github.com/tufin/oasdiff/diff"
"github.com/tufin/oasdiff/load"
)

type ChangelogFlags struct {
base string
revision string
base load.Source
revision load.Source
composed bool
prefixBase string
prefixRevision string
Expand Down Expand Up @@ -45,14 +46,66 @@ func (flags *ChangelogFlags) getComposed() bool {
return flags.composed
}

func (flags *ChangelogFlags) getBase() string {
func (flags *ChangelogFlags) getBase() load.Source {
return flags.base
}

func (flags *ChangelogFlags) getRevision() string {
func (flags *ChangelogFlags) getRevision() load.Source {
return flags.revision
}

func (flags *ChangelogFlags) getFlatten() bool {
return flags.flatten
}

func (flags *ChangelogFlags) getCircularReferenceCounter() int {
return flags.circularReferenceCounter
}

func (flags *ChangelogFlags) getIncludeChecks() []string {
return flags.includeChecks
}

func (flags *ChangelogFlags) getDeprecationDaysBeta() int {
return flags.deprecationDaysBeta
}

func (flags *ChangelogFlags) getDeprecationDaysStable() int {
return flags.deprecationDaysStable
}

func (flags *ChangelogFlags) getLang() string {
return flags.lang
}

func (flags *ChangelogFlags) getWarnIgnoreFile() string {
return flags.warnIgnoreFile
}

func (flags *ChangelogFlags) getErrIgnoreFile() string {
return flags.errIgnoreFile
}

func (flags *ChangelogFlags) getFormat() string {
return flags.format
}

func (flags *ChangelogFlags) getFailOn() string {
return flags.failOn
}

func (flags *ChangelogFlags) getFailOnDiff() bool {
return false
}

func (flags *ChangelogFlags) setBase(source load.Source) {
flags.base = source
}

func (flags *ChangelogFlags) setRevision(source load.Source) {
flags.revision = source
}

func (flags *ChangelogFlags) addExcludeElements(element string) {
flags.excludeElements = append(flags.excludeElements, element)
}
65 changes: 24 additions & 41 deletions internal/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,31 +19,9 @@ func getDiffCmd() *cobra.Command {
cmd := cobra.Command{
Use: "diff base revision [flags]",
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 matching endpoints between the two sets of files.
`,
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {

flags.base = args[0]
flags.revision = args[1]

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

failEmpty, err := runDiff(&flags, cmd.OutOrStdout())
if err != nil {
setReturnValue(cmd, err.Code)
return err
}

if failEmpty {
setReturnValue(cmd, 1)
}

return nil
},
Long: "Generate a diff report between base and revision specs." + specHelp,
Args: getParseArgs(&flags),
RunE: getRun(&flags, runDiff),
}

cmd.PersistentFlags().BoolVarP(&flags.composed, "composed", "c", false, "work in 'composed' mode, compare paths in all specs matching base and revision globs")
Expand All @@ -63,24 +41,24 @@ In 'composed' mode, base and revision can be a glob and oasdiff will compare mat
return &cmd
}

func runDiff(flags *DiffFlags, stdout io.Writer) (bool, *ReturnError) {
func runDiff(flags Flags, stdout io.Writer) (bool, *ReturnError) {

openapi3.CircularReferenceCounter = flags.circularReferenceCounter
openapi3.CircularReferenceCounter = flags.getCircularReferenceCounter()

if flags.format == FormatJSON {
flags.excludeElements = append(flags.excludeElements, diff.ExcludeEndpointsOption)
if flags.getFormat() == FormatJSON {
flags.addExcludeElements(diff.ExcludeEndpointsOption)
}

diffReport, _, err := calcDiff(flags)
if err != nil {
return false, err
}

if err := outputDiff(stdout, diffReport, flags.format); err != nil {
if err := outputDiff(stdout, diffReport, flags.getFormat()); err != nil {
return false, err
}

return flags.failOnDiff && !diffReport.Empty(), nil
return flags.getFailOnDiff() && !diffReport.Empty(), nil
}

func outputDiff(stdout io.Writer, diffReport *diff.Diff, format string) *ReturnError {
Expand Down Expand Up @@ -131,12 +109,17 @@ func normalDiff(loader load.Loader, flags Flags) (*diff.Diff, *diff.OperationsSo
return nil, nil, getErrFailedToLoadSpec("revision", flags.getRevision(), err)
}

if flags.getBase().Stdin && flags.getRevision().Stdin {
// io.ReadAll can only read stdin once, so in this edge case, we copy base into revision
s2.Spec = s1.Spec
}

if flags.getFlatten() {
if err := mergeAllOf("base", []*load.SpecInfo{s1}); err != nil {
if err := mergeAllOf("base", []*load.SpecInfo{s1}, flags.getBase()); err != nil {
return nil, nil, err
}

if err := mergeAllOf("revision", []*load.SpecInfo{s2}); err != nil {
if err := mergeAllOf("revision", []*load.SpecInfo{s2}, flags.getRevision()); err != nil {
return nil, nil, err
}
}
Expand All @@ -150,22 +133,22 @@ func normalDiff(loader load.Loader, flags Flags) (*diff.Diff, *diff.OperationsSo
}

func composedDiff(loader load.Loader, flags Flags) (*diff.Diff, *diff.OperationsSourcesMap, *ReturnError) {
s1, err := load.FromGlob(loader, flags.getBase())
s1, err := load.FromGlob(loader, flags.getBase().Path)
if err != nil {
return nil, nil, getErrFailedToLoadSpecs("base", flags.getBase(), err)
return nil, nil, getErrFailedToLoadSpecs("base", flags.getBase().Path, err)
}

s2, err := load.FromGlob(loader, flags.getRevision())
s2, err := load.FromGlob(loader, flags.getRevision().Path)
if err != nil {
return nil, nil, getErrFailedToLoadSpecs("revision", flags.getRevision(), err)
return nil, nil, getErrFailedToLoadSpecs("revision", flags.getRevision().Path, err)
}

if flags.getFlatten() {
if err := mergeAllOf("base", s1); err != nil {
if err := mergeAllOf("base", s1, flags.getBase()); err != nil {
return nil, nil, err
}

if err := mergeAllOf("revision", s2); err != nil {
if err := mergeAllOf("revision", s2, flags.getRevision()); err != nil {
return nil, nil, err
}
}
Expand All @@ -178,13 +161,13 @@ func composedDiff(loader load.Loader, flags Flags) (*diff.Diff, *diff.Operations
return diffReport, operationsSources, nil
}

func mergeAllOf(title string, specInfos []*load.SpecInfo) *ReturnError {
func mergeAllOf(title string, specInfos []*load.SpecInfo, source load.Source) *ReturnError {

var err error

for _, specInfo := range specInfos {
if specInfo.Spec, err = flatten.MergeSpec(specInfo.Spec); err != nil {
return getErrFailedToFlattenSpec(title, specInfo.Url, err)
return getErrFailedToFlattenSpec(title, source, err)
}
}

Expand Down
Loading

0 comments on commit 3618a31

Please sign in to comment.