diff --git a/pkg/aggregator/aggregator.go b/pkg/aggregator/aggregator.go index 4f325f86d..94bd24131 100644 --- a/pkg/aggregator/aggregator.go +++ b/pkg/aggregator/aggregator.go @@ -62,15 +62,22 @@ func FilterSpecByPathsWithoutSideEffects(sp *spec.Swagger, keepPathPrefixes []st // it is unused because of a path prune. initialUsedDefinitions := usedDefinitionForSpec(sp) + // Ensure prefixes ends with a "/", so that "/v1beta1" won't be treated as + // having the prefix "/v1" for example. + var normalizedPrefixes []string + for _, p := range keepPathPrefixes { + normalizedPrefixes = append(normalizedPrefixes, ensureTailingSlash(p)) + } + // First remove unwanted paths - prefixes := util.NewTrie(keepPathPrefixes) + prefixes := util.NewTrie(normalizedPrefixes) ret := *sp ret.Paths = &spec.Paths{ VendorExtensible: sp.Paths.VendorExtensible, Paths: map[string]spec.PathItem{}, } for path, pathItem := range sp.Paths.Paths { - if !prefixes.HasPrefix(path) { + if !prefixes.HasPrefix(ensureTailingSlash(path)) { continue } ret.Paths.Paths[path] = pathItem @@ -90,6 +97,13 @@ func FilterSpecByPathsWithoutSideEffects(sp *spec.Swagger, keepPathPrefixes []st return &ret } +func ensureTailingSlash(path string) string { + if strings.HasSuffix(path, "/") { + return path + } + return path + "/" +} + // renameDefinitions renames definition references, without mutating the input. // The output might share data structures with the input. func renameDefinitions(s *spec.Swagger, renames map[string]string) *spec.Swagger { diff --git a/pkg/aggregator/aggregator_test.go b/pkg/aggregator/aggregator_test.go index 62b871d36..99f404d66 100644 --- a/pkg/aggregator/aggregator_test.go +++ b/pkg/aggregator/aggregator_test.go @@ -253,6 +253,76 @@ definitions: ast.Equal(DebugSpec{orig_spec1}, DebugSpec{spec1}, "unexpected mutation of input") } +var trailingSlashesTestCases = []struct { + input string + prefix string + shouldKeep bool +}{ + {"/test", "/test", true}, + {"/test/", "/test/", true}, + {"/test", "/test/", true}, + {"/test/", "/test", true}, + {"/testv1", "/test", false}, + {"/testv1", "/test/", false}, +} + +func TestFilterSpecsTrailingSlashes(t *testing.T) { + specTemplate := ` +swagger: "2.0" +paths: + %s: + post: + tags: + - "test" + summary: "Test API" + operationId: "addTest" + parameters: + - in: "body" + name: "body" + description: "test object" + required: true + schema: + $ref: "#/definitions/Test" + responses: + 405: + description: "Invalid input" + $ref: "#/definitions/InvalidInput" +definitions: + Test: + type: "object" + properties: + id: + type: "integer" + format: "int64" + status: + type: "string" + description: "Status" + InvalidInput: + type: "string" + format: "string" + Unused: + type: "object" + ` + + for _, tc := range trailingSlashesTestCases { + var spec, specFiltered *spec.Swagger + + yaml.Unmarshal([]byte(fmt.Sprintf(specTemplate, tc.input)), &spec) + yaml.Unmarshal([]byte(` +swagger: "2.0" +paths: +`), &specFiltered) + + ast := assert.New(t) + filtered := FilterSpecByPathsWithoutSideEffects(spec, []string{tc.prefix}) + if tc.shouldKeep { + ast.Equal(DebugSpec{filtered}, DebugSpec{spec}, "input=%q, prefix=%q, should be included", tc.input, tc.prefix) + } else { + ast.Equal(DebugSpec{spec}, DebugSpec{spec}, "input=%q, prefix=%q, should not be included", tc.input, tc.prefix) + } + } +} + func TestMergeSpecsSimple(t *testing.T) { var spec1, spec2, expected *spec.Swagger require.NoError(t, yaml.Unmarshal([]byte(`