Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion epoch/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,45 @@ func (vm *VersionManager) GetVersion(c *gin.Context) (string, error) {
// Second, check URL path
matches := vm.versionRegex.FindStringSubmatch(c.Request.URL.Path)
if len(matches) > 1 {
return matches[1], nil
potentialVersion := matches[1]
// Only return as version if it matches a known version (exact or partial match)
if vm.isKnownVersion(potentialVersion) {
return potentialVersion, nil
}
}

// No version found in any location
return "", nil
}

// isKnownVersion checks if the potential version matches any known version
// (either exactly or as a partial match like "v1" matching "v1.0.0")
func (vm *VersionManager) isKnownVersion(potentialVersion string) bool {
// Fast path: exact match
if vm.possibleVersions[potentialVersion] {
return true
}

// Normalize: lowercase and remove 'v' prefix
normalized := strings.TrimPrefix(strings.ToLower(potentialVersion), "v")

for version := range vm.possibleVersions {
normalizedKnown := strings.TrimPrefix(strings.ToLower(version), "v")

// Exact match after normalization (handles "V1.0.0" == "v1.0.0")
if normalizedKnown == normalized {
return true
}

// Partial match: "1" matches "1.0.0", "1.2" matches "1.2.3"
if strings.HasPrefix(normalizedKnown, normalized+".") {
Comment thread
ichung08 marked this conversation as resolved.
return true
}
}

return false
}

// VersionMiddleware handles version detection and context setting
type VersionMiddleware struct {
versionBundle *VersionBundle
Expand Down
32 changes: 32 additions & 0 deletions epoch/middleware_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,38 @@ var _ = Describe("Middleware", func() {
})

Describe("Version Manager Edge Cases", func() {
It("should not treat numeric-prefixed path segments as versions", func() {
manager := NewVersionManager("X-API-Version", []string{"1.0.0", "2.0.0"})

cases := []string{
"/accounts/123-test-org/repositories",
"/users/12345/profile",
"/orgs/99-team/repos",
Comment thread
ichung08 marked this conversation as resolved.
}

for _, path := range cases {
req := httptest.NewRequest("GET", path, nil)
c, _ := gin.CreateTestContext(httptest.NewRecorder())
c.Request = req

version, err := manager.GetVersion(c)
Expect(err).NotTo(HaveOccurred())
Expect(version).To(Equal(""), "path %s should not extract a version", path)
}
})

It("should recognize registered versions in path", func() {
manager := NewVersionManager("X-API-Version", []string{"1.0.0", "2.0.0"})
req := httptest.NewRequest("GET", "/api/1.0.0/users", nil)

c, _ := gin.CreateTestContext(httptest.NewRecorder())
c.Request = req

version, err := manager.GetVersion(c)
Expect(err).NotTo(HaveOccurred())
Expect(version).To(Equal("1.0.0"))
})

It("should handle case-insensitive headers", func() {
manager := NewVersionManager("x-api-version", []string{"1.0.0", "2.0.0"})
req := httptest.NewRequest("GET", "/test", nil)
Expand Down