From 6eee2936caf93db82d8b335cd98c99eedf2c4c1a Mon Sep 17 00:00:00 2001 From: Reuven Harrison Date: Wed, 2 Oct 2024 14:54:35 +0300 Subject: [PATCH] add --unmatch-path flag --- diff/config.go | 3 ++- diff/diff_error_test.go | 2 +- diff/diff_test.go | 2 +- diff/endpoints_diff.go | 2 +- diff/paths_diff.go | 24 ++++++++++++++++-------- docs/FILTERING-ENDPOINTS.md | 7 +++++++ internal/cmd_flags.go | 1 + internal/flags.go | 3 ++- internal/run_test.go | 12 ++++++++++-- internal/viper.go | 1 + 10 files changed, 42 insertions(+), 15 deletions(-) diff --git a/diff/config.go b/diff/config.go index 6001c7a4..0158b3f1 100644 --- a/diff/config.go +++ b/diff/config.go @@ -6,7 +6,8 @@ import ( // Config includes various settings to control the diff type Config struct { - PathFilter string + MatchPath string + UnmatchPath string FilterExtension string PathPrefixBase string PathPrefixRevision string diff --git a/diff/diff_error_test.go b/diff/diff_error_test.go index 848491f3..d700934a 100644 --- a/diff/diff_error_test.go +++ b/diff/diff_error_test.go @@ -239,6 +239,6 @@ func TestDiff_ComponentCallbacksNil(t *testing.T) { } func TestFilterByRegex_Invalid(t *testing.T) { - _, err := diff.Get(&diff.Config{PathFilter: "["}, l(t, 1), l(t, 2)) + _, err := diff.Get(&diff.Config{MatchPath: "["}, l(t, 1), l(t, 2)) require.EqualError(t, err, "failed to compile filter regex \"[\": error parsing regexp: missing closing ]: `[`") } diff --git a/diff/diff_test.go b/diff/diff_test.go index 5420b7be..7d8986e3 100644 --- a/diff/diff_test.go +++ b/diff/diff_test.go @@ -540,7 +540,7 @@ func TestSummaryInvalidComponent(t *testing.T) { } func TestFilterByRegex(t *testing.T) { - d, err := diff.Get(&diff.Config{PathFilter: "x"}, l(t, 1), l(t, 2)) + d, err := diff.Get(&diff.Config{MatchPath: "x"}, l(t, 1), l(t, 2)) require.NoError(t, err) require.Nil(t, d.GetSummary().Details[diff.PathsDetail]) } diff --git a/diff/endpoints_diff.go b/diff/endpoints_diff.go index 83707f4f..eacbaef7 100644 --- a/diff/endpoints_diff.go +++ b/diff/endpoints_diff.go @@ -49,7 +49,7 @@ func getEndpointsDiff(config *Config, state *state, paths1, paths2 *openapi3.Pat return nil, nil } - if err := filterPaths(config.PathFilter, config.FilterExtension, paths1, paths2); err != nil { + if err := filterPaths(config.MatchPath, config.UnmatchPath, config.FilterExtension, paths1, paths2); err != nil { return nil, err } diff --git a/diff/paths_diff.go b/diff/paths_diff.go index 72538015..1e7e1af3 100644 --- a/diff/paths_diff.go +++ b/diff/paths_diff.go @@ -38,7 +38,7 @@ func newPathsDiff() *PathsDiff { func getPathsDiff(config *Config, state *state, paths1, paths2 *openapi3.Paths) (*PathsDiff, error) { - if err := filterPaths(config.PathFilter, config.FilterExtension, paths1, paths2); err != nil { + if err := filterPaths(config.MatchPath, config.UnmatchPath, config.FilterExtension, paths1, paths2); err != nil { return nil, err } @@ -103,9 +103,13 @@ func (pathsDiff *PathsDiff) addModifiedPath(config *Config, state *state, path1 return pathsDiff.Modified.addPathDiff(config, state, path1, pathItemPair) } -func filterPaths(filter, filterExtension string, paths1, paths2 *openapi3.Paths) error { +func filterPaths(matchPath, unmatchPath, filterExtension string, paths1, paths2 *openapi3.Paths) error { - if err := filterPathsByName(filter, paths1, paths2); err != nil { + if err := filterPathsByName(matchPath, true, paths1, paths2); err != nil { + return err + } + + if err := filterPathsByName(unmatchPath, false, paths1, paths2); err != nil { return err } @@ -116,7 +120,7 @@ func filterPaths(filter, filterExtension string, paths1, paths2 *openapi3.Paths) return nil } -func filterPathsByName(filter string, paths1, paths2 *openapi3.Paths) error { +func filterPathsByName(filter string, negate bool, paths1, paths2 *openapi3.Paths) error { if filter == "" { return nil } @@ -126,15 +130,19 @@ func filterPathsByName(filter string, paths1, paths2 *openapi3.Paths) error { return fmt.Errorf("failed to compile filter regex %q: %w", filter, err) } - filterPathsInternal(paths1, r) - filterPathsInternal(paths2, r) + filterPathsInternal(paths1, r, negate) + filterPathsInternal(paths2, r, negate) return nil } -func filterPathsInternal(paths *openapi3.Paths, r *regexp.Regexp) { +func filterPathsInternal(paths *openapi3.Paths, r *regexp.Regexp, negate bool) { for path := range paths.Map() { - if !r.MatchString(path) { + match := r.MatchString(path) + if negate { + match = !match + } + if match { paths.Delete(path) } } diff --git a/docs/FILTERING-ENDPOINTS.md b/docs/FILTERING-ENDPOINTS.md index 525bbead..9c865b20 100644 --- a/docs/FILTERING-ENDPOINTS.md +++ b/docs/FILTERING-ENDPOINTS.md @@ -8,6 +8,13 @@ For example, this diff includes only endpoints containing "/api" in the path: ``` oasdiff diff --match-path "/api" https://raw.githubusercontent.com/Tufin/oasdiff/main/data/openapi-test1.yaml https://raw.githubusercontent.com/Tufin/oasdiff/main/data/openapi-test3.yaml -f text ``` + +Use the `--unmatch-path` option to exclude paths that match the given regular expression. +For example, this diff excludes endpoints containing "beta" in the path: +``` +oasdiff diff --unmatch-path "beta" https://raw.githubusercontent.com/Tufin/oasdiff/main/data/openapi-test1.yaml https://raw.githubusercontent.com/Tufin/oasdiff/main/data/openapi-test3.yaml -f text +``` + Note: If a path contains a callback, the filter will be applied both to the path itself and to the callback path. To include both the path and the callback, use a regular expression with a filter for each level, for example: "path|callback-path" diff --git a/internal/cmd_flags.go b/internal/cmd_flags.go index 0909bb11..e7accfce 100644 --- a/internal/cmd_flags.go +++ b/internal/cmd_flags.go @@ -10,6 +10,7 @@ import ( func addCommonDiffFlags(cmd *cobra.Command) { cmd.PersistentFlags().BoolP("composed", "c", false, "work in 'composed' mode, compare paths in all specs matching base and revision globs") cmd.PersistentFlags().StringP("match-path", "p", "", "include only paths that match this regular expression") + cmd.PersistentFlags().StringP("unmatch-path", "q", "", "exclude paths that match this regular expression") cmd.PersistentFlags().String("filter-extension", "", "exclude paths and operations with an OpenAPI Extension matching this regular expression") cmd.PersistentFlags().String("prefix-base", "", "add this prefix to paths in base-spec before comparison") cmd.PersistentFlags().String("prefix-revision", "", "add this prefix to paths in revised-spec before comparison") diff --git a/internal/flags.go b/internal/flags.go index e8a86274..cef6e84f 100644 --- a/internal/flags.go +++ b/internal/flags.go @@ -20,7 +20,8 @@ func NewFlags() *Flags { func (flags *Flags) toConfig() *diff.Config { config := diff.NewConfig().WithExcludeElements(flags.getExcludeElements()) - config.PathFilter = flags.v.GetString("match-path") + config.MatchPath = flags.v.GetString("match-path") + config.UnmatchPath = flags.v.GetString("unmatch-path") config.FilterExtension = flags.v.GetString("filter-extension") config.PathPrefixBase = flags.v.GetString("prefix-base") config.PathPrefixRevision = flags.v.GetString("prefix-revision") diff --git a/internal/run_test.go b/internal/run_test.go index 18667987..10ceb7c5 100644 --- a/internal/run_test.go +++ b/internal/run_test.go @@ -344,7 +344,7 @@ func Test_CustomSeverityLevelsInvalidFile(t *testing.T) { require.Equal(t, 106, internal.Run(cmdToArgs("oasdiff changelog ../data/openapi-test1.yaml ../data/openapi-test3.yaml --severity-levels ../data/invalid.txt"), io.Discard, io.Discard)) } -func Test_Changelog_WithoutPathFilter(t *testing.T) { +func Test_Changelog_WithoutMatchPath(t *testing.T) { var stdout bytes.Buffer require.Zero(t, internal.Run(cmdToArgs("oasdiff changelog ../data/path-filter/base.yaml ../data/path-filter/revision.yaml --format json"), &stdout, io.Discard)) bc := formatters.Changes{} @@ -352,10 +352,18 @@ func Test_Changelog_WithoutPathFilter(t *testing.T) { require.Len(t, bc, 2) } -func Test_Changelog_WithPathFilter(t *testing.T) { +func Test_Changelog_WithMatchPath(t *testing.T) { var stdout bytes.Buffer require.Zero(t, internal.Run(cmdToArgs("oasdiff changelog ../data/path-filter/base.yaml ../data/path-filter/revision.yaml --format json -p a"), &stdout, io.Discard)) bc := formatters.Changes{} require.NoError(t, json.Unmarshal(stdout.Bytes(), &bc)) require.Len(t, bc, 1) } + +func Test_Changelog_WithUnmatchPath(t *testing.T) { + var stdout bytes.Buffer + require.Zero(t, internal.Run(cmdToArgs("oasdiff changelog ../data/path-filter/base.yaml ../data/path-filter/revision.yaml --format json -q a"), &stdout, io.Discard)) + bc := formatters.Changes{} + require.NoError(t, json.Unmarshal(stdout.Bytes(), &bc)) + require.Len(t, bc, 1) +} diff --git a/internal/viper.go b/internal/viper.go index 0c1610c6..4a76d567 100644 --- a/internal/viper.go +++ b/internal/viper.go @@ -100,6 +100,7 @@ type Config struct { Severity []string `mapstructure:"severity"` Tags []string `mapstructure:"tags"` MatchPath string `mapstructure:"match-path"` + UnmatchPath string `mapstructure:"unmatch-path"` FilterExtension string `mapstructure:"filter-extension"` PrefixBase string `mapstructure:"prefix-base"` PrefixRevision string `mapstructure:"prefix-revision"`