From 366e536958b3e8663d4cda0f3cad21ed3dfb7ee0 Mon Sep 17 00:00:00 2001 From: Anton Kucherov Date: Thu, 18 Jun 2020 17:19:00 +0300 Subject: [PATCH] Add operationId uniqueness check (#732) * Add operationId uniqueness check. * Fix test * Fix linter rules * Move operationsIds map and function to local scope Co-authored-by: Eason Lin Co-authored-by: sdghchj --- parser.go | 73 ++++++++++++++++++++++++++++++++-- parser_test.go | 9 +++++ testdata/duplicated/api/api.go | 17 ++++++++ testdata/duplicated/main.go | 22 ++++++++++ 4 files changed, 118 insertions(+), 3 deletions(-) create mode 100644 testdata/duplicated/api/api.go create mode 100644 testdata/duplicated/main.go diff --git a/parser.go b/parser.go index 2de6f2c3c..9c3c6a44f 100644 --- a/parser.go +++ b/parser.go @@ -141,7 +141,7 @@ func (parser *Parser) ParseAPI(searchDir string, mainAPIFile string) error { Printf("warning: failed to get package name in dir: %s, error: %s", searchDir, err.Error()) } - if err := parser.getAllGoFileInfo(packageDir, searchDir); err != nil { + if err = parser.getAllGoFileInfo(packageDir, searchDir); err != nil { return err } @@ -168,7 +168,7 @@ func (parser *Parser) ParseAPI(searchDir string, mainAPIFile string) error { } } - if err := parser.ParseGeneralAPIInfo(absMainAPIFilePath); err != nil { + if err = parser.ParseGeneralAPIInfo(absMainAPIFilePath); err != nil { return err } @@ -177,7 +177,11 @@ func (parser *Parser) ParseAPI(searchDir string, mainAPIFile string) error { return err } - return parser.packages.RangeFiles(parser.ParseRouterAPIInfo) + if err = parser.packages.RangeFiles(parser.ParseRouterAPIInfo); err != nil { + return err + } + + return parser.checkOperationIDUniqueness() } func getPkgName(searchDir string) (string, error) { @@ -1262,6 +1266,69 @@ func (parser *Parser) parseFile(packageDir, path string, src interface{}) error return nil } +func (parser *Parser) checkOperationIDUniqueness() error { + // operationsIds contains all operationId annotations to check it's unique + operationsIds := make(map[string]string) + saveOperationID := func(operationID, currentPath string) error { + if operationID == "" { + return nil + } + if previousPath, ok := operationsIds[operationID]; ok { + return fmt.Errorf( + "duplicated @id annotation '%s' found in '%s', previously declared in: '%s'", + operationID, currentPath, previousPath) + } + operationsIds[operationID] = currentPath + return nil + } + + for path, itm := range parser.swagger.Paths.Paths { + if itm.Get != nil { + currentPath := fmt.Sprintf("%s %s", "GET", path) + if err := saveOperationID(itm.Get.ID, currentPath); err != nil { + return err + } + } + if itm.Put != nil { + currentPath := fmt.Sprintf("%s %s", "PUT", path) + if err := saveOperationID(itm.Put.ID, currentPath); err != nil { + return err + } + } + if itm.Post != nil { + currentPath := fmt.Sprintf("%s %s", "POST", path) + if err := saveOperationID(itm.Post.ID, currentPath); err != nil { + return err + } + } + if itm.Delete != nil { + currentPath := fmt.Sprintf("%s %s", "DELETE", path) + if err := saveOperationID(itm.Delete.ID, currentPath); err != nil { + return err + } + } + if itm.Options != nil { + currentPath := fmt.Sprintf("%s %s", "OPTIONS", path) + if err := saveOperationID(itm.Options.ID, currentPath); err != nil { + return err + } + } + if itm.Head != nil { + currentPath := fmt.Sprintf("%s %s", "HEAD", path) + if err := saveOperationID(itm.Head.ID, currentPath); err != nil { + return err + } + } + if itm.Patch != nil { + currentPath := fmt.Sprintf("%s %s", "PATCH", path) + if err := saveOperationID(itm.Patch.ID, currentPath); err != nil { + return err + } + } + } + return nil +} + // Skip returns filepath.SkipDir error if match vendor and hidden folder func (parser *Parser) Skip(path string, f os.FileInfo) error { if f.IsDir() { diff --git a/parser_test.go b/parser_test.go index 19471b67f..605455ce6 100644 --- a/parser_test.go +++ b/parser_test.go @@ -1540,6 +1540,15 @@ func TestParseNested(t *testing.T) { assert.Equal(t, string(expected), string(b)) } +func TestParseDuplicated(t *testing.T) { + searchDir := "testdata/duplicated" + mainAPIFile := "main.go" + p := New() + p.ParseDependency = true + err := p.ParseAPI(searchDir, mainAPIFile) + assert.Errorf(t, err, "duplicated @id declarations successfully found") +} + func TestParser_ParseStructArrayObject(t *testing.T) { src := ` package api diff --git a/testdata/duplicated/api/api.go b/testdata/duplicated/api/api.go new file mode 100644 index 000000000..a840fd806 --- /dev/null +++ b/testdata/duplicated/api/api.go @@ -0,0 +1,17 @@ +package api + +import ( + "github.com/gin-gonic/gin" +) + +// @Description get Foo +// @ID get-foo +// @Success 200 {string} string +// @Router /testapi/get-foo [get] +func GetFoo(c *gin.Context) {} + +// @Description post Bar +// @ID get-foo +// @Success 200 {string} string +// @Router /testapi/post-bar [post] +func PostBar(c *gin.Context) {} diff --git a/testdata/duplicated/main.go b/testdata/duplicated/main.go new file mode 100644 index 000000000..75bada3ea --- /dev/null +++ b/testdata/duplicated/main.go @@ -0,0 +1,22 @@ +package composition + +import ( + "github.com/gin-gonic/gin" + + "github.com/swaggo/swag/testdata/duplicated/api" +) + +// @title Swagger Example API +// @version 1.0 +// @description This is a sample server +// @termsOfService http://swagger.io/terms/ + +// @host petstore.swagger.io +// @BasePath /v2 + +func main() { + r := gin.New() + r.GET("/testapi/get-foo", api.GetFoo) + r.POST("/testapi/post-bar", api.PostBar) + r.Run() +}