diff --git a/tools/goctl/api/swagger/annotation.go b/tools/goctl/api/swagger/annotation.go index 7b21bb650..cd6736ea9 100644 --- a/tools/goctl/api/swagger/annotation.go +++ b/tools/goctl/api/swagger/annotation.go @@ -24,10 +24,15 @@ func getFirstUsableString(def ...string) string { } for _, val := range def { - str, err := strconv.Unquote(val) - if err == nil && len(str) != 0 { + // Try to unquote if it's a quoted string + if str, err := strconv.Unquote(val); err == nil && len(str) != 0 { return str } + + // Otherwise, use the value as-is if it's not empty + if len(val) != 0 { + return val + } } return "" diff --git a/tools/goctl/api/swagger/annotation_test.go b/tools/goctl/api/swagger/annotation_test.go index f26b97f93..9f94d8895 100644 --- a/tools/goctl/api/swagger/annotation_test.go +++ b/tools/goctl/api/swagger/annotation_test.go @@ -89,3 +89,108 @@ func Test_getListFromInfoOrDefault(t *testing.T) { assert.Equal(t, []string{"query"}, getListFromInfoOrDefault(unquotedProperties, "tags", []string{"default"})) assert.Equal(t, []string{"default"}, getListFromInfoOrDefault(unquotedProperties, "empty", []string{"default"})) } + +func Test_getFirstUsableString(t *testing.T) { + t.Run("empty input", func(t *testing.T) { + result := getFirstUsableString() + assert.Equal(t, "", result, "should return empty string for no arguments") + }) + + t.Run("single plain string", func(t *testing.T) { + result := getFirstUsableString("Check server health status.") + assert.Equal(t, "Check server health status.", result) + }) + + t.Run("single quoted string", func(t *testing.T) { + // This is how Go would represent a quoted string literal + result := getFirstUsableString(`"Check server health status."`) + assert.Equal(t, "Check server health status.", result, "should unquote quoted strings") + }) + + t.Run("multiple plain strings", func(t *testing.T) { + result := getFirstUsableString("", "second", "third") + assert.Equal(t, "second", result, "should return first non-empty string") + }) + + t.Run("handler name fallback", func(t *testing.T) { + // Simulates the real use case: @doc text, handler name + result := getFirstUsableString("", "HealthCheck") + assert.Equal(t, "HealthCheck", result, "should fallback to handler name") + }) + + t.Run("doc text over handler name", func(t *testing.T) { + // Simulates the real use case with @doc text + result := getFirstUsableString("Check server health status.", "HealthCheck") + assert.Equal(t, "Check server health status.", result, "should use doc text over handler name") + }) + + t.Run("empty strings before valid", func(t *testing.T) { + result := getFirstUsableString("", "", "valid") + assert.Equal(t, "valid", result, "should skip empty strings") + }) + + t.Run("all empty strings", func(t *testing.T) { + result := getFirstUsableString("", "", "") + assert.Equal(t, "", result, "should return empty if all are empty") + }) + + t.Run("quoted then plain", func(t *testing.T) { + result := getFirstUsableString(`"quoted"`, "plain") + assert.Equal(t, "quoted", result, "should unquote first quoted string") + }) + + t.Run("plain then quoted", func(t *testing.T) { + result := getFirstUsableString("plain", `"quoted"`) + assert.Equal(t, "plain", result, "should use first plain string") + }) + + t.Run("invalid quoted string", func(t *testing.T) { + // String that looks quoted but isn't valid Go syntax + result := getFirstUsableString(`"incomplete`, "fallback") + assert.Equal(t, `"incomplete`, result, "should use as-is if unquote fails but not empty") + }) + + t.Run("whitespace only", func(t *testing.T) { + result := getFirstUsableString(" ", "fallback") + assert.Equal(t, " ", result, "should not trim whitespace, return as-is") + }) + + t.Run("real world API doc scenario", func(t *testing.T) { + // This is the actual bug scenario from issue #5229 + atDocText := "Check server health status." + handlerName := "HealthCheck" + + result := getFirstUsableString(atDocText, handlerName) + assert.Equal(t, "Check server health status.", result, + "should use @doc text for API summary") + }) + + t.Run("real world with empty doc", func(t *testing.T) { + // When @doc is empty, should fall back to handler name + atDocText := "" + handlerName := "HealthCheck" + + result := getFirstUsableString(atDocText, handlerName) + assert.Equal(t, "HealthCheck", result, + "should fallback to handler name when @doc is empty") + }) + + t.Run("complex summary with special characters", func(t *testing.T) { + result := getFirstUsableString("Get user by ID: /users/{id}") + assert.Equal(t, "Get user by ID: /users/{id}", result, + "should handle special characters in plain strings") + }) + + t.Run("multiline string", func(t *testing.T) { + result := getFirstUsableString("Line 1\nLine 2") + assert.Equal(t, "Line 1\nLine 2", result, + "should handle multiline strings") + }) + + t.Run("unicode characters", func(t *testing.T) { + result := getFirstUsableString("健康检查", "HealthCheck") + assert.Equal(t, "健康检查", result, + "should handle unicode characters") + }) +} +