From b09d3f53763d1a70fcbe7bf3689c29df44948255 Mon Sep 17 00:00:00 2001 From: Bruce A Downs Date: Wed, 18 Sep 2019 01:43:05 -0700 Subject: [PATCH 01/84] Correct error in checking for primitive data type (#516) * Add swag binary to git ignore * Correct error in checking for primitive data type * check if ref type is a GO primitive --- .gitignore | 3 +++ operation.go | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 0c958b98c..bea8db681 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,6 @@ cover.out # Etc .DS_Store + +swag +swag.exe diff --git a/operation.go b/operation.go index e977cdb28..ba5184360 100644 --- a/operation.go +++ b/operation.go @@ -538,7 +538,7 @@ func findTypeDef(importPath, typeName string) (*ast.TypeSpec, error) { var responsePattern = regexp.MustCompile(`([\d]+)[\s]+([\w\{\}]+)[\s]+([\w\-\.\/]+)[^"]*(.*)?`) -// ParseResponseComment parses comment for gived `response` comment string. +// ParseResponseComment parses comment for given `response` comment string. func (operation *Operation) ParseResponseComment(commentLine string, astFile *ast.File) error { var matches []string @@ -563,7 +563,7 @@ func (operation *Operation) ParseResponseComment(commentLine string, astFile *as schemaType := strings.Trim(matches[2], "{}") refType := matches[3] - if !IsPrimitiveType(refType) && !strings.Contains(refType, ".") { + if !IsGolangPrimitiveType(refType) && !strings.Contains(refType, ".") { currentPkgName := astFile.Name.String() refType = currentPkgName + "." + refType } From efcfb3d7926528b7906c1945458e8d16a31d5d9b Mon Sep 17 00:00:00 2001 From: Laurens van den Brink Date: Mon, 23 Sep 2019 11:57:10 +0200 Subject: [PATCH 02/84] support for read only, test by adding read only attribute to id of pet (#529) --- parser.go | 25 +++++++++++++++++++++---- parser_test.go | 1 + testdata/simple/web/handler.go | 2 +- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/parser.go b/parser.go index 3deabf746..11618acf0 100644 --- a/parser.go +++ b/parser.go @@ -751,6 +751,7 @@ type structField struct { arrayType string formatType string isRequired bool + readOnly bool crossPkg string exampleValue interface{} maximum *float64 @@ -835,6 +836,9 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st Ref: jsonreference.MustCreateRef("#/definitions/" + pkgName + "." + structField.schemaType), }, }, + SwaggerSchemaProps: spec.SwaggerSchemaProps{ + ReadOnly: structField.readOnly, + }, } } else if structField.schemaType == "array" { // array field type // if defined -- ref it @@ -855,6 +859,9 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st }, }, }, + SwaggerSchemaProps: spec.SwaggerSchemaProps{ + ReadOnly: structField.readOnly, + }, } } else if structField.arrayType == "object" { // Anonymous struct @@ -883,7 +890,11 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st }, }, }, - }} + }, + SwaggerSchemaProps: spec.SwaggerSchemaProps{ + ReadOnly: structField.readOnly, + }, + } } } } else { @@ -914,7 +925,8 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st }, }, SwaggerSchemaProps: spec.SwaggerSchemaProps{ - Example: structField.exampleValue, + Example: structField.exampleValue, + ReadOnly: structField.readOnly, }, } } @@ -937,7 +949,8 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st Default: structField.defaultValue, }, SwaggerSchemaProps: spec.SwaggerSchemaProps{ - Example: structField.exampleValue, + Example: structField.exampleValue, + ReadOnly: structField.readOnly, }, VendorExtensible: spec.VendorExtensible{ Extensions: structField.extensions, @@ -989,7 +1002,8 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st Default: structField.defaultValue, }, SwaggerSchemaProps: spec.SwaggerSchemaProps{ - Example: structField.exampleValue, + Example: structField.exampleValue, + ReadOnly: structField.readOnly, }, } } @@ -1185,6 +1199,9 @@ func (parser *Parser) parseField(field *ast.Field) (*structField, error) { } structField.minLength = minLength } + if readOnly := structTag.Get("readonly"); readOnly != "" { + structField.readOnly = readOnly == "true" + } return structField, nil } diff --git a/parser_test.go b/parser_test.go index 8818c3d3b..fc17dd5a6 100644 --- a/parser_test.go +++ b/parser_test.go @@ -669,6 +669,7 @@ func TestParseSimpleApi1(t *testing.T) { "id": { "type": "integer", "format": "int64", + "readOnly": true, "example": 1 }, "int_array": { diff --git a/testdata/simple/web/handler.go b/testdata/simple/web/handler.go index 4b14ac63c..ece1628dc 100644 --- a/testdata/simple/web/handler.go +++ b/testdata/simple/web/handler.go @@ -9,7 +9,7 @@ import ( ) type Pet struct { - ID int `json:"id" example:"1" format:"int64"` + ID int `json:"id" example:"1" format:"int64" readonly:"true"` Category struct { ID int `json:"id" example:"1"` Name string `json:"name" example:"category_name"` From e0c8bbb526b418ef2da9f2e91d768195467e066b Mon Sep 17 00:00:00 2001 From: pei Date: Wed, 25 Sep 2019 10:05:46 +0900 Subject: [PATCH 03/84] chore: use github action (#528) * use github action --- .github/workflows/ci.yml | 24 ++++++++++++++++++++++++ Makefile | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..c34dea456 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,24 @@ +on: push +jobs: + test: + strategy: + matrix: + go: [ '1.10.x', '1.11.x', '1.12.x' ] + platform: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.platform }} + steps: + - uses: actions/checkout@master + - name: Set up Go + uses: actions/setup-go@v1 + with: + go-version: ${{ matrix.go }} + - name: deps + run: make deps + - name: static program analysis + run: | + export PATH=$PATH:$(go env GOPATH)/bin # https://github.com/actions/setup-go/issues/14 + make fmt-check lint vet + - name: build + run: make build + - name: test + run: make test diff --git a/Makefile b/Makefile index b6976ec76..75f950268 100644 --- a/Makefile +++ b/Makefile @@ -59,7 +59,7 @@ deps: .PHONY: devel-deps devel-deps: - GO111MODULE=off $(GOGET) -v ${u} \ + GO111MODULE=off $(GOGET) -v -u \ golang.org/x/lint/golint \ github.com/swaggo/swag/cmd/swag \ github.com/swaggo/swag/gen From c9f403a30434dc43903a02f80bcf736f11169a86 Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Sat, 28 Sep 2019 19:08:45 +0800 Subject: [PATCH 04/84] chore: jump version to v1.6.3 (#531) --- version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.go b/version.go index d8852ac13..7e69b3750 100644 --- a/version.go +++ b/version.go @@ -1,4 +1,4 @@ package swag // Version of swag -const Version = "v1.6.2" +const Version = "v1.6.3" From 008c37baa77bf879a819e7a8eea45a487ec0a178 Mon Sep 17 00:00:00 2001 From: Bogdan U Date: Mon, 14 Oct 2019 18:08:07 +0300 Subject: [PATCH 05/84] chore(refactor): improve code quality (#540) --- cmd/swag/main.go | 135 +++--- example/basic/docs/docs.go | 27 +- gen/gen.go | 54 ++- gen/gen_test.go | 124 +++-- testdata/simple2/docs/docs.go | 524 --------------------- testdata/simple2/docs/swagger/swagger.json | 478 ------------------- testdata/simple2/docs/swagger/swagger.yaml | 326 ------------- testdata/simple3/docs/docs.go | 497 ------------------- testdata/simple3/docs/swagger/swagger.json | 451 ------------------ testdata/simple3/docs/swagger/swagger.yaml | 308 ------------ 10 files changed, 207 insertions(+), 2717 deletions(-) delete mode 100644 testdata/simple2/docs/docs.go delete mode 100644 testdata/simple2/docs/swagger/swagger.json delete mode 100644 testdata/simple2/docs/swagger/swagger.yaml delete mode 100644 testdata/simple3/docs/docs.go delete mode 100644 testdata/simple3/docs/swagger/swagger.json delete mode 100644 testdata/simple3/docs/swagger/swagger.yaml diff --git a/cmd/swag/main.go b/cmd/swag/main.go index fd3a67c76..83ba81bf6 100644 --- a/cmd/swag/main.go +++ b/cmd/swag/main.go @@ -10,13 +10,71 @@ import ( "github.com/urfave/cli" ) -const searchDirFlag = "dir" -const generalInfoFlag = "generalInfo" -const propertyStrategyFlag = "propertyStrategy" -const outputFlag = "output" -const parseVendorFlag = "parseVendor" -const parseDependency = "parseDependency" -const markdownFilesDirFlag = "markdownFiles" +const ( + searchDirFlag = "dir" + generalInfoFlag = "generalInfo" + propertyStrategyFlag = "propertyStrategy" + outputFlag = "output" + parseVendorFlag = "parseVendor" + parseDependencyFlag = "parseDependency" + markdownFilesFlag = "markdownFiles" +) + +var initFlags = []cli.Flag{ + cli.StringFlag{ + Name: generalInfoFlag + ", g", + Value: "main.go", + Usage: "Go file path in which 'swagger general API Info' is written", + }, + cli.StringFlag{ + Name: searchDirFlag + ", d", + Value: "./", + Usage: "Directory you want to parse", + }, + cli.StringFlag{ + Name: propertyStrategyFlag + ", p", + Value: "camelcase", + Usage: "Property Naming Strategy like snakecase,camelcase,pascalcase", + }, + cli.StringFlag{ + Name: outputFlag + ", o", + Value: "./docs", + Usage: "Output directory for al the generated files(swagger.json, swagger.yaml and doc.go)", + }, + cli.BoolFlag{ + Name: parseVendorFlag, + Usage: "Parse go files in 'vendor' folder, disabled by default", + }, + cli.BoolFlag{ + Name: parseDependencyFlag, + Usage: "Parse go files in outside dependency folder, disabled by default", + }, + cli.StringFlag{ + Name: markdownFilesFlag + ", md", + Value: "", + Usage: "Parse folder containing markdown files to use as description, disabled by default", + }, +} + +func initAction(c *cli.Context) error { + strategy := c.String(propertyStrategyFlag) + + switch strategy { + case swag.CamelCase, swag.SnakeCase, swag.PascalCase: + default: + return fmt.Errorf("not supported %s propertyStrategy", strategy) + } + + return gen.New().Build(&gen.Config{ + SearchDir: c.String(searchDirFlag), + MainAPIFile: c.String(generalInfoFlag), + PropNamingStrategy: strategy, + OutputDir: c.String(outputFlag), + ParseVendor: c.Bool(parseVendorFlag), + ParseDependency: c.Bool(parseDependencyFlag), + MarkdownFilesDir: c.String(markdownFilesFlag), + }) +} func main() { app := cli.NewApp() @@ -27,69 +85,10 @@ func main() { Name: "init", Aliases: []string{"i"}, Usage: "Create docs.go", - Action: func(c *cli.Context) error { - searchDir := c.String(searchDirFlag) - mainAPIFile := c.String(generalInfoFlag) - strategy := c.String(propertyStrategyFlag) - outputDir := c.String(outputFlag) - parseVendor := c.Bool(parseVendorFlag) - parseDependency := c.Bool(parseDependency) - markdownFilesDir := c.String(markdownFilesDirFlag) - - switch strategy { - case swag.CamelCase, swag.SnakeCase, swag.PascalCase: - default: - return fmt.Errorf("not supported %s propertyStrategy", strategy) - } - - return gen.New().Build(&gen.Config{ - SearchDir: searchDir, - MainAPIFile: mainAPIFile, - PropNamingStrategy: strategy, - OutputDir: outputDir, - ParseVendor: parseVendor, - ParseDependency: parseDependency, - MarkdownFilesDir: markdownFilesDir, - }) - }, - Flags: []cli.Flag{ - cli.StringFlag{ - Name: "generalInfo, g", - Value: "main.go", - Usage: "Go file path in which 'swagger general API Info' is written", - }, - cli.StringFlag{ - Name: "dir, d", - Value: "./", - Usage: "Directory you want to parse", - }, - cli.StringFlag{ - Name: "propertyStrategy, p", - Value: "camelcase", - Usage: "Property Naming Strategy like snakecase,camelcase,pascalcase", - }, - cli.StringFlag{ - Name: "output, o", - Value: "./docs", - Usage: "Output directory for al the generated files(swagger.json, swagger.yaml and doc.go)", - }, - cli.BoolFlag{ - Name: "parseVendor", - Usage: "Parse go files in 'vendor' folder, disabled by default", - }, - cli.BoolFlag{ - Name: "parseDependency", - Usage: "Parse go files in outside dependency folder, disabled by default", - }, - cli.StringFlag{ - Name: "markdownFiles, md", - Value: "", - Usage: "Parse folder containing markdown files to use as description, disabled by default", - }, - }, + Action: initAction, + Flags: initFlags, }, } - err := app.Run(os.Args) if err != nil { log.Fatal(err) diff --git a/example/basic/docs/docs.go b/example/basic/docs/docs.go index 5d436867e..472deb997 100644 --- a/example/basic/docs/docs.go +++ b/example/basic/docs/docs.go @@ -1,12 +1,13 @@ // GENERATED BY THE COMMAND ABOVE; DO NOT EDIT // This file was generated by swaggo/swag at -// 2019-07-17 15:34:26.038643394 +0300 EEST m=+0.024553500 +// 2019-10-09 20:20:57.769794778 +0300 EEST m=+0.013594334 package docs import ( "bytes" "encoding/json" + "strings" "github.com/alecthomas/template" "github.com/swaggo/swag" @@ -16,8 +17,8 @@ var doc = `{ "schemes": {{ marshal .Schemes }}, "swagger": "2.0", "info": { - "description": "This is a sample server Petstore server.", - "title": "Swagger Example API", + "description": "{{.Description}}", + "title": "{{.Title}}", "termsOfService": "http://swagger.io/terms/", "contact": { "name": "API Support", @@ -28,10 +29,10 @@ var doc = `{ "name": "Apache 2.0", "url": "http://www.apache.org/licenses/LICENSE-2.0.html" }, - "version": "1.0" + "version": "{{.Version}}" }, - "host": "petstore.swagger.io", - "basePath": "/v2", + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", "paths": { "/file/upload": { "post": { @@ -273,11 +274,21 @@ type swaggerInfo struct { } // SwaggerInfo holds exported Swagger Info so clients can modify it -var SwaggerInfo = swaggerInfo{Schemes: []string{}} +var SwaggerInfo = swaggerInfo{ + Version: "1.0", + Host: "petstore.swagger.io", + BasePath: "/v2", + Schemes: []string{}, + Title: "Swagger Example API", + Description: "This is a sample server Petstore server.", +} type s struct{} func (s *s) ReadDoc() string { + sInfo := SwaggerInfo + sInfo.Description = strings.Replace(sInfo.Description, "\n", "\\n", -1) + t, err := template.New("swagger_info").Funcs(template.FuncMap{ "marshal": func(v interface{}) string { a, _ := json.Marshal(v) @@ -289,7 +300,7 @@ func (s *s) ReadDoc() string { } var tpl bytes.Buffer - if err := t.Execute(&tpl, SwaggerInfo); err != nil { + if err := t.Execute(&tpl, sInfo); err != nil { return doc } diff --git a/gen/gen.go b/gen/gen.go index 7fed9d041..73c026499 100644 --- a/gen/gen.go +++ b/gen/gen.go @@ -20,11 +20,19 @@ import ( ) // Gen presents a generate tool for swag. -type Gen struct{} +type Gen struct { + jsonIndent func(data interface{}) ([]byte, error) + jsonToYAML func(data []byte) ([]byte, error) +} // New creates a new Gen. func New() *Gen { - return &Gen{} + return &Gen{ + jsonIndent: func(data interface{}) ([]byte, error) { + return json.MarshalIndent(data, "", " ") + }, + jsonToYAML: yaml.JSONToYAML, + } } // Config presents Gen configurations. @@ -77,34 +85,28 @@ func (g *Gen) Build(config *Config) error { return err } - docs, err := os.Create(path.Join(config.OutputDir, "docs.go")) - if err != nil { - return err - } - defer docs.Close() + docFileName := path.Join(config.OutputDir, "docs.go") + jsonFileName := path.Join(config.OutputDir, "swagger.json") + yamlFileName := path.Join(config.OutputDir, "swagger.yaml") - swaggerJSON, err := os.Create(path.Join(config.OutputDir, "swagger.json")) + docs, err := os.Create(docFileName) if err != nil { return err } - defer swaggerJSON.Close() - - if _, err := swaggerJSON.Write(b); err != nil { - return err - } + defer docs.Close() - swaggerYAML, err := os.Create(path.Join(config.OutputDir, "swagger.yaml")) + err = g.writeFile(b, jsonFileName) if err != nil { return err } - defer swaggerYAML.Close() - y, err := yaml.JSONToYAML(b) + y, err := g.jsonToYAML(b) if err != nil { return fmt.Errorf("cannot covert json to yaml error: %s", err) } - if _, err := swaggerYAML.Write(y); err != nil { + err = g.writeFile(y, yamlFileName) + if err != nil { return err } @@ -114,15 +116,22 @@ func (g *Gen) Build(config *Config) error { return err } - log.Printf("create docs.go at %+v", docs.Name()) - log.Printf("create swagger.json at %+v", swaggerJSON.Name()) - log.Printf("create swagger.yaml at %+v", swaggerYAML.Name()) + log.Printf("create docs.go at %+v", docFileName) + log.Printf("create swagger.json at %+v", jsonFileName) + log.Printf("create swagger.yaml at %+v", yamlFileName) return nil } -func (g *Gen) jsonIndent(data interface{}) ([]byte, error) { - return json.MarshalIndent(data, "", " ") +func (g *Gen) writeFile(b []byte, file string) error { + f, err := os.Create(file) + if err != nil { + return err + } + defer f.Close() + + _, err = f.Write(b) + return err } func (g *Gen) formatSource(src []byte) []byte { @@ -213,7 +222,6 @@ func (g *Gen) writeGoDoc(output io.Writer, swagger *spec.Swagger) error { // write _, err = output.Write(code) return err - } var packageTemplate = `// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT diff --git a/gen/gen_test.go b/gen/gen_test.go index 263b3243c..e566f566c 100644 --- a/gen/gen_test.go +++ b/gen/gen_test.go @@ -1,6 +1,7 @@ package gen import ( + "errors" "github.com/go-openapi/spec" "os" "os/exec" @@ -22,17 +23,17 @@ func TestGen_Build(t *testing.T) { } assert.NoError(t, New().Build(config)) - if _, err := os.Stat(path.Join("../testdata/simple/docs", "docs.go")); os.IsNotExist(err) { - t.Fatal(err) - } - if _, err := os.Stat(path.Join("../testdata/simple/docs", "swagger.json")); os.IsNotExist(err) { - t.Fatal(err) + expectedFiles := []string{ + path.Join(config.OutputDir, "docs.go"), + path.Join(config.OutputDir, "swagger.json"), + path.Join(config.OutputDir, "swagger.yaml"), } - if _, err := os.Stat(path.Join("../testdata/simple/docs", "swagger.yaml")); os.IsNotExist(err) { - t.Fatal(err) + for _, expectedFile := range expectedFiles { + if _, err := os.Stat(expectedFile); os.IsNotExist(err) { + t.Fatal(err) + } + os.Remove(expectedFile) } - - //TODO: remove gen files } func TestGen_BuildSnakecase(t *testing.T) { @@ -46,17 +47,17 @@ func TestGen_BuildSnakecase(t *testing.T) { assert.NoError(t, New().Build(config)) - if _, err := os.Stat(path.Join("../testdata/simple2/docs", "docs.go")); os.IsNotExist(err) { - t.Fatal(err) + expectedFiles := []string{ + path.Join(config.OutputDir, "docs.go"), + path.Join(config.OutputDir, "swagger.json"), + path.Join(config.OutputDir, "swagger.yaml"), } - if _, err := os.Stat(path.Join("../testdata/simple2/docs", "swagger.json")); os.IsNotExist(err) { - t.Fatal(err) - } - if _, err := os.Stat(path.Join("../testdata/simple2/docs", "swagger.yaml")); os.IsNotExist(err) { - t.Fatal(err) + for _, expectedFile := range expectedFiles { + if _, err := os.Stat(expectedFile); os.IsNotExist(err) { + t.Fatal(err) + } + os.Remove(expectedFile) } - - //TODO: remove gen files } func TestGen_BuildLowerCamelcase(t *testing.T) { @@ -70,17 +71,60 @@ func TestGen_BuildLowerCamelcase(t *testing.T) { assert.NoError(t, New().Build(config)) - if _, err := os.Stat(path.Join("../testdata/simple3/docs", "docs.go")); os.IsNotExist(err) { - t.Fatal(err) + expectedFiles := []string{ + path.Join(config.OutputDir, "docs.go"), + path.Join(config.OutputDir, "swagger.json"), + path.Join(config.OutputDir, "swagger.yaml"), } - if _, err := os.Stat(path.Join("../testdata/simple3/docs", "swagger.json")); os.IsNotExist(err) { - t.Fatal(err) + for _, expectedFile := range expectedFiles { + if _, err := os.Stat(expectedFile); os.IsNotExist(err) { + t.Fatal(err) + } + os.Remove(expectedFile) } - if _, err := os.Stat(path.Join("../testdata/simple3/docs", "swagger.yaml")); os.IsNotExist(err) { - t.Fatal(err) +} + +func TestGen_jsonIndent(t *testing.T) { + searchDir := "../testdata/simple" + + config := &Config{ + SearchDir: searchDir, + MainAPIFile: "./main.go", + OutputDir: "../testdata/simple/docs", + PropNamingStrategy: "", + } + gen := New() + gen.jsonIndent = func(data interface{}) ([]byte, error) { + return nil, errors.New("fail") } + assert.Error(t, gen.Build(config)) +} + +func TestGen_jsonToYAML(t *testing.T) { + searchDir := "../testdata/simple" - //TODO: remove gen files + config := &Config{ + SearchDir: searchDir, + MainAPIFile: "./main.go", + OutputDir: "../testdata/simple/docs", + PropNamingStrategy: "", + } + gen := New() + gen.jsonToYAML = func(data []byte) ([]byte, error) { + return nil, errors.New("fail") + } + assert.Error(t, gen.Build(config)) + + expectedFiles := []string{ + path.Join(config.OutputDir, "docs.go"), + path.Join(config.OutputDir, "swagger.json"), + } + for _, expectedFile := range expectedFiles { + if _, err := os.Stat(expectedFile); os.IsNotExist(err) { + t.Fatal(err) + } + os.Remove(expectedFile) + } } func TestGen_SearchDirIsNotExist(t *testing.T) { @@ -181,17 +225,17 @@ func TestGen_configWithOutputDir(t *testing.T) { assert.NoError(t, New().Build(config)) - if _, err := os.Stat(path.Join("../testdata/simple/docs", "docs.go")); os.IsNotExist(err) { - t.Fatal(err) + expectedFiles := []string{ + path.Join(config.OutputDir, "docs.go"), + path.Join(config.OutputDir, "swagger.json"), + path.Join(config.OutputDir, "swagger.yaml"), } - if _, err := os.Stat(path.Join("../testdata/simple/docs", "swagger.json")); os.IsNotExist(err) { - t.Fatal(err) + for _, expectedFile := range expectedFiles { + if _, err := os.Stat(expectedFile); os.IsNotExist(err) { + t.Fatal(err) + } + os.Remove(expectedFile) } - if _, err := os.Stat(path.Join("../testdata/simple/docs", "swagger.yaml")); os.IsNotExist(err) { - t.Fatal(err) - } - - //TODO: remove gen files } func TestGen_formatSource(t *testing.T) { @@ -267,4 +311,16 @@ func TestGen_GeneratedDoc(t *testing.T) { cmd.Stderr = os.Stderr assert.NoError(t, cmd.Run()) + + expectedFiles := []string{ + path.Join(config.OutputDir, "docs.go"), + path.Join(config.OutputDir, "swagger.json"), + path.Join(config.OutputDir, "swagger.yaml"), + } + for _, expectedFile := range expectedFiles { + if _, err := os.Stat(expectedFile); os.IsNotExist(err) { + t.Fatal(err) + } + os.Remove(expectedFile) + } } diff --git a/testdata/simple2/docs/docs.go b/testdata/simple2/docs/docs.go deleted file mode 100644 index 0d5fb68b1..000000000 --- a/testdata/simple2/docs/docs.go +++ /dev/null @@ -1,524 +0,0 @@ -// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT -// This file was generated by swaggo/swag at -// 2019-07-17 15:34:45.975640886 +0300 EEST m=+0.051468436 - -package docs - -import ( - "bytes" - "encoding/json" - - "github.com/alecthomas/template" - "github.com/swaggo/swag" -) - -var doc = `{ - "schemes": {{ marshal .Schemes }}, - "swagger": "2.0", - "info": { - "description": "This is a sample server Petstore server.", - "title": "Swagger Example API", - "termsOfService": "http://swagger.io/terms/", - "contact": { - "name": "API Support", - "url": "http://www.swagger.io/support", - "email": "support@swagger.io" - }, - "license": { - "name": "Apache 2.0", - "url": "http://www.apache.org/licenses/LICENSE-2.0.html" - }, - "version": "1.0" - }, - "host": "petstore.swagger.io", - "basePath": "/v2", - "paths": { - "/file/upload": { - "post": { - "description": "Upload file", - "consumes": [ - "multipart/form-data" - ], - "produces": [ - "application/json" - ], - "summary": "Upload file", - "operationId": "file.upload", - "parameters": [ - { - "type": "file", - "description": "this is a test file", - "name": "file", - "in": "formData", - "required": true - } - ], - "responses": { - "200": { - "description": "ok", - "schema": { - "type": "string" - } - }, - "400": { - "description": "We need ID!!", - "schema": { - "$ref": "#/definitions/web.APIError" - } - }, - "404": { - "description": "Can not find ID", - "schema": { - "$ref": "#/definitions/web.APIError" - } - } - } - } - }, - "/testapi/get-string-by-int/{some_id}": { - "get": { - "description": "get string by ID", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "summary": "Add a new pet to the store", - "operationId": "get-string-by-int", - "parameters": [ - { - "type": "integer", - "format": "int64", - "description": "Some ID", - "name": "some_id", - "in": "path", - "required": true - }, - { - "description": "Some ID", - "name": "some_id", - "in": "body", - "required": true, - "schema": { - "type": "object", - "$ref": "#/definitions/web.Pet" - } - } - ], - "responses": { - "200": { - "description": "ok", - "schema": { - "type": "string" - } - }, - "400": { - "description": "We need ID!!", - "schema": { - "$ref": "#/definitions/web.APIError" - } - }, - "404": { - "description": "Can not find ID", - "schema": { - "$ref": "#/definitions/web.APIError" - } - } - } - } - }, - "/testapi/get-struct-array-by-string/{some_id}": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - }, - { - "BasicAuth": [] - }, - { - "OAuth2Application": [ - "write" - ] - }, - { - "OAuth2Implicit": [ - "read", - "admin" - ] - }, - { - "OAuth2AccessCode": [ - "read" - ] - }, - { - "OAuth2Password": [ - "admin" - ] - } - ], - "description": "get struct array by ID", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "operationId": "get-struct-array-by-string", - "parameters": [ - { - "type": "string", - "description": "Some ID", - "name": "some_id", - "in": "path", - "required": true - }, - { - "enum": [ - 1, - 2, - 3 - ], - "type": "integer", - "description": "Category", - "name": "category", - "in": "query", - "required": true - }, - { - "minimum": 0, - "type": "integer", - "default": 0, - "description": "Offset", - "name": "offset", - "in": "query", - "required": true - }, - { - "maximum": 50, - "type": "integer", - "default": 10, - "description": "Limit", - "name": "limit", - "in": "query", - "required": true - }, - { - "maxLength": 50, - "minLength": 1, - "type": "string", - "default": "\"\"", - "description": "q", - "name": "q", - "in": "query", - "required": true - } - ], - "responses": { - "200": { - "description": "ok", - "schema": { - "type": "string" - } - }, - "400": { - "description": "We need ID!!", - "schema": { - "$ref": "#/definitions/web.APIError" - } - }, - "404": { - "description": "Can not find ID", - "schema": { - "$ref": "#/definitions/web.APIError" - } - } - } - } - } - }, - "definitions": { - "web.APIError": { - "type": "object", - "properties": { - "created_at": { - "type": "string" - }, - "error_code": { - "type": "integer" - }, - "error_message": { - "type": "string" - } - } - }, - "web.Pet": { - "type": "object", - "required": [ - "price" - ], - "properties": { - "birthday": { - "type": "integer" - }, - "category": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "example": 1 - }, - "name": { - "type": "string", - "example": "category_name" - }, - "photo_urls": { - "type": "array", - "format": "url", - "items": { - "type": "string" - }, - "example": [ - "http://test/image/1.jpg", - "http://test/image/2.jpg" - ] - }, - "small_category": { - "type": "object", - "required": [ - "name" - ], - "properties": { - "id": { - "type": "integer", - "example": 1 - }, - "name": { - "type": "string", - "example": "detail_category_name" - }, - "photo_urls": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "http://test/image/1.jpg", - "http://test/image/2.jpg" - ] - } - } - } - } - }, - "coeffs": { - "type": "array", - "items": { - "type": "number" - } - }, - "custom_string": { - "type": "string" - }, - "custom_string_arr": { - "type": "array", - "items": { - "type": "string" - } - }, - "data": { - "type": "object" - }, - "decimal": { - "type": "number" - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "is_alive": { - "type": "boolean", - "example": true - }, - "name": { - "type": "string", - "example": "poti" - }, - "null_int": { - "type": "integer" - }, - "pets": { - "type": "array", - "items": { - "$ref": "#/definitions/web.Pet2" - } - }, - "pets2": { - "type": "array", - "items": { - "$ref": "#/definitions/web.Pet2" - } - }, - "photo_urls": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "http://test/image/1.jpg", - "http://test/image/2.jpg" - ] - }, - "price": { - "type": "number", - "example": 3.25 - }, - "status": { - "type": "string" - }, - "tags": { - "type": "array", - "items": { - "$ref": "#/definitions/web.Tag" - } - }, - "uuid": { - "type": "string" - } - } - }, - "web.Pet2": { - "type": "object", - "properties": { - "deleted_at": { - "type": "string" - }, - "id": { - "type": "integer" - }, - "middle_name": { - "type": "string" - } - } - }, - "web.RevValue": { - "type": "object", - "properties": { - "data": { - "type": "integer" - }, - "err": { - "type": "integer" - }, - "status": { - "type": "boolean" - } - } - }, - "web.Tag": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - }, - "pets": { - "type": "array", - "items": { - "$ref": "#/definitions/web.Pet" - } - } - } - } - }, - "securityDefinitions": { - "ApiKeyAuth": { - "type": "apiKey", - "name": "Authorization", - "in": "header" - }, - "BasicAuth": { - "type": "basic" - }, - "OAuth2AccessCode": { - "type": "oauth2", - "flow": "accessCode", - "authorizationUrl": "https://example.com/oauth/authorize", - "tokenUrl": "https://example.com/oauth/token", - "scopes": { - "admin": " Grants read and write access to administrative information" - } - }, - "OAuth2Application": { - "type": "oauth2", - "flow": "application", - "tokenUrl": "https://example.com/oauth/token", - "scopes": { - "admin": " Grants read and write access to administrative information", - "write": " Grants write access" - } - }, - "OAuth2Implicit": { - "type": "oauth2", - "flow": "implicit", - "authorizationUrl": "https://example.com/oauth/authorize", - "scopes": { - "admin": " Grants read and write access to administrative information", - "write": " Grants write access" - } - }, - "OAuth2Password": { - "type": "oauth2", - "flow": "password", - "tokenUrl": "https://example.com/oauth/token", - "scopes": { - "admin": " Grants read and write access to administrative information", - "read": " Grants read access", - "write": " Grants write access" - } - } - } -}` - -type swaggerInfo struct { - Version string - Host string - BasePath string - Schemes []string - Title string - Description string -} - -// SwaggerInfo holds exported Swagger Info so clients can modify it -var SwaggerInfo = swaggerInfo{Schemes: []string{}} - -type s struct{} - -func (s *s) ReadDoc() string { - t, err := template.New("swagger_info").Funcs(template.FuncMap{ - "marshal": func(v interface{}) string { - a, _ := json.Marshal(v) - return string(a) - }, - }).Parse(doc) - if err != nil { - return doc - } - - var tpl bytes.Buffer - if err := t.Execute(&tpl, SwaggerInfo); err != nil { - return doc - } - - return tpl.String() -} - -func init() { - swag.Register(swag.Name, &s{}) -} diff --git a/testdata/simple2/docs/swagger/swagger.json b/testdata/simple2/docs/swagger/swagger.json deleted file mode 100644 index 85d80040b..000000000 --- a/testdata/simple2/docs/swagger/swagger.json +++ /dev/null @@ -1,478 +0,0 @@ -{ - "swagger": "2.0", - "info": { - "description": "This is a sample server Petstore server.", - "title": "Swagger Example API", - "termsOfService": "http://swagger.io/terms/", - "contact": { - "name": "API Support", - "url": "http://www.swagger.io/support", - "email": "support@swagger.io" - }, - "license": { - "name": "Apache 2.0", - "url": "http://www.apache.org/licenses/LICENSE-2.0.html" - }, - "version": "1.0" - }, - "host": "petstore.swagger.io", - "basePath": "/v2", - "paths": { - "/file/upload": { - "post": { - "description": "Upload file", - "consumes": [ - "multipart/form-data" - ], - "produces": [ - "application/json" - ], - "summary": "Upload file", - "operationId": "file.upload", - "parameters": [ - { - "type": "file", - "description": "this is a test file", - "name": "file", - "in": "formData", - "required": true - } - ], - "responses": { - "200": { - "description": "ok", - "schema": { - "type": "string" - } - }, - "400": { - "description": "We need ID!!", - "schema": { - "type": "object", - "$ref": "#/definitions/web.APIError" - } - }, - "404": { - "description": "Can not find ID", - "schema": { - "type": "object", - "$ref": "#/definitions/web.APIError" - } - } - } - } - }, - "/testapi/get-string-by-int/{some_id}": { - "get": { - "description": "get string by ID", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "summary": "Add a new pet to the store", - "operationId": "get-string-by-int", - "parameters": [ - { - "type": "integer", - "format": "int64", - "description": "Some ID", - "name": "some_id", - "in": "path", - "required": true - }, - { - "description": "Some ID", - "name": "some_id", - "in": "body", - "required": true, - "schema": { - "type": "object", - "$ref": "#/definitions/web.Pet" - } - } - ], - "responses": { - "200": { - "description": "ok", - "schema": { - "type": "string" - } - }, - "400": { - "description": "We need ID!!", - "schema": { - "type": "object", - "$ref": "#/definitions/web.APIError" - } - }, - "404": { - "description": "Can not find ID", - "schema": { - "type": "object", - "$ref": "#/definitions/web.APIError" - } - } - } - } - }, - "/testapi/get-struct-array-by-string/{some_id}": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - }, - { - "BasicAuth": [] - }, - { - "OAuth2Application": [ - "write" - ] - }, - { - "OAuth2Implicit": [ - "read", - "admin" - ] - }, - { - "OAuth2AccessCode": [ - "read" - ] - }, - { - "OAuth2Password": [ - "admin" - ] - } - ], - "description": "get struct array by ID", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "operationId": "get-struct-array-by-string", - "parameters": [ - { - "type": "string", - "description": "Some ID", - "name": "some_id", - "in": "path", - "required": true - }, - { - "enum": [ - 1, - 2, - 3 - ], - "type": "integer", - "description": "Category", - "name": "category", - "in": "query", - "required": true - }, - { - "minimum": 0, - "type": "integer", - "default": 0, - "description": "Offset", - "name": "offset", - "in": "query", - "required": true - }, - { - "maximum": 50, - "type": "integer", - "default": 10, - "description": "Limit", - "name": "limit", - "in": "query", - "required": true - }, - { - "maxLength": 50, - "minLength": 1, - "type": "string", - "default": "\"\"", - "description": "q", - "name": "q", - "in": "query", - "required": true - } - ], - "responses": { - "200": { - "description": "ok", - "schema": { - "type": "string" - } - }, - "400": { - "description": "We need ID!!", - "schema": { - "type": "object", - "$ref": "#/definitions/web.APIError" - } - }, - "404": { - "description": "Can not find ID", - "schema": { - "type": "object", - "$ref": "#/definitions/web.APIError" - } - } - } - } - } - }, - "definitions": { - "web.APIError": { - "type": "object", - "properties": { - "created_at": { - "type": "string" - }, - "error_code": { - "type": "integer" - }, - "error_message": { - "type": "string" - } - } - }, - "web.Pet": { - "type": "object", - "required": [ - "price" - ], - "properties": { - "birthday": { - "type": "integer" - }, - "category": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "example": 1 - }, - "name": { - "type": "string", - "example": "category_name" - }, - "photo_urls": { - "type": "array", - "format": "url", - "items": { - "type": "string" - }, - "example": [ - "http://test/image/1.jpg", - "http://test/image/2.jpg" - ] - }, - "small_category": { - "type": "object", - "required": [ - "name" - ], - "properties": { - "id": { - "type": "integer", - "example": 1 - }, - "name": { - "type": "string", - "example": "detail_category_name" - }, - "photo_urls": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "http://test/image/1.jpg", - "http://test/image/2.jpg" - ] - } - } - } - } - }, - "coeffs": { - "type": "array", - "items": { - "type": "number" - } - }, - "custom_string": { - "type": "string" - }, - "custom_string_arr": { - "type": "array", - "items": { - "type": "string" - } - }, - "data": { - "type": "object" - }, - "decimal": { - "type": "number" - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "is_alive": { - "type": "boolean", - "example": true - }, - "name": { - "type": "string", - "example": "poti" - }, - "null_int": { - "type": "integer" - }, - "pets": { - "type": "array", - "items": { - "$ref": "#/definitions/web.Pet2" - } - }, - "pets2": { - "type": "array", - "items": { - "$ref": "#/definitions/web.Pet2" - } - }, - "photo_urls": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "http://test/image/1.jpg", - "http://test/image/2.jpg" - ] - }, - "price": { - "type": "number", - "example": 3.25 - }, - "status": { - "type": "string" - }, - "tags": { - "type": "array", - "items": { - "$ref": "#/definitions/web.Tag" - } - }, - "uuid": { - "type": "string" - } - } - }, - "web.Pet2": { - "type": "object", - "properties": { - "deleted_at": { - "type": "string" - }, - "id": { - "type": "integer" - }, - "middle_name": { - "type": "string" - } - } - }, - "web.RevValue": { - "type": "object", - "properties": { - "data": { - "type": "integer" - }, - "err": { - "type": "integer" - }, - "status": { - "type": "boolean" - } - } - }, - "web.Tag": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - }, - "pets": { - "type": "array", - "items": { - "$ref": "#/definitions/web.Pet" - } - } - } - } - }, - "securityDefinitions": { - "ApiKeyAuth": { - "type": "apiKey", - "name": "Authorization", - "in": "header" - }, - "BasicAuth": { - "type": "basic" - }, - "OAuth2AccessCode": { - "type": "oauth2", - "flow": "accessCode", - "authorizationUrl": "https://example.com/oauth/authorize", - "tokenUrl": "https://example.com/oauth/token", - "scopes": { - "admin": " Grants read and write access to administrative information" - } - }, - "OAuth2Application": { - "type": "oauth2", - "flow": "application", - "tokenUrl": "https://example.com/oauth/token", - "scopes": { - "admin": " Grants read and write access to administrative information", - "write": " Grants write access" - } - }, - "OAuth2Implicit": { - "type": "oauth2", - "flow": "implicit", - "authorizationUrl": "https://example.com/oauth/authorize", - "scopes": { - "admin": " Grants read and write access to administrative information", - "write": " Grants write access" - } - }, - "OAuth2Password": { - "type": "oauth2", - "flow": "password", - "tokenUrl": "https://example.com/oauth/token", - "scopes": { - "admin": " Grants read and write access to administrative information", - "read": " Grants read access", - "write": " Grants write access" - } - } - } -} \ No newline at end of file diff --git a/testdata/simple2/docs/swagger/swagger.yaml b/testdata/simple2/docs/swagger/swagger.yaml deleted file mode 100644 index 3f70f7d6a..000000000 --- a/testdata/simple2/docs/swagger/swagger.yaml +++ /dev/null @@ -1,326 +0,0 @@ -basePath: /v2 -definitions: - web.APIError: - properties: - created_at: - type: string - error_code: - type: integer - error_message: - type: string - type: object - web.Pet: - properties: - birthday: - type: integer - category: - properties: - id: - example: 1 - type: integer - name: - example: category_name - type: string - photo_urls: - example: - - http://test/image/1.jpg - - http://test/image/2.jpg - format: url - items: - type: string - type: array - small_category: - properties: - id: - example: 1 - type: integer - name: - example: detail_category_name - type: string - photo_urls: - example: - - http://test/image/1.jpg - - http://test/image/2.jpg - items: - type: string - type: array - required: - - name - type: object - type: object - coeffs: - items: - type: number - type: array - custom_string: - type: string - custom_string_arr: - items: - type: string - type: array - data: - type: object - decimal: - type: number - id: - example: 1 - format: int64 - type: integer - is_alive: - example: true - type: boolean - name: - example: poti - type: string - null_int: - type: integer - pets: - items: - $ref: '#/definitions/web.Pet2' - type: array - pets2: - items: - $ref: '#/definitions/web.Pet2' - type: array - photo_urls: - example: - - http://test/image/1.jpg - - http://test/image/2.jpg - items: - type: string - type: array - price: - example: 3.25 - type: number - status: - type: string - tags: - items: - $ref: '#/definitions/web.Tag' - type: array - uuid: - type: string - required: - - price - type: object - web.Pet2: - properties: - deleted_at: - type: string - id: - type: integer - middle_name: - type: string - type: object - web.RevValue: - properties: - data: - type: integer - err: - type: integer - status: - type: boolean - type: object - web.Tag: - properties: - id: - format: int64 - type: integer - name: - type: string - pets: - items: - $ref: '#/definitions/web.Pet' - type: array - type: object -host: petstore.swagger.io -info: - contact: - email: support@swagger.io - name: API Support - url: http://www.swagger.io/support - description: This is a sample server Petstore server. - license: - name: Apache 2.0 - url: http://www.apache.org/licenses/LICENSE-2.0.html - termsOfService: http://swagger.io/terms/ - title: Swagger Example API - version: "1.0" -paths: - /file/upload: - post: - consumes: - - multipart/form-data - description: Upload file - operationId: file.upload - parameters: - - description: this is a test file - in: formData - name: file - required: true - type: file - produces: - - application/json - responses: - "200": - description: ok - schema: - type: string - "400": - description: We need ID!! - schema: - $ref: '#/definitions/web.APIError' - type: object - "404": - description: Can not find ID - schema: - $ref: '#/definitions/web.APIError' - type: object - summary: Upload file - /testapi/get-string-by-int/{some_id}: - get: - consumes: - - application/json - description: get string by ID - operationId: get-string-by-int - parameters: - - description: Some ID - format: int64 - in: path - name: some_id - required: true - type: integer - - description: Some ID - in: body - name: some_id - required: true - schema: - $ref: '#/definitions/web.Pet' - type: object - produces: - - application/json - responses: - "200": - description: ok - schema: - type: string - "400": - description: We need ID!! - schema: - $ref: '#/definitions/web.APIError' - type: object - "404": - description: Can not find ID - schema: - $ref: '#/definitions/web.APIError' - type: object - summary: Add a new pet to the store - /testapi/get-struct-array-by-string/{some_id}: - get: - consumes: - - application/json - description: get struct array by ID - operationId: get-struct-array-by-string - parameters: - - description: Some ID - in: path - name: some_id - required: true - type: string - - description: Category - enum: - - 1 - - 2 - - 3 - in: query - name: category - required: true - type: integer - - default: 0 - description: Offset - in: query - minimum: 0 - name: offset - required: true - type: integer - - default: 10 - description: Limit - in: query - maximum: 50 - name: limit - required: true - type: integer - - default: '""' - description: q - in: query - maxLength: 50 - minLength: 1 - name: q - required: true - type: string - produces: - - application/json - responses: - "200": - description: ok - schema: - type: string - "400": - description: We need ID!! - schema: - $ref: '#/definitions/web.APIError' - type: object - "404": - description: Can not find ID - schema: - $ref: '#/definitions/web.APIError' - type: object - security: - - ApiKeyAuth: [] - - BasicAuth: [] - - OAuth2Application: - - write - - OAuth2Implicit: - - read - - admin - - OAuth2AccessCode: - - read - - OAuth2Password: - - admin -securityDefinitions: - ApiKeyAuth: - in: header - name: Authorization - type: apiKey - BasicAuth: - type: basic - OAuth2AccessCode: - authorizationUrl: https://example.com/oauth/authorize - flow: accessCode - scopes: - admin: ' Grants read and write access to administrative information' - tokenUrl: https://example.com/oauth/token - type: oauth2 - OAuth2Application: - flow: application - scopes: - admin: ' Grants read and write access to administrative information' - write: ' Grants write access' - tokenUrl: https://example.com/oauth/token - type: oauth2 - OAuth2Implicit: - authorizationUrl: https://example.com/oauth/authorize - flow: implicit - scopes: - admin: ' Grants read and write access to administrative information' - write: ' Grants write access' - type: oauth2 - OAuth2Password: - flow: password - scopes: - admin: ' Grants read and write access to administrative information' - read: ' Grants read access' - write: ' Grants write access' - tokenUrl: https://example.com/oauth/token - type: oauth2 -swagger: "2.0" diff --git a/testdata/simple3/docs/docs.go b/testdata/simple3/docs/docs.go deleted file mode 100644 index f76bad30a..000000000 --- a/testdata/simple3/docs/docs.go +++ /dev/null @@ -1,497 +0,0 @@ -// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT -// This file was generated by swaggo/swag at -// 2019-07-17 15:54:04.765591008 +0300 EEST m=+0.039056976 - -package docs - -import ( - "bytes" - "encoding/json" - - "github.com/alecthomas/template" - "github.com/swaggo/swag" -) - -var doc = `{ - "schemes": {{ marshal .Schemes }}, - "swagger": "2.0", - "info": { - "description": "This is a sample server Petstore server.", - "title": "Swagger Example API", - "termsOfService": "http://swagger.io/terms/", - "contact": { - "name": "API Support", - "url": "http://www.swagger.io/support", - "email": "support@swagger.io" - }, - "license": { - "name": "Apache 2.0", - "url": "http://www.apache.org/licenses/LICENSE-2.0.html" - }, - "version": "1.0" - }, - "host": "petstore.swagger.io", - "basePath": "/v2", - "paths": { - "/file/upload": { - "post": { - "description": "Upload file", - "consumes": [ - "multipart/form-data" - ], - "produces": [ - "application/json" - ], - "summary": "Upload file", - "operationId": "file.upload", - "parameters": [ - { - "type": "file", - "description": "this is a test file", - "name": "file", - "in": "formData", - "required": true - } - ], - "responses": { - "200": { - "description": "ok", - "schema": { - "type": "string" - } - }, - "400": { - "description": "We need ID!!", - "schema": { - "$ref": "#/definitions/web.APIError" - } - }, - "404": { - "description": "Can not find ID", - "schema": { - "$ref": "#/definitions/web.APIError" - } - } - } - } - }, - "/testapi/get-string-by-int/{some_id}": { - "get": { - "description": "get string by ID", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "summary": "Add a new pet to the store", - "operationId": "get-string-by-int", - "parameters": [ - { - "type": "integer", - "format": "int64", - "description": "Some ID", - "name": "some_id", - "in": "path", - "required": true - }, - { - "description": "Some ID", - "name": "some_id", - "in": "body", - "required": true, - "schema": { - "type": "object", - "$ref": "#/definitions/web.Pet" - } - } - ], - "responses": { - "200": { - "description": "ok", - "schema": { - "type": "string" - } - }, - "400": { - "description": "We need ID!!", - "schema": { - "$ref": "#/definitions/web.APIError" - } - }, - "404": { - "description": "Can not find ID", - "schema": { - "$ref": "#/definitions/web.APIError" - } - } - } - } - }, - "/testapi/get-struct-array-by-string/{some_id}": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - }, - { - "BasicAuth": [] - }, - { - "OAuth2Application": [ - "write" - ] - }, - { - "OAuth2Implicit": [ - "read", - "admin" - ] - }, - { - "OAuth2AccessCode": [ - "read" - ] - }, - { - "OAuth2Password": [ - "admin" - ] - } - ], - "description": "get struct array by ID", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "operationId": "get-struct-array-by-string", - "parameters": [ - { - "type": "string", - "description": "Some ID", - "name": "some_id", - "in": "path", - "required": true - }, - { - "enum": [ - 1, - 2, - 3 - ], - "type": "integer", - "description": "Category", - "name": "category", - "in": "query", - "required": true - }, - { - "minimum": 0, - "type": "integer", - "default": 0, - "description": "Offset", - "name": "offset", - "in": "query", - "required": true - }, - { - "maximum": 50, - "type": "integer", - "default": 10, - "description": "Limit", - "name": "limit", - "in": "query", - "required": true - }, - { - "maxLength": 50, - "minLength": 1, - "type": "string", - "default": "\"\"", - "description": "q", - "name": "q", - "in": "query", - "required": true - } - ], - "responses": { - "200": { - "description": "ok", - "schema": { - "type": "string" - } - }, - "400": { - "description": "We need ID!!", - "schema": { - "$ref": "#/definitions/web.APIError" - } - }, - "404": { - "description": "Can not find ID", - "schema": { - "$ref": "#/definitions/web.APIError" - } - } - } - } - } - }, - "definitions": { - "web.APIError": { - "type": "object", - "properties": { - "createdAt": { - "type": "string" - }, - "errorCode": { - "type": "integer" - }, - "errorMessage": { - "type": "string" - } - } - }, - "web.Pet": { - "type": "object", - "properties": { - "category": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "example": 1 - }, - "name": { - "type": "string", - "example": "category_name" - }, - "photoURLs": { - "type": "array", - "format": "url", - "items": { - "type": "string" - }, - "example": [ - "http://test/image/1.jpg", - "http://test/image/2.jpg" - ] - }, - "smallCategory": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "example": 1 - }, - "name": { - "type": "string", - "example": "detail_category_name" - }, - "photoURLs": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "http://test/image/1.jpg", - "http://test/image/2.jpg" - ] - } - } - } - } - }, - "data": { - "type": "object" - }, - "decimal": { - "type": "number" - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "isAlive": { - "type": "boolean", - "example": true - }, - "name": { - "type": "string", - "example": "poti" - }, - "pets": { - "type": "array", - "items": { - "$ref": "#/definitions/web.Pet2" - } - }, - "pets2": { - "type": "array", - "items": { - "$ref": "#/definitions/web.Pet2" - } - }, - "photoURLs": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "http://test/image/1.jpg", - "http://test/image/2.jpg" - ] - }, - "price": { - "type": "number", - "example": 3.25 - }, - "status": { - "type": "string" - }, - "tags": { - "type": "array", - "items": { - "$ref": "#/definitions/web.Tag" - } - }, - "uuid": { - "type": "string" - } - } - }, - "web.Pet2": { - "type": "object", - "properties": { - "deletedAt": { - "type": "string" - }, - "id": { - "type": "integer" - }, - "middleName": { - "type": "string" - } - } - }, - "web.RevValue": { - "type": "object", - "properties": { - "data": { - "type": "integer" - }, - "err": { - "type": "integer" - }, - "status": { - "type": "boolean" - } - } - }, - "web.Tag": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - }, - "pets": { - "type": "array", - "items": { - "$ref": "#/definitions/web.Pet" - } - } - } - } - }, - "securityDefinitions": { - "ApiKeyAuth": { - "type": "apiKey", - "name": "Authorization", - "in": "header" - }, - "BasicAuth": { - "type": "basic" - }, - "OAuth2AccessCode": { - "type": "oauth2", - "flow": "accessCode", - "authorizationUrl": "https://example.com/oauth/authorize", - "tokenUrl": "https://example.com/oauth/token", - "scopes": { - "admin": " Grants read and write access to administrative information" - } - }, - "OAuth2Application": { - "type": "oauth2", - "flow": "application", - "tokenUrl": "https://example.com/oauth/token", - "scopes": { - "admin": " Grants read and write access to administrative information", - "write": " Grants write access" - } - }, - "OAuth2Implicit": { - "type": "oauth2", - "flow": "implicit", - "authorizationUrl": "https://example.com/oauth/authorize", - "scopes": { - "admin": " Grants read and write access to administrative information", - "write": " Grants write access" - } - }, - "OAuth2Password": { - "type": "oauth2", - "flow": "password", - "tokenUrl": "https://example.com/oauth/token", - "scopes": { - "admin": " Grants read and write access to administrative information", - "read": " Grants read access", - "write": " Grants write access" - } - } - } -}` - -type swaggerInfo struct { - Version string - Host string - BasePath string - Schemes []string - Title string - Description string -} - -// SwaggerInfo holds exported Swagger Info so clients can modify it -var SwaggerInfo = swaggerInfo{Schemes: []string{}} - -type s struct{} - -func (s *s) ReadDoc() string { - t, err := template.New("swagger_info").Funcs(template.FuncMap{ - "marshal": func(v interface{}) string { - a, _ := json.Marshal(v) - return string(a) - }, - }).Parse(doc) - if err != nil { - return doc - } - - var tpl bytes.Buffer - if err := t.Execute(&tpl, SwaggerInfo); err != nil { - return doc - } - - return tpl.String() -} - -func init() { - swag.Register(swag.Name, &s{}) -} diff --git a/testdata/simple3/docs/swagger/swagger.json b/testdata/simple3/docs/swagger/swagger.json deleted file mode 100644 index 59e416ddf..000000000 --- a/testdata/simple3/docs/swagger/swagger.json +++ /dev/null @@ -1,451 +0,0 @@ -{ - "swagger": "2.0", - "info": { - "description": "This is a sample server Petstore server.", - "title": "Swagger Example API", - "termsOfService": "http://swagger.io/terms/", - "contact": { - "name": "API Support", - "url": "http://www.swagger.io/support", - "email": "support@swagger.io" - }, - "license": { - "name": "Apache 2.0", - "url": "http://www.apache.org/licenses/LICENSE-2.0.html" - }, - "version": "1.0" - }, - "host": "petstore.swagger.io", - "basePath": "/v2", - "paths": { - "/file/upload": { - "post": { - "description": "Upload file", - "consumes": [ - "multipart/form-data" - ], - "produces": [ - "application/json" - ], - "summary": "Upload file", - "operationId": "file.upload", - "parameters": [ - { - "type": "file", - "description": "this is a test file", - "name": "file", - "in": "formData", - "required": true - } - ], - "responses": { - "200": { - "description": "ok", - "schema": { - "type": "string" - } - }, - "400": { - "description": "We need ID!!", - "schema": { - "type": "object", - "$ref": "#/definitions/web.APIError" - } - }, - "404": { - "description": "Can not find ID", - "schema": { - "type": "object", - "$ref": "#/definitions/web.APIError" - } - } - } - } - }, - "/testapi/get-string-by-int/{some_id}": { - "get": { - "description": "get string by ID", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "summary": "Add a new pet to the store", - "operationId": "get-string-by-int", - "parameters": [ - { - "type": "integer", - "format": "int64", - "description": "Some ID", - "name": "some_id", - "in": "path", - "required": true - }, - { - "description": "Some ID", - "name": "some_id", - "in": "body", - "required": true, - "schema": { - "type": "object", - "$ref": "#/definitions/web.Pet" - } - } - ], - "responses": { - "200": { - "description": "ok", - "schema": { - "type": "string" - } - }, - "400": { - "description": "We need ID!!", - "schema": { - "type": "object", - "$ref": "#/definitions/web.APIError" - } - }, - "404": { - "description": "Can not find ID", - "schema": { - "type": "object", - "$ref": "#/definitions/web.APIError" - } - } - } - } - }, - "/testapi/get-struct-array-by-string/{some_id}": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - }, - { - "BasicAuth": [] - }, - { - "OAuth2Application": [ - "write" - ] - }, - { - "OAuth2Implicit": [ - "read", - "admin" - ] - }, - { - "OAuth2AccessCode": [ - "read" - ] - }, - { - "OAuth2Password": [ - "admin" - ] - } - ], - "description": "get struct array by ID", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "operationId": "get-struct-array-by-string", - "parameters": [ - { - "type": "string", - "description": "Some ID", - "name": "some_id", - "in": "path", - "required": true - }, - { - "enum": [ - 1, - 2, - 3 - ], - "type": "integer", - "description": "Category", - "name": "category", - "in": "query", - "required": true - }, - { - "minimum": 0, - "type": "integer", - "default": 0, - "description": "Offset", - "name": "offset", - "in": "query", - "required": true - }, - { - "maximum": 50, - "type": "integer", - "default": 10, - "description": "Limit", - "name": "limit", - "in": "query", - "required": true - }, - { - "maxLength": 50, - "minLength": 1, - "type": "string", - "default": "\"\"", - "description": "q", - "name": "q", - "in": "query", - "required": true - } - ], - "responses": { - "200": { - "description": "ok", - "schema": { - "type": "string" - } - }, - "400": { - "description": "We need ID!!", - "schema": { - "type": "object", - "$ref": "#/definitions/web.APIError" - } - }, - "404": { - "description": "Can not find ID", - "schema": { - "type": "object", - "$ref": "#/definitions/web.APIError" - } - } - } - } - } - }, - "definitions": { - "web.APIError": { - "type": "object", - "properties": { - "createdAt": { - "type": "string" - }, - "errorCode": { - "type": "integer" - }, - "errorMessage": { - "type": "string" - } - } - }, - "web.Pet": { - "type": "object", - "properties": { - "category": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "example": 1 - }, - "name": { - "type": "string", - "example": "category_name" - }, - "photoURLs": { - "type": "array", - "format": "url", - "items": { - "type": "string" - }, - "example": [ - "http://test/image/1.jpg", - "http://test/image/2.jpg" - ] - }, - "smallCategory": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "example": 1 - }, - "name": { - "type": "string", - "example": "detail_category_name" - }, - "photoURLs": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "http://test/image/1.jpg", - "http://test/image/2.jpg" - ] - } - } - } - } - }, - "data": { - "type": "object" - }, - "decimal": { - "type": "number" - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "isAlive": { - "type": "boolean", - "example": true - }, - "name": { - "type": "string", - "example": "poti" - }, - "pets": { - "type": "array", - "items": { - "$ref": "#/definitions/web.Pet2" - } - }, - "pets2": { - "type": "array", - "items": { - "$ref": "#/definitions/web.Pet2" - } - }, - "photoURLs": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "http://test/image/1.jpg", - "http://test/image/2.jpg" - ] - }, - "price": { - "type": "number", - "example": 3.25 - }, - "status": { - "type": "string" - }, - "tags": { - "type": "array", - "items": { - "$ref": "#/definitions/web.Tag" - } - }, - "uuid": { - "type": "string" - } - } - }, - "web.Pet2": { - "type": "object", - "properties": { - "deletedAt": { - "type": "string" - }, - "id": { - "type": "integer" - }, - "middleName": { - "type": "string" - } - } - }, - "web.RevValue": { - "type": "object", - "properties": { - "data": { - "type": "integer" - }, - "err": { - "type": "integer" - }, - "status": { - "type": "boolean" - } - } - }, - "web.Tag": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - }, - "pets": { - "type": "array", - "items": { - "$ref": "#/definitions/web.Pet" - } - } - } - } - }, - "securityDefinitions": { - "ApiKeyAuth": { - "type": "apiKey", - "name": "Authorization", - "in": "header" - }, - "BasicAuth": { - "type": "basic" - }, - "OAuth2AccessCode": { - "type": "oauth2", - "flow": "accessCode", - "authorizationUrl": "https://example.com/oauth/authorize", - "tokenUrl": "https://example.com/oauth/token", - "scopes": { - "admin": " Grants read and write access to administrative information" - } - }, - "OAuth2Application": { - "type": "oauth2", - "flow": "application", - "tokenUrl": "https://example.com/oauth/token", - "scopes": { - "admin": " Grants read and write access to administrative information", - "write": " Grants write access" - } - }, - "OAuth2Implicit": { - "type": "oauth2", - "flow": "implicit", - "authorizationUrl": "https://example.com/oauth/authorize", - "scopes": { - "admin": " Grants read and write access to administrative information", - "write": " Grants write access" - } - }, - "OAuth2Password": { - "type": "oauth2", - "flow": "password", - "tokenUrl": "https://example.com/oauth/token", - "scopes": { - "admin": " Grants read and write access to administrative information", - "read": " Grants read access", - "write": " Grants write access" - } - } - } -} \ No newline at end of file diff --git a/testdata/simple3/docs/swagger/swagger.yaml b/testdata/simple3/docs/swagger/swagger.yaml deleted file mode 100644 index 773147d11..000000000 --- a/testdata/simple3/docs/swagger/swagger.yaml +++ /dev/null @@ -1,308 +0,0 @@ -basePath: /v2 -definitions: - web.APIError: - properties: - createdAt: - type: string - errorCode: - type: integer - errorMessage: - type: string - type: object - web.Pet: - properties: - category: - properties: - id: - example: 1 - type: integer - name: - example: category_name - type: string - photoURLs: - example: - - http://test/image/1.jpg - - http://test/image/2.jpg - format: url - items: - type: string - type: array - smallCategory: - properties: - id: - example: 1 - type: integer - name: - example: detail_category_name - type: string - photoURLs: - example: - - http://test/image/1.jpg - - http://test/image/2.jpg - items: - type: string - type: array - type: object - type: object - data: - type: object - decimal: - type: number - id: - example: 1 - format: int64 - type: integer - isAlive: - example: true - type: boolean - name: - example: poti - type: string - pets: - items: - $ref: '#/definitions/web.Pet2' - type: array - pets2: - items: - $ref: '#/definitions/web.Pet2' - type: array - photoURLs: - example: - - http://test/image/1.jpg - - http://test/image/2.jpg - items: - type: string - type: array - price: - example: 3.25 - type: number - status: - type: string - tags: - items: - $ref: '#/definitions/web.Tag' - type: array - uuid: - type: string - type: object - web.Pet2: - properties: - deletedAt: - type: string - id: - type: integer - middleName: - type: string - type: object - web.RevValue: - properties: - data: - type: integer - err: - type: integer - status: - type: boolean - type: object - web.Tag: - properties: - id: - format: int64 - type: integer - name: - type: string - pets: - items: - $ref: '#/definitions/web.Pet' - type: array - type: object -host: petstore.swagger.io -info: - contact: - email: support@swagger.io - name: API Support - url: http://www.swagger.io/support - description: This is a sample server Petstore server. - license: - name: Apache 2.0 - url: http://www.apache.org/licenses/LICENSE-2.0.html - termsOfService: http://swagger.io/terms/ - title: Swagger Example API - version: "1.0" -paths: - /file/upload: - post: - consumes: - - multipart/form-data - description: Upload file - operationId: file.upload - parameters: - - description: this is a test file - in: formData - name: file - required: true - type: file - produces: - - application/json - responses: - "200": - description: ok - schema: - type: string - "400": - description: We need ID!! - schema: - $ref: '#/definitions/web.APIError' - type: object - "404": - description: Can not find ID - schema: - $ref: '#/definitions/web.APIError' - type: object - summary: Upload file - /testapi/get-string-by-int/{some_id}: - get: - consumes: - - application/json - description: get string by ID - operationId: get-string-by-int - parameters: - - description: Some ID - format: int64 - in: path - name: some_id - required: true - type: integer - - description: Some ID - in: body - name: some_id - required: true - schema: - $ref: '#/definitions/web.Pet' - type: object - produces: - - application/json - responses: - "200": - description: ok - schema: - type: string - "400": - description: We need ID!! - schema: - $ref: '#/definitions/web.APIError' - type: object - "404": - description: Can not find ID - schema: - $ref: '#/definitions/web.APIError' - type: object - summary: Add a new pet to the store - /testapi/get-struct-array-by-string/{some_id}: - get: - consumes: - - application/json - description: get struct array by ID - operationId: get-struct-array-by-string - parameters: - - description: Some ID - in: path - name: some_id - required: true - type: string - - description: Category - enum: - - 1 - - 2 - - 3 - in: query - name: category - required: true - type: integer - - default: 0 - description: Offset - in: query - minimum: 0 - name: offset - required: true - type: integer - - default: 10 - description: Limit - in: query - maximum: 50 - name: limit - required: true - type: integer - - default: '""' - description: q - in: query - maxLength: 50 - minLength: 1 - name: q - required: true - type: string - produces: - - application/json - responses: - "200": - description: ok - schema: - type: string - "400": - description: We need ID!! - schema: - $ref: '#/definitions/web.APIError' - type: object - "404": - description: Can not find ID - schema: - $ref: '#/definitions/web.APIError' - type: object - security: - - ApiKeyAuth: [] - - BasicAuth: [] - - OAuth2Application: - - write - - OAuth2Implicit: - - read - - admin - - OAuth2AccessCode: - - read - - OAuth2Password: - - admin -securityDefinitions: - ApiKeyAuth: - in: header - name: Authorization - type: apiKey - BasicAuth: - type: basic - OAuth2AccessCode: - authorizationUrl: https://example.com/oauth/authorize - flow: accessCode - scopes: - admin: ' Grants read and write access to administrative information' - tokenUrl: https://example.com/oauth/token - type: oauth2 - OAuth2Application: - flow: application - scopes: - admin: ' Grants read and write access to administrative information' - write: ' Grants write access' - tokenUrl: https://example.com/oauth/token - type: oauth2 - OAuth2Implicit: - authorizationUrl: https://example.com/oauth/authorize - flow: implicit - scopes: - admin: ' Grants read and write access to administrative information' - write: ' Grants write access' - type: oauth2 - OAuth2Password: - flow: password - scopes: - admin: ' Grants read and write access to administrative information' - read: ' Grants read access' - write: ' Grants write access' - tokenUrl: https://example.com/oauth/token - type: oauth2 -swagger: "2.0" From 6567f07e41b2a30f2d2221e19b506f4032f7d2fa Mon Sep 17 00:00:00 2001 From: Bogdan U Date: Sat, 19 Oct 2019 18:40:28 +0300 Subject: [PATCH 06/84] fix: unnecessary type field in parameter body schema --- operation.go | 4 ++-- operation_test.go | 1 - parser_test.go | 4 ---- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/operation.go b/operation.go index ba5184360..2cbd797df 100644 --- a/operation.go +++ b/operation.go @@ -186,7 +186,7 @@ func (operation *Operation) ParseParamComment(commentLine string, astFile *ast.F SchemaProps: spec.SchemaProps{}, }, } - // Arrau of Primitive or Object + // Array of Primitive or Object if IsPrimitiveType(refType) { param.Schema.Items.Schema.Type = spec.StringOrArray{refType} } else { @@ -199,7 +199,7 @@ func (operation *Operation) ParseParamComment(commentLine string, astFile *ast.F if err := operation.registerSchemaType(refType, astFile); err != nil { return err } - param.Schema.Type = spec.StringOrArray{objectType} + param.Schema.Type = []string{} param.Schema.Ref = spec.Ref{ Ref: jsonreference.MustCreateRef("#/definitions/" + refType), } diff --git a/operation_test.go b/operation_test.go index 43fa83d80..e95716e78 100644 --- a/operation_test.go +++ b/operation_test.go @@ -468,7 +468,6 @@ func TestParseParamCommentByBodyType(t *testing.T) { "in": "body", "required": true, "schema": { - "type": "object", "$ref": "#/definitions/model.OrderRow" } } diff --git a/parser_test.go b/parser_test.go index fc17dd5a6..be0ba3c92 100644 --- a/parser_test.go +++ b/parser_test.go @@ -378,7 +378,6 @@ func TestParseSimpleApi1(t *testing.T) { "in": "body", "required": true, "schema": { - "type": "object", "$ref": "#/definitions/web.Pet" } } @@ -1006,7 +1005,6 @@ func TestParseSimpleApi_ForSnakecase(t *testing.T) { "in": "body", "required": true, "schema": { - "type": "object", "$ref": "#/definitions/web.Pet" } } @@ -1490,7 +1488,6 @@ func TestParseSimpleApi_ForLowerCamelcase(t *testing.T) { "in": "body", "required": true, "schema": { - "type": "object", "$ref": "#/definitions/web.Pet" } } @@ -2046,7 +2043,6 @@ func TestParseModelNotUnderRoot(t *testing.T) { "in": "body", "required": true, "schema": { - "type": "object", "$ref": "#/definitions/data.Foo" } } From ab69c2399a4b62ecf9fe39ebe2cab91e91a437e9 Mon Sep 17 00:00:00 2001 From: manuelgu Date: Mon, 21 Oct 2019 13:18:37 +0200 Subject: [PATCH 07/84] Fix some typos (#543) --- README.md | 6 +++--- cmd/swag/main.go | 2 +- gen/gen.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 4d24e6c8c..9fa8dd608 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ $ go get -u github.com/swaggo/swag/cmd/swag ``` To build from source you need [Go](https://golang.org/dl/) (1.9 or newer). -Or download the pre-compiled binaries binray form [release page](https://github.com/swaggo/swag/releases). +Or download the pre-compiled binaries binary form [release page](https://github.com/swaggo/swag/releases). 3. Run `swag init` in the project's root folder which contains the `main.go` file. This will parse your comments and generate the required files (`docs` folder and `docs/docs.go`). ```sh @@ -71,7 +71,7 @@ OPTIONS: --generalInfo value, -g value Go file path in which 'swagger general API Info' is written (default: "main.go") --dir value, -d value Directory you want to parse (default: "./") --propertyStrategy value, -p value Property Naming Strategy like snakecase,camelcase,pascalcase (default: "camelcase") - --output value, -o value Output directory for al the generated files(swagger.json, swagger.yaml and doc.go) (default: "./docs") + --output value, -o value Output directory for all the generated files(swagger.json, swagger.yaml and doc.go) (default: "./docs") --parseVendor Parse go files in 'vendor' folder, disabled by default --parseDependency Parse go files in outside dependency folder, disabled by default ``` @@ -277,7 +277,7 @@ func (c *Controller) ListAccounts(ctx *gin.Context) { $ swag init ``` -4.Run your app, and browse to http://localhost:8080/swagger/index.html. You will see Swagger 2.0 Api documents as shown below: +4. Run your app, and browse to http://localhost:8080/swagger/index.html. You will see Swagger 2.0 Api documents as shown below: ![swagger_index.html](https://raw.githubusercontent.com/swaggo/swag/master/assets/swagger-image.png) diff --git a/cmd/swag/main.go b/cmd/swag/main.go index 83ba81bf6..cb8443a93 100644 --- a/cmd/swag/main.go +++ b/cmd/swag/main.go @@ -39,7 +39,7 @@ var initFlags = []cli.Flag{ cli.StringFlag{ Name: outputFlag + ", o", Value: "./docs", - Usage: "Output directory for al the generated files(swagger.json, swagger.yaml and doc.go)", + Usage: "Output directory for all the generated files(swagger.json, swagger.yaml and doc.go)", }, cli.BoolFlag{ Name: parseVendorFlag, diff --git a/gen/gen.go b/gen/gen.go index 73c026499..11c3002be 100644 --- a/gen/gen.go +++ b/gen/gen.go @@ -40,7 +40,7 @@ type Config struct { // SearchDir the swag would be parse SearchDir string - // OutputDir represents the output directory for al the generated files + // OutputDir represents the output directory for all the generated files OutputDir string // MainAPIFile the Go file path in which 'swagger general API Info' is written From c05c2737fb276deb93b7e6316c9971e7180a8d8d Mon Sep 17 00:00:00 2001 From: MIKI Takushi Date: Fri, 25 Oct 2019 17:50:33 +0900 Subject: [PATCH 08/84] fix: required tag doesn't work with nested object property #504 (#541) --- parser.go | 19 ++++++++++++------- testdata/nested/api/api.go | 9 ++++++++- testdata/nested/expected.json | 30 ++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 8 deletions(-) diff --git a/parser.go b/parser.go index 11618acf0..f13192fc6 100644 --- a/parser.go +++ b/parser.go @@ -605,9 +605,7 @@ func (parser *Parser) collectRequiredFields(pkgName string, properties map[strin tspec := parser.TypeDefinitions[pkgName][tname] parser.ParseDefinition(pkgName, tname, tspec) } - if tname != "object" { - requiredFields = append(requiredFields, prop.SchemaProps.Required...) - } + requiredFields = append(requiredFields, prop.SchemaProps.Required...) properties[k] = prop } @@ -730,10 +728,7 @@ func (parser *Parser) parseStruct(pkgName string, fields *ast.FieldList) (spec.S // unset required from properties because we've collected them for k, prop := range properties { - tname := prop.SchemaProps.Type[0] - if tname != "object" { - prop.SchemaProps.Required = make([]string, 0) - } + prop.SchemaProps.Required = make([]string, 0) properties[k] = prop } @@ -828,10 +823,15 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st // write definition if not yet present parser.ParseDefinition(pkgName, structField.schemaType, parser.TypeDefinitions[pkgName][structField.schemaType]) + required := make([]string, 0) + if structField.isRequired { + required = append(required, structField.name) + } properties[structField.name] = spec.Schema{ SchemaProps: spec.SchemaProps{ Type: []string{"object"}, // to avoid swagger validation error Description: desc, + Required: required, Ref: spec.Ref{ Ref: jsonreference.MustCreateRef("#/definitions/" + pkgName + "." + structField.schemaType), }, @@ -845,10 +845,15 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st if _, ok := parser.TypeDefinitions[pkgName][structField.arrayType]; ok { // user type in array parser.ParseDefinition(pkgName, structField.arrayType, parser.TypeDefinitions[pkgName][structField.arrayType]) + required := make([]string, 0) + if structField.isRequired { + required = append(required, structField.name) + } properties[structField.name] = spec.Schema{ SchemaProps: spec.SchemaProps{ Type: []string{structField.schemaType}, Description: desc, + Required: required, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ diff --git a/testdata/nested/api/api.go b/testdata/nested/api/api.go index 8f7ddda13..e7869da85 100644 --- a/testdata/nested/api/api.go +++ b/testdata/nested/api/api.go @@ -6,8 +6,15 @@ import ( ) type Foo struct { - Field1 string + Field1 string `validate:"required"` OutsideData *nested2.Body + InsideData Bar `validate:"required"` + ArrayField1 []string `validate:"required"` + ArrayField2 []Bar `validate:"required"` +} + +type Bar struct { + Field string } // @Description get Foo diff --git a/testdata/nested/expected.json b/testdata/nested/expected.json index c8dba775a..e6ebb1cc4 100644 --- a/testdata/nested/expected.json +++ b/testdata/nested/expected.json @@ -33,12 +33,42 @@ } }, "definitions": { + "api.Bar": { + "type": "object", + "properties": { + "field": { + "type": "string" + } + } + }, "api.Foo": { "type": "object", + "required": [ + "arrayField1", + "arrayField2", + "field1", + "insideData" + ], "properties": { + "arrayField1": { + "type": "array", + "items": { + "type": "string" + } + }, + "arrayField2": { + "type": "array", + "items": { + "$ref": "#/definitions/api.Bar" + } + }, "field1": { "type": "string" }, + "insideData": { + "type": "object", + "$ref": "#/definitions/api.Bar" + }, "outsideData": { "type": "object", "$ref": "#/definitions/nested2.Body" From fb06668d93a2d71e99d536722ccfe9dc43b308d3 Mon Sep 17 00:00:00 2001 From: Thomas Coussot Date: Sat, 9 Nov 2019 20:57:52 +0100 Subject: [PATCH 09/84] fix: router now support colon sign. Useful for custom methods using custom verbs (#552) Thanks for your contribution. --- operation.go | 2 +- operation_test.go | 25 ++++++++++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/operation.go b/operation.go index 2cbd797df..f207413d2 100644 --- a/operation.go +++ b/operation.go @@ -437,7 +437,7 @@ func parseMimeTypeList(mimeTypeList string, typeList *[]string, format string) e return nil } -var routerPattern = regexp.MustCompile(`([\w\.\/\-{}\+]+)[^\[]+\[([^\]]+)`) +var routerPattern = regexp.MustCompile(`^(/[\w\.\/\-{}\+:]+)[[:blank:]]+\[(\w+)]`) // ParseRouterComment parses comment for gived `router` comment string. func (operation *Operation) ParseRouterComment(commentLine string) error { diff --git a/operation_test.go b/operation_test.go index e95716e78..f3d5273af 100644 --- a/operation_test.go +++ b/operation_test.go @@ -120,7 +120,30 @@ func TestParseRouterCommentWithPlusSign(t *testing.T) { assert.Equal(t, "POST", operation.HTTPMethod) } -func TestParseRouterCommentOccursErr(t *testing.T) { +func TestParseRouterCommentWithColonSign(t *testing.T) { + comment := `/@Router /customer/get-wishlist/{wishlist_id}:move [post]` + operation := NewOperation() + err := operation.ParseComment(comment, nil) + assert.NoError(t, err) + assert.Equal(t, "/customer/get-wishlist/{wishlist_id}:move", operation.Path) + assert.Equal(t, "POST", operation.HTTPMethod) +} + +func TestParseRouterCommentNoColonSignAtPathStartErr(t *testing.T) { + comment := `/@Router :customer/get-wishlist/{wishlist_id}:move [post]` + operation := NewOperation() + err := operation.ParseComment(comment, nil) + assert.Error(t, err) +} + +func TestParseRouterCommentMethodSeparationErr(t *testing.T) { + comment := `/@Router /api/{id}|,*[get` + operation := NewOperation() + err := operation.ParseComment(comment, nil) + assert.Error(t, err) +} + +func TestParseRouterCommentMethodMissingErr(t *testing.T) { comment := `/@Router /customer/get-wishlist/{wishlist_id}` operation := NewOperation() err := operation.ParseComment(comment, nil) From 75ecf95675e2f96b1edebc0b3c15f5c4ec64cf4f Mon Sep 17 00:00:00 2001 From: Bogdan U Date: Tue, 12 Nov 2019 12:25:37 +0200 Subject: [PATCH 10/84] fix issue with urfave/cli (#557) --- Makefile | 21 +++++++++++++++------ cmd/swag/main.go | 2 +- go.mod | 13 ++++++++----- go.sum | 40 +++++++++++++++++++++++++++++++++++++++- 4 files changed, 63 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index 75f950268..d318b64a0 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,7 @@ GOTEST:=$(GOCMD) test GOGET:=$(GOCMD) get GOLIST:=$(GOCMD) list GOVET:=$(GOCMD) vet +GOPATH:=$(shell $(GOCMD) env GOPATH) u := $(if $(update),-u) BINARY_NAME:=swag @@ -52,17 +53,25 @@ clean: rm -f $(BINARY_NAME) .PHONY: deps -deps: - $(GOGET) ${u} -d +deps: ensure-gopath + $(GOGET) github.com/swaggo/cli + $(GOGET) github.com/ghodss/yaml + $(GOGET) github.com/KyleBanks/depth + $(GOGET) github.com/go-openapi/jsonreference + $(GOGET) github.com/go-openapi/spec $(GOGET) github.com/stretchr/testify/assert $(GOGET) github.com/alecthomas/template .PHONY: devel-deps -devel-deps: +devel-deps: ensure-gopath GO111MODULE=off $(GOGET) -v -u \ - golang.org/x/lint/golint \ - github.com/swaggo/swag/cmd/swag \ - github.com/swaggo/swag/gen + golang.org/x/lint/golint + +.PHONY: ensure-gopath +ensure-gopath: + mkdir -p ${GOPATH}/github.com/swaggo + if [ -L ${GOPATH}/github.com/swaggo/swag ]; then rm ${GOPATH}/github.com/swaggo/swag; fi + ln -s "$(shell pwd)" ${GOPATH}/github.com/swaggo/swag .PHONY: lint lint: devel-deps diff --git a/cmd/swag/main.go b/cmd/swag/main.go index cb8443a93..99f02d76e 100644 --- a/cmd/swag/main.go +++ b/cmd/swag/main.go @@ -5,9 +5,9 @@ import ( "log" "os" + "github.com/swaggo/cli" "github.com/swaggo/swag" "github.com/swaggo/swag/gen" - "github.com/urfave/cli" ) const ( diff --git a/go.mod b/go.mod index 9b9c0628c..498406887 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,14 @@ require ( github.com/KyleBanks/depth v1.2.1 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 github.com/ghodss/yaml v1.0.0 - github.com/go-openapi/jsonreference v0.19.0 - github.com/go-openapi/spec v0.19.0 - github.com/stretchr/testify v1.3.0 + github.com/gin-gonic/gin v1.4.0 + github.com/go-openapi/jsonreference v0.19.3 + github.com/go-openapi/spec v0.19.4 + github.com/stretchr/testify v1.4.0 + github.com/swaggo/cli v1.20.0 github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14 github.com/swaggo/gin-swagger v1.2.0 - github.com/urfave/cli v1.20.0 - golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b + golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59 ) + +go 1.13 diff --git a/go.sum b/go.sum index b5aa48327..a7a343388 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/PuerkitoBio/purell v1.1.0 h1:rmGxhojJlM0tuKtfdvliR84CFHljx9ag64t2xmVkjK4= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= @@ -14,29 +16,48 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME github.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w= github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y= +github.com/gin-gonic/gin v1.4.0 h1:3tMoCCfM7ppqsR0ptz/wi1impNpT7/9wQtMZ8lr1mCQ= github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= github.com/go-openapi/jsonpointer v0.17.0 h1:nH6xp8XdXHx8dqveo0ZuJBluCO2qGrPbDNZ0dwoRHP0= github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.19.0 h1:BqWKpV1dFd+AuiKlgtddwVIFQsuMpxfBDBHGfM2yNpk= github.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= github.com/go-openapi/spec v0.19.0 h1:A4SZ6IWh3lnjH0rG0Z5lkxazMGBECtrZcbyYQi+64k4= github.com/go-openapi/spec v0.19.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.19.4 h1:ixzUSnHTd6hCemgtAJgluaTSGYpLNpJY4mA2DIkdOAo= +github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= github.com/go-openapi/swag v0.17.0 h1:iqrgMg7Q7SvtbWLlltPrkMs0UBJI6oTSs79JFRUi880= github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= @@ -44,31 +65,45 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/swaggo/cli v1.20.0 h1:r3zaMDTQDEq5iMe2OGMrehbHyg8q0Et74g8j6uPK6iY= +github.com/swaggo/cli v1.20.0/go.mod h1:7jzoQluD0EWMc0rxx6kkPoRNfYNHkNJI/NokjEwJiwM= github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E= github.com/swaggo/gin-swagger v1.2.0/go.mod h1:qlH2+W7zXGZkczuL+r2nEBR2JTT+/lX05Nn6vPhc7OI= github.com/swaggo/swag v1.5.1/go.mod h1:1Bl9F/ZBpVWh22nY0zmYyASPO1lI/zIwRDrpZU+tv8Y= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go v1.1.5-pre h1:jyJKFOSEbdOc2HODrf2qcCkYOdq7zzXqA9bhW5oV4fM= github.com/ugorji/go v1.1.5-pre/go.mod h1:FwP/aQVg39TXzItUBMwnWp9T9gPQnXw4Poh4/oBQZ/0= github.com/ugorji/go/codec v0.0.0-20181022190402-e5e69e061d4f/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ugorji/go/codec v1.1.5-pre h1:5YV9PsFAN+ndcCtTM7s60no7nY7eTG3LPtxhSwuxzCs= github.com/ugorji/go/codec v1.1.5-pre/go.mod h1:tULtS6Gy1AE1yCENaw4Vb//HLH5njI2tfCQDUqRd8fI= -github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190611141213-3f473d35a33a h1:+KkCgOMgnKSgenxTBoiwkMqTiouMIy/3o8RLdmSbGoY= golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae h1:xiXzMMEQdQcric9hXtr1QU98MHunKK7OTtsoU6bYWs4= golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -76,9 +111,12 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b h1:/mJ+GKieZA6hFDQGdWZrjj4AXPl5ylY+5HusG80roy0= golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59 h1:QjA/9ArTfVTLfEhClDCG7SGrZkZixxWpwNCDiwJfh88= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= From b56402325ac403daf1b228b2025e3ba9b57e3703 Mon Sep 17 00:00:00 2001 From: Rafael Vinicius Date: Thu, 14 Nov 2019 02:17:45 -0300 Subject: [PATCH 11/84] chore: correcting the comet (#555) --- example/markdown/api/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/markdown/api/api.go b/example/markdown/api/api.go index 17b5838a6..2bc900e27 100644 --- a/example/markdown/api/api.go +++ b/example/markdown/api/api.go @@ -61,7 +61,7 @@ func AddUser(w http.ResponseWriter, r *http.Request) { // write your code } -// AddUser example +// UpdateUser example // @Summary Add a new user to the store // @Tags admin // @Accept json From 68ab45d226ee06a6a17062997017246244508a80 Mon Sep 17 00:00:00 2001 From: Laurens van den Brink Date: Fri, 22 Nov 2019 11:54:56 +0100 Subject: [PATCH 12/84] add support for map nested in struct (#521) * add support for map nested in struct --- parser.go | 46 ++++++++++++++++++++++++++++++ testdata/composition/api/api.go | 19 ++++++++++++ testdata/composition/expected.json | 39 +++++++++++++++++++++++++ 3 files changed, 104 insertions(+) diff --git a/parser.go b/parser.go index f13192fc6..b2ac415c1 100644 --- a/parser.go +++ b/parser.go @@ -935,6 +935,52 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st }, } } + } else if astTypeMap, ok := field.Type.(*ast.MapType); ok { // if map + _, err := parser.parseTypeExpr(pkgName, "", astTypeMap.Value) + if err != nil { + return properties, nil, err + } + + fullTypeName, err := getFieldType(astTypeMap.Value) + if err != nil { + return properties, nil, err + } + mapValueScheme := &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: spec.Ref{ + Ref: jsonreference.MustCreateRef("#/definitions/" + fullTypeName), + }, + }, + } + + required := make([]string, 0) + if structField.isRequired { + required = append(required, structField.name) + } + properties[structField.name] = spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{structField.schemaType}, + Description: desc, + Format: structField.formatType, + Required: required, + Maximum: structField.maximum, + Minimum: structField.minimum, + MaxLength: structField.maxLength, + MinLength: structField.minLength, + Enum: structField.enums, + Default: structField.defaultValue, + AdditionalProperties: &spec.SchemaOrBool{ + Schema: mapValueScheme, + }, + }, + SwaggerSchemaProps: spec.SwaggerSchemaProps{ + Example: structField.exampleValue, + ReadOnly: structField.readOnly, + }, + VendorExtensible: spec.VendorExtensible{ + Extensions: structField.extensions, + }, + } } else { required := make([]string, 0) if structField.isRequired { diff --git a/testdata/composition/api/api.go b/testdata/composition/api/api.go index ef3a66e24..0845b6b92 100644 --- a/testdata/composition/api/api.go +++ b/testdata/composition/api/api.go @@ -25,6 +25,14 @@ type FooBarPointer struct { type BarMap map[string]Bar +type FooBarMap struct { + Field3 map[string]MapValue +} + +type MapValue struct { + Field4 string +} + // @Description get Foo // @ID get-foo // @Accept json @@ -79,3 +87,14 @@ func GetBarMap(c *gin.Context) { //write your code var _ = BarMap{} } + +// @Description get FoorBarMap +// @ID get-foo-bar-map +// @Accept json +// @Produce json +// @Success 200 {object} api.FooBarMap +// @Router /testapi/get-foobarmap [get] +func GetFooBarMap(c *gin.Context) { + //write your code + var _ = FooBarMap{} +} diff --git a/testdata/composition/expected.json b/testdata/composition/expected.json index efb7b081c..cbf2c1363 100644 --- a/testdata/composition/expected.json +++ b/testdata/composition/expected.json @@ -110,6 +110,26 @@ } } } + }, + "/testapi/get-foobarmap": { + "get": { + "description": "get FoorBarMap", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "operationId": "get-foo-bar-map", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.FooBarMap" + } + } + } + } } }, "definitions": { @@ -151,6 +171,17 @@ } } }, + "api.FooBarMap": { + "type": "object", + "properties": { + "field3": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/MapValue" + } + } + } + }, "api.FooBarPointer": { "type": "object", "properties": { @@ -164,6 +195,14 @@ "type": "string" } } + }, + "api.MapValue": { + "type": "object", + "properties": { + "field4": { + "type": "string" + } + } } } } \ No newline at end of file From de444e5b497f68331eae907614b25dc908100359 Mon Sep 17 00:00:00 2001 From: Bogdan U Date: Mon, 25 Nov 2019 12:47:08 +0200 Subject: [PATCH 13/84] chore: add GitHub action (#568) --- .github/actions/danger/Dockerfile | 14 ++++++++++++++ .github/main.workflow | 9 +++++++++ .github/workflows/ci.yml | 2 ++ Makefile | 11 +++-------- parser_test.go | 5 +++-- 5 files changed, 31 insertions(+), 10 deletions(-) create mode 100644 .github/actions/danger/Dockerfile create mode 100644 .github/main.workflow diff --git a/.github/actions/danger/Dockerfile b/.github/actions/danger/Dockerfile new file mode 100644 index 000000000..479081c6f --- /dev/null +++ b/.github/actions/danger/Dockerfile @@ -0,0 +1,14 @@ +FROM ruby:2.6 + +LABEL "com.github.actions.name"="Danger" +LABEL "com.github.actions.description"="Run Danger" +LABEL "com.github.actions.icon"="alert-triangle" +LABEL "com.github.actions.color"="yellow" + +RUN apt-get update -qq && apt-get install -y build-essential p7zip unzip + +RUN gem install danger -v '>= 5.10.3' +RUN gem install danger-checkstyle_format + +ENTRYPOINT "danger" +CMD "--verbose" diff --git a/.github/main.workflow b/.github/main.workflow new file mode 100644 index 000000000..f2a65f623 --- /dev/null +++ b/.github/main.workflow @@ -0,0 +1,9 @@ +workflow "DangerPullRequest" { + on = "pull_request" + resolves = ["Danger"] +} + +action "Danger" { + uses = "pei0804/GithubActions/danger@master" + secrets = ["GITHUB_TOKEN"] +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c34dea456..31bf7878f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,6 +17,8 @@ jobs: - name: static program analysis run: | export PATH=$PATH:$(go env GOPATH)/bin # https://github.com/actions/setup-go/issues/14 + mkdir -p $(go env GOPATH)/src/github.com/swaggo + ln -s $(pwd) $(go env GOPATH)/src/github.com/swaggo/swag make fmt-check lint vet - name: build run: make build diff --git a/Makefile b/Makefile index d318b64a0..2c7156e9e 100644 --- a/Makefile +++ b/Makefile @@ -53,9 +53,10 @@ clean: rm -f $(BINARY_NAME) .PHONY: deps -deps: ensure-gopath +deps: $(GOGET) github.com/swaggo/cli $(GOGET) github.com/ghodss/yaml + $(GOGET) github.com/gin-gonic/gin $(GOGET) github.com/KyleBanks/depth $(GOGET) github.com/go-openapi/jsonreference $(GOGET) github.com/go-openapi/spec @@ -63,16 +64,10 @@ deps: ensure-gopath $(GOGET) github.com/alecthomas/template .PHONY: devel-deps -devel-deps: ensure-gopath +devel-deps: GO111MODULE=off $(GOGET) -v -u \ golang.org/x/lint/golint -.PHONY: ensure-gopath -ensure-gopath: - mkdir -p ${GOPATH}/github.com/swaggo - if [ -L ${GOPATH}/github.com/swaggo/swag ]; then rm ${GOPATH}/github.com/swaggo/swag; fi - ln -s "$(shell pwd)" ${GOPATH}/github.com/swaggo/swag - .PHONY: lint lint: devel-deps for PKG in $(PACKAGES); do golint -set_exit_status $$PKG || exit 1; done; diff --git a/parser_test.go b/parser_test.go index be0ba3c92..b7dc14f96 100644 --- a/parser_test.go +++ b/parser_test.go @@ -2240,7 +2240,8 @@ func TestParseComposition(t *testing.T) { assert.Equal(t, string(expected), string(b)) } -func TestParseNested(t *testing.T) { +// Disabled for resting purpose +func disabledTestParseNested(t *testing.T) { searchDir := "testdata/nested" mainAPIFile := "main.go" p := New() @@ -2762,7 +2763,7 @@ func TestFixes432(t *testing.T) { } } -func TestParseOutsideDependencies(t *testing.T) { +func disabledTestParseOutsideDependencies(t *testing.T) { searchDir := "testdata/pare_outside_dependencies" mainAPIFile := "cmd/main.go" From d6d8b49bda9749d9c219187e4628fb403a9d62a9 Mon Sep 17 00:00:00 2001 From: Bogdan U Date: Sun, 1 Dec 2019 17:36:01 +0200 Subject: [PATCH 14/84] chore(CI): add GitHub action (#571) --- .github/workflows/ci.yml | 12 +++++++++++- Makefile | 1 + testdata/pare_outside_dependencies/cmd/main.go | 1 - 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 31bf7878f..6f92b2580 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,19 +8,29 @@ jobs: runs-on: ${{ matrix.platform }} steps: - uses: actions/checkout@master + with: + path: ./src/github.com/${{ github.repository }} - name: Set up Go uses: actions/setup-go@v1 with: go-version: ${{ matrix.go }} - name: deps - run: make deps + run: make deps + env: + GOPATH: ${{ runner.workspace }} - name: static program analysis run: | export PATH=$PATH:$(go env GOPATH)/bin # https://github.com/actions/setup-go/issues/14 mkdir -p $(go env GOPATH)/src/github.com/swaggo ln -s $(pwd) $(go env GOPATH)/src/github.com/swaggo/swag make fmt-check lint vet + env: + GOPATH: ${{ runner.workspace }} - name: build run: make build + env: + GOPATH: ${{ runner.workspace }} - name: test run: make test + env: + GOPATH: ${{ runner.workspace }} \ No newline at end of file diff --git a/Makefile b/Makefile index 2c7156e9e..f78299612 100644 --- a/Makefile +++ b/Makefile @@ -62,6 +62,7 @@ deps: $(GOGET) github.com/go-openapi/spec $(GOGET) github.com/stretchr/testify/assert $(GOGET) github.com/alecthomas/template + $(GOGET) golang.org/x/tools/go/loader .PHONY: devel-deps devel-deps: diff --git a/testdata/pare_outside_dependencies/cmd/main.go b/testdata/pare_outside_dependencies/cmd/main.go index bef4849ae..a93ea32c3 100644 --- a/testdata/pare_outside_dependencies/cmd/main.go +++ b/testdata/pare_outside_dependencies/cmd/main.go @@ -20,7 +20,6 @@ import ( // @host petstore.swagger.io // @BasePath /v2 func main() { - _ := web.Pet{} r := gin.New() r.GET("/testapi/get-string-by-int/:some_id", api.GetStringByInt) r.GET("//testapi/get-struct-array-by-string/:some_id", api.GetStructArrayByString) From b734f40071cdd9808551168499c525f0d4402b40 Mon Sep 17 00:00:00 2001 From: Smotrov Dmitriy Date: Wed, 4 Dec 2019 02:43:07 +0300 Subject: [PATCH 15/84] fix issue with import aliases (#483) (#573) --- parser.go | 21 ++++++ parser_test.go | 14 ++++ property.go | 13 ++++ testdata/alias_import/api/api.go | 28 ++++++++ .../alias_import/data/applicationresponse.go | 11 +++ testdata/alias_import/expected.json | 71 +++++++++++++++++++ testdata/alias_import/main.go | 26 +++++++ testdata/alias_import/types/application.go | 9 +++ 8 files changed, 193 insertions(+) create mode 100644 testdata/alias_import/api/api.go create mode 100644 testdata/alias_import/data/applicationresponse.go create mode 100644 testdata/alias_import/expected.json create mode 100644 testdata/alias_import/main.go create mode 100644 testdata/alias_import/types/application.go diff --git a/parser.go b/parser.go index b2ac415c1..9fccb23e7 100644 --- a/parser.go +++ b/parser.go @@ -46,6 +46,9 @@ type Parser struct { // TypeDefinitions is a map that stores [package name][type name][*ast.TypeSpec] TypeDefinitions map[string]map[string]*ast.TypeSpec + // ImportAliases is map that stores [import name][import package name][*ast.ImportSpec] + ImportAliases map[string]map[string]*ast.ImportSpec + // CustomPrimitiveTypes is a map that stores custom primitive types to actual golang types [type name][string] CustomPrimitiveTypes map[string]string @@ -85,6 +88,7 @@ func New(options ...func(*Parser)) *Parser { }, files: make(map[string]*ast.File), TypeDefinitions: make(map[string]map[string]*ast.TypeSpec), + ImportAliases: make(map[string]map[string]*ast.ImportSpec), CustomPrimitiveTypes: make(map[string]string), registerTypes: make(map[string]*ast.TypeSpec), } @@ -521,6 +525,23 @@ func (parser *Parser) ParseType(astFile *ast.File) { } } } + + for _, importSpec := range astFile.Imports { + if importSpec.Name == nil { + continue + } + + alias := importSpec.Name.Name + + if _, ok := parser.ImportAliases[alias]; !ok { + parser.ImportAliases[alias] = make(map[string]*ast.ImportSpec) + } + + importParts := strings.Split(strings.Trim(importSpec.Path.Value, "\""), "/") + importPkgName := importParts[len(importParts)-1] + + parser.ImportAliases[alias][importPkgName] = importSpec + } } func (parser *Parser) isInStructStack(refTypeName string) bool { diff --git a/parser_test.go b/parser_test.go index b7dc14f96..6985fbbad 100644 --- a/parser_test.go +++ b/parser_test.go @@ -2240,6 +2240,20 @@ func TestParseComposition(t *testing.T) { assert.Equal(t, string(expected), string(b)) } +func TestParseImportAliases(t *testing.T) { + searchDir := "testdata/alias_import" + mainAPIFile := "main.go" + p := New() + err := p.ParseAPI(searchDir, mainAPIFile) + assert.NoError(t, err) + + expected, err := ioutil.ReadFile(path.Join(searchDir, "expected.json")) + assert.NoError(t, err) + + b, _ := json.MarshalIndent(p.swagger, "", " ") + assert.Equal(t, string(expected), string(b)) +} + // Disabled for resting purpose func disabledTestParseNested(t *testing.T) { searchDir := "testdata/nested" diff --git a/property.go b/property.go index 5b846d63d..198e8852d 100644 --- a/property.go +++ b/property.go @@ -60,6 +60,19 @@ func parseFieldSelectorExpr(astTypeSelectorExpr *ast.SelectorExpr, parser *Parse parser.ParseDefinition(pkgName.Name, astTypeSelectorExpr.Sel.Name, typeDefinitions) return propertyNewFunc(astTypeSelectorExpr.Sel.Name, pkgName.Name) } + if aliasedNames, ok := parser.ImportAliases[pkgName.Name]; ok { + for aliasedName := range aliasedNames { + if typeDefinitions, ok := parser.TypeDefinitions[aliasedName][astTypeSelectorExpr.Sel.Name]; ok { + if expr, ok := typeDefinitions.Type.(*ast.SelectorExpr); ok { + if primitiveType, err := convertFromSpecificToPrimitive(expr.Sel.Name); err == nil { + return propertyNewFunc(primitiveType, "") + } + } + parser.ParseDefinition(aliasedName, astTypeSelectorExpr.Sel.Name, typeDefinitions) + return propertyNewFunc(astTypeSelectorExpr.Sel.Name, aliasedName) + } + } + } if actualPrimitiveType, isCustomType := parser.CustomPrimitiveTypes[astTypeSelectorExpr.Sel.Name]; isCustomType { return propertyName{SchemaType: actualPrimitiveType, ArrayType: actualPrimitiveType} } diff --git a/testdata/alias_import/api/api.go b/testdata/alias_import/api/api.go new file mode 100644 index 000000000..0e24583f3 --- /dev/null +++ b/testdata/alias_import/api/api.go @@ -0,0 +1,28 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "github.com/swaggo/swag/testdata/alias_import/data" + "github.com/swaggo/swag/testdata/alias_type/types" + "log" +) + +// @Summary Get application +// @Description test get application +// @ID get-application +// @Accept json +// @Produce json +// @Success 200 {object} data.ApplicationResponse "ok" +// @Router /testapi/application [get] +func GetApplication(c *gin.Context) { + var foo = data.ApplicationResponse{ + Application: types.Application{ + Name: "name", + }, + ApplicationArray: []types.Application{ + {Name: "name"}, + }, + } + log.Println(foo) + //write your code +} diff --git a/testdata/alias_import/data/applicationresponse.go b/testdata/alias_import/data/applicationresponse.go new file mode 100644 index 000000000..b6bcf3c58 --- /dev/null +++ b/testdata/alias_import/data/applicationresponse.go @@ -0,0 +1,11 @@ +package data + +import ( + typesapplication "github.com/swaggo/swag/testdata/alias_import/types" +) + +type ApplicationResponse struct { + Application typesapplication.Application `json:"application"` + ApplicationArray []typesapplication.Application `json:"application_array"` + ApplicationTime typesapplication.DateOnly `json:"application_time"` +} diff --git a/testdata/alias_import/expected.json b/testdata/alias_import/expected.json new file mode 100644 index 000000000..4b6232987 --- /dev/null +++ b/testdata/alias_import/expected.json @@ -0,0 +1,71 @@ +{ + "swagger": "2.0", + "info": { + "description": "This is a sample server Petstore server.", + "title": "Swagger Example API", + "termsOfService": "http://swagger.io/terms/", + "contact": { + "name": "API Support", + "url": "http://www.swagger.io/support", + "email": "support@swagger.io" + }, + "license": { + "name": "Apache 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + }, + "version": "1.0" + }, + "host": "petstore.swagger.io", + "basePath": "/v2", + "paths": { + "/testapi/application": { + "get": { + "description": "test get application", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Get application", + "operationId": "get-application", + "responses": { + "200": { + "description": "ok", + "schema": { + "$ref": "#/definitions/data.ApplicationResponse" + } + } + } + } + } + }, + "definitions": { + "data.ApplicationResponse": { + "type": "object", + "properties": { + "application": { + "type": "object", + "$ref": "#/definitions/types.Application" + }, + "application_array": { + "type": "array", + "items": { + "$ref": "#/definitions/types.Application" + } + }, + "application_time": { + "type": "string" + } + } + }, + "types.Application": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + } + } +} \ No newline at end of file diff --git a/testdata/alias_import/main.go b/testdata/alias_import/main.go new file mode 100644 index 000000000..bc8768d94 --- /dev/null +++ b/testdata/alias_import/main.go @@ -0,0 +1,26 @@ +package alias_import + +import ( + "github.com/gin-gonic/gin" + "github.com/swaggo/swag/testdata/alias_import/api" +) + +// @title Swagger Example API +// @version 1.0 +// @description This is a sample server Petstore server. +// @termsOfService http://swagger.io/terms/ + +// @contact.name API Support +// @contact.url http://www.swagger.io/support +// @contact.email support@swagger.io + +// @license.name Apache 2.0 +// @license.url http://www.apache.org/licenses/LICENSE-2.0.html + +// @host petstore.swagger.io +// @BasePath /v2 +func main() { + r := gin.New() + r.GET("/testapi/application", api.GetApplication) + r.Run() +} diff --git a/testdata/alias_import/types/application.go b/testdata/alias_import/types/application.go new file mode 100644 index 000000000..ceb7120ec --- /dev/null +++ b/testdata/alias_import/types/application.go @@ -0,0 +1,9 @@ +package types + +import "time" + +type Application struct { + Name string +} + +type DateOnly time.Time From a478a3216e71ec5216c982cab74bde8c8b564a0c Mon Sep 17 00:00:00 2001 From: Bogdan U Date: Wed, 4 Dec 2019 16:38:16 +0200 Subject: [PATCH 16/84] test: re-enable tests after fixing github action(#572) --- parser_test.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/parser_test.go b/parser_test.go index 6985fbbad..01ec1ba1e 100644 --- a/parser_test.go +++ b/parser_test.go @@ -2254,8 +2254,7 @@ func TestParseImportAliases(t *testing.T) { assert.Equal(t, string(expected), string(b)) } -// Disabled for resting purpose -func disabledTestParseNested(t *testing.T) { +func TestParseNested(t *testing.T) { searchDir := "testdata/nested" mainAPIFile := "main.go" p := New() @@ -2777,7 +2776,7 @@ func TestFixes432(t *testing.T) { } } -func disabledTestParseOutsideDependencies(t *testing.T) { +func TestParseOutsideDependencies(t *testing.T) { searchDir := "testdata/pare_outside_dependencies" mainAPIFile := "cmd/main.go" From b7317157b2df219f9911f5ec12b4b217d72eca23 Mon Sep 17 00:00:00 2001 From: sdghchj Date: Fri, 13 Dec 2019 07:00:44 +0800 Subject: [PATCH 17/84] fix bug in array param in body (#579) * fix bug in array param in body --- operation.go | 1 + operation_test.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/operation.go b/operation.go index f207413d2..11bc6e156 100644 --- a/operation.go +++ b/operation.go @@ -181,6 +181,7 @@ func (operation *Operation) ParseParamComment(commentLine string, astFile *ast.F case "primitive": param.Schema.Type = spec.StringOrArray{refType} case "array": + param.Schema.Type = spec.StringOrArray{objectType} param.Schema.Items = &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{}, diff --git a/operation_test.go b/operation_test.go index f3d5273af..dcf9044b9 100644 --- a/operation_test.go +++ b/operation_test.go @@ -394,7 +394,7 @@ func TestParseParamCommentBodyArray(t *testing.T) { "in": "body", "required": true, "schema": { - "type": "string", + "type": "array", "items": { "type": "string" } From 6d49876b1196959fa1ced704afd7d23d803b1915 Mon Sep 17 00:00:00 2001 From: Bogdan U Date: Mon, 16 Dec 2019 09:17:14 +0200 Subject: [PATCH 18/84] fix(githubCI):working directory issue. (#580) --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f92b2580..da1ea693e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,10 +15,12 @@ jobs: with: go-version: ${{ matrix.go }} - name: deps + working-directory: ./src/github.com/${{ github.repository }} run: make deps env: GOPATH: ${{ runner.workspace }} - name: static program analysis + working-directory: ./src/github.com/${{ github.repository }} run: | export PATH=$PATH:$(go env GOPATH)/bin # https://github.com/actions/setup-go/issues/14 mkdir -p $(go env GOPATH)/src/github.com/swaggo @@ -28,9 +30,11 @@ jobs: GOPATH: ${{ runner.workspace }} - name: build run: make build + working-directory: ./src/github.com/${{ github.repository }} env: GOPATH: ${{ runner.workspace }} - name: test + working-directory: ./src/github.com/${{ github.repository }} run: make test env: GOPATH: ${{ runner.workspace }} \ No newline at end of file From eb4805c2825320ed4d4ae61e80f9f9481a4cadf3 Mon Sep 17 00:00:00 2001 From: sdghchj Date: Fri, 20 Dec 2019 17:56:29 +0800 Subject: [PATCH 19/84] fix: fully support map and optimization (#583) * 1. parseTypeExpr return poionter to spec.Schema. 2. fully support map type, including map[string]interface{},map[string]primitive. 3. optimization: struct parsed as definition ref. * remove useless comment * 1.fix bug: lost pointer member'description . 2.fix bug: lost schema's general information such as field extensions. * remove GOOS Co-authored-by: Eason Lin --- README.md | 2 +- parser.go | 265 ++++++++++++++--------------- parser_test.go | 217 +++++++++++++++++++---- testdata/composition/expected.json | 9 +- 4 files changed, 315 insertions(+), 178 deletions(-) diff --git a/README.md b/README.md index 9fa8dd608..1c912babb 100644 --- a/README.md +++ b/README.md @@ -598,7 +598,7 @@ generated swagger doc as follows: ```go type Account struct { - ID int `json:"id" extensions:"x-nullable,x-abc=def"` // extensions fields must start with "x-" + ID string `json:"id" extensions:"x-nullable,x-abc=def"` // extensions fields must start with "x-" } ``` diff --git a/parser.go b/parser.go index 9fccb23e7..535cca2bf 100644 --- a/parser.go +++ b/parser.go @@ -602,7 +602,7 @@ func (parser *Parser) ParseDefinition(pkgName, typeName string, typeSpec *ast.Ty if err != nil { return err } - parser.swagger.Definitions[refTypeName] = schema + parser.swagger.Definitions[refTypeName] = *schema return nil } @@ -648,28 +648,40 @@ func fullTypeName(pkgName, typeName string) string { // parseTypeExpr parses given type expression that corresponds to the type under // given name and package, and returns swagger schema for it. -func (parser *Parser) parseTypeExpr(pkgName, typeName string, typeExpr ast.Expr) (spec.Schema, error) { - //TODO: return pointer to spec.Schema +func (parser *Parser) parseTypeExpr(pkgName, typeName string, typeExpr ast.Expr) (*spec.Schema, error) { switch expr := typeExpr.(type) { // type Foo struct {...} case *ast.StructType: refTypeName := fullTypeName(pkgName, typeName) if schema, isParsed := parser.swagger.Definitions[refTypeName]; isParsed { - return schema, nil + return &schema, nil } return parser.parseStruct(pkgName, expr.Fields) // type Foo Baz case *ast.Ident: + if IsGolangPrimitiveType(expr.Name) { + return &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: spec.StringOrArray{TransToValidSchemeType(expr.Name)}, + }, + }, nil + } refTypeName := fullTypeName(pkgName, expr.Name) if _, isParsed := parser.swagger.Definitions[refTypeName]; !isParsed { if typedef, ok := parser.TypeDefinitions[pkgName][expr.Name]; ok { parser.ParseDefinition(pkgName, expr.Name, typedef) } } - return parser.swagger.Definitions[refTypeName], nil + return &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: spec.Ref{ + Ref: jsonreference.MustCreateRef("#/definitions/" + refTypeName), + }, + }, + }, nil // type Foo *Baz case *ast.StarExpr: @@ -679,13 +691,13 @@ func (parser *Parser) parseTypeExpr(pkgName, typeName string, typeExpr ast.Expr) case *ast.ArrayType: itemSchema, err := parser.parseTypeExpr(pkgName, "", expr.Elt) if err != nil { - return spec.Schema{}, err + return &spec.Schema{}, err } - return spec.Schema{ + return &spec.Schema{ SchemaProps: spec.SchemaProps{ Type: []string{"array"}, Items: &spec.SchemaOrArray{ - Schema: &itemSchema, + Schema: itemSchema, }, }, }, nil @@ -693,28 +705,25 @@ func (parser *Parser) parseTypeExpr(pkgName, typeName string, typeExpr ast.Expr) // type Foo pkg.Bar case *ast.SelectorExpr: if xIdent, ok := expr.X.(*ast.Ident); ok { - pkgName = xIdent.Name - typeName = expr.Sel.Name - refTypeName := fullTypeName(pkgName, typeName) - if _, isParsed := parser.swagger.Definitions[refTypeName]; !isParsed { - typedef := parser.TypeDefinitions[pkgName][typeName] - parser.ParseDefinition(pkgName, typeName, typedef) - } - return parser.swagger.Definitions[refTypeName], nil + return parser.parseTypeExpr(xIdent.Name, expr.Sel.Name, expr.Sel) } // type Foo map[string]Bar case *ast.MapType: - itemSchema, err := parser.parseTypeExpr(pkgName, "", expr.Value) - if err != nil { - return spec.Schema{}, err + var valueSchema spec.SchemaOrBool + if _, ok := expr.Value.(*ast.InterfaceType); ok { + valueSchema.Allows = true + } else { + schema, err := parser.parseTypeExpr(pkgName, "", expr.Value) + if err != nil { + return &spec.Schema{}, err + } + valueSchema.Schema = schema } - return spec.Schema{ + return &spec.Schema{ SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - AdditionalProperties: &spec.SchemaOrBool{ - Schema: &itemSchema, - }, + Type: []string{"object"}, + AdditionalProperties: &valueSchema, }, }, nil // ... @@ -722,21 +731,21 @@ func (parser *Parser) parseTypeExpr(pkgName, typeName string, typeExpr ast.Expr) Printf("Type definition of type '%T' is not supported yet. Using 'object' instead.\n", typeExpr) } - return spec.Schema{ + return &spec.Schema{ SchemaProps: spec.SchemaProps{ Type: []string{"object"}, }, }, nil } -func (parser *Parser) parseStruct(pkgName string, fields *ast.FieldList) (spec.Schema, error) { +func (parser *Parser) parseStruct(pkgName string, fields *ast.FieldList) (*spec.Schema, error) { extraRequired := make([]string, 0) properties := make(map[string]spec.Schema) for _, field := range fields.List { fieldProps, requiredFromAnon, err := parser.parseStructField(pkgName, field) if err != nil { - return spec.Schema{}, err + return &spec.Schema{}, err } extraRequired = append(extraRequired, requiredFromAnon...) for k, v := range fieldProps { @@ -753,7 +762,7 @@ func (parser *Parser) parseStruct(pkgName string, fields *ast.FieldList) (spec.S properties[k] = prop } - return spec.Schema{ + return &spec.Schema{ SchemaProps: spec.SchemaProps{ Type: []string{"object"}, Properties: properties, @@ -763,6 +772,7 @@ func (parser *Parser) parseStruct(pkgName string, fields *ast.FieldList) (spec.S type structField struct { name string + desc string schemaType string arrayType string formatType string @@ -779,6 +789,34 @@ type structField struct { extensions map[string]interface{} } +func (sf *structField) toStandardSchema() *spec.Schema { + required := make([]string, 0) + if sf.isRequired { + required = append(required, sf.name) + } + return &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{sf.schemaType}, + Description: sf.desc, + Format: sf.formatType, + Required: required, + Maximum: sf.maximum, + Minimum: sf.minimum, + MaxLength: sf.maxLength, + MinLength: sf.minLength, + Enum: sf.enums, + Default: sf.defaultValue, + }, + SwaggerSchemaProps: spec.SwaggerSchemaProps{ + Example: sf.exampleValue, + ReadOnly: sf.readOnly, + }, + VendorExtensible: spec.VendorExtensible{ + Extensions: sf.extensions, + }, + } +} + func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[string]spec.Schema, []string, error) { properties := map[string]spec.Schema{} @@ -812,7 +850,7 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st properties[k] = v } case "array": - properties[typeName] = schema + properties[typeName] = *schema default: Printf("Can't extract properties from a schema of type '%s'", schemaType) } @@ -829,17 +867,33 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st if structField.name == "" { return properties, nil, nil } - var desc string - if field.Doc != nil { - desc = strings.TrimSpace(field.Doc.Text()) - } - if desc == "" && field.Comment != nil { - desc = strings.TrimSpace(field.Comment.Text()) - } + // TODO: find package of schemaType and/or arrayType if structField.crossPkg != "" { pkgName = structField.crossPkg } + + fillObject := func(src, dest interface{}) error { + bin, err := json.Marshal(src) + if err != nil { + return err + } + return json.Unmarshal(bin, dest) + } + + //for spec.Schema have implemented json.Marshaler, here in another way to convert + fillSchema := func(src, dest *spec.Schema) error { + err = fillObject(&src.SchemaProps, &dest.SchemaProps) + if err != nil { + return err + } + err = fillObject(&src.SwaggerSchemaProps, &dest.SwaggerSchemaProps) + if err != nil { + return err + } + return fillObject(&src.VendorExtensible, &dest.VendorExtensible) + } + if _, ok := parser.TypeDefinitions[pkgName][structField.schemaType]; ok { // user type field // write definition if not yet present parser.ParseDefinition(pkgName, structField.schemaType, @@ -851,7 +905,7 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st properties[structField.name] = spec.Schema{ SchemaProps: spec.SchemaProps{ Type: []string{"object"}, // to avoid swagger validation error - Description: desc, + Description: structField.desc, Required: required, Ref: spec.Ref{ Ref: jsonreference.MustCreateRef("#/definitions/" + pkgName + "." + structField.schemaType), @@ -873,7 +927,7 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st properties[structField.name] = spec.Schema{ SchemaProps: spec.SchemaProps{ Type: []string{structField.schemaType}, - Description: desc, + Description: structField.desc, Required: required, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -907,7 +961,7 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st properties[structField.name] = spec.Schema{ SchemaProps: spec.SchemaProps{ Type: []string{structField.schemaType}, - Description: desc, + Description: structField.desc, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -933,7 +987,7 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st properties[structField.name] = spec.Schema{ SchemaProps: spec.SchemaProps{ Type: []string{structField.schemaType}, - Description: desc, + Description: structField.desc, Format: structField.formatType, Required: required, Items: &spec.SchemaOrArray{ @@ -957,92 +1011,35 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st } } } else if astTypeMap, ok := field.Type.(*ast.MapType); ok { // if map - _, err := parser.parseTypeExpr(pkgName, "", astTypeMap.Value) + stdSchema := structField.toStandardSchema() + mapValueSchema, err := parser.parseTypeExpr(pkgName, "", astTypeMap) if err != nil { return properties, nil, err } - - fullTypeName, err := getFieldType(astTypeMap.Value) - if err != nil { - return properties, nil, err - } - mapValueScheme := &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Ref: spec.Ref{ - Ref: jsonreference.MustCreateRef("#/definitions/" + fullTypeName), - }, - }, - } - - required := make([]string, 0) - if structField.isRequired { - required = append(required, structField.name) - } - properties[structField.name] = spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{structField.schemaType}, - Description: desc, - Format: structField.formatType, - Required: required, - Maximum: structField.maximum, - Minimum: structField.minimum, - MaxLength: structField.maxLength, - MinLength: structField.minLength, - Enum: structField.enums, - Default: structField.defaultValue, - AdditionalProperties: &spec.SchemaOrBool{ - Schema: mapValueScheme, - }, - }, - SwaggerSchemaProps: spec.SwaggerSchemaProps{ - Example: structField.exampleValue, - ReadOnly: structField.readOnly, - }, - VendorExtensible: spec.VendorExtensible{ - Extensions: structField.extensions, - }, - } + stdSchema.Type = mapValueSchema.Type + stdSchema.AdditionalProperties = mapValueSchema.AdditionalProperties + properties[structField.name] = *stdSchema } else { - required := make([]string, 0) - if structField.isRequired { - required = append(required, structField.name) - } - properties[structField.name] = spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{structField.schemaType}, - Description: desc, - Format: structField.formatType, - Required: required, - Maximum: structField.maximum, - Minimum: structField.minimum, - MaxLength: structField.maxLength, - MinLength: structField.minLength, - Enum: structField.enums, - Default: structField.defaultValue, - }, - SwaggerSchemaProps: spec.SwaggerSchemaProps{ - Example: structField.exampleValue, - ReadOnly: structField.readOnly, - }, - VendorExtensible: spec.VendorExtensible{ - Extensions: structField.extensions, - }, - } + stdSchema := structField.toStandardSchema() + properties[structField.name] = *stdSchema - if nestStruct, ok := field.Type.(*ast.StarExpr); ok { - schema, err := parser.parseTypeExpr(pkgName, structField.schemaType, nestStruct.X) - if err != nil { - return nil, nil, err - } + if nestStar, ok := field.Type.(*ast.StarExpr); ok { + if !IsGolangPrimitiveType(structField.schemaType) { + schema, err := parser.parseTypeExpr(pkgName, structField.schemaType, nestStar.X) + if err != nil { + return properties, nil, err + } - if len(schema.SchemaProps.Type) > 0 { - properties[structField.name] = schema - return properties, nil, nil + if len(schema.SchemaProps.Type) > 0 { + err = fillSchema(schema, stdSchema) + if err != nil { + return properties, nil, err + } + properties[structField.name] = *stdSchema + return properties, nil, nil + } } - } - - nestStruct, ok := field.Type.(*ast.StructType) - if ok { + } else if nestStruct, ok := field.Type.(*ast.StructType); ok { props := map[string]spec.Schema{} nestRequired := make([]string, 0) for _, v := range nestStruct.Fields.List { @@ -1058,26 +1055,9 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st props[k] = v } } - - properties[structField.name] = spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{structField.schemaType}, - Description: desc, - Format: structField.formatType, - Properties: props, - Required: nestRequired, - Maximum: structField.maximum, - Minimum: structField.minimum, - MaxLength: structField.maxLength, - MinLength: structField.minLength, - Enum: structField.enums, - Default: structField.defaultValue, - }, - SwaggerSchemaProps: spec.SwaggerSchemaProps{ - Example: structField.exampleValue, - ReadOnly: structField.readOnly, - }, - } + stdSchema.Properties = props + stdSchema.Required = nestRequired + properties[structField.name] = *stdSchema } } return properties, nil, nil @@ -1141,6 +1121,13 @@ func (parser *Parser) parseField(field *ast.Field) (*structField, error) { structField.name = toLowerCamelCase(structField.name) } + if field.Doc != nil { + structField.desc = strings.TrimSpace(field.Doc.Text()) + } + if structField.desc == "" && field.Comment != nil { + structField.desc = strings.TrimSpace(field.Comment.Text()) + } + if field.Tag == nil { return structField, nil } diff --git a/parser_test.go b/parser_test.go index 01ec1ba1e..953e06e52 100644 --- a/parser_test.go +++ b/parser_test.go @@ -520,7 +520,9 @@ func TestParseSimpleApi1(t *testing.T) { "type": "array", "items": { "type": "object", - "additionalProperties": {} + "additionalProperties": { + "type": "string" + } } }, "cross.Cross": { @@ -563,18 +565,7 @@ func TestParseSimpleApi1(t *testing.T) { } }, "web.CrossAlias": { - "type": "object", - "properties": { - "Array": { - "type": "array", - "items": { - "type": "string" - } - }, - "String": { - "type": "string" - } - } + "$ref": "#/definitions/cross.Cross" }, "web.IndirectRecursiveTest": { "type": "object", @@ -838,22 +829,7 @@ func TestParseSimpleApi1(t *testing.T) { "web.Tags": { "type": "array", "items": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - }, - "pets": { - "type": "array", - "items": { - "$ref": "#/definitions/web.Pet" - } - } - } + "$ref": "#/definitions/web.Tag" } } }, @@ -2237,6 +2213,8 @@ func TestParseComposition(t *testing.T) { assert.NoError(t, err) b, _ := json.MarshalIndent(p.swagger, "", " ") + + //windows will fail: \r\n \n assert.Equal(t, string(expected), string(b)) } @@ -2251,6 +2229,7 @@ func TestParseImportAliases(t *testing.T) { assert.NoError(t, err) b, _ := json.MarshalIndent(p.swagger, "", " ") + //windows will fail: \r\n \n assert.Equal(t, string(expected), string(b)) } @@ -2266,11 +2245,10 @@ func TestParseNested(t *testing.T) { assert.NoError(t, err) b, _ := json.MarshalIndent(p.swagger, "", " ") - Printf(string(b)) assert.Equal(t, string(expected), string(b)) } -func TestParser_ParseStuctArrayObject(t *testing.T) { +func TestParser_ParseStructArrayObject(t *testing.T) { src := ` package api @@ -2398,6 +2376,183 @@ type ResponseWrapper struct { } +func TestParser_ParseStructPointerMembers(t *testing.T) { + src := ` +package api + +type Child struct { + Name string +} + +type Parent struct { + Test1 *string //test1 + Test2 *Child //test2 +} + +// @Success 200 {object} Parent +// @Router /api/{id} [get] +func Test(){ +} +` + + expected := `{ + "api.Child": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + }, + "api.Parent": { + "type": "object", + "properties": { + "test1": { + "description": "test1", + "type": "string" + }, + "test2": { + "description": "test2", + "type": "object", + "$ref": "#/definitions/api.Child" + } + } + } +}` + + f, err := goparser.ParseFile(token.NewFileSet(), "", src, goparser.ParseComments) + assert.NoError(t, err) + + p := New() + p.ParseType(f) + err = p.ParseRouterAPIInfo("", f) + assert.NoError(t, err) + + typeSpec := p.TypeDefinitions["api"]["Parent"] + err = p.ParseDefinition("api", typeSpec.Name.Name, typeSpec) + assert.NoError(t, err) + + out, err := json.MarshalIndent(p.swagger.Definitions, "", " ") + assert.NoError(t, err) + assert.Equal(t, expected, string(out)) +} + +func TestParser_ParseStructMapMember(t *testing.T) { + src := ` +package api + +type MyMapType map[string]string + +type Child struct { + Name string +} + +type Parent struct { + Test1 map[string]interface{} //test1 + Test2 map[string]string //test2 + Test3 map[string]*string //test3 + Test4 map[string]Child //test4 + Test5 map[string]*Child //test5 + Test6 MyMapType //test6 + Test7 []Child //test7 + Test8 []*Child //test8 +} + +// @Success 200 {object} Parent +// @Router /api/{id} [get] +func Test(){ +} +` + expected := `{ + "api.Child": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + }, + "api.MyMapType": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "api.Parent": { + "type": "object", + "properties": { + "test1": { + "description": "test1", + "type": "object", + "additionalProperties": true + }, + "test2": { + "description": "test2", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "test3": { + "description": "test3", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "test4": { + "description": "test4", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/api.Child" + } + }, + "test5": { + "description": "test5", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/api.Child" + } + }, + "test6": { + "description": "test6", + "type": "object", + "$ref": "#/definitions/api.MyMapType" + }, + "test7": { + "description": "test7", + "type": "array", + "items": { + "$ref": "#/definitions/api.Child" + } + }, + "test8": { + "description": "test8", + "type": "array", + "items": { + "$ref": "#/definitions/api.Child" + } + } + } + } +}` + f, err := goparser.ParseFile(token.NewFileSet(), "", src, goparser.ParseComments) + assert.NoError(t, err) + + p := New() + p.ParseType(f) + err = p.ParseRouterAPIInfo("", f) + assert.NoError(t, err) + + typeSpec := p.TypeDefinitions["api"]["Parent"] + err = p.ParseDefinition("api", typeSpec.Name.Name, typeSpec) + assert.NoError(t, err) + + out, err := json.MarshalIndent(p.swagger.Definitions, "", " ") + assert.NoError(t, err) + assert.Equal(t, expected, string(out)) +} + func TestParser_ParseRouterApiInfoErr(t *testing.T) { src := ` package test diff --git a/testdata/composition/expected.json b/testdata/composition/expected.json index cbf2c1363..d14bfbd91 100644 --- a/testdata/composition/expected.json +++ b/testdata/composition/expected.json @@ -144,12 +144,7 @@ "api.BarMap": { "type": "object", "additionalProperties": { - "type": "object", - "properties": { - "field2": { - "type": "string" - } - } + "$ref": "#/definitions/api.Bar" } }, "api.Foo": { @@ -177,7 +172,7 @@ "field3": { "type": "object", "additionalProperties": { - "$ref": "#/definitions/MapValue" + "$ref": "#/definitions/api.MapValue" } } } From 0e5dd7d85383252d6e05b9b646c36d43f1245eec Mon Sep 17 00:00:00 2001 From: sdghchj Date: Mon, 23 Dec 2019 17:04:46 +0800 Subject: [PATCH 20/84] feat: support array of maps (#586) --- parser.go | 14 ++++++++++++++ parser_test.go | 15 +++++++++++++-- property.go | 12 ++++++------ 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/parser.go b/parser.go index 535cca2bf..b3f42f002 100644 --- a/parser.go +++ b/parser.go @@ -975,6 +975,20 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st ReadOnly: structField.readOnly, }, } + } else { + schema, _ := parser.parseTypeExpr(pkgName, "", astTypeArray.Elt) + properties[structField.name] = spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{structField.schemaType}, + Description: structField.desc, + Items: &spec.SchemaOrArray{ + Schema: schema, + }, + }, + SwaggerSchemaProps: spec.SwaggerSchemaProps{ + ReadOnly: structField.readOnly, + }, + } } } } else { diff --git a/parser_test.go b/parser_test.go index 953e06e52..b610275e9 100644 --- a/parser_test.go +++ b/parser_test.go @@ -253,8 +253,8 @@ func TestGetAllGoFileInfo(t *testing.T) { err := p.getAllGoFileInfo(searchDir) assert.NoError(t, err) - assert.NotEmpty(t, p.files["testdata/pet/main.go"]) - assert.NotEmpty(t, p.files["testdata/pet/web/handler.go"]) + assert.NotEmpty(t, p.files[filepath.Join("testdata", "pet", "main.go")]) + assert.NotEmpty(t, p.files[filepath.Join("testdata", "pet", "web", "handler.go")]) assert.Equal(t, 2, len(p.files)) } @@ -2456,6 +2456,7 @@ type Parent struct { Test6 MyMapType //test6 Test7 []Child //test7 Test8 []*Child //test8 + Test9 []map[string]string //test9 } // @Success 200 {object} Parent @@ -2532,6 +2533,16 @@ func Test(){ "items": { "$ref": "#/definitions/api.Child" } + }, + "test9": { + "description": "test9", + "type": "array", + "items": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } } } } diff --git a/property.go b/property.go index 198e8852d..999aec8f6 100644 --- a/property.go +++ b/property.go @@ -104,9 +104,6 @@ func getPropertyName(expr ast.Expr, parser *Parser) (propertyName, error) { } if astTypeArray, ok := expr.(*ast.ArrayType); ok { // if array - if _, ok := astTypeArray.Elt.(*ast.StructType); ok { - return propertyName{SchemaType: "array", ArrayType: "object"}, nil - } return getArrayPropertyName(astTypeArray, parser), nil } @@ -125,10 +122,13 @@ func getPropertyName(expr ast.Expr, parser *Parser) (propertyName, error) { } func getArrayPropertyName(astTypeArray *ast.ArrayType, parser *Parser) propertyName { - if astTypeArrayExpr, ok := astTypeArray.Elt.(*ast.SelectorExpr); ok { + if _, ok := astTypeArray.Elt.(*ast.StructType); ok { + return propertyName{SchemaType: "array", ArrayType: "object"} + } else if _, ok := astTypeArray.Elt.(*ast.MapType); ok { + return propertyName{SchemaType: "array", ArrayType: "object"} + } else if astTypeArrayExpr, ok := astTypeArray.Elt.(*ast.SelectorExpr); ok { return parseFieldSelectorExpr(astTypeArrayExpr, parser, newArrayProperty) - } - if astTypeArrayExpr, ok := astTypeArray.Elt.(*ast.StarExpr); ok { + } else if astTypeArrayExpr, ok := astTypeArray.Elt.(*ast.StarExpr); ok { if astTypeArraySel, ok := astTypeArrayExpr.X.(*ast.SelectorExpr); ok { return parseFieldSelectorExpr(astTypeArraySel, parser, newArrayProperty) } From 10630b9af07a6b9ec3a254979234c7661d2e7074 Mon Sep 17 00:00:00 2001 From: sdghchj Date: Wed, 25 Dec 2019 14:46:55 +0800 Subject: [PATCH 21/84] feat: optimize getArrayPropertyName; support [][]string fixing #562 (#588) * optimize getArrayPropertyName; support [][]string * []interface{} is deprecated but can be parsed by one's strong will --- parser.go | 16 ++++++++++++++++ parser_test.go | 10 ++++++++++ property.go | 38 ++++++++++++++++++++------------------ 3 files changed, 46 insertions(+), 18 deletions(-) diff --git a/parser.go b/parser.go index b3f42f002..30a9d1be1 100644 --- a/parser.go +++ b/parser.go @@ -991,6 +991,22 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st } } } + } else if structField.arrayType == "array" { + if astTypeArray, ok := field.Type.(*ast.ArrayType); ok { + schema, _ := parser.parseTypeExpr(pkgName, "", astTypeArray.Elt) + properties[structField.name] = spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{structField.schemaType}, + Description: structField.desc, + Items: &spec.SchemaOrArray{ + Schema: schema, + }, + }, + SwaggerSchemaProps: spec.SwaggerSchemaProps{ + ReadOnly: structField.readOnly, + }, + } + } } else { // standard type in array required := make([]string, 0) diff --git a/parser_test.go b/parser_test.go index b610275e9..c1bca74ba 100644 --- a/parser_test.go +++ b/parser_test.go @@ -2254,6 +2254,7 @@ package api type Response struct { Code int + Table [][]string Data []struct{ Field1 uint Field2 string @@ -2285,6 +2286,15 @@ func Test(){ } } } + }, + "table": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "string" + } + } } } } diff --git a/property.go b/property.go index 999aec8f6..04ae649f5 100644 --- a/property.go +++ b/property.go @@ -104,7 +104,7 @@ func getPropertyName(expr ast.Expr, parser *Parser) (propertyName, error) { } if astTypeArray, ok := expr.(*ast.ArrayType); ok { // if array - return getArrayPropertyName(astTypeArray, parser), nil + return getArrayPropertyName(astTypeArray.Elt, parser), nil } if _, ok := expr.(*ast.MapType); ok { // if map @@ -121,25 +121,27 @@ func getPropertyName(expr ast.Expr, parser *Parser) (propertyName, error) { return propertyName{}, errors.New("not supported" + fmt.Sprint(expr)) } -func getArrayPropertyName(astTypeArray *ast.ArrayType, parser *Parser) propertyName { - if _, ok := astTypeArray.Elt.(*ast.StructType); ok { +func getArrayPropertyName(astTypeArrayElt ast.Expr, parser *Parser) propertyName { + switch elt := astTypeArrayElt.(type) { + case *ast.StructType, *ast.MapType, *ast.InterfaceType: return propertyName{SchemaType: "array", ArrayType: "object"} - } else if _, ok := astTypeArray.Elt.(*ast.MapType); ok { - return propertyName{SchemaType: "array", ArrayType: "object"} - } else if astTypeArrayExpr, ok := astTypeArray.Elt.(*ast.SelectorExpr); ok { - return parseFieldSelectorExpr(astTypeArrayExpr, parser, newArrayProperty) - } else if astTypeArrayExpr, ok := astTypeArray.Elt.(*ast.StarExpr); ok { - if astTypeArraySel, ok := astTypeArrayExpr.X.(*ast.SelectorExpr); ok { - return parseFieldSelectorExpr(astTypeArraySel, parser, newArrayProperty) + case *ast.ArrayType: + return propertyName{SchemaType: "array", ArrayType: "array"} + case *ast.StarExpr: + return getArrayPropertyName(elt.X, parser) + case *ast.SelectorExpr: + return parseFieldSelectorExpr(elt, parser, newArrayProperty) + case *ast.Ident: + name := TransToValidSchemeType(elt.Name) + if actualPrimitiveType, isCustomType := parser.CustomPrimitiveTypes[name]; isCustomType { + name = actualPrimitiveType } - if astTypeArrayIdent, ok := astTypeArrayExpr.X.(*ast.Ident); ok { - name := TransToValidSchemeType(astTypeArrayIdent.Name) - return propertyName{SchemaType: "array", ArrayType: name} + return propertyName{SchemaType: "array", ArrayType: name} + default: + name := TransToValidSchemeType(fmt.Sprintf("%s", astTypeArrayElt)) + if actualPrimitiveType, isCustomType := parser.CustomPrimitiveTypes[name]; isCustomType { + name = actualPrimitiveType } + return propertyName{SchemaType: "array", ArrayType: name} } - itemTypeName := TransToValidSchemeType(fmt.Sprintf("%s", astTypeArray.Elt)) - if actualPrimitiveType, isCustomType := parser.CustomPrimitiveTypes[itemTypeName]; isCustomType { - itemTypeName = actualPrimitiveType - } - return propertyName{SchemaType: "array", ArrayType: itemTypeName} } From 73e3f315436ea9442f41a1fe8c8c9fcea2dc4972 Mon Sep 17 00:00:00 2001 From: sdghchj Date: Thu, 26 Dec 2019 00:27:47 +0800 Subject: [PATCH 22/84] fix: change the condition for checking custom arrayType (#590) --- parser.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/parser.go b/parser.go index 30a9d1be1..b3d1dac56 100644 --- a/parser.go +++ b/parser.go @@ -862,7 +862,7 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st structField, err := parser.parseField(field) if err != nil { - return properties, nil, nil + return properties, nil, err } if structField.name == "" { return properties, nil, nil @@ -1187,6 +1187,9 @@ func (parser *Parser) parseField(field *ast.Field) (*structField, error) { if len(parts) >= 2 { if newSchemaType == "array" { newArrayType = parts[1] + if err := CheckSchemaType(newArrayType); err != nil { + return nil, err + } } else if newSchemaType == "primitive" { newSchemaType = parts[1] newArrayType = parts[1] @@ -1196,9 +1199,7 @@ func (parser *Parser) parseField(field *ast.Field) (*structField, error) { if err := CheckSchemaType(newSchemaType); err != nil { return nil, err } - if err := CheckSchemaType(newArrayType); err != nil { - return nil, err - } + structField.schemaType = newSchemaType structField.arrayType = newArrayType } From bef48a65ebf1aa60bbae0b13187c21f3d3156690 Mon Sep 17 00:00:00 2001 From: Xu Zhipei Date: Fri, 27 Dec 2019 18:10:49 +0800 Subject: [PATCH 23/84] Add generated time flag (#501) * feat: add generated time flag * test(generatetime): add test Co-authored-by: Bogdan U --- cmd/swag/main.go | 6 ++++++ gen/gen.go | 45 +++++++++++++++++++++++++-------------------- gen/gen_test.go | 25 ++++++++++++++++++++++--- 3 files changed, 53 insertions(+), 23 deletions(-) diff --git a/cmd/swag/main.go b/cmd/swag/main.go index 99f02d76e..af55bb199 100644 --- a/cmd/swag/main.go +++ b/cmd/swag/main.go @@ -18,6 +18,7 @@ const ( parseVendorFlag = "parseVendor" parseDependencyFlag = "parseDependency" markdownFilesFlag = "markdownFiles" + generatedTimeFlag = "generatedTime" ) var initFlags = []cli.Flag{ @@ -54,6 +55,10 @@ var initFlags = []cli.Flag{ Value: "", Usage: "Parse folder containing markdown files to use as description, disabled by default", }, + cli.BoolTFlag{ + Name: "generatedTime", + Usage: "Generate timestamp at the top of docs.go, true by default", + }, } func initAction(c *cli.Context) error { @@ -73,6 +78,7 @@ func initAction(c *cli.Context) error { ParseVendor: c.Bool(parseVendorFlag), ParseDependency: c.Bool(parseDependencyFlag), MarkdownFilesDir: c.String(markdownFilesFlag), + GeneratedTime: c.BoolT(generatedTimeFlag), }) } diff --git a/gen/gen.go b/gen/gen.go index 11c3002be..39bd7b3c8 100644 --- a/gen/gen.go +++ b/gen/gen.go @@ -57,6 +57,9 @@ type Config struct { // MarkdownFilesDir used to find markdownfiles, which can be used for tag descriptions MarkdownFilesDir string + + // GeneratedTime whether swag should generate the timestamp at the top of docs.go + GeneratedTime bool } // Build builds swagger json file for given searchDir and mainAPIFile. Returns json @@ -111,7 +114,7 @@ func (g *Gen) Build(config *Config) error { } // Write doc - err = g.writeGoDoc(docs, swagger) + err = g.writeGoDoc(docs, swagger, config) if err != nil { return err } @@ -142,7 +145,7 @@ func (g *Gen) formatSource(src []byte) []byte { return code } -func (g *Gen) writeGoDoc(output io.Writer, swagger *spec.Swagger) error { +func (g *Gen) writeGoDoc(output io.Writer, swagger *spec.Swagger, config *Config) error { generator, err := template.New("swagger_info").Funcs(template.FuncMap{ "printDoc": func(v string) string { @@ -195,23 +198,25 @@ func (g *Gen) writeGoDoc(output io.Writer, swagger *spec.Swagger) error { buffer := &bytes.Buffer{} err = generator.Execute(buffer, struct { - Timestamp time.Time - Doc string - Host string - BasePath string - Schemes []string - Title string - Description string - Version string + Timestamp time.Time + GeneratedTime bool + Doc string + Host string + BasePath string + Schemes []string + Title string + Description string + Version string }{ - Timestamp: time.Now(), - Doc: string(buf), - Host: swagger.Host, - BasePath: swagger.BasePath, - Schemes: swagger.Schemes, - Title: swagger.Info.Title, - Description: swagger.Info.Description, - Version: swagger.Info.Version, + Timestamp: time.Now(), + GeneratedTime: config.GeneratedTime, + Doc: string(buf), + Host: swagger.Host, + BasePath: swagger.BasePath, + Schemes: swagger.Schemes, + Title: swagger.Info.Title, + Description: swagger.Info.Description, + Version: swagger.Info.Version, }) if err != nil { return err @@ -225,8 +230,8 @@ func (g *Gen) writeGoDoc(output io.Writer, swagger *spec.Swagger) error { } var packageTemplate = `// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT -// This file was generated by swaggo/swag at -// {{ .Timestamp }} +// This file was generated by swaggo/swag{{ if .GeneratedTime }} at +// {{ .Timestamp }}{{ end }} package docs diff --git a/gen/gen_test.go b/gen/gen_test.go index e566f566c..fb47b9b3a 100644 --- a/gen/gen_test.go +++ b/gen/gen_test.go @@ -262,9 +262,14 @@ fmt.Print("Helo world") assert.NotEqual(t, []byte(src2), res, "Should return fmt code") } -type mocWriter struct{} +type mocWriter struct { + hook func([]byte) +} func (w *mocWriter) Write(data []byte) (int, error) { + if w.hook != nil { + w.hook(data) + } return len(data), nil } @@ -274,7 +279,7 @@ func TestGen_writeGoDoc(t *testing.T) { swapTemplate := packageTemplate packageTemplate = `{{{` - err := gen.writeGoDoc(nil, nil) + err := gen.writeGoDoc(nil, nil, &Config{}) assert.Error(t, err) packageTemplate = `{{.Data}}` @@ -284,9 +289,23 @@ func TestGen_writeGoDoc(t *testing.T) { Info: &spec.Info{}, }, } - err = gen.writeGoDoc(&mocWriter{}, swagger) + err = gen.writeGoDoc(&mocWriter{}, swagger, &Config{}) assert.Error(t, err) + packageTemplate = `{{ if .GeneratedTime }}Fake Time{{ end }}` + err = gen.writeGoDoc(&mocWriter{ + hook: func(data []byte) { + assert.Equal(t, "Fake Time", string(data)) + }, + }, swagger, &Config{GeneratedTime: true}) + assert.NoError(t, err) + err = gen.writeGoDoc(&mocWriter{ + hook: func(data []byte) { + assert.Equal(t, "", string(data)) + }, + }, swagger, &Config{GeneratedTime: false}) + assert.NoError(t, err) + packageTemplate = swapTemplate } From 549622face3ce712c0a3898654a85dfdc9a0a21c Mon Sep 17 00:00:00 2001 From: sdghchj Date: Sun, 29 Dec 2019 15:54:32 +0800 Subject: [PATCH 24/84] fix: body object without package name in param comments use default package name (#591) --- operation.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/operation.go b/operation.go index 11bc6e156..db598ade9 100644 --- a/operation.go +++ b/operation.go @@ -191,12 +191,18 @@ func (operation *Operation) ParseParamComment(commentLine string, astFile *ast.F if IsPrimitiveType(refType) { param.Schema.Items.Schema.Type = spec.StringOrArray{refType} } else { + if !strings.Contains(refType, ".") { + refType = astFile.Name.String() + "." + refType + } if err := operation.registerSchemaType(refType, astFile); err != nil { return err } param.Schema.Items.Schema.Ref = spec.Ref{Ref: jsonreference.MustCreateRef("#/definitions/" + refType)} } case "object": + if !strings.Contains(refType, ".") { + refType = astFile.Name.String() + "." + refType + } if err := operation.registerSchemaType(refType, astFile); err != nil { return err } From 8cedf4206a5650204be4065e109c4e7c62be5e0a Mon Sep 17 00:00:00 2001 From: erikfarhanmalik-ralali <56909677+erikfarhanmalik-ralali@users.noreply.github.com> Date: Tue, 31 Dec 2019 20:06:49 +0700 Subject: [PATCH 25/84] Fix: Use of go primitive array type in body give error (#593) * Fixed issues_592: Use of go primitive array type in body give error * Added unit test for ParseParamComment with body type is array of primitive Go type. --- operation.go | 1 + operation_test.go | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/operation.go b/operation.go index db598ade9..490d855e4 100644 --- a/operation.go +++ b/operation.go @@ -144,6 +144,7 @@ func (operation *Operation) ParseParamComment(commentLine string, astFile *ast.F if strings.HasPrefix(refType, "[]") == true { objectType = "array" refType = strings.TrimPrefix(refType, "[]") + refType = TransToValidSchemeType(refType) } else if IsPrimitiveType(refType) || paramType == "formData" && refType == "file" { objectType = "primitive" diff --git a/operation_test.go b/operation_test.go index dcf9044b9..160bd89ef 100644 --- a/operation_test.go +++ b/operation_test.go @@ -499,6 +499,33 @@ func TestParseParamCommentByBodyType(t *testing.T) { assert.Equal(t, expected, string(b)) } +func TestParseParamCommentByBodyTypeArrayOfPrimitiveGo(t *testing.T) { + comment := `@Param some_id body []int true "Some ID"` + operation := NewOperation() + operation.parser = New() + err := operation.ParseComment(comment, nil) + + assert.NoError(t, err) + b, _ := json.MarshalIndent(operation, "", " ") + expected := `{ + "parameters": [ + { + "description": "Some ID", + "name": "some_id", + "in": "body", + "required": true, + "schema": { + "type": "array", + "items": { + "type": "integer" + } + } + } + ] +}` + assert.Equal(t, expected, string(b)) +} + func TestParseParamCommentByBodyTypeErr(t *testing.T) { comment := `@Param some_id body model.OrderRow true "Some ID"` operation := NewOperation() From 74d6d6c5dfb75f3bd399712c088cae8e4cbca237 Mon Sep 17 00:00:00 2001 From: sdghchj Date: Fri, 3 Jan 2020 11:25:15 +0800 Subject: [PATCH 26/84] feature: extend struct's simple members in query comment (#594) --- operation.go | 110 +++++++++++++++++++++++++++++++++++----------- operation_test.go | 4 +- parser_test.go | 29 ++++++++++++ schema.go | 10 +++++ 4 files changed, 125 insertions(+), 28 deletions(-) diff --git a/operation.go b/operation.go index 490d855e4..d966b36ce 100644 --- a/operation.go +++ b/operation.go @@ -175,7 +175,66 @@ func (operation *Operation) ParseParamComment(commentLine string, astFile *ast.F }, } case "object": - return fmt.Errorf("%s is not supported type for %s", refType, paramType) + refType, typeSpec, err := operation.registerSchemaType(refType, astFile) + if err != nil { + return err + } + structType, ok := typeSpec.Type.(*ast.StructType) + if !ok { + return fmt.Errorf("%s is not supported type for %s", refType, paramType) + } + refSplit := strings.Split(refType, ".") + schema, err := operation.parser.parseStruct(refSplit[0], structType.Fields) + if err != nil { + return err + } + if len(schema.Properties) == 0 { + return nil + } + find := func(arr []string, target string) bool { + for _, str := range arr { + if str == target { + return true + } + } + return false + } + for name, prop := range schema.Properties { + if len(prop.Type) == 0 { + continue + } + if prop.Type[0] == "array" && + prop.Items.Schema != nil && + len(prop.Items.Schema.Type) > 0 && + IsSimplePrimitiveType(prop.Items.Schema.Type[0]) { + param = createParameter(paramType, prop.Description, name, prop.Type[0], find(schema.Required, name)) + param.SimpleSchema.Type = prop.Type[0] + param.SimpleSchema.Items = &spec.Items{ + SimpleSchema: spec.SimpleSchema{ + Type: prop.Items.Schema.Type[0], + }, + } + } else if IsSimplePrimitiveType(prop.Type[0]) { + param = createParameter(paramType, prop.Description, name, prop.Type[0], find(schema.Required, name)) + } else { + Println(fmt.Sprintf("skip field [%s] in %s is not supported type for %s", name, refType, paramType)) + continue + } + param.CommonValidations.Maximum = prop.Maximum + param.CommonValidations.Minimum = prop.Minimum + param.CommonValidations.ExclusiveMaximum = prop.ExclusiveMaximum + param.CommonValidations.ExclusiveMinimum = prop.ExclusiveMinimum + param.CommonValidations.MaxLength = prop.MaxLength + param.CommonValidations.MinLength = prop.MinLength + param.CommonValidations.Pattern = prop.Pattern + param.CommonValidations.MaxItems = prop.MaxItems + param.CommonValidations.MinItems = prop.MinItems + param.CommonValidations.UniqueItems = prop.UniqueItems + param.CommonValidations.MultipleOf = prop.MultipleOf + param.CommonValidations.Enum = prop.Enum + operation.Operation.Parameters = append(operation.Operation.Parameters, param) + } + return nil } case "body": switch objectType { @@ -192,19 +251,17 @@ func (operation *Operation) ParseParamComment(commentLine string, astFile *ast.F if IsPrimitiveType(refType) { param.Schema.Items.Schema.Type = spec.StringOrArray{refType} } else { - if !strings.Contains(refType, ".") { - refType = astFile.Name.String() + "." + refType - } - if err := operation.registerSchemaType(refType, astFile); err != nil { + var err error + refType, _, err = operation.registerSchemaType(refType, astFile) + if err != nil { return err } param.Schema.Items.Schema.Ref = spec.Ref{Ref: jsonreference.MustCreateRef("#/definitions/" + refType)} } case "object": - if !strings.Contains(refType, ".") { - refType = astFile.Name.String() + "." + refType - } - if err := operation.registerSchemaType(refType, astFile); err != nil { + var err error + refType, _, err = operation.registerSchemaType(refType, astFile) + if err != nil { return err } param.Schema.Type = []string{} @@ -223,20 +280,23 @@ func (operation *Operation) ParseParamComment(commentLine string, astFile *ast.F return nil } -func (operation *Operation) registerSchemaType(schemaType string, astFile *ast.File) error { - refSplit := strings.Split(schemaType, ".") - if len(refSplit) != 2 { - return nil +func (operation *Operation) registerSchemaType(schemaType string, astFile *ast.File) (string, *ast.TypeSpec, error) { + if !strings.ContainsRune(schemaType, '.') { + if astFile == nil { + return schemaType, nil, fmt.Errorf("no package name for type %s", schemaType) + } + schemaType = astFile.Name.String() + "." + schemaType } + refSplit := strings.Split(schemaType, ".") pkgName := refSplit[0] typeName := refSplit[1] if typeSpec, ok := operation.parser.TypeDefinitions[pkgName][typeName]; ok { operation.parser.registerTypes[schemaType] = typeSpec - return nil + return schemaType, typeSpec, nil } var typeSpec *ast.TypeSpec if astFile == nil { - return fmt.Errorf("can not register schema type: %q reason: astFile == nil", schemaType) + return schemaType, nil, fmt.Errorf("can not register schema type: %q reason: astFile == nil", schemaType) } for _, imp := range astFile.Imports { if imp.Name != nil && imp.Name.Name == pkgName { // the import had an alias that matched @@ -247,14 +307,14 @@ func (operation *Operation) registerSchemaType(schemaType string, astFile *ast.F var err error typeSpec, err = findTypeDef(impPath, typeName) if err != nil { - return fmt.Errorf("can not find type def: %q error: %s", schemaType, err) + return schemaType, nil, fmt.Errorf("can not find type def: %q error: %s", schemaType, err) } break } } if typeSpec == nil { - return fmt.Errorf("can not find schema type: %q", schemaType) + return schemaType, nil, fmt.Errorf("can not find schema type: %q", schemaType) } if _, ok := operation.parser.TypeDefinitions[pkgName]; !ok { @@ -263,7 +323,7 @@ func (operation *Operation) registerSchemaType(schemaType string, astFile *ast.F operation.parser.TypeDefinitions[pkgName][typeName] = typeSpec operation.parser.registerTypes[schemaType] = typeSpec - return nil + return schemaType, typeSpec, nil } var regexAttributes = map[string]*regexp.Regexp{ @@ -571,14 +631,12 @@ func (operation *Operation) ParseResponseComment(commentLine string, astFile *as schemaType := strings.Trim(matches[2], "{}") refType := matches[3] - if !IsGolangPrimitiveType(refType) && !strings.Contains(refType, ".") { - currentPkgName := astFile.Name.String() - refType = currentPkgName + "." + refType - } - - if operation.parser != nil { // checking refType has existing in 'TypeDefinitions' - if err := operation.registerSchemaType(refType, astFile); err != nil { - return err + if !IsGolangPrimitiveType(refType) { + if operation.parser != nil { // checking refType has existing in 'TypeDefinitions' + var err error + if refType, _, err = operation.registerSchemaType(refType, astFile); err != nil { + return err + } } } diff --git a/operation_test.go b/operation_test.go index 160bd89ef..64951a3a0 100644 --- a/operation_test.go +++ b/operation_test.go @@ -944,7 +944,6 @@ func TestParseDeprecationDescription(t *testing.T) { func TestRegisterSchemaType(t *testing.T) { operation := NewOperation() - assert.NoError(t, operation.registerSchemaType("string", nil)) fset := token.NewFileSet() astFile, err := goparser.ParseFile(fset, "main.go", `package main @@ -954,7 +953,8 @@ func TestRegisterSchemaType(t *testing.T) { assert.NoError(t, err) operation.parser = New() - assert.Error(t, operation.registerSchemaType("timer.Location", astFile)) + _, _, err = operation.registerSchemaType("timer.Location", astFile) + assert.Error(t, err) } func TestParseExtentions(t *testing.T) { diff --git a/parser_test.go b/parser_test.go index c1bca74ba..063f938af 100644 --- a/parser_test.go +++ b/parser_test.go @@ -2962,3 +2962,32 @@ func TestParseOutsideDependencies(t *testing.T) { t.Error("Failed to parse api: " + err.Error()) } } + +func TestParseStructParamCommentByQueryType(t *testing.T) { + src := ` +package main + +type Student struct { + Name string + Age int + Teachers []string + SkipField map[string]string +} + +// @Param request query Student true "query params" +// @Success 200 +// @Router /test [get] +func Fun() { + +} +` + f, err := goparser.ParseFile(token.NewFileSet(), "", src, goparser.ParseComments) + assert.NoError(t, err) + + p := New() + p.ParseType(f) + err = p.ParseRouterAPIInfo("", f) + assert.NoError(t, err) + + assert.Equal(t, 3, len(p.swagger.Paths.Paths["/test"].Get.Parameters)) +} diff --git a/schema.go b/schema.go index 2ebef1678..ba5fe19e1 100644 --- a/schema.go +++ b/schema.go @@ -10,6 +10,16 @@ func CheckSchemaType(typeName string) error { return nil } +// IsSimplePrimitiveType determine whether the type name is a simple primitive type +func IsSimplePrimitiveType(typeName string) bool { + switch typeName { + case "string", "number", "integer", "boolean": + return true + default: + return false + } +} + // IsPrimitiveType determine whether the type name is a primitive type func IsPrimitiveType(typeName string) bool { switch typeName { From 95cdaaff5a792f3aa1b9009ab12ef8a4702a26c5 Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Sun, 5 Jan 2020 14:07:01 +0800 Subject: [PATCH 27/84] jump to v1.6.4 (#597) --- version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.go b/version.go index 7e69b3750..2ae9c96fe 100644 --- a/version.go +++ b/version.go @@ -1,4 +1,4 @@ package swag // Version of swag -const Version = "v1.6.3" +const Version = "v1.6.4" From 5b1151f90cc4c5643e3a718a5fd0970cb2cd7f5e Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Fri, 10 Jan 2020 04:58:58 +0800 Subject: [PATCH 28/84] fix: fix cli dependencies (#603) * fix: fix cli dependencies * drop go1.10 --- .github/workflows/ci.yml | 2 +- .travis.yml | 1 - cmd/swag/main.go | 2 +- go.mod | 3 ++- go.sum | 24 ++++++++++++++++++++++-- 5 files changed, 26 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index da1ea693e..4abd3fc28 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,7 +3,7 @@ jobs: test: strategy: matrix: - go: [ '1.10.x', '1.11.x', '1.12.x' ] + go: [ '1.11.x', '1.12.x' ] platform: [ubuntu-latest, macos-latest] runs-on: ${{ matrix.platform }} steps: diff --git a/.travis.yml b/.travis.yml index 560bdf389..fe3166cd0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,6 @@ language: go sudo: false go: - - 1.10.x - 1.11.x - 1.12.x diff --git a/cmd/swag/main.go b/cmd/swag/main.go index af55bb199..5d79685db 100644 --- a/cmd/swag/main.go +++ b/cmd/swag/main.go @@ -5,9 +5,9 @@ import ( "log" "os" - "github.com/swaggo/cli" "github.com/swaggo/swag" "github.com/swaggo/swag/gen" + "github.com/urfave/cli" ) const ( diff --git a/go.mod b/go.mod index 498406887..e250c1a7d 100644 --- a/go.mod +++ b/go.mod @@ -7,10 +7,11 @@ require ( github.com/gin-gonic/gin v1.4.0 github.com/go-openapi/jsonreference v0.19.3 github.com/go-openapi/spec v0.19.4 + github.com/satori/go.uuid v1.2.0 github.com/stretchr/testify v1.4.0 - github.com/swaggo/cli v1.20.0 github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14 github.com/swaggo/gin-swagger v1.2.0 + github.com/urfave/cli v1.22.2 golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59 ) diff --git a/go.sum b/go.sum index a7a343388..156dabc4e 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,4 @@ +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/PuerkitoBio/purell v1.1.0 h1:rmGxhojJlM0tuKtfdvliR84CFHljx9ag64t2xmVkjK4= @@ -8,11 +9,14 @@ github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/gzip v0.0.1 h1:ezvKOL6jH+jlzdHNE4h9h8q8uMpDQjyl0NN0Jd7jozc= github.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w= github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= @@ -45,10 +49,13 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -59,11 +66,19 @@ github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -71,9 +86,9 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/swaggo/cli v1.20.0 h1:r3zaMDTQDEq5iMe2OGMrehbHyg8q0Et74g8j6uPK6iY= -github.com/swaggo/cli v1.20.0/go.mod h1:7jzoQluD0EWMc0rxx6kkPoRNfYNHkNJI/NokjEwJiwM= +github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14 h1:PyYN9JH5jY9j6av01SpfRMb+1DWg/i3MbGOKPxJ2wjM= github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E= +github.com/swaggo/gin-swagger v1.2.0 h1:YskZXEiv51fjOMTsXrOetAjrMDfFaXD79PEoQBOe2W0= github.com/swaggo/gin-swagger v1.2.0/go.mod h1:qlH2+W7zXGZkczuL+r2nEBR2JTT+/lX05Nn6vPhc7OI= github.com/swaggo/swag v1.5.1/go.mod h1:1Bl9F/ZBpVWh22nY0zmYyASPO1lI/zIwRDrpZU+tv8Y= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= @@ -83,6 +98,8 @@ github.com/ugorji/go/codec v0.0.0-20181022190402-e5e69e061d4f/go.mod h1:VFNgLljT github.com/ugorji/go/codec v1.1.5-pre h1:5YV9PsFAN+ndcCtTM7s60no7nY7eTG3LPtxhSwuxzCs= github.com/ugorji/go/codec v1.1.5-pre/go.mod h1:tULtS6Gy1AE1yCENaw4Vb//HLH5njI2tfCQDUqRd8fI= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo= +github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -103,6 +120,7 @@ golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae h1:xiXzMMEQdQcric9hXtr1QU98MHunKK7OTtsoU6bYWs4= golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f h1:25KHgbfyiSm6vwQLbM3zZIe1v9p/3ea4Rz+nnM5K/i4= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= @@ -114,7 +132,9 @@ golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59 h1:QjA/9ArTfVTLfEhClDCG7SGrZkZixxWpwNCDiwJfh88= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= From ca464bf74793f6bc18b622bd751c1a2a8d6d023f Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Fri, 10 Jan 2020 19:49:27 +0800 Subject: [PATCH 29/84] feat: support only remain slash (#605) * support only remain slash * fix gofmt * move test in right place Co-authored-by: sdghchj Co-authored-by: Bogdan U --- operation.go | 2 +- operation_test.go | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/operation.go b/operation.go index d966b36ce..b3ee04379 100644 --- a/operation.go +++ b/operation.go @@ -505,7 +505,7 @@ func parseMimeTypeList(mimeTypeList string, typeList *[]string, format string) e return nil } -var routerPattern = regexp.MustCompile(`^(/[\w\.\/\-{}\+:]+)[[:blank:]]+\[(\w+)]`) +var routerPattern = regexp.MustCompile(`^(/[\w\.\/\-{}\+:]*)[[:blank:]]+\[(\w+)]`) // ParseRouterComment parses comment for gived `router` comment string. func (operation *Operation) ParseRouterComment(commentLine string) error { diff --git a/operation_test.go b/operation_test.go index 64951a3a0..6653a63e1 100644 --- a/operation_test.go +++ b/operation_test.go @@ -111,6 +111,15 @@ func TestParseRouterComment(t *testing.T) { assert.Equal(t, "GET", operation.HTTPMethod) } +func TestParseRouterOnlySlash(t *testing.T) { + comment := `// @Router / [get]` + operation := NewOperation() + err := operation.ParseComment(comment, nil) + assert.NoError(t, err) + assert.Equal(t, "/", operation.Path) + assert.Equal(t, "GET", operation.HTTPMethod) +} + func TestParseRouterCommentWithPlusSign(t *testing.T) { comment := `/@Router /customer/get-wishlist/{proxy+} [post]` operation := NewOperation() From aa2a12da74772a630c900f86750ba3c2129cfe0b Mon Sep 17 00:00:00 2001 From: Stein Fletcher Date: Fri, 10 Jan 2020 15:31:25 +0000 Subject: [PATCH 30/84] Fix typo on README page (#607) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1c912babb..f0a471d67 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ $ go get -u github.com/swaggo/swag/cmd/swag ``` To build from source you need [Go](https://golang.org/dl/) (1.9 or newer). -Or download the pre-compiled binaries binary form [release page](https://github.com/swaggo/swag/releases). +Or download a pre-compiled binary from the [release page](https://github.com/swaggo/swag/releases). 3. Run `swag init` in the project's root folder which contains the `main.go` file. This will parse your comments and generate the required files (`docs` folder and `docs/docs.go`). ```sh From eebe67fc31c39a02a763bcaa815baab805112cf3 Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Sat, 11 Jan 2020 22:04:14 +0800 Subject: [PATCH 31/84] chore: jump to v1.6.5 (#609) --- version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.go b/version.go index 2ae9c96fe..60c01d518 100644 --- a/version.go +++ b/version.go @@ -1,4 +1,4 @@ package swag // Version of swag -const Version = "v1.6.4" +const Version = "v1.6.5" From de5290bdc2aaaae17da8e640481f3ffe3406dd40 Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Fri, 17 Jan 2020 18:16:04 +0800 Subject: [PATCH 32/84] chore: add go1.13 support (#613) * chore: add go1.13 support * remove sudo * change external pkg --- .travis.yml | 2 +- operation_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index fe3166cd0..825e2482f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,9 @@ language: go -sudo: false go: - 1.11.x - 1.12.x + - 1.13.x install: - make deps diff --git a/operation_test.go b/operation_test.go index 6653a63e1..ee58db0f6 100644 --- a/operation_test.go +++ b/operation_test.go @@ -871,7 +871,7 @@ func TestFindTypeDefCoreLib(t *testing.T) { } func TestFindTypeDefExternalPkg(t *testing.T) { - spec, err := findTypeDef("github.com/stretchr/testify/assert", "Assertions") + spec, err := findTypeDef("github.com/KyleBanks/depth", "Tree") assert.NoError(t, err) assert.NotNil(t, spec) } From 87eac7eb926173ec78c7438e83f020b520338c14 Mon Sep 17 00:00:00 2001 From: Anton Kucherov Date: Sun, 19 Jan 2020 01:12:01 +0300 Subject: [PATCH 33/84] Fix dataTypeFormatlink link. (#614) * fix datatype link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f0a471d67..9d987c265 100644 --- a/README.md +++ b/README.md @@ -453,7 +453,7 @@ Field Name | Type | Description maxLength | `integer` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.1. minLength | `integer` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.2. enums | [\*] | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.5.1. -format | `string` | The extending format for the previously mentioned [`type`](#parameterType). See [Data Type Formats](#dataTypeFormat) for further details. +format | `string` | The extending format for the previously mentioned [`type`](#parameterType). See [Data Type Formats](https://swagger.io/specification/v2/#dataTypeFormat) for further details. ### Future From 51a9215418daa241339467b190e72ceeb7aafeb8 Mon Sep 17 00:00:00 2001 From: Bruce A Downs Date: Fri, 24 Jan 2020 07:59:34 -0800 Subject: [PATCH 34/84] Reflect optional output directory in the generated docs.go (#617) --- gen/gen.go | 23 ++++++++++++----------- gen/gen_test.go | 32 +++++++++++++++++--------------- 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/gen/gen.go b/gen/gen.go index 39bd7b3c8..0edd211c2 100644 --- a/gen/gen.go +++ b/gen/gen.go @@ -13,9 +13,8 @@ import ( "text/template" "time" - "github.com/go-openapi/spec" - "github.com/ghodss/yaml" + "github.com/go-openapi/spec" "github.com/swaggo/swag" ) @@ -88,6 +87,7 @@ func (g *Gen) Build(config *Config) error { return err } + packageName := path.Base(config.OutputDir) docFileName := path.Join(config.OutputDir, "docs.go") jsonFileName := path.Join(config.OutputDir, "swagger.json") yamlFileName := path.Join(config.OutputDir, "swagger.yaml") @@ -105,7 +105,7 @@ func (g *Gen) Build(config *Config) error { y, err := g.jsonToYAML(b) if err != nil { - return fmt.Errorf("cannot covert json to yaml error: %s", err) + return fmt.Errorf("cannot convert json to yaml error: %s", err) } err = g.writeFile(y, yamlFileName) @@ -114,14 +114,14 @@ func (g *Gen) Build(config *Config) error { } // Write doc - err = g.writeGoDoc(docs, swagger, config) + err = g.writeGoDoc(packageName, docs, swagger, config) if err != nil { return err } - log.Printf("create docs.go at %+v", docFileName) - log.Printf("create swagger.json at %+v", jsonFileName) - log.Printf("create swagger.yaml at %+v", yamlFileName) + log.Printf("create docs.go at %+v", docFileName) + log.Printf("create swagger.json at %+v", jsonFileName) + log.Printf("create swagger.yaml at %+v", yamlFileName) return nil } @@ -140,13 +140,12 @@ func (g *Gen) writeFile(b []byte, file string) error { func (g *Gen) formatSource(src []byte) []byte { code, err := format.Source(src) if err != nil { - code = src // Output the unformated code anyway + code = src // Output the unformatted code anyway } return code } -func (g *Gen) writeGoDoc(output io.Writer, swagger *spec.Swagger, config *Config) error { - +func (g *Gen) writeGoDoc(packageName string, output io.Writer, swagger *spec.Swagger, config *Config) error { generator, err := template.New("swagger_info").Funcs(template.FuncMap{ "printDoc": func(v string) string { // Add schemes @@ -202,6 +201,7 @@ func (g *Gen) writeGoDoc(output io.Writer, swagger *spec.Swagger, config *Config GeneratedTime bool Doc string Host string + PackageName string BasePath string Schemes []string Title string @@ -212,6 +212,7 @@ func (g *Gen) writeGoDoc(output io.Writer, swagger *spec.Swagger, config *Config GeneratedTime: config.GeneratedTime, Doc: string(buf), Host: swagger.Host, + PackageName: packageName, BasePath: swagger.BasePath, Schemes: swagger.Schemes, Title: swagger.Info.Title, @@ -233,7 +234,7 @@ var packageTemplate = `// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT // This file was generated by swaggo/swag{{ if .GeneratedTime }} at // {{ .Timestamp }}{{ end }} -package docs +package {{.PackageName}} import ( "bytes" diff --git a/gen/gen_test.go b/gen/gen_test.go index fb47b9b3a..075917b58 100644 --- a/gen/gen_test.go +++ b/gen/gen_test.go @@ -2,13 +2,13 @@ package gen import ( "errors" - "github.com/go-openapi/spec" "os" "os/exec" "path" "path/filepath" "testing" + "github.com/go-openapi/spec" "github.com/stretchr/testify/assert" ) @@ -262,11 +262,11 @@ fmt.Print("Helo world") assert.NotEqual(t, []byte(src2), res, "Should return fmt code") } -type mocWriter struct { +type mockWriter struct { hook func([]byte) } -func (w *mocWriter) Write(data []byte) (int, error) { +func (w *mockWriter) Write(data []byte) (int, error) { if w.hook != nil { w.hook(data) } @@ -279,7 +279,7 @@ func TestGen_writeGoDoc(t *testing.T) { swapTemplate := packageTemplate packageTemplate = `{{{` - err := gen.writeGoDoc(nil, nil, &Config{}) + err := gen.writeGoDoc("docs", nil, nil, &Config{}) assert.Error(t, err) packageTemplate = `{{.Data}}` @@ -289,21 +289,23 @@ func TestGen_writeGoDoc(t *testing.T) { Info: &spec.Info{}, }, } - err = gen.writeGoDoc(&mocWriter{}, swagger, &Config{}) + err = gen.writeGoDoc("docs", &mockWriter{}, swagger, &Config{}) assert.Error(t, err) packageTemplate = `{{ if .GeneratedTime }}Fake Time{{ end }}` - err = gen.writeGoDoc(&mocWriter{ - hook: func(data []byte) { - assert.Equal(t, "Fake Time", string(data)) - }, - }, swagger, &Config{GeneratedTime: true}) + err = gen.writeGoDoc("docs", + &mockWriter{ + hook: func(data []byte) { + assert.Equal(t, "Fake Time", string(data)) + }, + }, swagger, &Config{GeneratedTime: true}) assert.NoError(t, err) - err = gen.writeGoDoc(&mocWriter{ - hook: func(data []byte) { - assert.Equal(t, "", string(data)) - }, - }, swagger, &Config{GeneratedTime: false}) + err = gen.writeGoDoc("docs", + &mockWriter{ + hook: func(data []byte) { + assert.Equal(t, "", string(data)) + }, + }, swagger, &Config{GeneratedTime: false}) assert.NoError(t, err) packageTemplate = swapTemplate From 86e76b764018c8e0dc882f0756e665dafe42b662 Mon Sep 17 00:00:00 2001 From: xiaodiannao <54172185+xiaodiannao@users.noreply.github.com> Date: Sat, 1 Feb 2020 03:42:25 +0100 Subject: [PATCH 35/84] feat: migrate to using`/urfave/cli/v2` (#618) * Fixing cli.Flag errors * BoolTFlag fix * BoolTFlag is now BoolFlag * Update main.go * Update main.go * Update main.go * Update main.go * Update main.go * Changed CLI version, updated go.mod --- cmd/swag/main.go | 22 +++++++++++----------- go.mod | 2 +- go.sum | 5 +++-- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/cmd/swag/main.go b/cmd/swag/main.go index 5d79685db..59dd0d4a8 100644 --- a/cmd/swag/main.go +++ b/cmd/swag/main.go @@ -7,7 +7,7 @@ import ( "github.com/swaggo/swag" "github.com/swaggo/swag/gen" - "github.com/urfave/cli" + "github.com/urfave/cli/v2" ) const ( @@ -22,40 +22,40 @@ const ( ) var initFlags = []cli.Flag{ - cli.StringFlag{ + &cli.StringFlag{ Name: generalInfoFlag + ", g", Value: "main.go", Usage: "Go file path in which 'swagger general API Info' is written", }, - cli.StringFlag{ + &cli.StringFlag{ Name: searchDirFlag + ", d", Value: "./", Usage: "Directory you want to parse", }, - cli.StringFlag{ + &cli.StringFlag{ Name: propertyStrategyFlag + ", p", Value: "camelcase", Usage: "Property Naming Strategy like snakecase,camelcase,pascalcase", }, - cli.StringFlag{ + &cli.StringFlag{ Name: outputFlag + ", o", Value: "./docs", Usage: "Output directory for all the generated files(swagger.json, swagger.yaml and doc.go)", }, - cli.BoolFlag{ + &cli.BoolFlag{ Name: parseVendorFlag, Usage: "Parse go files in 'vendor' folder, disabled by default", }, - cli.BoolFlag{ + &cli.BoolFlag{ Name: parseDependencyFlag, Usage: "Parse go files in outside dependency folder, disabled by default", }, - cli.StringFlag{ + &cli.StringFlag{ Name: markdownFilesFlag + ", md", Value: "", Usage: "Parse folder containing markdown files to use as description, disabled by default", }, - cli.BoolTFlag{ + &cli.BoolFlag{ Name: "generatedTime", Usage: "Generate timestamp at the top of docs.go, true by default", }, @@ -78,7 +78,7 @@ func initAction(c *cli.Context) error { ParseVendor: c.Bool(parseVendorFlag), ParseDependency: c.Bool(parseDependencyFlag), MarkdownFilesDir: c.String(markdownFilesFlag), - GeneratedTime: c.BoolT(generatedTimeFlag), + GeneratedTime: c.Bool(generatedTimeFlag), }) } @@ -86,7 +86,7 @@ func main() { app := cli.NewApp() app.Version = swag.Version app.Usage = "Automatically generate RESTful API documentation with Swagger 2.0 for Go." - app.Commands = []cli.Command{ + app.Commands = []*cli.Command{ { Name: "init", Aliases: []string{"i"}, diff --git a/go.mod b/go.mod index e250c1a7d..f02834645 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/stretchr/testify v1.4.0 github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14 github.com/swaggo/gin-swagger v1.2.0 - github.com/urfave/cli v1.22.2 + github.com/urfave/cli/v2 v2.1.1 golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59 ) diff --git a/go.sum b/go.sum index 156dabc4e..ab7e7c11c 100644 --- a/go.sum +++ b/go.sum @@ -97,9 +97,10 @@ github.com/ugorji/go v1.1.5-pre/go.mod h1:FwP/aQVg39TXzItUBMwnWp9T9gPQnXw4Poh4/o github.com/ugorji/go/codec v0.0.0-20181022190402-e5e69e061d4f/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v1.1.5-pre h1:5YV9PsFAN+ndcCtTM7s60no7nY7eTG3LPtxhSwuxzCs= github.com/ugorji/go/codec v1.1.5-pre/go.mod h1:tULtS6Gy1AE1yCENaw4Vb//HLH5njI2tfCQDUqRd8fI= +github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo= -github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k= +github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= From f150c139699a0333974d879a76a1ee1f451e6b51 Mon Sep 17 00:00:00 2001 From: Anton Kucherov Date: Sat, 8 Feb 2020 10:45:33 +0300 Subject: [PATCH 36/84] fix: add code to handle and skip structs with "func" fields. (#616) Co-authored-by: Eason Lin --- parser.go | 5 +++++ property.go | 5 +++++ schema.go | 2 +- testdata/simple3/web/handler.go | 1 + 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/parser.go b/parser.go index b3d1dac56..438630d98 100644 --- a/parser.go +++ b/parser.go @@ -1133,6 +1133,11 @@ func (parser *Parser) parseField(field *ast.Field) (*structField, error) { } } + // Skip func fields. + if prop.SchemaType == "func" { + return &structField{name: ""}, nil + } + structField := &structField{ name: field.Names[0].Name, schemaType: prop.SchemaType, diff --git a/property.go b/property.go index 04ae649f5..f65f17849 100644 --- a/property.go +++ b/property.go @@ -118,6 +118,11 @@ func getPropertyName(expr ast.Expr, parser *Parser) (propertyName, error) { if _, ok := expr.(*ast.InterfaceType); ok { // if interface{} return propertyName{SchemaType: "object", ArrayType: "object"}, nil } + + if _, ok := expr.(*ast.FuncType); ok { // if func() + return propertyName{SchemaType: "func", ArrayType: ""}, nil + } + return propertyName{}, errors.New("not supported" + fmt.Sprint(expr)) } diff --git a/schema.go b/schema.go index ba5fe19e1..c05a08c12 100644 --- a/schema.go +++ b/schema.go @@ -23,7 +23,7 @@ func IsSimplePrimitiveType(typeName string) bool { // IsPrimitiveType determine whether the type name is a primitive type func IsPrimitiveType(typeName string) bool { switch typeName { - case "string", "number", "integer", "boolean", "array", "object": + case "string", "number", "integer", "boolean", "array", "object", "func": return true default: return false diff --git a/testdata/simple3/web/handler.go b/testdata/simple3/web/handler.go index b10849525..0447e4d26 100644 --- a/testdata/simple3/web/handler.go +++ b/testdata/simple3/web/handler.go @@ -31,6 +31,7 @@ type Pet struct { Hidden string `json:"-"` UUID uuid.UUID Decimal decimal.Decimal + Function func() } type Tag struct { From ec7b993a71ece699075d48df773e11f5776c6940 Mon Sep 17 00:00:00 2001 From: Eugene Denisenko Date: Wed, 19 Feb 2020 20:00:21 +0300 Subject: [PATCH 37/84] Issue #225 --- parser.go | 9 +++++---- property.go | 18 ++++++++++-------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/parser.go b/parser.go index 438630d98..16f4fb96c 100644 --- a/parser.go +++ b/parser.go @@ -516,7 +516,8 @@ func (parser *Parser) ParseType(astFile *ast.File) { typeName := fmt.Sprintf("%v", typeSpec.Type) // check if its a custom primitive type if IsGolangPrimitiveType(typeName) { - parser.CustomPrimitiveTypes[typeSpec.Name.String()] = TransToValidSchemeType(typeName) + var typeSpecFullName = fmt.Sprintf("%s.%s", astFile.Name.String(), typeSpec.Name.String()) + parser.CustomPrimitiveTypes[typeSpecFullName] = TransToValidSchemeType(typeName) } else { parser.TypeDefinitions[astFile.Name.String()][typeSpec.Name.String()] = typeSpec } @@ -860,7 +861,7 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st return properties, nil, nil } - structField, err := parser.parseField(field) + structField, err := parser.parseField(pkgName, field) if err != nil { return properties, nil, err } @@ -1117,8 +1118,8 @@ func getFieldType(field interface{}) (string, error) { return "", fmt.Errorf("unknown field type %#v", field) } -func (parser *Parser) parseField(field *ast.Field) (*structField, error) { - prop, err := getPropertyName(field.Type, parser) +func (parser *Parser) parseField(pkgName string, field *ast.Field) (*structField, error) { + prop, err := getPropertyName(pkgName, field.Type, parser) if err != nil { return nil, err } diff --git a/property.go b/property.go index f65f17849..342041b80 100644 --- a/property.go +++ b/property.go @@ -73,7 +73,8 @@ func parseFieldSelectorExpr(astTypeSelectorExpr *ast.SelectorExpr, parser *Parse } } } - if actualPrimitiveType, isCustomType := parser.CustomPrimitiveTypes[astTypeSelectorExpr.Sel.Name]; isCustomType { + name := fmt.Sprintf("%s.%v", pkgName, astTypeSelectorExpr.Sel.Name) + if actualPrimitiveType, isCustomType := parser.CustomPrimitiveTypes[name]; isCustomType { return propertyName{SchemaType: actualPrimitiveType, ArrayType: actualPrimitiveType} } } @@ -82,13 +83,13 @@ func parseFieldSelectorExpr(astTypeSelectorExpr *ast.SelectorExpr, parser *Parse // getPropertyName returns the string value for the given field if it exists // allowedValues: array, boolean, integer, null, number, object, string -func getPropertyName(expr ast.Expr, parser *Parser) (propertyName, error) { +func getPropertyName(pkgName string, expr ast.Expr, parser *Parser) (propertyName, error) { if astTypeSelectorExpr, ok := expr.(*ast.SelectorExpr); ok { return parseFieldSelectorExpr(astTypeSelectorExpr, parser, newProperty), nil } // check if it is a custom type - typeName := fmt.Sprintf("%v", expr) + typeName := fmt.Sprintf("%s.%v", pkgName, expr) if actualPrimitiveType, isCustomType := parser.CustomPrimitiveTypes[typeName]; isCustomType { return propertyName{SchemaType: actualPrimitiveType, ArrayType: actualPrimitiveType}, nil } @@ -100,11 +101,11 @@ func getPropertyName(expr ast.Expr, parser *Parser) (propertyName, error) { } if ptr, ok := expr.(*ast.StarExpr); ok { - return getPropertyName(ptr.X, parser) + return getPropertyName(pkgName, ptr.X, parser) } if astTypeArray, ok := expr.(*ast.ArrayType); ok { // if array - return getArrayPropertyName(astTypeArray.Elt, parser), nil + return getArrayPropertyName(pkgName, astTypeArray.Elt, parser), nil } if _, ok := expr.(*ast.MapType); ok { // if map @@ -126,24 +127,25 @@ func getPropertyName(expr ast.Expr, parser *Parser) (propertyName, error) { return propertyName{}, errors.New("not supported" + fmt.Sprint(expr)) } -func getArrayPropertyName(astTypeArrayElt ast.Expr, parser *Parser) propertyName { +func getArrayPropertyName(pkgName string, astTypeArrayElt ast.Expr, parser *Parser) propertyName { switch elt := astTypeArrayElt.(type) { case *ast.StructType, *ast.MapType, *ast.InterfaceType: return propertyName{SchemaType: "array", ArrayType: "object"} case *ast.ArrayType: return propertyName{SchemaType: "array", ArrayType: "array"} case *ast.StarExpr: - return getArrayPropertyName(elt.X, parser) + return getArrayPropertyName(pkgName, elt.X, parser) case *ast.SelectorExpr: return parseFieldSelectorExpr(elt, parser, newArrayProperty) case *ast.Ident: - name := TransToValidSchemeType(elt.Name) + name := fmt.Sprintf("%s.%s", pkgName, TransToValidSchemeType(elt.Name)) if actualPrimitiveType, isCustomType := parser.CustomPrimitiveTypes[name]; isCustomType { name = actualPrimitiveType } return propertyName{SchemaType: "array", ArrayType: name} default: name := TransToValidSchemeType(fmt.Sprintf("%s", astTypeArrayElt)) + name = fmt.Sprintf("%s.%s", pkgName, name) if actualPrimitiveType, isCustomType := parser.CustomPrimitiveTypes[name]; isCustomType { name = actualPrimitiveType } From 577f62e4e1d8cc21711f4b3d4fb5a6643e6a82da Mon Sep 17 00:00:00 2001 From: Eugene Denisenko Date: Wed, 19 Feb 2020 20:36:44 +0300 Subject: [PATCH 38/84] Issue #225 --- property_test.go | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/property_test.go b/property_test.go index 854348cfa..c55ab74ec 100644 --- a/property_test.go +++ b/property_test.go @@ -25,7 +25,7 @@ func TestGetPropertyNameSelectorExpr(t *testing.T) { "string", "", } - propertyName, err := getPropertyName(input, New()) + propertyName, err := getPropertyName("test", input, New()) assert.NoError(t, err) assert.Equal(t, expected, propertyName) } @@ -49,7 +49,7 @@ func TestGetPropertyNameIdentObjectId(t *testing.T) { "", } - propertyName, err := getPropertyName(input, New()) + propertyName, err := getPropertyName("test", input, New()) assert.NoError(t, err) assert.Equal(t, expected, propertyName) } @@ -73,7 +73,7 @@ func TestGetPropertyNameIdentUUID(t *testing.T) { "", } - propertyName, err := getPropertyName(input, New()) + propertyName, err := getPropertyName("test", input, New()) assert.NoError(t, err) assert.Equal(t, expected, propertyName) } @@ -96,7 +96,7 @@ func TestGetPropertyNameIdentDecimal(t *testing.T) { "string", "", } - propertyName, err := getPropertyName(input, New()) + propertyName, err := getPropertyName("test", input, New()) assert.NoError(t, err) assert.Equal(t, expected, propertyName) } @@ -120,7 +120,7 @@ func TestGetPropertyNameIdentTime(t *testing.T) { "", } - propertyName, err := getPropertyName(input, nil) + propertyName, err := getPropertyName("test", input, nil) assert.NoError(t, err) assert.Equal(t, expected, propertyName) } @@ -140,7 +140,7 @@ func TestGetPropertyNameStarExprIdent(t *testing.T) { "", } - propertyName, err := getPropertyName(input, New()) + propertyName, err := getPropertyName("test", input, New()) assert.NoError(t, err) assert.Equal(t, expected, propertyName) } @@ -168,7 +168,7 @@ func TestGetPropertyNameStarExprMap(t *testing.T) { "", } - propertyName, err := getPropertyName(input, New()) + propertyName, err := getPropertyName("test", input, New()) assert.NoError(t, err) assert.Equal(t, expected, propertyName) } @@ -187,10 +187,10 @@ func TestGetPropertyNameArrayStarExpr(t *testing.T) { } expected := propertyName{ "array", - "string", + "test.string", "", } - propertyName, err := getPropertyName(input, New()) + propertyName, err := getPropertyName("test", input, New()) assert.NoError(t, err) assert.Equal(t, expected, propertyName) } @@ -220,7 +220,7 @@ func TestGetPropertyNameArrayStarExprSelector(t *testing.T) { "", } - propertyName, err := getPropertyName(input, New()) + propertyName, err := getPropertyName("test", input, New()) assert.NoError(t, err) assert.Equal(t, expected, propertyName) } @@ -237,7 +237,7 @@ func TestGetPropertyNameArrayStructType(t *testing.T) { "", } - propertyName, err := getPropertyName(input, New()) + propertyName, err := getPropertyName("test", input, New()) assert.NoError(t, err) assert.Equal(t, expected, propertyName) } @@ -257,7 +257,7 @@ func TestGetPropertyNameMap(t *testing.T) { "", } - propertyName, err := getPropertyName(input, New()) + propertyName, err := getPropertyName("test", input, New()) assert.NoError(t, err) assert.Equal(t, expected, propertyName) } @@ -270,7 +270,7 @@ func TestGetPropertyNameStruct(t *testing.T) { "", } - propertyName, err := getPropertyName(input, New()) + propertyName, err := getPropertyName("test", input, New()) assert.NoError(t, err) assert.Equal(t, expected, propertyName) } @@ -283,14 +283,14 @@ func TestGetPropertyNameInterface(t *testing.T) { "", } - propertyName, err := getPropertyName(input, New()) + propertyName, err := getPropertyName("test", input, New()) assert.NoError(t, err) assert.Equal(t, expected, propertyName) } func TestGetPropertyNameChannel(t *testing.T) { input := &ast.ChanType{} - _, err := getPropertyName(input, New()) + _, err := getPropertyName("test", input, New()) assert.Error(t, err) } From 51c115b07cff673ac552a14acfeeb7e4ab4db3bb Mon Sep 17 00:00:00 2001 From: Eugene Denisenko Date: Sat, 22 Feb 2020 16:20:45 +0300 Subject: [PATCH 39/84] Issue #225 --- property.go | 63 +++++++++++++++++------------------------------- property_test.go | 2 +- 2 files changed, 23 insertions(+), 42 deletions(-) diff --git a/property.go b/property.go index 342041b80..fa4de1149 100644 --- a/property.go +++ b/property.go @@ -84,47 +84,29 @@ func parseFieldSelectorExpr(astTypeSelectorExpr *ast.SelectorExpr, parser *Parse // getPropertyName returns the string value for the given field if it exists // allowedValues: array, boolean, integer, null, number, object, string func getPropertyName(pkgName string, expr ast.Expr, parser *Parser) (propertyName, error) { - if astTypeSelectorExpr, ok := expr.(*ast.SelectorExpr); ok { - return parseFieldSelectorExpr(astTypeSelectorExpr, parser, newProperty), nil - } - - // check if it is a custom type - typeName := fmt.Sprintf("%s.%v", pkgName, expr) - if actualPrimitiveType, isCustomType := parser.CustomPrimitiveTypes[typeName]; isCustomType { - return propertyName{SchemaType: actualPrimitiveType, ArrayType: actualPrimitiveType}, nil - } + switch tp := expr.(type) { + case *ast.SelectorExpr: + return parseFieldSelectorExpr(tp, parser, newProperty), nil + case *ast.StarExpr: + return getPropertyName(pkgName, tp.X, parser) + case *ast.ArrayType: + return getArrayPropertyName(pkgName, tp.Elt, parser), nil + case *ast.MapType, *ast.StructType, *ast.InterfaceType: + return propertyName{SchemaType: "object", ArrayType: "object"}, nil + case *ast.FuncType: + return propertyName{SchemaType: "func", ArrayType: ""}, nil + case *ast.Ident: + name := tp.Name + // check if it is a custom type + if actualPrimitiveType, isCustomType := parser.CustomPrimitiveTypes[pkgName+"."+name]; isCustomType { + return propertyName{SchemaType: actualPrimitiveType, ArrayType: actualPrimitiveType}, nil + } - if astTypeIdent, ok := expr.(*ast.Ident); ok { - name := astTypeIdent.Name schemeType := TransToValidSchemeType(name) return propertyName{SchemaType: schemeType, ArrayType: schemeType}, nil + default: + return propertyName{}, errors.New("not supported" + fmt.Sprint(expr)) } - - if ptr, ok := expr.(*ast.StarExpr); ok { - return getPropertyName(pkgName, ptr.X, parser) - } - - if astTypeArray, ok := expr.(*ast.ArrayType); ok { // if array - return getArrayPropertyName(pkgName, astTypeArray.Elt, parser), nil - } - - if _, ok := expr.(*ast.MapType); ok { // if map - return propertyName{SchemaType: "object", ArrayType: "object"}, nil - } - - if _, ok := expr.(*ast.StructType); ok { // if struct - return propertyName{SchemaType: "object", ArrayType: "object"}, nil - } - - if _, ok := expr.(*ast.InterfaceType); ok { // if interface{} - return propertyName{SchemaType: "object", ArrayType: "object"}, nil - } - - if _, ok := expr.(*ast.FuncType); ok { // if func() - return propertyName{SchemaType: "func", ArrayType: ""}, nil - } - - return propertyName{}, errors.New("not supported" + fmt.Sprint(expr)) } func getArrayPropertyName(pkgName string, astTypeArrayElt ast.Expr, parser *Parser) propertyName { @@ -138,15 +120,14 @@ func getArrayPropertyName(pkgName string, astTypeArrayElt ast.Expr, parser *Pars case *ast.SelectorExpr: return parseFieldSelectorExpr(elt, parser, newArrayProperty) case *ast.Ident: - name := fmt.Sprintf("%s.%s", pkgName, TransToValidSchemeType(elt.Name)) - if actualPrimitiveType, isCustomType := parser.CustomPrimitiveTypes[name]; isCustomType { + name := TransToValidSchemeType(elt.Name) + if actualPrimitiveType, isCustomType := parser.CustomPrimitiveTypes[pkgName+"."+name]; isCustomType { name = actualPrimitiveType } return propertyName{SchemaType: "array", ArrayType: name} default: name := TransToValidSchemeType(fmt.Sprintf("%s", astTypeArrayElt)) - name = fmt.Sprintf("%s.%s", pkgName, name) - if actualPrimitiveType, isCustomType := parser.CustomPrimitiveTypes[name]; isCustomType { + if actualPrimitiveType, isCustomType := parser.CustomPrimitiveTypes[pkgName+"."+name]; isCustomType { name = actualPrimitiveType } return propertyName{SchemaType: "array", ArrayType: name} diff --git a/property_test.go b/property_test.go index c55ab74ec..2e65183b6 100644 --- a/property_test.go +++ b/property_test.go @@ -187,7 +187,7 @@ func TestGetPropertyNameArrayStarExpr(t *testing.T) { } expected := propertyName{ "array", - "test.string", + "string", "", } propertyName, err := getPropertyName("test", input, New()) From 12801d5f69237b0ae1943ac5ecea3ea35b7ef8fd Mon Sep 17 00:00:00 2001 From: Eugene Denisenko Date: Sun, 23 Feb 2020 10:22:15 +0300 Subject: [PATCH 40/84] Issue #225 --- property.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/property.go b/property.go index fa4de1149..52b5814b2 100644 --- a/property.go +++ b/property.go @@ -120,15 +120,19 @@ func getArrayPropertyName(pkgName string, astTypeArrayElt ast.Expr, parser *Pars case *ast.SelectorExpr: return parseFieldSelectorExpr(elt, parser, newArrayProperty) case *ast.Ident: - name := TransToValidSchemeType(elt.Name) + name := elt.Name if actualPrimitiveType, isCustomType := parser.CustomPrimitiveTypes[pkgName+"."+name]; isCustomType { name = actualPrimitiveType + } else { + name = TransToValidSchemeType(elt.Name) } return propertyName{SchemaType: "array", ArrayType: name} default: - name := TransToValidSchemeType(fmt.Sprintf("%s", astTypeArrayElt)) + name := fmt.Sprintf("%s", astTypeArrayElt) if actualPrimitiveType, isCustomType := parser.CustomPrimitiveTypes[pkgName+"."+name]; isCustomType { name = actualPrimitiveType + } else { + name = TransToValidSchemeType(name) } return propertyName{SchemaType: "array", ArrayType: name} } From 2c8dd2296b9e84968beed8d6c77b1bc44a3886fa Mon Sep 17 00:00:00 2001 From: sdghchj Date: Sun, 23 Feb 2020 19:06:28 +0800 Subject: [PATCH 41/84] Update property.go optimize --- property.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/property.go b/property.go index 52b5814b2..3a3fbbcbc 100644 --- a/property.go +++ b/property.go @@ -102,8 +102,8 @@ func getPropertyName(pkgName string, expr ast.Expr, parser *Parser) (propertyNam return propertyName{SchemaType: actualPrimitiveType, ArrayType: actualPrimitiveType}, nil } - schemeType := TransToValidSchemeType(name) - return propertyName{SchemaType: schemeType, ArrayType: schemeType}, nil + name = TransToValidSchemeType(name) + return propertyName{SchemaType: name, ArrayType: name}, nil default: return propertyName{}, errors.New("not supported" + fmt.Sprint(expr)) } From c168a634b7ade959e5850a1e702178c23f09ef12 Mon Sep 17 00:00:00 2001 From: Nerzal Date: Tue, 25 Feb 2020 20:59:20 +0100 Subject: [PATCH 42/84] added markdwon files to single modules (#630) * add markdown description to operation --- README.md | 4 +++- operation.go | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9d987c265..72c4df355 100644 --- a/README.md +++ b/README.md @@ -341,7 +341,6 @@ When a short string in your documentation is insufficient, or you need images, c | tag.description.markdown | Description of the tag this is an alternative to tag.description. The description will be read from a file named like tagname.md | // @tag.description.markdown | - ## API Operation **Example** @@ -351,6 +350,7 @@ When a short string in your documentation is insufficient, or you need images, c | annotation | description | |-------------|----------------------------------------------------------------------------------------------------------------------------| | description | A verbose explanation of the operation behavior. | +| description.markdown | A short description of the application. The description will be read from a file named like endpointname.md| // @description.file endpoint.description.markdown | | id | A unique string used to identify the operation. Must be unique among all API operations. | | tags | A list of tags to each API operation that separated by commas. | | summary | A short summary of what the operation does. | @@ -364,6 +364,8 @@ When a short string in your documentation is insufficient, or you need images, c | router | Path definition that separated by spaces. `path`,`[httpMethod]` | | x-name | The extension key, must be start by x- and take only json value. | + + ## Mime Types `swag` accepts all MIME Types which are in the correct format, that is, match `*/*`. diff --git a/operation.go b/operation.go index b3ee04379..fa1655532 100644 --- a/operation.go +++ b/operation.go @@ -69,6 +69,12 @@ func (operation *Operation) ParseComment(comment string, astFile *ast.File) erro switch lowerAttribute { case "@description": operation.ParseDescriptionComment(lineRemainder) + case "@description.markdown": + commentInfo, err := getMarkdownForTag(lineRemainder, operation.parser.markdownFileDir) + if err != nil { + return err + } + operation.ParseDescriptionComment(string(commentInfo)) case "@summary": operation.Summary = lineRemainder case "@id": From be7ef6b81f878045849ea87dee4d4bcfcbff2b38 Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Fri, 28 Feb 2020 11:23:04 +0800 Subject: [PATCH 43/84] chore(travis): add go 1.14 test (#633) --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 825e2482f..d2ac67355 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ go: - 1.11.x - 1.12.x - 1.13.x + - 1.14.x install: - make deps From 7290e9b484db299d24a38ab2e9a778d02185c3b0 Mon Sep 17 00:00:00 2001 From: sdghchj Date: Fri, 28 Feb 2020 11:33:04 +0800 Subject: [PATCH 44/84] feat: support renaming model to display in swagger UI (#631) Co-authored-by: Eason Lin --- README.md | 7 +++++++ operation.go | 25 ++++++++++++------------- parser.go | 27 +++++++++++++++------------ parser_test.go | 45 +++++++++++++++++++++++++++++++++++++++++++++ property.go | 6 +++--- schema.go | 28 +++++++++++++++++++++++++++- 6 files changed, 109 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 72c4df355..e76e13c9c 100644 --- a/README.md +++ b/README.md @@ -618,6 +618,13 @@ generate swagger doc as follows: } } ``` +### Rename model to display + +```golang +type Resp struct { + Code int +}//@name Response +``` ### How to using security annotations diff --git a/operation.go b/operation.go index fa1655532..af60b1f0a 100644 --- a/operation.go +++ b/operation.go @@ -257,22 +257,22 @@ func (operation *Operation) ParseParamComment(commentLine string, astFile *ast.F if IsPrimitiveType(refType) { param.Schema.Items.Schema.Type = spec.StringOrArray{refType} } else { - var err error - refType, _, err = operation.registerSchemaType(refType, astFile) + refType, typeSpec, err := operation.registerSchemaType(refType, astFile) if err != nil { return err } - param.Schema.Items.Schema.Ref = spec.Ref{Ref: jsonreference.MustCreateRef("#/definitions/" + refType)} + param.Schema.Items.Schema.Ref = spec.Ref{ + Ref: jsonreference.MustCreateRef("#/definitions/" + TypeDocName(refType, typeSpec)), + } } case "object": - var err error - refType, _, err = operation.registerSchemaType(refType, astFile) + refType, typeSpec, err := operation.registerSchemaType(refType, astFile) if err != nil { return err } param.Schema.Type = []string{} param.Schema.Ref = spec.Ref{ - Ref: jsonreference.MustCreateRef("#/definitions/" + refType), + Ref: jsonreference.MustCreateRef("#/definitions/" + TypeDocName(refType, typeSpec)), } } default: @@ -291,7 +291,7 @@ func (operation *Operation) registerSchemaType(schemaType string, astFile *ast.F if astFile == nil { return schemaType, nil, fmt.Errorf("no package name for type %s", schemaType) } - schemaType = astFile.Name.String() + "." + schemaType + schemaType = fullTypeName(astFile.Name.String(), schemaType) } refSplit := strings.Split(schemaType, ".") pkgName := refSplit[0] @@ -637,10 +637,11 @@ func (operation *Operation) ParseResponseComment(commentLine string, astFile *as schemaType := strings.Trim(matches[2], "{}") refType := matches[3] + var typeSpec *ast.TypeSpec if !IsGolangPrimitiveType(refType) { if operation.parser != nil { // checking refType has existing in 'TypeDefinitions' var err error - if refType, _, err = operation.registerSchemaType(refType, astFile); err != nil { + if refType, typeSpec, err = operation.registerSchemaType(refType, astFile); err != nil { return err } } @@ -652,11 +653,9 @@ func (operation *Operation) ParseResponseComment(commentLine string, astFile *as if schemaType == "object" { response.Schema.SchemaProps = spec.SchemaProps{} response.Schema.Ref = spec.Ref{ - Ref: jsonreference.MustCreateRef("#/definitions/" + refType), + Ref: jsonreference.MustCreateRef("#/definitions/" + TypeDocName(refType, typeSpec)), } - } - - if schemaType == "array" { + } else if schemaType == "array" { refType = TransToValidSchemeType(refType) if IsPrimitiveType(refType) { response.Schema.Items = &spec.SchemaOrArray{ @@ -670,7 +669,7 @@ func (operation *Operation) ParseResponseComment(commentLine string, astFile *as response.Schema.Items = &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Ref: spec.Ref{Ref: jsonreference.MustCreateRef("#/definitions/" + refType)}, + Ref: spec.Ref{Ref: jsonreference.MustCreateRef("#/definitions/" + TypeDocName(refType, typeSpec))}, }, }, } diff --git a/parser.go b/parser.go index 16f4fb96c..a58f7664e 100644 --- a/parser.go +++ b/parser.go @@ -579,7 +579,7 @@ func (parser *Parser) parseDefinitions() error { // given name and package, and populates swagger schema definitions registry // with a schema for the given type func (parser *Parser) ParseDefinition(pkgName, typeName string, typeSpec *ast.TypeSpec) error { - refTypeName := fullTypeName(pkgName, typeName) + refTypeName := TypeDocName(pkgName, typeSpec) if typeSpec == nil { Println("Skipping '" + refTypeName + "', pkg '" + pkgName + "' not found, try add flag --parseDependency or --parseVendor.") @@ -654,9 +654,11 @@ func (parser *Parser) parseTypeExpr(pkgName, typeName string, typeExpr ast.Expr) switch expr := typeExpr.(type) { // type Foo struct {...} case *ast.StructType: - refTypeName := fullTypeName(pkgName, typeName) - if schema, isParsed := parser.swagger.Definitions[refTypeName]; isParsed { - return &schema, nil + if typedef, ok := parser.TypeDefinitions[pkgName][typeName]; ok { + refTypeName := TypeDocName(pkgName, typedef) + if schema, isParsed := parser.swagger.Definitions[refTypeName]; isParsed { + return &schema, nil + } } return parser.parseStruct(pkgName, expr.Fields) @@ -671,11 +673,13 @@ func (parser *Parser) parseTypeExpr(pkgName, typeName string, typeExpr ast.Expr) }, nil } refTypeName := fullTypeName(pkgName, expr.Name) - if _, isParsed := parser.swagger.Definitions[refTypeName]; !isParsed { - if typedef, ok := parser.TypeDefinitions[pkgName][expr.Name]; ok { + if typedef, ok := parser.TypeDefinitions[pkgName][expr.Name]; ok { + refTypeName = TypeDocName(pkgName, typedef) + if _, isParsed := parser.swagger.Definitions[refTypeName]; !isParsed { parser.ParseDefinition(pkgName, expr.Name, typedef) } } + return &spec.Schema{ SchemaProps: spec.SchemaProps{ Ref: spec.Ref{ @@ -895,10 +899,9 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st return fillObject(&src.VendorExtensible, &dest.VendorExtensible) } - if _, ok := parser.TypeDefinitions[pkgName][structField.schemaType]; ok { // user type field + if typeSpec, ok := parser.TypeDefinitions[pkgName][structField.schemaType]; ok { // user type field // write definition if not yet present - parser.ParseDefinition(pkgName, structField.schemaType, - parser.TypeDefinitions[pkgName][structField.schemaType]) + parser.ParseDefinition(pkgName, structField.schemaType, typeSpec) required := make([]string, 0) if structField.isRequired { required = append(required, structField.name) @@ -909,7 +912,7 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st Description: structField.desc, Required: required, Ref: spec.Ref{ - Ref: jsonreference.MustCreateRef("#/definitions/" + pkgName + "." + structField.schemaType), + Ref: jsonreference.MustCreateRef("#/definitions/" + TypeDocName(pkgName, typeSpec)), }, }, SwaggerSchemaProps: spec.SwaggerSchemaProps{ @@ -918,7 +921,7 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st } } else if structField.schemaType == "array" { // array field type // if defined -- ref it - if _, ok := parser.TypeDefinitions[pkgName][structField.arrayType]; ok { // user type in array + if typeSpec, ok := parser.TypeDefinitions[pkgName][structField.arrayType]; ok { // user type in array parser.ParseDefinition(pkgName, structField.arrayType, parser.TypeDefinitions[pkgName][structField.arrayType]) required := make([]string, 0) @@ -934,7 +937,7 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Ref: spec.Ref{ - Ref: jsonreference.MustCreateRef("#/definitions/" + pkgName + "." + structField.arrayType), + Ref: jsonreference.MustCreateRef("#/definitions/" + TypeDocName(pkgName, typeSpec)), }, }, }, diff --git a/parser_test.go b/parser_test.go index 063f938af..a06146712 100644 --- a/parser_test.go +++ b/parser_test.go @@ -2991,3 +2991,48 @@ func Fun() { assert.Equal(t, 3, len(p.swagger.Paths.Paths["/test"].Get.Parameters)) } + +func TestParseRenamedStructDefinition(t *testing.T) { + src := ` +package main + +type Child struct { + Name string +}//@name Student + +type Parent struct { + Name string + Child Child +}//@name Teacher + +// @Param request body Parent true "query params" +// @Success 200 {object} Parent +// @Router /test [get] +func Fun() { + +} +` + f, err := goparser.ParseFile(token.NewFileSet(), "", src, goparser.ParseComments) + assert.NoError(t, err) + + p := New() + p.ParseType(f) + err = p.ParseRouterAPIInfo("", f) + assert.NoError(t, err) + + err = p.ParseRouterAPIInfo("", f) + assert.NoError(t, err) + + err = p.parseDefinitions() + assert.NoError(t, err) + teacher, ok := p.swagger.Definitions["Teacher"] + assert.True(t, ok) + ref := teacher.Properties["child"].SchemaProps.Ref + assert.Equal(t, "#/definitions/Student", ref.String()) + _, ok = p.swagger.Definitions["Student"] + assert.True(t, ok) + path, ok := p.swagger.Paths.Paths["/test"] + assert.Equal(t, "#/definitions/Teacher", path.Get.Parameters[0].Schema.Ref.String()) + ref = path.Get.Responses.ResponsesProps.StatusCodeResponses[200].ResponseProps.Schema.Ref + assert.Equal(t, "#/definitions/Teacher", ref.String()) +} diff --git a/property.go b/property.go index 3a3fbbcbc..6d4563273 100644 --- a/property.go +++ b/property.go @@ -98,7 +98,7 @@ func getPropertyName(pkgName string, expr ast.Expr, parser *Parser) (propertyNam case *ast.Ident: name := tp.Name // check if it is a custom type - if actualPrimitiveType, isCustomType := parser.CustomPrimitiveTypes[pkgName+"."+name]; isCustomType { + if actualPrimitiveType, isCustomType := parser.CustomPrimitiveTypes[fullTypeName(pkgName, name)]; isCustomType { return propertyName{SchemaType: actualPrimitiveType, ArrayType: actualPrimitiveType}, nil } @@ -121,7 +121,7 @@ func getArrayPropertyName(pkgName string, astTypeArrayElt ast.Expr, parser *Pars return parseFieldSelectorExpr(elt, parser, newArrayProperty) case *ast.Ident: name := elt.Name - if actualPrimitiveType, isCustomType := parser.CustomPrimitiveTypes[pkgName+"."+name]; isCustomType { + if actualPrimitiveType, isCustomType := parser.CustomPrimitiveTypes[fullTypeName(pkgName, name)]; isCustomType { name = actualPrimitiveType } else { name = TransToValidSchemeType(elt.Name) @@ -129,7 +129,7 @@ func getArrayPropertyName(pkgName string, astTypeArrayElt ast.Expr, parser *Pars return propertyName{SchemaType: "array", ArrayType: name} default: name := fmt.Sprintf("%s", astTypeArrayElt) - if actualPrimitiveType, isCustomType := parser.CustomPrimitiveTypes[pkgName+"."+name]; isCustomType { + if actualPrimitiveType, isCustomType := parser.CustomPrimitiveTypes[fullTypeName(pkgName, name)]; isCustomType { name = actualPrimitiveType } else { name = TransToValidSchemeType(name) diff --git a/schema.go b/schema.go index c05a08c12..161211f8c 100644 --- a/schema.go +++ b/schema.go @@ -1,6 +1,10 @@ package swag -import "fmt" +import ( + "fmt" + "go/ast" + "strings" +) // CheckSchemaType checks if typeName is not a name of primitive type func CheckSchemaType(typeName string) error { @@ -79,3 +83,25 @@ func IsGolangPrimitiveType(typeName string) bool { return false } } + +// TypeDocName get alias from comment '// @name ', otherwise the original type name to display in doc +func TypeDocName(pkgName string, spec *ast.TypeSpec) string { + if spec != nil { + if spec.Comment != nil { + for _, comment := range spec.Comment.List { + text := strings.TrimSpace(comment.Text) + text = strings.TrimLeft(text, "//") + text = strings.TrimSpace(text) + texts := strings.Split(text, " ") + if len(texts) > 1 && strings.ToLower(texts[0]) == "@name" { + return texts[1] + } + } + } + if spec.Name != nil { + return fullTypeName(strings.Split(pkgName, ".")[0], spec.Name.Name) + } + } + + return pkgName +} From 4676cb1e16b38996b9df19567be8e637a4e8cecc Mon Sep 17 00:00:00 2001 From: sdghchj Date: Sat, 14 Mar 2020 16:21:55 +0800 Subject: [PATCH 45/84] feat: add tag 'swaggerignore' to exclude a field from struct (#635) * add tag 'swaggerignore' to exclude a field from struct * update --- README.md | 13 +++++++++++++ parser.go | 7 +++++++ 2 files changed, 20 insertions(+) diff --git a/README.md b/README.md index e76e13c9c..5a96f33ee 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,9 @@ Swag converts Go annotations to Swagger Documentation 2.0. We've created a varie - [Example value of struct](#example-value-of-struct) - [Description of struct](#description-of-struct) - [Use swaggertype tag to supported custom type](#use-swaggertype-tag-to-supported-custom-type) + - [Use swaggerignore tag to exclude a field](#use-swaggerignore-tag-to-exclude-a-field) - [Add extension info to struct field](#add-extension-info-to-struct-field) + - [Rename model to display](#rename-model-to-display) - [How to using security annotations](#how-to-using-security-annotations) - [About the Project](#about-the-project) @@ -596,6 +598,17 @@ generated swagger doc as follows: ``` + +### Use swaggerignore tag to exclude a field + +```go +type Account struct { + ID string `json:"id"` + Name string `json:"name"` + Ignored int `swaggerignore:"true"` +} +``` + ### Add extension info to struct field ```go diff --git a/parser.go b/parser.go index a58f7664e..67bab4f50 100644 --- a/parser.go +++ b/parser.go @@ -1172,6 +1172,12 @@ func (parser *Parser) parseField(pkgName string, field *ast.Field) (*structField } // `json:"tag"` -> json:"tag" structTag := reflect.StructTag(strings.Replace(field.Tag.Value, "`", "", -1)) + + if ignoreTag := structTag.Get("swaggerignore"); ignoreTag == "true" { + structField.name = "" + return structField, nil + } + jsonTag := structTag.Get("json") // json:"tag,hoge" if strings.Contains(jsonTag, ",") { @@ -1184,6 +1190,7 @@ func (parser *Parser) parseField(pkgName string, field *ast.Field) (*structField } if jsonTag == "-" { structField.name = "" + return structField, nil } else if jsonTag != "" { structField.name = jsonTag } From c6d9620157ad2c973f8024f65e3dbbdb373350d4 Mon Sep 17 00:00:00 2001 From: Theotime Leveque Date: Sat, 14 Mar 2020 19:31:30 +0100 Subject: [PATCH 46/84] Examples: Fix doc typos in accounts. --- example/celler/controller/accounts.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/example/celler/controller/accounts.go b/example/celler/controller/accounts.go index 72d4d46dc..0c0b0709d 100644 --- a/example/celler/controller/accounts.go +++ b/example/celler/controller/accounts.go @@ -11,7 +11,7 @@ import ( ) // ShowAccount godoc -// @Summary Show a account +// @Summary Show an account // @Description get string by ID // @Tags accounts // @Accept json @@ -60,7 +60,7 @@ func (c *Controller) ListAccounts(ctx *gin.Context) { } // AddAccount godoc -// @Summary Add a account +// @Summary Add an account // @Description add by json account // @Tags accounts // @Accept json @@ -94,7 +94,7 @@ func (c *Controller) AddAccount(ctx *gin.Context) { } // UpdateAccount godoc -// @Summary Update a account +// @Summary Update an account // @Description Update by json account // @Tags accounts // @Accept json @@ -131,7 +131,7 @@ func (c *Controller) UpdateAccount(ctx *gin.Context) { } // DeleteAccount godoc -// @Summary Update a account +// @Summary Delete an account // @Description Delete by account ID // @Tags accounts // @Accept json From 2f74ff20cd3c562ff0db5f193c7c73f6e1733f7c Mon Sep 17 00:00:00 2001 From: sdghchj Date: Sun, 22 Mar 2020 22:18:17 +0800 Subject: [PATCH 47/84] Support colletionFormat for array params in query; Exclude directories and files (#642) * feature: support setting collectionFormat for array param in query * Use filepath.Dir to take dir of main file so that it runs properly on windows when --parseDependency is set. Optimize func Skip. * add test and update README.md * comment * fix nil pointer panic in test * remove print line in test * feature: exclude dirs and files * update README.md * add comment * Change the implementation of collectionFormat in @Param comment * update README.md --- README.md | 6 +++++- cmd/swag/main.go | 6 ++++++ gen/gen.go | 6 +++++- operation.go | 42 +++++++++++++++++++++++++++++++----------- operation_test.go | 26 ++++++++++++++++++++++++++ parser.go | 46 ++++++++++++++++++++++++++++++++-------------- schema.go | 10 ++++++++++ 7 files changed, 115 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 5a96f33ee..9771dff41 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,7 @@ USAGE: OPTIONS: --generalInfo value, -g value Go file path in which 'swagger general API Info' is written (default: "main.go") --dir value, -d value Directory you want to parse (default: "./") + --exclude value Exclude directoies and files, comma separated --propertyStrategy value, -p value Property Naming Strategy like snakecase,camelcase,pascalcase (default: "camelcase") --output value, -o value Output directory for all the generated files(swagger.json, swagger.yaml and doc.go) (default: "./docs") --parseVendor Parse go files in 'vendor' folder, disabled by default @@ -112,6 +113,7 @@ import "github.com/swaggo/files" // swagger embed files // @host localhost:8080 // @BasePath /api/v1 +// @query.collection.format multi // @securityDefinitions.basic BasicAuth @@ -327,6 +329,7 @@ $ swag init | license.url | A URL to the license used for the API. MUST be in the format of a URL. | // @license.url http://www.apache.org/licenses/LICENSE-2.0.html | | host | The host (name or ip) serving the API. | // @host localhost:8080 | | BasePath | The base path on which the API is served. | // @BasePath /api/v1 | +| query.collection.format | The default collection(array) param format in query,enums:csv,multi,pipes,tsv,ssv. If not set, csv is the default.| // @query.collection.format multi | schemes | The transfer protocol for the operation that separated by spaces. | // @schemes http https | | x-name | The extension key, must be start by x- and take only json value | // @x-example-key {"key": "value"} | @@ -435,6 +438,7 @@ Besides that, `swag` also accepts aliases for some MIME Types as follows: // @Param string query string false "string valid" minlength(5) maxlength(10) // @Param int query int false "int valid" mininum(1) maxinum(10) // @Param default query string false "string default" default(A) +// @Param collection query []string false "string collection" collectionFormat(multi) ``` It also works for the struct fields: @@ -458,6 +462,7 @@ Field Name | Type | Description minLength | `integer` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.2. enums | [\*] | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.5.1. format | `string` | The extending format for the previously mentioned [`type`](#parameterType). See [Data Type Formats](https://swagger.io/specification/v2/#dataTypeFormat) for further details. +collectionFormat | `string` |Determines the format of the array if type array is used. Possible values are:
  • `csv` - comma separated values `foo,bar`.
  • `ssv` - space separated values `foo bar`.
  • `tsv` - tab separated values `foo\tbar`.
  • `pipes` - pipe separated values foo|bar.
  • `multi` - corresponds to multiple parameter instances instead of multiple values for a single instance `foo=bar&foo=baz`. This is valid only for parameters [`in`](#parameterIn) "query" or "formData".
Default value is `csv`. ### Future @@ -468,7 +473,6 @@ Field Name | Type | Description maxItems | `integer` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.3.2. minItems | `integer` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.3.3. uniqueItems | `boolean` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.3.4. -collectionFormat | `string` | Determines the format of the array if type array is used. Possible values are:
  • `csv` - comma separated values `foo,bar`.
  • `ssv` - space separated values `foo bar`.
  • `tsv` - tab separated values `foo\tbar`.
  • `pipes` - pipe separated values foo|bar.
  • `multi` - corresponds to multiple parameter instances instead of multiple values for a single instance `foo=bar&foo=baz`. This is valid only for parameters [`in`](#parameterIn) "query" or "formData".
Default value is `csv`. ## Examples diff --git a/cmd/swag/main.go b/cmd/swag/main.go index 59dd0d4a8..5ff7d79f3 100644 --- a/cmd/swag/main.go +++ b/cmd/swag/main.go @@ -12,6 +12,7 @@ import ( const ( searchDirFlag = "dir" + excludeFlag = "exclude" generalInfoFlag = "generalInfo" propertyStrategyFlag = "propertyStrategy" outputFlag = "output" @@ -32,6 +33,10 @@ var initFlags = []cli.Flag{ Value: "./", Usage: "Directory you want to parse", }, + &cli.StringFlag{ + Name: excludeFlag, + Usage: "exclude directories and files when searching, comma separated", + }, &cli.StringFlag{ Name: propertyStrategyFlag + ", p", Value: "camelcase", @@ -72,6 +77,7 @@ func initAction(c *cli.Context) error { return gen.New().Build(&gen.Config{ SearchDir: c.String(searchDirFlag), + Excludes: c.String(excludeFlag), MainAPIFile: c.String(generalInfoFlag), PropNamingStrategy: strategy, OutputDir: c.String(outputFlag), diff --git a/gen/gen.go b/gen/gen.go index 0edd211c2..78fccdc79 100644 --- a/gen/gen.go +++ b/gen/gen.go @@ -39,6 +39,9 @@ type Config struct { // SearchDir the swag would be parse SearchDir string + // excludes dirs and files in SearchDir,comma separated + Excludes string + // OutputDir represents the output directory for all the generated files OutputDir string @@ -68,7 +71,8 @@ func (g *Gen) Build(config *Config) error { } log.Println("Generate swagger docs....") - p := swag.New(swag.SetMarkdownFileDirectory(config.MarkdownFilesDir)) + p := swag.New(swag.SetMarkdownFileDirectory(config.MarkdownFilesDir), + swag.SetExcludedDirsAndFiles(config.Excludes)) p.PropNamingStrategy = config.PropNamingStrategy p.ParseVendor = config.ParseVendor p.ParseDependency = config.ParseDependency diff --git a/operation.go b/operation.go index af60b1f0a..5ba170f87 100644 --- a/operation.go +++ b/operation.go @@ -147,7 +147,7 @@ func (operation *Operation) ParseParamComment(commentLine string, astFile *ast.F // Detect refType objectType := "object" - if strings.HasPrefix(refType, "[]") == true { + if strings.HasPrefix(refType, "[]") { objectType = "array" refType = strings.TrimPrefix(refType, "[]") refType = TransToValidSchemeType(refType) @@ -175,6 +175,9 @@ func (operation *Operation) ParseParamComment(commentLine string, astFile *ast.F return fmt.Errorf("%s is not supported array type for %s", refType, paramType) } param.SimpleSchema.Type = "array" + if operation.parser != nil { + param.CollectionFormat = TransToValidCollectionFormat(operation.parser.collectionFormatInQuery) + } param.SimpleSchema.Items = &spec.Items{ SimpleSchema: spec.SimpleSchema{ Type: refType, @@ -215,6 +218,9 @@ func (operation *Operation) ParseParamComment(commentLine string, astFile *ast.F IsSimplePrimitiveType(prop.Items.Schema.Type[0]) { param = createParameter(paramType, prop.Description, name, prop.Type[0], find(schema.Required, name)) param.SimpleSchema.Type = prop.Type[0] + if operation.parser != nil && operation.parser.collectionFormatInQuery != "" && param.CollectionFormat == "" { + param.CollectionFormat = TransToValidCollectionFormat(operation.parser.collectionFormatInQuery) + } param.SimpleSchema.Items = &spec.Items{ SimpleSchema: spec.SimpleSchema{ Type: prop.Items.Schema.Type[0], @@ -279,7 +285,7 @@ func (operation *Operation) ParseParamComment(commentLine string, astFile *ast.F return fmt.Errorf("%s is not supported paramType", paramType) } - if err := operation.parseAndExtractionParamAttribute(commentLine, refType, ¶m); err != nil { + if err := operation.parseAndExtractionParamAttribute(commentLine, objectType, refType, ¶m); err != nil { return err } operation.Operation.Parameters = append(operation.Operation.Parameters, param) @@ -334,22 +340,24 @@ func (operation *Operation) registerSchemaType(schemaType string, astFile *ast.F var regexAttributes = map[string]*regexp.Regexp{ // for Enums(A, B) - "enums": regexp.MustCompile(`(?i)enums\(.*\)`), + "enums": regexp.MustCompile(`(?i)\s+enums\(.*\)`), // for Minimum(0) - "maxinum": regexp.MustCompile(`(?i)maxinum\(.*\)`), + "maxinum": regexp.MustCompile(`(?i)\s+maxinum\(.*\)`), // for Maximum(0) - "mininum": regexp.MustCompile(`(?i)mininum\(.*\)`), + "mininum": regexp.MustCompile(`(?i)\s+mininum\(.*\)`), // for Maximum(0) - "default": regexp.MustCompile(`(?i)default\(.*\)`), + "default": regexp.MustCompile(`(?i)\s+default\(.*\)`), // for minlength(0) - "minlength": regexp.MustCompile(`(?i)minlength\(.*\)`), + "minlength": regexp.MustCompile(`(?i)\s+minlength\(.*\)`), // for maxlength(0) - "maxlength": regexp.MustCompile(`(?i)maxlength\(.*\)`), + "maxlength": regexp.MustCompile(`(?i)\s+maxlength\(.*\)`), // for format(email) - "format": regexp.MustCompile(`(?i)format\(.*\)`), + "format": regexp.MustCompile(`(?i)\s+format\(.*\)`), + // for collectionFormat(csv) + "collectionFormat": regexp.MustCompile(`(?i)\s+collectionFormat\(.*\)`), } -func (operation *Operation) parseAndExtractionParamAttribute(commentLine, schemaType string, param *spec.Parameter) error { +func (operation *Operation) parseAndExtractionParamAttribute(commentLine, objectType, schemaType string, param *spec.Parameter) error { schemaType = TransToValidSchemeType(schemaType) for attrKey, re := range regexAttributes { attr, err := findAttr(re, commentLine) @@ -394,8 +402,13 @@ func (operation *Operation) parseAndExtractionParamAttribute(commentLine, schema param.MinLength = &n case "format": param.Format = attr + case "collectionFormat": + n, err := setCollectionFormatParam(attrKey, objectType, attr, commentLine) + if err != nil { + return err + } + param.CollectionFormat = n } - } return nil } @@ -445,6 +458,13 @@ func setEnumParam(attr, schemaType string, param *spec.Parameter) error { return nil } +func setCollectionFormatParam(name, schemaType, attr, commentLine string) (string, error) { + if schemaType != "array" { + return "", fmt.Errorf("%s is attribute to set to an array. comment=%s got=%s", name, commentLine, schemaType) + } + return TransToValidCollectionFormat(attr), nil +} + // defineType enum value define the type (object and array unsupported) func defineType(schemaType string, value string) (interface{}, error) { schemaType = TransToValidSchemeType(schemaType) diff --git a/operation_test.go b/operation_test.go index ee58db0f6..8df6909b8 100644 --- a/operation_test.go +++ b/operation_test.go @@ -439,6 +439,32 @@ func TestParseParamCommentQueryArray(t *testing.T) { assert.Equal(t, expected, string(b)) } +// Test ParseParamComment Query Params +func TestParseParamCommentQueryArrayFormat(t *testing.T) { + comment := `@Param names query []string true "Users List" collectionFormat(multi)` + operation := NewOperation() + err := operation.ParseComment(comment, nil) + + assert.NoError(t, err) + b, _ := json.MarshalIndent(operation, "", " ") + expected := `{ + "parameters": [ + { + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi", + "description": "Users List", + "name": "names", + "in": "query", + "required": true + } + ] +}` + assert.Equal(t, expected, string(b)) +} + func TestParseParamCommentByID(t *testing.T) { comment := `@Param unsafe_id[lte] query int true "Unsafe query param"` operation := NewOperation() diff --git a/parser.go b/parser.go index 67bab4f50..79dce6c1b 100644 --- a/parser.go +++ b/parser.go @@ -11,7 +11,6 @@ import ( "net/http" "os" "os/exec" - "path" "path/filepath" "reflect" "sort" @@ -67,6 +66,12 @@ type Parser struct { // markdownFileDir holds the path to the folder, where markdown files are stored markdownFileDir string + + // collectionFormatInQuery set the default collectionFormat otherwise then 'csv' for array in query params + collectionFormatInQuery string + + // excludes excludes dirs and files in SearchDir + excludes map[string]bool } // New creates a new Parser with default properties. @@ -91,6 +96,7 @@ func New(options ...func(*Parser)) *Parser { ImportAliases: make(map[string]map[string]*ast.ImportSpec), CustomPrimitiveTypes: make(map[string]string), registerTypes: make(map[string]*ast.TypeSpec), + excludes: make(map[string]bool), } for _, option := range options { @@ -107,6 +113,19 @@ func SetMarkdownFileDirectory(directoryPath string) func(*Parser) { } } +// SetExcludedDirsAndFiles sets directories and files to be excluded when searching +func SetExcludedDirsAndFiles(excludes string) func(*Parser) { + return func(p *Parser) { + for _, f := range strings.Split(excludes, ",") { + f = strings.TrimSpace(f) + if f != "" { + f = filepath.Clean(f) + p.excludes[f] = true + } + } + } +} + // ParseAPI parses general api info for given searchDir and mainAPIFile func (parser *Parser) ParseAPI(searchDir string, mainAPIFile string) error { Printf("Generate general API Info, search dir:%s", searchDir) @@ -123,7 +142,7 @@ func (parser *Parser) ParseAPI(searchDir string, mainAPIFile string) error { } if parser.ParseDependency { - pkgName, err := getPkgName(path.Dir(absMainAPIFilePath)) + pkgName, err := getPkgName(filepath.Dir(absMainAPIFilePath)) if err != nil { return err } @@ -299,7 +318,8 @@ func (parser *Parser) ParseGeneralAPIInfo(mainAPIFile string) error { return err } securityMap[value] = securitySchemeOAuth2AccessToken(attrMap["@authorizationurl"], attrMap["@tokenurl"], scopes) - + case "@query.collection.format": + parser.collectionFormatInQuery = value default: prefixExtension := "@x-" if len(attribute) > 5 { // Prefix extension + 1 char + 1 space + 1 char @@ -1474,22 +1494,20 @@ func (parser *Parser) parseFile(path string) error { // Skip returns filepath.SkipDir error if match vendor and hidden folder func (parser *Parser) Skip(path string, f os.FileInfo) error { - - if !parser.ParseVendor { // ignore vendor - if f.IsDir() && f.Name() == "vendor" { + if f.IsDir() { + if !parser.ParseVendor && f.Name() == "vendor" || //ignore "vendor" + f.Name() == "docs" || //exclude docs + len(f.Name()) > 1 && f.Name()[0] == '.' { // exclude all hidden folder return filepath.SkipDir } - } - // issue - if f.IsDir() && f.Name() == "docs" { - return filepath.SkipDir + if parser.excludes != nil { + if _, ok := parser.excludes[path]; ok { + return filepath.SkipDir + } + } } - // exclude all hidden folder - if f.IsDir() && len(f.Name()) > 1 && f.Name()[0] == '.' { - return filepath.SkipDir - } return nil } diff --git a/schema.go b/schema.go index 161211f8c..539340fae 100644 --- a/schema.go +++ b/schema.go @@ -84,6 +84,16 @@ func IsGolangPrimitiveType(typeName string) bool { } } +// TransToValidCollectionFormat determine valid collection format +func TransToValidCollectionFormat(format string) string { + switch format { + case "csv", "multi", "pipes", "tsv", "ssv": + return format + default: + return "" + } +} + // TypeDocName get alias from comment '// @name ', otherwise the original type name to display in doc func TypeDocName(pkgName string, spec *ast.TypeSpec) string { if spec != nil { From ecf0baa46903544fffd6fa15deb33df129f9d998 Mon Sep 17 00:00:00 2001 From: hongmin Date: Sun, 22 Mar 2020 23:59:24 +0800 Subject: [PATCH 48/84] Support Model Composition in Response with allOf and ref https://github.com/swaggo/swag/issues/650 --- operation.go | 81 ++++++++++++++++++++- operation_test.go | 174 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 253 insertions(+), 2 deletions(-) diff --git a/operation.go b/operation.go index af60b1f0a..5703efa46 100644 --- a/operation.go +++ b/operation.go @@ -610,7 +610,72 @@ func findTypeDef(importPath, typeName string) (*ast.TypeSpec, error) { return nil, fmt.Errorf("type spec not found") } -var responsePattern = regexp.MustCompile(`([\d]+)[\s]+([\w\{\}]+)[\s]+([\w\-\.\/]+)[^"]*(.*)?`) +var responsePattern = regexp.MustCompile(`([\d]+)[\s]+([\w\{\}]+)[\s]+([\w\-\.\/\{\}=\[\]]+)[^"]*(.*)?`) + +type NestedField struct { + Name string + Type string + IsArray bool + + Ref spec.Ref +} + +func (nested *NestedField) GetSchema() *spec.Schema{ + if IsGolangPrimitiveType(nested.Type) { + return &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{nested.Type}}} + } else { + return &spec.Schema{SchemaProps: spec.SchemaProps{Ref: nested.Ref}} + } +} + +func (nested *NestedField) FillNestedSchema(response spec.Response, ref spec.Ref) { + props := make(map[string]spec.Schema, 0) + if nested.IsArray { + props[nested.Name] = spec.Schema{SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{Schema: nested.GetSchema()}, + }} + } else { + props[nested.Name] = *nested.GetSchema() + } + nestedSpec := spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: props, + }, + } + response.Schema.AllOf = make([]spec.Schema, 0) + response.Schema.AllOf = append(response.Schema.AllOf, spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}) + response.Schema.AllOf = append(response.Schema.AllOf, nestedSpec) +} + +var nestedObjectPattern = regexp.MustCompile(`^([\w\-\.\/]+)\{(.*)=([^\[\]]*)\}$`) +var nestedArrayPattern = regexp.MustCompile(`^([\w\-\.\/]+)\{(.*)=\[\]([^\[\]]*)\}$`) + +func (operation *Operation)tryExtractNestedField(specStr string, astFile *ast.File) (refType string, nested *NestedField, err error) { + if matches := nestedObjectPattern.FindStringSubmatch(specStr); len(matches) == 4 { + refType, nested = matches[1], &NestedField{Name: matches[2], Type: matches[3], IsArray: false} + } else if matches := nestedArrayPattern.FindStringSubmatch(specStr); len(matches) == 4 { + refType, nested = matches[1], &NestedField{Name: matches[2], Type: matches[3], IsArray: true} + } else { + return specStr, nil, nil + } + + if !IsGolangPrimitiveType(nested.Type) { + if operation.parser != nil { // checking refType has existing in 'TypeDefinitions' + refType, typeSpec, err := operation.registerSchemaType(nested.Type, astFile) + if err != nil { + return specStr, nil, err + } + + nested.Ref = spec.Ref{ + Ref: jsonreference.MustCreateRef("#/definitions/" + TypeDocName(refType, typeSpec)), + } + } + } + + return +} // ParseResponseComment parses comment for given `response` comment string. func (operation *Operation) ParseResponseComment(commentLine string, astFile *ast.File) error { @@ -637,6 +702,11 @@ func (operation *Operation) ParseResponseComment(commentLine string, astFile *as schemaType := strings.Trim(matches[2], "{}") refType := matches[3] + refType, nested, err := operation.tryExtractNestedField(refType, astFile) + if err != nil { + return err + } + var typeSpec *ast.TypeSpec if !IsGolangPrimitiveType(refType) { if operation.parser != nil { // checking refType has existing in 'TypeDefinitions' @@ -652,9 +722,16 @@ func (operation *Operation) ParseResponseComment(commentLine string, astFile *as if schemaType == "object" { response.Schema.SchemaProps = spec.SchemaProps{} - response.Schema.Ref = spec.Ref{ + ref := spec.Ref { Ref: jsonreference.MustCreateRef("#/definitions/" + TypeDocName(refType, typeSpec)), } + + if nested == nil { + response.Schema.Ref = ref + } else { + nested.FillNestedSchema(response, ref) + } + } else if schemaType == "array" { refType = TransToValidSchemeType(refType) if IsPrimitiveType(refType) { diff --git a/operation_test.go b/operation_test.go index ee58db0f6..e84e112bf 100644 --- a/operation_test.go +++ b/operation_test.go @@ -188,6 +188,180 @@ func TestParseResponseCommentWithObjectType(t *testing.T) { assert.Equal(t, expected, string(b)) } +func TestParseResponseCommentWithNestedPrimitiveType(t *testing.T) { + comment := `@Success 200 {object} model.CommonHeader{data=string} "Error message, if code != 200` + operation := NewOperation() + operation.parser = New() + + operation.parser.TypeDefinitions["model"] = make(map[string]*ast.TypeSpec) + operation.parser.TypeDefinitions["model"]["CommonHeader"] = &ast.TypeSpec{} + + err := operation.ParseComment(comment, nil) + assert.NoError(t, err) + + response := operation.Responses.StatusCodeResponses[200] + assert.Equal(t, `Error message, if code != 200`, response.Description) + + b, _ := json.MarshalIndent(operation, "", " ") + + expected := `{ + "responses": { + "200": { + "description": "Error message, if code != 200", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/model.CommonHeader" + }, + { + "type": "object", + "properties": { + "data": { + "type": "string" + } + } + } + ] + } + } + } +}` + assert.Equal(t, expected, string(b)) +} + + +func TestParseResponseCommentWithNestedPrimitiveArrayType(t *testing.T) { + comment := `@Success 200 {object} model.CommonHeader{data=[]string} "Error message, if code != 200` + operation := NewOperation() + operation.parser = New() + + operation.parser.TypeDefinitions["model"] = make(map[string]*ast.TypeSpec) + operation.parser.TypeDefinitions["model"]["CommonHeader"] = &ast.TypeSpec{} + + err := operation.ParseComment(comment, nil) + assert.NoError(t, err) + + response := operation.Responses.StatusCodeResponses[200] + assert.Equal(t, `Error message, if code != 200`, response.Description) + + b, _ := json.MarshalIndent(operation, "", " ") + + expected := `{ + "responses": { + "200": { + "description": "Error message, if code != 200", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/model.CommonHeader" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + ] + } + } + } +}` + assert.Equal(t, expected, string(b)) +} + + +func TestParseResponseCommentWithNestedObjectType(t *testing.T) { + comment := `@Success 200 {object} model.CommonHeader{data=model.Payload} "Error message, if code != 200` + operation := NewOperation() + operation.parser = New() + + operation.parser.TypeDefinitions["model"] = make(map[string]*ast.TypeSpec) + operation.parser.TypeDefinitions["model"]["CommonHeader"] = &ast.TypeSpec{} + operation.parser.TypeDefinitions["model"]["Payload"] = &ast.TypeSpec{} + + err := operation.ParseComment(comment, nil) + assert.NoError(t, err) + + response := operation.Responses.StatusCodeResponses[200] + assert.Equal(t, `Error message, if code != 200`, response.Description) + + b, _ := json.MarshalIndent(operation, "", " ") + + expected := `{ + "responses": { + "200": { + "description": "Error message, if code != 200", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/model.CommonHeader" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.Payload" + } + } + } + ] + } + } + } +}` + assert.Equal(t, expected, string(b)) +} + +func TestParseResponseCommentWithNestedArrayObjectType(t *testing.T) { + comment := `@Success 200 {object} model.CommonHeader{data=[]model.Payload} "Error message, if code != 200` + operation := NewOperation() + operation.parser = New() + + operation.parser.TypeDefinitions["model"] = make(map[string]*ast.TypeSpec) + operation.parser.TypeDefinitions["model"]["CommonHeader"] = &ast.TypeSpec{} + operation.parser.TypeDefinitions["model"]["Payload"] = &ast.TypeSpec{} + + err := operation.ParseComment(comment, nil) + assert.NoError(t, err) + + response := operation.Responses.StatusCodeResponses[200] + assert.Equal(t, `Error message, if code != 200`, response.Description) + + b, _ := json.MarshalIndent(operation, "", " ") + + expected := `{ + "responses": { + "200": { + "description": "Error message, if code != 200", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/model.CommonHeader" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/model.Payload" + } + } + } + } + ] + } + } + } +}` + assert.Equal(t, expected, string(b)) +} + func TestParseResponseCommentWithObjectTypeInSameFile(t *testing.T) { comment := `@Success 200 {object} testOwner "Error message, if code != 200"` operation := NewOperation() From 2107ee24bfc036a7bffceb50dc32815c5662f8df Mon Sep 17 00:00:00 2001 From: hongmin Date: Mon, 23 Mar 2020 00:11:46 +0800 Subject: [PATCH 49/84] Add documents in README.md --- README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/README.md b/README.md index 5a96f33ee..d4e9f514b 100644 --- a/README.md +++ b/README.md @@ -496,6 +496,32 @@ type Account struct { Name string `json:"name" example:"account name"` } ``` + +### Model composition in response +```go +@success 200 {object} jsonresult.JSONResult{data=proto.Order} "desc" +``` + +```go +type JSONResult struct { + Code int `json:"code" ` + Message string `json:"message"` + Data interface{} `json:"data"` +} + +type Order struct { //in `proto` package + ... +} +``` + +- also support array of objects and primitive types as nested response +```go +@success 200 {object} jsonresult.JSONResult{data=[]proto.Order} "desc" +@success 200 {object} jsonresult.JSONResult{data=string} "desc" +@success 200 {object} jsonresult.JSONResult{data=[]string} "desc" +``` + + ### Add a headers in response ```go From e1b41ed988255b7bf334998f10ddd8c81ba6cd8a Mon Sep 17 00:00:00 2001 From: hongmin Date: Mon, 23 Mar 2020 00:26:14 +0800 Subject: [PATCH 50/84] Fix make lint and make fmt-check --- operation.go | 31 +++++++++++++++---------------- operation_test.go | 2 -- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/operation.go b/operation.go index 5703efa46..9d18704e4 100644 --- a/operation.go +++ b/operation.go @@ -612,31 +612,30 @@ func findTypeDef(importPath, typeName string) (*ast.TypeSpec, error) { var responsePattern = regexp.MustCompile(`([\d]+)[\s]+([\w\{\}]+)[\s]+([\w\-\.\/\{\}=\[\]]+)[^"]*(.*)?`) -type NestedField struct { - Name string - Type string +type nestedField struct { + Name string + Type string IsArray bool - - Ref spec.Ref + Ref spec.Ref } -func (nested *NestedField) GetSchema() *spec.Schema{ +func (nested *nestedField) getSchema() *spec.Schema { if IsGolangPrimitiveType(nested.Type) { return &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{nested.Type}}} - } else { - return &spec.Schema{SchemaProps: spec.SchemaProps{Ref: nested.Ref}} } + + return &spec.Schema{SchemaProps: spec.SchemaProps{Ref: nested.Ref}} } -func (nested *NestedField) FillNestedSchema(response spec.Response, ref spec.Ref) { +func (nested *nestedField) fillNestedSchema(response spec.Response, ref spec.Ref) { props := make(map[string]spec.Schema, 0) if nested.IsArray { props[nested.Name] = spec.Schema{SchemaProps: spec.SchemaProps{ Type: []string{"array"}, - Items: &spec.SchemaOrArray{Schema: nested.GetSchema()}, + Items: &spec.SchemaOrArray{Schema: nested.getSchema()}, }} } else { - props[nested.Name] = *nested.GetSchema() + props[nested.Name] = *nested.getSchema() } nestedSpec := spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -652,11 +651,11 @@ func (nested *NestedField) FillNestedSchema(response spec.Response, ref spec.Ref var nestedObjectPattern = regexp.MustCompile(`^([\w\-\.\/]+)\{(.*)=([^\[\]]*)\}$`) var nestedArrayPattern = regexp.MustCompile(`^([\w\-\.\/]+)\{(.*)=\[\]([^\[\]]*)\}$`) -func (operation *Operation)tryExtractNestedField(specStr string, astFile *ast.File) (refType string, nested *NestedField, err error) { +func (operation *Operation) tryExtractNestedField(specStr string, astFile *ast.File) (refType string, nested *nestedField, err error) { if matches := nestedObjectPattern.FindStringSubmatch(specStr); len(matches) == 4 { - refType, nested = matches[1], &NestedField{Name: matches[2], Type: matches[3], IsArray: false} + refType, nested = matches[1], &nestedField{Name: matches[2], Type: matches[3], IsArray: false} } else if matches := nestedArrayPattern.FindStringSubmatch(specStr); len(matches) == 4 { - refType, nested = matches[1], &NestedField{Name: matches[2], Type: matches[3], IsArray: true} + refType, nested = matches[1], &nestedField{Name: matches[2], Type: matches[3], IsArray: true} } else { return specStr, nil, nil } @@ -722,14 +721,14 @@ func (operation *Operation) ParseResponseComment(commentLine string, astFile *as if schemaType == "object" { response.Schema.SchemaProps = spec.SchemaProps{} - ref := spec.Ref { + ref := spec.Ref{ Ref: jsonreference.MustCreateRef("#/definitions/" + TypeDocName(refType, typeSpec)), } if nested == nil { response.Schema.Ref = ref } else { - nested.FillNestedSchema(response, ref) + nested.fillNestedSchema(response, ref) } } else if schemaType == "array" { diff --git a/operation_test.go b/operation_test.go index e84e112bf..a1de597a3 100644 --- a/operation_test.go +++ b/operation_test.go @@ -229,7 +229,6 @@ func TestParseResponseCommentWithNestedPrimitiveType(t *testing.T) { assert.Equal(t, expected, string(b)) } - func TestParseResponseCommentWithNestedPrimitiveArrayType(t *testing.T) { comment := `@Success 200 {object} model.CommonHeader{data=[]string} "Error message, if code != 200` operation := NewOperation() @@ -274,7 +273,6 @@ func TestParseResponseCommentWithNestedPrimitiveArrayType(t *testing.T) { assert.Equal(t, expected, string(b)) } - func TestParseResponseCommentWithNestedObjectType(t *testing.T) { comment := `@Success 200 {object} model.CommonHeader{data=model.Payload} "Error message, if code != 200` operation := NewOperation() From 4922334a1a05056751083abe3fd8171288860caa Mon Sep 17 00:00:00 2001 From: hongmin Date: Mon, 23 Mar 2020 08:06:51 +0800 Subject: [PATCH 51/84] Add ref link in README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 31c76b8f2..ec6ed4967 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ Swag converts Go annotations to Swagger Documentation 2.0. We've created a varie - [Examples](#examples) - [Descriptions over multiple lines](#descriptions-over-multiple-lines) - [User defined structure with an array type](#user-defined-structure-with-an-array-type) + - [Model composition in response](#model-composition-in-response) - [Add a headers in response](#add-a-headers-in-response) - [Use multiple path params](#use-multiple-path-params) - [Example value of struct](#example-value-of-struct) From a4c0f6d459de82660408c7f9064fe75619b77e81 Mon Sep 17 00:00:00 2001 From: sdghchj Date: Mon, 23 Mar 2020 14:44:39 +0800 Subject: [PATCH 52/84] optimize code optimization --- operation.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/operation.go b/operation.go index 0f56427d5..20b740b65 100644 --- a/operation.go +++ b/operation.go @@ -647,7 +647,7 @@ func (nested *nestedField) getSchema() *spec.Schema { return &spec.Schema{SchemaProps: spec.SchemaProps{Ref: nested.Ref}} } -func (nested *nestedField) fillNestedSchema(response spec.Response, ref spec.Ref) { +func (nested *nestedField) fillNestedSchema(response *spec.Response, ref spec.Ref) { props := make(map[string]spec.Schema, 0) if nested.IsArray { props[nested.Name] = spec.Schema{SchemaProps: spec.SchemaProps{ @@ -663,9 +663,7 @@ func (nested *nestedField) fillNestedSchema(response spec.Response, ref spec.Ref Properties: props, }, } - response.Schema.AllOf = make([]spec.Schema, 0) - response.Schema.AllOf = append(response.Schema.AllOf, spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}) - response.Schema.AllOf = append(response.Schema.AllOf, nestedSpec) + response.Schema.AllOf = []spec.Schema{spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}},nestedSpec} } var nestedObjectPattern = regexp.MustCompile(`^([\w\-\.\/]+)\{(.*)=([^\[\]]*)\}$`) @@ -748,7 +746,7 @@ func (operation *Operation) ParseResponseComment(commentLine string, astFile *as if nested == nil { response.Schema.Ref = ref } else { - nested.fillNestedSchema(response, ref) + nested.fillNestedSchema(&response, ref) } } else if schemaType == "array" { From 5ba88c82ed7d049072de07c3d639c19037c87448 Mon Sep 17 00:00:00 2001 From: sdghchj Date: Mon, 23 Mar 2020 14:56:22 +0800 Subject: [PATCH 53/84] optimize code optimization --- operation.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/operation.go b/operation.go index 20b740b65..33da3637a 100644 --- a/operation.go +++ b/operation.go @@ -663,7 +663,7 @@ func (nested *nestedField) fillNestedSchema(response *spec.Response, ref spec.Re Properties: props, }, } - response.Schema.AllOf = []spec.Schema{spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}},nestedSpec} + response.Schema.AllOf = []spec.Schema{spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}, nestedSpec} } var nestedObjectPattern = regexp.MustCompile(`^([\w\-\.\/]+)\{(.*)=([^\[\]]*)\}$`) From 187ec48bb682eee01fa7f8076e9e412a8c14fd2d Mon Sep 17 00:00:00 2001 From: sdghchj Date: Mon, 23 Mar 2020 15:07:42 +0800 Subject: [PATCH 54/84] optimize code optimization --- operation.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/operation.go b/operation.go index 33da3637a..cf3152c73 100644 --- a/operation.go +++ b/operation.go @@ -663,7 +663,7 @@ func (nested *nestedField) fillNestedSchema(response *spec.Response, ref spec.Re Properties: props, }, } - response.Schema.AllOf = []spec.Schema{spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}, nestedSpec} + response.Schema.AllOf = []spec.Schema{{SchemaProps: spec.SchemaProps{Ref: ref}}, nestedSpec} } var nestedObjectPattern = regexp.MustCompile(`^([\w\-\.\/]+)\{(.*)=([^\[\]]*)\}$`) From 3b82b47aece6792a09fab48edb05a0d9d868569f Mon Sep 17 00:00:00 2001 From: sdghchj Date: Sat, 4 Apr 2020 17:57:05 +0800 Subject: [PATCH 55/84] issue 650: fix integer bug in response; support multi fields overriding. (#655) * issue 650: fix integer bug in response; support multi fields overriding. * issue 650: fix integer bug in response; support multi fields overriding. * report error --- README.md | 5 +++ operation.go | 87 +++++++++++++++++++++++++---------------------- operation_test.go | 85 ++++++++++++++++++++++++++++++++++++++++++--- parser.go | 5 ++- 4 files changed, 136 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index ec6ed4967..4fb00cf6a 100644 --- a/README.md +++ b/README.md @@ -504,6 +504,7 @@ type Account struct { ### Model composition in response ```go +// JSONResult's data field will be overridden by the specific type proto.Order @success 200 {object} jsonresult.JSONResult{data=proto.Order} "desc" ``` @@ -526,6 +527,10 @@ type Order struct { //in `proto` package @success 200 {object} jsonresult.JSONResult{data=[]string} "desc" ``` +- overriding multiple fields. field will be added if not exists +```go +@success 200 {object} jsonresult.JSONResult{data1=string,data2=[]string,data3=proto.Order,data4=[]proto.Order} "desc" +``` ### Add a headers in response diff --git a/operation.go b/operation.go index cf3152c73..dfc529c1f 100644 --- a/operation.go +++ b/operation.go @@ -630,7 +630,7 @@ func findTypeDef(importPath, typeName string) (*ast.TypeSpec, error) { return nil, fmt.Errorf("type spec not found") } -var responsePattern = regexp.MustCompile(`([\d]+)[\s]+([\w\{\}]+)[\s]+([\w\-\.\/\{\}=\[\]]+)[^"]*(.*)?`) +var responsePattern = regexp.MustCompile(`([\d]+)[\s]+([\w\{\}]+)[\s]+([\w\-\.\/\{\}=,\[\]]+)[^"]*(.*)?`) type nestedField struct { Name string @@ -640,57 +640,45 @@ type nestedField struct { } func (nested *nestedField) getSchema() *spec.Schema { - if IsGolangPrimitiveType(nested.Type) { + if IsPrimitiveType(nested.Type) { return &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{nested.Type}}} } return &spec.Schema{SchemaProps: spec.SchemaProps{Ref: nested.Ref}} } -func (nested *nestedField) fillNestedSchema(response *spec.Response, ref spec.Ref) { - props := make(map[string]spec.Schema, 0) - if nested.IsArray { - props[nested.Name] = spec.Schema{SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, - Items: &spec.SchemaOrArray{Schema: nested.getSchema()}, - }} - } else { - props[nested.Name] = *nested.getSchema() - } - nestedSpec := spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: props, - }, - } - response.Schema.AllOf = []spec.Schema{{SchemaProps: spec.SchemaProps{Ref: ref}}, nestedSpec} -} +//RepsonseType{data1=Type1,data2=Type2} +var nestedPattern = regexp.MustCompile(`^([\w\-\.\/]+)\{(.*)\}$`) -var nestedObjectPattern = regexp.MustCompile(`^([\w\-\.\/]+)\{(.*)=([^\[\]]*)\}$`) -var nestedArrayPattern = regexp.MustCompile(`^([\w\-\.\/]+)\{(.*)=\[\]([^\[\]]*)\}$`) - -func (operation *Operation) tryExtractNestedField(specStr string, astFile *ast.File) (refType string, nested *nestedField, err error) { - if matches := nestedObjectPattern.FindStringSubmatch(specStr); len(matches) == 4 { - refType, nested = matches[1], &nestedField{Name: matches[2], Type: matches[3], IsArray: false} - } else if matches := nestedArrayPattern.FindStringSubmatch(specStr); len(matches) == 4 { - refType, nested = matches[1], &nestedField{Name: matches[2], Type: matches[3], IsArray: true} - } else { +func (operation *Operation) tryExtractNestedFields(specStr string, astFile *ast.File) (refType string, nestedFields []*nestedField, err error) { + matches := nestedPattern.FindStringSubmatch(specStr) + if len(matches) != 3 { return specStr, nil, nil } - - if !IsGolangPrimitiveType(nested.Type) { - if operation.parser != nil { // checking refType has existing in 'TypeDefinitions' - refType, typeSpec, err := operation.registerSchemaType(nested.Type, astFile) - if err != nil { - return specStr, nil, err + refType = matches[1] + fields := strings.Split(matches[2], ",") + for _, field := range fields { + if matches := strings.Split(field, "="); len(matches) == 2 { + nested := &nestedField{Name: matches[0], Type: matches[1], IsArray: strings.HasPrefix(matches[1], "[]")} + if nested.IsArray { + nested.Type = nested.Type[2:] } + nested.Type = TransToValidSchemeType(nested.Type) + if !IsPrimitiveType(nested.Type) { + if operation.parser != nil { // checking refType has existing in 'TypeDefinitions' + refType, typeSpec, err := operation.registerSchemaType(nested.Type, astFile) + if err != nil { + return specStr, nil, err + } - nested.Ref = spec.Ref{ - Ref: jsonreference.MustCreateRef("#/definitions/" + TypeDocName(refType, typeSpec)), + nested.Ref = spec.Ref{ + Ref: jsonreference.MustCreateRef("#/definitions/" + TypeDocName(refType, typeSpec)), + } + } } + nestedFields = append(nestedFields, nested) } } - return } @@ -719,7 +707,7 @@ func (operation *Operation) ParseResponseComment(commentLine string, astFile *as schemaType := strings.Trim(matches[2], "{}") refType := matches[3] - refType, nested, err := operation.tryExtractNestedField(refType, astFile) + refType, nestedFields, err := operation.tryExtractNestedFields(refType, astFile) if err != nil { return err } @@ -743,10 +731,27 @@ func (operation *Operation) ParseResponseComment(commentLine string, astFile *as Ref: jsonreference.MustCreateRef("#/definitions/" + TypeDocName(refType, typeSpec)), } - if nested == nil { + if nestedFields == nil { response.Schema.Ref = ref } else { - nested.fillNestedSchema(&response, ref) + props := make(map[string]spec.Schema) + for _, nested := range nestedFields { + if nested.IsArray { + props[nested.Name] = spec.Schema{SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{Schema: nested.getSchema()}, + }} + } else { + props[nested.Name] = *nested.getSchema() + } + } + nestedSpec := spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: props, + }, + } + response.Schema.AllOf = []spec.Schema{{SchemaProps: spec.SchemaProps{Ref: ref}}, nestedSpec} } } else if schemaType == "array" { diff --git a/operation_test.go b/operation_test.go index f9c339260..c85cc5584 100644 --- a/operation_test.go +++ b/operation_test.go @@ -189,7 +189,7 @@ func TestParseResponseCommentWithObjectType(t *testing.T) { } func TestParseResponseCommentWithNestedPrimitiveType(t *testing.T) { - comment := `@Success 200 {object} model.CommonHeader{data=string} "Error message, if code != 200` + comment := `@Success 200 {object} model.CommonHeader{data=string,data2=int} "Error message, if code != 200` operation := NewOperation() operation.parser = New() @@ -218,6 +218,9 @@ func TestParseResponseCommentWithNestedPrimitiveType(t *testing.T) { "properties": { "data": { "type": "string" + }, + "data2": { + "type": "integer" } } } @@ -230,7 +233,7 @@ func TestParseResponseCommentWithNestedPrimitiveType(t *testing.T) { } func TestParseResponseCommentWithNestedPrimitiveArrayType(t *testing.T) { - comment := `@Success 200 {object} model.CommonHeader{data=[]string} "Error message, if code != 200` + comment := `@Success 200 {object} model.CommonHeader{data=[]string,data2=[]int} "Error message, if code != 200` operation := NewOperation() operation.parser = New() @@ -262,6 +265,12 @@ func TestParseResponseCommentWithNestedPrimitiveArrayType(t *testing.T) { "items": { "type": "string" } + }, + "data2": { + "type": "array", + "items": { + "type": "integer" + } } } } @@ -274,13 +283,14 @@ func TestParseResponseCommentWithNestedPrimitiveArrayType(t *testing.T) { } func TestParseResponseCommentWithNestedObjectType(t *testing.T) { - comment := `@Success 200 {object} model.CommonHeader{data=model.Payload} "Error message, if code != 200` + comment := `@Success 200 {object} model.CommonHeader{data=model.Payload,data2=model.Payload2} "Error message, if code != 200` operation := NewOperation() operation.parser = New() operation.parser.TypeDefinitions["model"] = make(map[string]*ast.TypeSpec) operation.parser.TypeDefinitions["model"]["CommonHeader"] = &ast.TypeSpec{} operation.parser.TypeDefinitions["model"]["Payload"] = &ast.TypeSpec{} + operation.parser.TypeDefinitions["model"]["Payload2"] = &ast.TypeSpec{} err := operation.ParseComment(comment, nil) assert.NoError(t, err) @@ -304,6 +314,9 @@ func TestParseResponseCommentWithNestedObjectType(t *testing.T) { "properties": { "data": { "$ref": "#/definitions/model.Payload" + }, + "data2": { + "$ref": "#/definitions/model.Payload2" } } } @@ -316,13 +329,14 @@ func TestParseResponseCommentWithNestedObjectType(t *testing.T) { } func TestParseResponseCommentWithNestedArrayObjectType(t *testing.T) { - comment := `@Success 200 {object} model.CommonHeader{data=[]model.Payload} "Error message, if code != 200` + comment := `@Success 200 {object} model.CommonHeader{data=[]model.Payload,data2=[]model.Payload2} "Error message, if code != 200` operation := NewOperation() operation.parser = New() operation.parser.TypeDefinitions["model"] = make(map[string]*ast.TypeSpec) operation.parser.TypeDefinitions["model"]["CommonHeader"] = &ast.TypeSpec{} operation.parser.TypeDefinitions["model"]["Payload"] = &ast.TypeSpec{} + operation.parser.TypeDefinitions["model"]["Payload2"] = &ast.TypeSpec{} err := operation.ParseComment(comment, nil) assert.NoError(t, err) @@ -349,6 +363,69 @@ func TestParseResponseCommentWithNestedArrayObjectType(t *testing.T) { "items": { "$ref": "#/definitions/model.Payload" } + }, + "data2": { + "type": "array", + "items": { + "$ref": "#/definitions/model.Payload2" + } + } + } + } + ] + } + } + } +}` + assert.Equal(t, expected, string(b)) +} + +func TestParseResponseCommentWithNestedFields(t *testing.T) { + comment := `@Success 200 {object} model.CommonHeader{data1=int,data2=[]int,data3=model.Payload,data4=[]model.Payload} "Error message, if code != 200` + operation := NewOperation() + operation.parser = New() + + operation.parser.TypeDefinitions["model"] = make(map[string]*ast.TypeSpec) + operation.parser.TypeDefinitions["model"]["CommonHeader"] = &ast.TypeSpec{} + operation.parser.TypeDefinitions["model"]["Payload"] = &ast.TypeSpec{} + + err := operation.ParseComment(comment, nil) + assert.NoError(t, err) + + response := operation.Responses.StatusCodeResponses[200] + assert.Equal(t, `Error message, if code != 200`, response.Description) + + b, _ := json.MarshalIndent(operation, "", " ") + + expected := `{ + "responses": { + "200": { + "description": "Error message, if code != 200", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/model.CommonHeader" + }, + { + "type": "object", + "properties": { + "data1": { + "type": "integer" + }, + "data2": { + "type": "array", + "items": { + "type": "integer" + } + }, + "data3": { + "$ref": "#/definitions/model.Payload" + }, + "data4": { + "type": "array", + "items": { + "$ref": "#/definitions/model.Payload" + } } } } diff --git a/parser.go b/parser.go index 79dce6c1b..bb983355e 100644 --- a/parser.go +++ b/parser.go @@ -921,7 +921,10 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st if typeSpec, ok := parser.TypeDefinitions[pkgName][structField.schemaType]; ok { // user type field // write definition if not yet present - parser.ParseDefinition(pkgName, structField.schemaType, typeSpec) + err = parser.ParseDefinition(pkgName, structField.schemaType, typeSpec) + if err != nil { + return properties, nil, err + } required := make([]string, 0) if structField.isRequired { required = append(required, structField.name) From e10c4d7eb26782b9c1b252521a0c61dd7ede5eae Mon Sep 17 00:00:00 2001 From: poccariswet Date: Tue, 7 Apr 2020 23:58:11 +0900 Subject: [PATCH 56/84] [FIX] parse comment error integer to string --- example/celler/controller/examples.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/celler/controller/examples.go b/example/celler/controller/examples.go index 5670ec8d0..cd2b89c5d 100644 --- a/example/celler/controller/examples.go +++ b/example/celler/controller/examples.go @@ -33,7 +33,7 @@ func (c *Controller) PingExample(ctx *gin.Context) { // @Produce json // @Param val1 query int true "used for calc" // @Param val2 query int true "used for calc" -// @Success 200 {integer} integer "answer" +// @Success 200 {integer} string "answer" // @Failure 400 {string} string "ok" // @Failure 404 {string} string "ok" // @Failure 500 {string} string "ok" From b9554c4f82269749992af764baefb583bd3bbd47 Mon Sep 17 00:00:00 2001 From: Damien Date: Wed, 8 Apr 2020 17:22:57 +1100 Subject: [PATCH 57/84] skip cgo package --- parser.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/parser.go b/parser.go index bb983355e..0043fa743 100644 --- a/parser.go +++ b/parser.go @@ -1449,6 +1449,10 @@ func (parser *Parser) getAllGoFileInfoFromDeps(pkg *depth.Pkg) error { return nil } + // Skip cgo + if pkg.Raw == nil && pkg.Name == "C" { + return nil + } srcDir := pkg.Raw.Dir files, err := ioutil.ReadDir(srcDir) // only parsing files in the dir(don't contains sub dir files) if err != nil { From 1e6c432ead6c480e8121e8d1a4aa8217c1d65590 Mon Sep 17 00:00:00 2001 From: Damien Date: Fri, 10 Apr 2020 08:35:27 +1100 Subject: [PATCH 58/84] Added unit test for cgo package import --- gen/gen_test.go | 26 +++++++ testdata/simple_cgo/api/api.go | 116 +++++++++++++++++++++++++++++ testdata/simple_cgo/cross/test.go | 6 ++ testdata/simple_cgo/main.go | 66 ++++++++++++++++ testdata/simple_cgo/web/handler.go | 97 ++++++++++++++++++++++++ 5 files changed, 311 insertions(+) create mode 100644 testdata/simple_cgo/api/api.go create mode 100644 testdata/simple_cgo/cross/test.go create mode 100644 testdata/simple_cgo/main.go create mode 100644 testdata/simple_cgo/web/handler.go diff --git a/gen/gen_test.go b/gen/gen_test.go index 075917b58..ac7f2bf9e 100644 --- a/gen/gen_test.go +++ b/gen/gen_test.go @@ -345,3 +345,29 @@ func TestGen_GeneratedDoc(t *testing.T) { os.Remove(expectedFile) } } + +func TestGen_cgoImports(t *testing.T) { + searchDir := "../testdata/simple_cgo" + + config := &Config{ + SearchDir: searchDir, + MainAPIFile: "./main.go", + OutputDir: "../testdata/simple_cgo/docs", + PropNamingStrategy: "", + ParseDependency: true, + } + + assert.NoError(t, New().Build(config)) + + expectedFiles := []string{ + path.Join(config.OutputDir, "docs.go"), + path.Join(config.OutputDir, "swagger.json"), + path.Join(config.OutputDir, "swagger.yaml"), + } + for _, expectedFile := range expectedFiles { + if _, err := os.Stat(expectedFile); os.IsNotExist(err) { + t.Fatal(err) + } + os.Remove(expectedFile) + } +} diff --git a/testdata/simple_cgo/api/api.go b/testdata/simple_cgo/api/api.go new file mode 100644 index 000000000..ba6734d71 --- /dev/null +++ b/testdata/simple_cgo/api/api.go @@ -0,0 +1,116 @@ +package api + +import ( + "github.com/gin-gonic/gin" +) + +// @Summary Add a new pet to the store +// @Description get string by ID +// @ID get-string-by-int +// @Accept json +// @Produce json +// @Param some_id path int true "Some ID" Format(int64) +// @Param some_id body web.Pet true "Some ID" +// @Success 200 {string} string "ok" +// @Failure 400 {object} web.APIError "We need ID!!" +// @Failure 404 {object} web.APIError "Can not find ID" +// @Router /testapi/get-string-by-int/{some_id} [get] +func GetStringByInt(c *gin.Context) { + //write your code +} + +// @Description get struct array by ID +// @ID get-struct-array-by-string +// @Accept json +// @Produce json +// @Param some_id path string true "Some ID" +// @Param category query int true "Category" Enums(1, 2, 3) +// @Param offset query int true "Offset" Mininum(0) default(0) +// @Param limit query int true "Limit" Maxinum(50) default(10) +// @Param q query string true "q" Minlength(1) Maxlength(50) default("") +// @Success 200 {string} string "ok" +// @Failure 400 {object} web.APIError "We need ID!!" +// @Failure 404 {object} web.APIError "Can not find ID" +// @Security ApiKeyAuth +// @Security BasicAuth +// @Security OAuth2Application[write] +// @Security OAuth2Implicit[read, admin] +// @Security OAuth2AccessCode[read] +// @Security OAuth2Password[admin] +// @Router /testapi/get-struct-array-by-string/{some_id} [get] +func GetStructArrayByString(c *gin.Context) { + //write your code +} + +// @Summary Upload file +// @Description Upload file +// @ID file.upload +// @Accept multipart/form-data +// @Produce json +// @Param file formData file true "this is a test file" +// @Success 200 {string} string "ok" +// @Failure 400 {object} web.APIError "We need ID!!" +// @Failure 401 {array} string +// @Failure 404 {object} web.APIError "Can not find ID" +// @Router /file/upload [post] +func Upload(ctx *gin.Context) { + //write your code +} + +// @Summary use Anonymous field +// @Success 200 {object} web.RevValue "ok" +func AnonymousField() { + +} + +// @Summary use pet2 +// @Success 200 {object} web.Pet2 "ok" +func Pet2() { + +} + +// @Summary Use IndirectRecursiveTest +// @Success 200 {object} web.IndirectRecursiveTest +func IndirectRecursiveTest() { +} + +// @Summary Use Tags +// @Success 200 {object} web.Tags +func Tags() { +} + +// @Summary Use CrossAlias +// @Success 200 {object} web.CrossAlias +func CrossAlias() { +} + +// @Summary Use AnonymousStructArray +// @Success 200 {object} web.AnonymousStructArray +func AnonymousStructArray() { +} + +type Pet3 struct { + ID int `json:"id"` +} + +// @Success 200 {object} web.Pet5a "ok" +func GetPet5a() { + +} + +// @Success 200 {object} web.Pet5b "ok" +func GetPet5b() { + +} + +// @Success 200 {object} web.Pet5c "ok" +func GetPet5c() { + +} + +type SwagReturn []map[string]string + +// @Success 200 {object} api.SwagReturn "ok" +func GetPet6MapString() { + +} diff --git a/testdata/simple_cgo/cross/test.go b/testdata/simple_cgo/cross/test.go new file mode 100644 index 000000000..540e53f4f --- /dev/null +++ b/testdata/simple_cgo/cross/test.go @@ -0,0 +1,6 @@ +package cross + +type Cross struct { + Array []string + String string +} diff --git a/testdata/simple_cgo/main.go b/testdata/simple_cgo/main.go new file mode 100644 index 000000000..63deacc33 --- /dev/null +++ b/testdata/simple_cgo/main.go @@ -0,0 +1,66 @@ +package main + +/* +#include + +void Hello(){ + printf("Hello world\n"); +} +*/ +import "C" + +import ( + "github.com/gin-gonic/gin" + "github.com/swaggo/swag/testdata/simple/api" +) + +// @title Swagger Example API +// @version 1.0 +// @description This is a sample server Petstore server. +// @termsOfService http://swagger.io/terms/ + +// @contact.name API Support +// @contact.url http://www.swagger.io/support +// @contact.email support@swagger.io + +// @license.name Apache 2.0 +// @license.url http://www.apache.org/licenses/LICENSE-2.0.html + +// @host petstore.swagger.io +// @BasePath /v2 + +// @securityDefinitions.basic BasicAuth + +// @securityDefinitions.apikey ApiKeyAuth +// @in header +// @name Authorization + +// @securitydefinitions.oauth2.application OAuth2Application +// @tokenUrl https://example.com/oauth/token +// @scope.write Grants write access +// @scope.admin Grants read and write access to administrative information + +// @securitydefinitions.oauth2.implicit OAuth2Implicit +// @authorizationurl https://example.com/oauth/authorize +// @scope.write Grants write access +// @scope.admin Grants read and write access to administrative information + +// @securitydefinitions.oauth2.password OAuth2Password +// @tokenUrl https://example.com/oauth/token +// @scope.read Grants read access +// @scope.write Grants write access +// @scope.admin Grants read and write access to administrative information + +// @securitydefinitions.oauth2.accessCode OAuth2AccessCode +// @tokenUrl https://example.com/oauth/token +// @authorizationurl https://example.com/oauth/authorize +// @scope.admin Grants read and write access to administrative information +func main() { + C.Hello() + + r := gin.New() + r.GET("/testapi/get-string-by-int/:some_id", api.GetStringByInt) + r.GET("/testapi/get-struct-array-by-string/:some_id", api.GetStructArrayByString) + r.POST("/testapi/upload", api.Upload) + r.Run() +} diff --git a/testdata/simple_cgo/web/handler.go b/testdata/simple_cgo/web/handler.go new file mode 100644 index 000000000..ece1628dc --- /dev/null +++ b/testdata/simple_cgo/web/handler.go @@ -0,0 +1,97 @@ +package web + +import ( + "time" + + "github.com/satori/go.uuid" + "github.com/shopspring/decimal" + "github.com/swaggo/swag/testdata/simple/cross" +) + +type Pet struct { + ID int `json:"id" example:"1" format:"int64" readonly:"true"` + Category struct { + ID int `json:"id" example:"1"` + Name string `json:"name" example:"category_name"` + PhotoUrls []string `json:"photo_urls" example:"http://test/image/1.jpg,http://test/image/2.jpg" format:"url"` + SmallCategory struct { + ID int `json:"id" example:"1"` + Name string `json:"name" example:"detail_category_name" binding:"required" minLength:"4" maxLength:"16"` + PhotoUrls []string `json:"photo_urls" example:"http://test/image/1.jpg,http://test/image/2.jpg"` + } `json:"small_category"` + } `json:"category"` + Name string `json:"name" example:"poti" binding:"required"` + PhotoUrls []string `json:"photo_urls" example:"http://test/image/1.jpg,http://test/image/2.jpg" binding:"required"` + Tags []Tag `json:"tags"` + Pets *[]Pet2 `json:"pets"` + Pets2 []*Pet2 `json:"pets2"` + Status string `json:"status" enums:"healthy,ill"` + Price float32 `json:"price" example:"3.25" minimum:"1.0" maximum:"1000"` + IsAlive bool `json:"is_alive" example:"true" default:"true"` + Data interface{} `json:"data"` + Hidden string `json:"-"` + UUID uuid.UUID `json:"uuid"` + Decimal decimal.Decimal `json:"decimal"` + IntArray []int `json:"int_array" example:"1,2"` + EnumArray []int `json:"enum_array" enums:"1,2,3,5,7"` +} + +type Tag struct { + ID int `json:"id" format:"int64"` + Name string `json:"name"` + Pets []Pet `json:"pets"` +} + +type Tags []*Tag + +type AnonymousStructArray []struct { + Foo string `json:"foo"` +} + +type CrossAlias cross.Cross + +type Pet2 struct { + ID int `json:"id"` + MiddleName *string `json:"middlename" extensions:"x-nullable,x-abc=def"` + DeletedAt *time.Time `json:"deleted_at"` +} + +type IndirectRecursiveTest struct { + Tags []Tag +} + +type APIError struct { + ErrorCode int + ErrorMessage string + CreatedAt time.Time +} + +type RevValueBase struct { + Status bool `json:"Status"` + + Err int32 `json:"Err,omitempty"` +} +type RevValue struct { + RevValueBase `json:"rev_value_base"` + + Data int `json:"Data"` + Cross cross.Cross `json:"cross"` + Crosses []cross.Cross `json:"crosses"` +} + +// Below we have Pet5b as base type and Pet5a and Pet5c both have Pet5b as anonymous field, inheriting it's properties +// By using these names we ensure that our test will fill if the order of parsing matters at all + +type Pet5a struct { + *Pet5b + Odd bool `json:"odd" binding:"required"` +} + +type Pet5b struct { + Name string `json:"name" binding:"required"` +} + +type Pet5c struct { + *Pet5b + Odd bool `json:"odd" binding:"required"` +} From 4c502aa34e8c9dd39762fb50488d0f5087bef866 Mon Sep 17 00:00:00 2001 From: Shoshin Nikita Date: Fri, 10 Apr 2020 20:51:01 +0300 Subject: [PATCH 59/84] testdata/alias_import: add test for embedded field --- testdata/alias_import/data/applicationresponse.go | 2 ++ testdata/alias_import/expected.json | 3 +++ testdata/alias_import/types/application.go | 4 ++++ 3 files changed, 9 insertions(+) diff --git a/testdata/alias_import/data/applicationresponse.go b/testdata/alias_import/data/applicationresponse.go index b6bcf3c58..da4170508 100644 --- a/testdata/alias_import/data/applicationresponse.go +++ b/testdata/alias_import/data/applicationresponse.go @@ -5,6 +5,8 @@ import ( ) type ApplicationResponse struct { + typesapplication.TypeToEmbed + Application typesapplication.Application `json:"application"` ApplicationArray []typesapplication.Application `json:"application_array"` ApplicationTime typesapplication.DateOnly `json:"application_time"` diff --git a/testdata/alias_import/expected.json b/testdata/alias_import/expected.json index 4b6232987..1b477398c 100644 --- a/testdata/alias_import/expected.json +++ b/testdata/alias_import/expected.json @@ -56,6 +56,9 @@ }, "application_time": { "type": "string" + }, + "embedded": { + "type": "string" } } }, diff --git a/testdata/alias_import/types/application.go b/testdata/alias_import/types/application.go index ceb7120ec..ddff98ef0 100644 --- a/testdata/alias_import/types/application.go +++ b/testdata/alias_import/types/application.go @@ -7,3 +7,7 @@ type Application struct { } type DateOnly time.Time + +type TypeToEmbed struct { + Embedded string +} From b35892a668840277f8a359cc9ee814725cd4c68c Mon Sep 17 00:00:00 2001 From: Shoshin Nikita Date: Fri, 10 Apr 2020 22:03:38 +0300 Subject: [PATCH 60/84] support embedded structures with alias instead of package name --- parser.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/parser.go b/parser.go index bb983355e..f7c5612db 100644 --- a/parser.go +++ b/parser.go @@ -859,6 +859,17 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st } typeSpec := parser.TypeDefinitions[pkgName][typeName] + if typeSpec == nil { + // Check if the pkg name is an alias and try to define type spec using real package name + if aliases, ok := parser.ImportAliases[pkgName]; ok { + for alias := range aliases { + typeSpec = parser.TypeDefinitions[alias][typeName] + if typeSpec != nil { + break + } + } + } + } if typeSpec != nil { schema, err := parser.parseTypeExpr(pkgName, typeName, typeSpec.Type) if err != nil { From ccc5ab233bb9a8627574348d4a91d546a389f134 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hagen=20H=C3=BCbel?= Date: Mon, 13 Apr 2020 22:59:38 +0200 Subject: [PATCH 61/84] Added Dockerfile (#671) --- Dockerfile | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..0a5d9b8db --- /dev/null +++ b/Dockerfile @@ -0,0 +1,29 @@ +# Dockerfile References: https://docs.docker.com/engine/reference/builder/ + +# Start from the latest golang base image +FROM golang:1.14-alpine as builder + +# Set the Current Working Directory inside the container +WORKDIR /app + +# Copy go mod and sum files +COPY go.mod go.sum ./ + +# Download all dependencies. Dependencies will be cached if the go.mod and go.sum files are not changed +RUN go mod download + +# Copy the source from the current directory to the Working Directory inside the container +COPY . . + +# Build the Go app +RUN CGO_ENABLED=0 GOOS=linux go build -v -a -installsuffix cgo -o swag cmd/swag/main.go + + +######## Start a new stage from scratch ####### +FROM scratch + +WORKDIR /root/ + +# Copy the Pre-built binary file from the previous stage +COPY --from=builder /app/swag . + From 5fcb6d5ce987b08c35240cd7de688a40dacd4659 Mon Sep 17 00:00:00 2001 From: tcarreira Date: Mon, 13 Apr 2020 23:41:31 -0300 Subject: [PATCH 62/84] parseField skips not-exported (encoding/json-like) --- parser.go | 5 + parser_test.go | 170 ++++++++++++++++++++++ testdata/non_exported_json_fields/main.go | 44 ++++++ testdata/simple2/web/handler.go | 4 +- 4 files changed, 221 insertions(+), 2 deletions(-) create mode 100644 testdata/non_exported_json_fields/main.go diff --git a/parser.go b/parser.go index bb983355e..eab7776bc 100644 --- a/parser.go +++ b/parser.go @@ -1165,6 +1165,11 @@ func (parser *Parser) parseField(pkgName string, field *ast.Field) (*structField return &structField{name: ""}, nil } + // Skip non-exported fields. + if !ast.IsExported(field.Names[0].Name) { + return &structField{name: ""}, nil + } + structField := &structField{ name: field.Names[0].Name, schemaType: prop.SchemaType, diff --git a/parser_test.go b/parser_test.go index a06146712..d09de8928 100644 --- a/parser_test.go +++ b/parser_test.go @@ -1947,6 +1947,176 @@ func TestParseStructComment(t *testing.T) { assert.Equal(t, expected, string(b)) } +func TestParseNonExportedJSONFields(t *testing.T) { + + //region declaration + expected := `{ + "swagger": "2.0", + "info": { + "description": "This is a sample server.", + "title": "Swagger Example API", + "contact": {}, + "license": {}, + "version": "1.0" + }, + "host": "localhost:4000", + "basePath": "/api", + "paths": { + "/posts/{post_id}": { + "get": { + "description": "get string by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Add a new pet to the store", + "parameters": [ + { + "type": "integer", + "format": "int64", + "description": "Some ID", + "name": "post_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + }, + "400": { + "description": "We need ID!!", + "schema": { + "$ref": "#/definitions/web.APIError" + } + }, + "404": { + "description": "Can not find ID", + "schema": { + "$ref": "#/definitions/web.APIError" + } + } + } + } + }, + "/so-something": { + "get": { + "description": "Does something, but internal (non-exported) fields inside a struct won't be marshaled into JSON", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Call DoSomething", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + } + } + } + }, + "definitions": { + "main.MyStruct": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + }, + "web.APIError": { + "type": "object", + "properties": { + "createdAt": { + "description": "Error time", + "type": "string" + }, + "error": { + "description": "Error an Api error", + "type": "string" + }, + "errorCtx": { + "description": "Error ` + "`" + `context` + "`" + ` tick comment", + "type": "string" + }, + "errorNo": { + "description": "Error ` + "`" + `number` + "`" + ` tick comment", + "type": "integer" + } + } + }, + "web.Post": { + "type": "object", + "properties": { + "data": { + "description": "Post data", + "type": "object", + "properties": { + "name": { + "description": "Post tag", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "description": "Post name", + "type": "string", + "example": "poti" + } + } + } + } +}` + //endregion declaration + + searchDir := "testdata/non_exported_json_fields" + mainAPIFile := "main.go" + p := New() + err := p.ParseAPI(searchDir, mainAPIFile) + assert.NoError(t, err) + b, _ := json.MarshalIndent(p.swagger, "", " ") + ioutil.WriteFile("/tmp/test1", b, 0644) + assert.Equal(t, expected, string(b)) +} + func TestParsePetApi(t *testing.T) { expected := `{ "schemes": [ diff --git a/testdata/non_exported_json_fields/main.go b/testdata/non_exported_json_fields/main.go new file mode 100644 index 000000000..ed641c38d --- /dev/null +++ b/testdata/non_exported_json_fields/main.go @@ -0,0 +1,44 @@ +package main + +import ( + "github.com/gin-gonic/gin" +) + +type MyStruct struct { + ID int `json:"id" example:"1" format:"int64"` + // Post name + Name string `json:"name" example:"poti"` + // Post data + Data struct { + // Post tag + Tag []string `json:"name"` + } `json:"data"` + // not-exported variable, for internal use only, not marshaled + internal1 string + internal2 int + internal3 bool + internal4 struct { + NestedInternal string + } +} + +// @Summary Call DoSomething +// @Description Does something, but internal (non-exported) fields inside a struct won't be marshaled into JSON +// @Accept json +// @Produce json +// @Success 200 {string} MyStruct +// @Router /so-something [get] +func DoSomething(c *gin.Context) { + //write your code +} + +// @title Swagger Example API +// @version 1.0 +// @description This is a sample server. +// @host localhost:4000 +// @basePath /api +func main() { + r := gin.New() + r.GET("/do-something", api.DoSomething) + r.Run() +} diff --git a/testdata/simple2/web/handler.go b/testdata/simple2/web/handler.go index d9a568692..1ab61b920 100644 --- a/testdata/simple2/web/handler.go +++ b/testdata/simple2/web/handler.go @@ -53,8 +53,8 @@ type Pet struct { Hidden string `json:"-"` UUID uuid.UUID Decimal decimal.Decimal - customString CustomString - customStringArr []CustomString + CustomString CustomString + CustomStringArr []CustomString NullInt sql.NullInt64 `swaggertype:"integer"` Coeffs []big.Float `swaggertype:"array,number"` Birthday TimestampTime `swaggertype:"primitive,integer"` From 6fb2a2d77291382c8483e201dc51cd0b32060c92 Mon Sep 17 00:00:00 2001 From: tcarreira Date: Tue, 14 Apr 2020 00:02:53 -0300 Subject: [PATCH 63/84] fix TestParseNonExportedJSONFields --- parser_test.go | 95 ------------------------------ testdata/json_field_string/main.go | 39 ++++++++++++ 2 files changed, 39 insertions(+), 95 deletions(-) create mode 100644 testdata/json_field_string/main.go diff --git a/parser_test.go b/parser_test.go index d09de8928..7baa835c0 100644 --- a/parser_test.go +++ b/parser_test.go @@ -1948,8 +1948,6 @@ func TestParseStructComment(t *testing.T) { } func TestParseNonExportedJSONFields(t *testing.T) { - - //region declaration expected := `{ "swagger": "2.0", "info": { @@ -1962,48 +1960,6 @@ func TestParseNonExportedJSONFields(t *testing.T) { "host": "localhost:4000", "basePath": "/api", "paths": { - "/posts/{post_id}": { - "get": { - "description": "get string by ID", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "summary": "Add a new pet to the store", - "parameters": [ - { - "type": "integer", - "format": "int64", - "description": "Some ID", - "name": "post_id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "string" - } - }, - "400": { - "description": "We need ID!!", - "schema": { - "$ref": "#/definitions/web.APIError" - } - }, - "404": { - "description": "Can not find ID", - "schema": { - "$ref": "#/definitions/web.APIError" - } - } - } - } - }, "/so-something": { "get": { "description": "Does something, but internal (non-exported) fields inside a struct won't be marshaled into JSON", @@ -2053,59 +2009,9 @@ func TestParseNonExportedJSONFields(t *testing.T) { "example": "poti" } } - }, - "web.APIError": { - "type": "object", - "properties": { - "createdAt": { - "description": "Error time", - "type": "string" - }, - "error": { - "description": "Error an Api error", - "type": "string" - }, - "errorCtx": { - "description": "Error ` + "`" + `context` + "`" + ` tick comment", - "type": "string" - }, - "errorNo": { - "description": "Error ` + "`" + `number` + "`" + ` tick comment", - "type": "integer" - } - } - }, - "web.Post": { - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } } } }` - //endregion declaration searchDir := "testdata/non_exported_json_fields" mainAPIFile := "main.go" @@ -2113,7 +2019,6 @@ func TestParseNonExportedJSONFields(t *testing.T) { err := p.ParseAPI(searchDir, mainAPIFile) assert.NoError(t, err) b, _ := json.MarshalIndent(p.swagger, "", " ") - ioutil.WriteFile("/tmp/test1", b, 0644) assert.Equal(t, expected, string(b)) } diff --git a/testdata/json_field_string/main.go b/testdata/json_field_string/main.go new file mode 100644 index 000000000..04515dccb --- /dev/null +++ b/testdata/json_field_string/main.go @@ -0,0 +1,39 @@ +package main + +import ( + "github.com/gin-gonic/gin" +) + +type MyStruct struct { + ID int `json:"id" example:"1" format:"int64"` + // Post name + Name string `json:"name" example:"poti"` + // Post data + Data struct { + // Post tag + Tag []string `json:"name"` + } `json:"data"` + // Integer represented by a string + MyInt int `json:"myint,string"` +} + +// @Summary Call DoSomething +// @Description Does something, but internal (non-exported) fields inside a struct won't be marshaled into JSON +// @Accept json +// @Produce json +// @Success 200 {string} MyStruct +// @Router /so-something [get] +func DoSomething(c *gin.Context) { + //write your code +} + +// @title Swagger Example API +// @version 1.0 +// @description This is a sample server. +// @host localhost:4000 +// @basePath /api +func main() { + r := gin.New() + r.GET("/do-something", api.DoSomething) + r.Run() +} From fa785502f971b37ab4b12ab4093b8942ece57f33 Mon Sep 17 00:00:00 2001 From: tcarreira Date: Tue, 14 Apr 2020 22:15:36 -0300 Subject: [PATCH 64/84] issue-665: fix example when using `json=",string"` --- parser.go | 43 ++++++++++++-- parser_test.go | 93 ++++++++++++++++++++++++++++++ testdata/json_field_string/main.go | 36 +++++++----- 3 files changed, 152 insertions(+), 20 deletions(-) mode change 100644 => 100755 testdata/json_field_string/main.go diff --git a/parser.go b/parser.go index eab7776bc..edf623737 100644 --- a/parser.go +++ b/parser.go @@ -1207,8 +1207,14 @@ func (parser *Parser) parseField(pkgName string, field *ast.Field) (*structField } jsonTag := structTag.Get("json") + hasStringTag := false // json:"tag,hoge" if strings.Contains(jsonTag, ",") { + // json:"name,string" or json:",string" + if strings.Contains(jsonTag, ",string") { + hasStringTag = true + } + // json:",hoge" if strings.HasPrefix(jsonTag, ",") { jsonTag = "" @@ -1249,11 +1255,16 @@ func (parser *Parser) parseField(pkgName string, field *ast.Field) (*structField } } if exampleTag := structTag.Get("example"); exampleTag != "" { - example, err := defineTypeOfExample(structField.schemaType, structField.arrayType, exampleTag) - if err != nil { - return nil, err + if hasStringTag { + // then the example must be in string format + structField.exampleValue = exampleTag + } else { + example, err := defineTypeOfExample(structField.schemaType, structField.arrayType, exampleTag) + if err != nil { + return nil, err + } + structField.exampleValue = example } - structField.exampleValue = example } if formatTag := structTag.Get("format"); formatTag != "" { structField.formatType = formatTag @@ -1337,6 +1348,30 @@ func (parser *Parser) parseField(pkgName string, field *ast.Field) (*structField structField.readOnly = readOnly == "true" } + // perform this after setting everything else (min, max, etc...) + if hasStringTag { + + // @encoding/json: "It applies only to fields of string, floating point, integer, or boolean types." + defaultValues := map[string]string{ + // Zero Values as string + "string": "", + "integer": "0", + "boolean": "false", + "number": "0", + } + + if defaultValue, ok := defaultValues[structField.schemaType]; ok { + structField.schemaType = "string" + + if structField.exampleValue == nil { + // if exampleValue is not defined by the user, + // we will force an example with a correct value + // (eg: int->"0", bool:"false") + structField.exampleValue = defaultValue + } + } + } + return structField, nil } diff --git a/parser_test.go b/parser_test.go index 7baa835c0..1bf371bee 100644 --- a/parser_test.go +++ b/parser_test.go @@ -3111,3 +3111,96 @@ func Fun() { ref = path.Get.Responses.ResponsesProps.StatusCodeResponses[200].ResponseProps.Schema.Ref assert.Equal(t, "#/definitions/Teacher", ref.String()) } + +func TestParseJSONFieldString(t *testing.T) { + expected := `{ + "swagger": "2.0", + "info": { + "description": "This is a sample server.", + "title": "Swagger Example API", + "contact": {}, + "license": {}, + "version": "1.0" + }, + "host": "localhost:4000", + "basePath": "/", + "paths": { + "/do-something": { + "post": { + "description": "Does something", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Call DoSomething", + "parameters": [ + { + "description": "My Struct", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/main.MyStruct" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/main.MyStruct" + } + }, + "500": {} + } + } + } + }, + "definitions": { + "main.MyStruct": { + "type": "object", + "properties": { + "boolvar": { + "description": "boolean as a string", + "type": "string", + "example": "false" + }, + "floatvar": { + "description": "float as a string", + "type": "string", + "example": "0" + }, + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "myint": { + "description": "integer as string", + "type": "string", + "example": "0" + }, + "name": { + "type": "string", + "example": "poti" + }, + "truebool": { + "description": "boolean as a string", + "type": "string", + "example": "true" + } + } + } + } +}` + + searchDir := "testdata/json_field_string" + mainAPIFile := "main.go" + p := New() + err := p.ParseAPI(searchDir, mainAPIFile) + assert.NoError(t, err) + b, _ := json.MarshalIndent(p.swagger, "", " ") + assert.Equal(t, expected, string(b)) +} diff --git a/testdata/json_field_string/main.go b/testdata/json_field_string/main.go old mode 100644 new mode 100755 index 04515dccb..599545224 --- a/testdata/json_field_string/main.go +++ b/testdata/json_field_string/main.go @@ -1,39 +1,43 @@ package main import ( + "net/http" + "github.com/gin-gonic/gin" ) type MyStruct struct { - ID int `json:"id" example:"1" format:"int64"` - // Post name - Name string `json:"name" example:"poti"` - // Post data - Data struct { - // Post tag - Tag []string `json:"name"` - } `json:"data"` - // Integer represented by a string - MyInt int `json:"myint,string"` + ID int `json:"id" example:"1" format:"int64"` + Name string `json:"name" example:"poti"` + Intvar int `json:"myint,string"` // integer as string + Boolvar bool `json:",string"` // boolean as a string + TrueBool bool `json:"truebool,string" example:"true"` // boolean as a string + Floatvar float64 `json:",string"` // float as a string } // @Summary Call DoSomething -// @Description Does something, but internal (non-exported) fields inside a struct won't be marshaled into JSON +// @Description Does something // @Accept json // @Produce json -// @Success 200 {string} MyStruct -// @Router /so-something [get] +// @Param body body MyStruct true "My Struct" +// @Success 200 {object} MyStruct +// @Failure 500 +// @Router /do-something [post] func DoSomething(c *gin.Context) { - //write your code + objectFromJSON := new(MyStruct) + if err := c.BindJSON(&objectFromJSON); err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + } + c.JSON(http.StatusOK, objectFromJSON) } // @title Swagger Example API // @version 1.0 // @description This is a sample server. // @host localhost:4000 -// @basePath /api +// @basePath / func main() { r := gin.New() - r.GET("/do-something", api.DoSomething) + r.POST("/do-something", DoSomething) r.Run() } From 070af50f4f7408932e08e47dd574684db6639bba Mon Sep 17 00:00:00 2001 From: Damien Date: Thu, 16 Apr 2020 15:35:31 +1100 Subject: [PATCH 65/84] cleanup : removed unused source --- testdata/simple_cgo/api/api.go | 116 ----------------------------- testdata/simple_cgo/cross/test.go | 6 -- testdata/simple_cgo/web/handler.go | 97 ------------------------ 3 files changed, 219 deletions(-) delete mode 100644 testdata/simple_cgo/api/api.go delete mode 100644 testdata/simple_cgo/cross/test.go delete mode 100644 testdata/simple_cgo/web/handler.go diff --git a/testdata/simple_cgo/api/api.go b/testdata/simple_cgo/api/api.go deleted file mode 100644 index ba6734d71..000000000 --- a/testdata/simple_cgo/api/api.go +++ /dev/null @@ -1,116 +0,0 @@ -package api - -import ( - "github.com/gin-gonic/gin" -) - -// @Summary Add a new pet to the store -// @Description get string by ID -// @ID get-string-by-int -// @Accept json -// @Produce json -// @Param some_id path int true "Some ID" Format(int64) -// @Param some_id body web.Pet true "Some ID" -// @Success 200 {string} string "ok" -// @Failure 400 {object} web.APIError "We need ID!!" -// @Failure 404 {object} web.APIError "Can not find ID" -// @Router /testapi/get-string-by-int/{some_id} [get] -func GetStringByInt(c *gin.Context) { - //write your code -} - -// @Description get struct array by ID -// @ID get-struct-array-by-string -// @Accept json -// @Produce json -// @Param some_id path string true "Some ID" -// @Param category query int true "Category" Enums(1, 2, 3) -// @Param offset query int true "Offset" Mininum(0) default(0) -// @Param limit query int true "Limit" Maxinum(50) default(10) -// @Param q query string true "q" Minlength(1) Maxlength(50) default("") -// @Success 200 {string} string "ok" -// @Failure 400 {object} web.APIError "We need ID!!" -// @Failure 404 {object} web.APIError "Can not find ID" -// @Security ApiKeyAuth -// @Security BasicAuth -// @Security OAuth2Application[write] -// @Security OAuth2Implicit[read, admin] -// @Security OAuth2AccessCode[read] -// @Security OAuth2Password[admin] -// @Router /testapi/get-struct-array-by-string/{some_id} [get] -func GetStructArrayByString(c *gin.Context) { - //write your code -} - -// @Summary Upload file -// @Description Upload file -// @ID file.upload -// @Accept multipart/form-data -// @Produce json -// @Param file formData file true "this is a test file" -// @Success 200 {string} string "ok" -// @Failure 400 {object} web.APIError "We need ID!!" -// @Failure 401 {array} string -// @Failure 404 {object} web.APIError "Can not find ID" -// @Router /file/upload [post] -func Upload(ctx *gin.Context) { - //write your code -} - -// @Summary use Anonymous field -// @Success 200 {object} web.RevValue "ok" -func AnonymousField() { - -} - -// @Summary use pet2 -// @Success 200 {object} web.Pet2 "ok" -func Pet2() { - -} - -// @Summary Use IndirectRecursiveTest -// @Success 200 {object} web.IndirectRecursiveTest -func IndirectRecursiveTest() { -} - -// @Summary Use Tags -// @Success 200 {object} web.Tags -func Tags() { -} - -// @Summary Use CrossAlias -// @Success 200 {object} web.CrossAlias -func CrossAlias() { -} - -// @Summary Use AnonymousStructArray -// @Success 200 {object} web.AnonymousStructArray -func AnonymousStructArray() { -} - -type Pet3 struct { - ID int `json:"id"` -} - -// @Success 200 {object} web.Pet5a "ok" -func GetPet5a() { - -} - -// @Success 200 {object} web.Pet5b "ok" -func GetPet5b() { - -} - -// @Success 200 {object} web.Pet5c "ok" -func GetPet5c() { - -} - -type SwagReturn []map[string]string - -// @Success 200 {object} api.SwagReturn "ok" -func GetPet6MapString() { - -} diff --git a/testdata/simple_cgo/cross/test.go b/testdata/simple_cgo/cross/test.go deleted file mode 100644 index 540e53f4f..000000000 --- a/testdata/simple_cgo/cross/test.go +++ /dev/null @@ -1,6 +0,0 @@ -package cross - -type Cross struct { - Array []string - String string -} diff --git a/testdata/simple_cgo/web/handler.go b/testdata/simple_cgo/web/handler.go deleted file mode 100644 index ece1628dc..000000000 --- a/testdata/simple_cgo/web/handler.go +++ /dev/null @@ -1,97 +0,0 @@ -package web - -import ( - "time" - - "github.com/satori/go.uuid" - "github.com/shopspring/decimal" - "github.com/swaggo/swag/testdata/simple/cross" -) - -type Pet struct { - ID int `json:"id" example:"1" format:"int64" readonly:"true"` - Category struct { - ID int `json:"id" example:"1"` - Name string `json:"name" example:"category_name"` - PhotoUrls []string `json:"photo_urls" example:"http://test/image/1.jpg,http://test/image/2.jpg" format:"url"` - SmallCategory struct { - ID int `json:"id" example:"1"` - Name string `json:"name" example:"detail_category_name" binding:"required" minLength:"4" maxLength:"16"` - PhotoUrls []string `json:"photo_urls" example:"http://test/image/1.jpg,http://test/image/2.jpg"` - } `json:"small_category"` - } `json:"category"` - Name string `json:"name" example:"poti" binding:"required"` - PhotoUrls []string `json:"photo_urls" example:"http://test/image/1.jpg,http://test/image/2.jpg" binding:"required"` - Tags []Tag `json:"tags"` - Pets *[]Pet2 `json:"pets"` - Pets2 []*Pet2 `json:"pets2"` - Status string `json:"status" enums:"healthy,ill"` - Price float32 `json:"price" example:"3.25" minimum:"1.0" maximum:"1000"` - IsAlive bool `json:"is_alive" example:"true" default:"true"` - Data interface{} `json:"data"` - Hidden string `json:"-"` - UUID uuid.UUID `json:"uuid"` - Decimal decimal.Decimal `json:"decimal"` - IntArray []int `json:"int_array" example:"1,2"` - EnumArray []int `json:"enum_array" enums:"1,2,3,5,7"` -} - -type Tag struct { - ID int `json:"id" format:"int64"` - Name string `json:"name"` - Pets []Pet `json:"pets"` -} - -type Tags []*Tag - -type AnonymousStructArray []struct { - Foo string `json:"foo"` -} - -type CrossAlias cross.Cross - -type Pet2 struct { - ID int `json:"id"` - MiddleName *string `json:"middlename" extensions:"x-nullable,x-abc=def"` - DeletedAt *time.Time `json:"deleted_at"` -} - -type IndirectRecursiveTest struct { - Tags []Tag -} - -type APIError struct { - ErrorCode int - ErrorMessage string - CreatedAt time.Time -} - -type RevValueBase struct { - Status bool `json:"Status"` - - Err int32 `json:"Err,omitempty"` -} -type RevValue struct { - RevValueBase `json:"rev_value_base"` - - Data int `json:"Data"` - Cross cross.Cross `json:"cross"` - Crosses []cross.Cross `json:"crosses"` -} - -// Below we have Pet5b as base type and Pet5a and Pet5c both have Pet5b as anonymous field, inheriting it's properties -// By using these names we ensure that our test will fill if the order of parsing matters at all - -type Pet5a struct { - *Pet5b - Odd bool `json:"odd" binding:"required"` -} - -type Pet5b struct { - Name string `json:"name" binding:"required"` -} - -type Pet5c struct { - *Pet5b - Odd bool `json:"odd" binding:"required"` -} From 3cafd99d2fc4251b955e5d3800404e4d5016be7e Mon Sep 17 00:00:00 2001 From: Damien Date: Thu, 16 Apr 2020 16:24:30 +1100 Subject: [PATCH 66/84] Revert "cleanup : removed unused source" This reverts commit 070af50f4f7408932e08e47dd574684db6639bba. --- testdata/simple_cgo/api/api.go | 116 +++++++++++++++++++++++++++++ testdata/simple_cgo/cross/test.go | 6 ++ testdata/simple_cgo/web/handler.go | 97 ++++++++++++++++++++++++ 3 files changed, 219 insertions(+) create mode 100644 testdata/simple_cgo/api/api.go create mode 100644 testdata/simple_cgo/cross/test.go create mode 100644 testdata/simple_cgo/web/handler.go diff --git a/testdata/simple_cgo/api/api.go b/testdata/simple_cgo/api/api.go new file mode 100644 index 000000000..ba6734d71 --- /dev/null +++ b/testdata/simple_cgo/api/api.go @@ -0,0 +1,116 @@ +package api + +import ( + "github.com/gin-gonic/gin" +) + +// @Summary Add a new pet to the store +// @Description get string by ID +// @ID get-string-by-int +// @Accept json +// @Produce json +// @Param some_id path int true "Some ID" Format(int64) +// @Param some_id body web.Pet true "Some ID" +// @Success 200 {string} string "ok" +// @Failure 400 {object} web.APIError "We need ID!!" +// @Failure 404 {object} web.APIError "Can not find ID" +// @Router /testapi/get-string-by-int/{some_id} [get] +func GetStringByInt(c *gin.Context) { + //write your code +} + +// @Description get struct array by ID +// @ID get-struct-array-by-string +// @Accept json +// @Produce json +// @Param some_id path string true "Some ID" +// @Param category query int true "Category" Enums(1, 2, 3) +// @Param offset query int true "Offset" Mininum(0) default(0) +// @Param limit query int true "Limit" Maxinum(50) default(10) +// @Param q query string true "q" Minlength(1) Maxlength(50) default("") +// @Success 200 {string} string "ok" +// @Failure 400 {object} web.APIError "We need ID!!" +// @Failure 404 {object} web.APIError "Can not find ID" +// @Security ApiKeyAuth +// @Security BasicAuth +// @Security OAuth2Application[write] +// @Security OAuth2Implicit[read, admin] +// @Security OAuth2AccessCode[read] +// @Security OAuth2Password[admin] +// @Router /testapi/get-struct-array-by-string/{some_id} [get] +func GetStructArrayByString(c *gin.Context) { + //write your code +} + +// @Summary Upload file +// @Description Upload file +// @ID file.upload +// @Accept multipart/form-data +// @Produce json +// @Param file formData file true "this is a test file" +// @Success 200 {string} string "ok" +// @Failure 400 {object} web.APIError "We need ID!!" +// @Failure 401 {array} string +// @Failure 404 {object} web.APIError "Can not find ID" +// @Router /file/upload [post] +func Upload(ctx *gin.Context) { + //write your code +} + +// @Summary use Anonymous field +// @Success 200 {object} web.RevValue "ok" +func AnonymousField() { + +} + +// @Summary use pet2 +// @Success 200 {object} web.Pet2 "ok" +func Pet2() { + +} + +// @Summary Use IndirectRecursiveTest +// @Success 200 {object} web.IndirectRecursiveTest +func IndirectRecursiveTest() { +} + +// @Summary Use Tags +// @Success 200 {object} web.Tags +func Tags() { +} + +// @Summary Use CrossAlias +// @Success 200 {object} web.CrossAlias +func CrossAlias() { +} + +// @Summary Use AnonymousStructArray +// @Success 200 {object} web.AnonymousStructArray +func AnonymousStructArray() { +} + +type Pet3 struct { + ID int `json:"id"` +} + +// @Success 200 {object} web.Pet5a "ok" +func GetPet5a() { + +} + +// @Success 200 {object} web.Pet5b "ok" +func GetPet5b() { + +} + +// @Success 200 {object} web.Pet5c "ok" +func GetPet5c() { + +} + +type SwagReturn []map[string]string + +// @Success 200 {object} api.SwagReturn "ok" +func GetPet6MapString() { + +} diff --git a/testdata/simple_cgo/cross/test.go b/testdata/simple_cgo/cross/test.go new file mode 100644 index 000000000..540e53f4f --- /dev/null +++ b/testdata/simple_cgo/cross/test.go @@ -0,0 +1,6 @@ +package cross + +type Cross struct { + Array []string + String string +} diff --git a/testdata/simple_cgo/web/handler.go b/testdata/simple_cgo/web/handler.go new file mode 100644 index 000000000..ece1628dc --- /dev/null +++ b/testdata/simple_cgo/web/handler.go @@ -0,0 +1,97 @@ +package web + +import ( + "time" + + "github.com/satori/go.uuid" + "github.com/shopspring/decimal" + "github.com/swaggo/swag/testdata/simple/cross" +) + +type Pet struct { + ID int `json:"id" example:"1" format:"int64" readonly:"true"` + Category struct { + ID int `json:"id" example:"1"` + Name string `json:"name" example:"category_name"` + PhotoUrls []string `json:"photo_urls" example:"http://test/image/1.jpg,http://test/image/2.jpg" format:"url"` + SmallCategory struct { + ID int `json:"id" example:"1"` + Name string `json:"name" example:"detail_category_name" binding:"required" minLength:"4" maxLength:"16"` + PhotoUrls []string `json:"photo_urls" example:"http://test/image/1.jpg,http://test/image/2.jpg"` + } `json:"small_category"` + } `json:"category"` + Name string `json:"name" example:"poti" binding:"required"` + PhotoUrls []string `json:"photo_urls" example:"http://test/image/1.jpg,http://test/image/2.jpg" binding:"required"` + Tags []Tag `json:"tags"` + Pets *[]Pet2 `json:"pets"` + Pets2 []*Pet2 `json:"pets2"` + Status string `json:"status" enums:"healthy,ill"` + Price float32 `json:"price" example:"3.25" minimum:"1.0" maximum:"1000"` + IsAlive bool `json:"is_alive" example:"true" default:"true"` + Data interface{} `json:"data"` + Hidden string `json:"-"` + UUID uuid.UUID `json:"uuid"` + Decimal decimal.Decimal `json:"decimal"` + IntArray []int `json:"int_array" example:"1,2"` + EnumArray []int `json:"enum_array" enums:"1,2,3,5,7"` +} + +type Tag struct { + ID int `json:"id" format:"int64"` + Name string `json:"name"` + Pets []Pet `json:"pets"` +} + +type Tags []*Tag + +type AnonymousStructArray []struct { + Foo string `json:"foo"` +} + +type CrossAlias cross.Cross + +type Pet2 struct { + ID int `json:"id"` + MiddleName *string `json:"middlename" extensions:"x-nullable,x-abc=def"` + DeletedAt *time.Time `json:"deleted_at"` +} + +type IndirectRecursiveTest struct { + Tags []Tag +} + +type APIError struct { + ErrorCode int + ErrorMessage string + CreatedAt time.Time +} + +type RevValueBase struct { + Status bool `json:"Status"` + + Err int32 `json:"Err,omitempty"` +} +type RevValue struct { + RevValueBase `json:"rev_value_base"` + + Data int `json:"Data"` + Cross cross.Cross `json:"cross"` + Crosses []cross.Cross `json:"crosses"` +} + +// Below we have Pet5b as base type and Pet5a and Pet5c both have Pet5b as anonymous field, inheriting it's properties +// By using these names we ensure that our test will fill if the order of parsing matters at all + +type Pet5a struct { + *Pet5b + Odd bool `json:"odd" binding:"required"` +} + +type Pet5b struct { + Name string `json:"name" binding:"required"` +} + +type Pet5c struct { + *Pet5b + Odd bool `json:"odd" binding:"required"` +} From e21ff5ddc1bf20f49c9ad0fdf5aa02c7a7b4468a Mon Sep 17 00:00:00 2001 From: Damien Date: Thu, 16 Apr 2020 16:25:41 +1100 Subject: [PATCH 67/84] test: use good api reference --- testdata/simple_cgo/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testdata/simple_cgo/main.go b/testdata/simple_cgo/main.go index 63deacc33..13b15c02d 100644 --- a/testdata/simple_cgo/main.go +++ b/testdata/simple_cgo/main.go @@ -11,7 +11,7 @@ import "C" import ( "github.com/gin-gonic/gin" - "github.com/swaggo/swag/testdata/simple/api" + "github.com/swaggo/swag/testdata/simple_cgo/api" ) // @title Swagger Example API From 5b6f1b79ceda701f5526a89b2165648567e3d386 Mon Sep 17 00:00:00 2001 From: Damien Date: Thu, 16 Apr 2020 18:10:38 +1100 Subject: [PATCH 68/84] simplified cgo test --- testdata/simple_cgo/api/api.go | 103 +---------------------------- testdata/simple_cgo/cross/test.go | 6 -- testdata/simple_cgo/main.go | 2 - testdata/simple_cgo/web/handler.go | 97 --------------------------- 4 files changed, 3 insertions(+), 205 deletions(-) delete mode 100644 testdata/simple_cgo/cross/test.go delete mode 100644 testdata/simple_cgo/web/handler.go diff --git a/testdata/simple_cgo/api/api.go b/testdata/simple_cgo/api/api.go index ba6734d71..9de5c6ca8 100644 --- a/testdata/simple_cgo/api/api.go +++ b/testdata/simple_cgo/api/api.go @@ -10,107 +10,10 @@ import ( // @Accept json // @Produce json // @Param some_id path int true "Some ID" Format(int64) -// @Param some_id body web.Pet true "Some ID" +// @Param some_id body int true "Some ID" // @Success 200 {string} string "ok" -// @Failure 400 {object} web.APIError "We need ID!!" -// @Failure 404 {object} web.APIError "Can not find ID" +// @Failure 400 {object} string "We need ID!!" +// @Failure 404 {object} string "Can not find ID" // @Router /testapi/get-string-by-int/{some_id} [get] func GetStringByInt(c *gin.Context) { - //write your code -} - -// @Description get struct array by ID -// @ID get-struct-array-by-string -// @Accept json -// @Produce json -// @Param some_id path string true "Some ID" -// @Param category query int true "Category" Enums(1, 2, 3) -// @Param offset query int true "Offset" Mininum(0) default(0) -// @Param limit query int true "Limit" Maxinum(50) default(10) -// @Param q query string true "q" Minlength(1) Maxlength(50) default("") -// @Success 200 {string} string "ok" -// @Failure 400 {object} web.APIError "We need ID!!" -// @Failure 404 {object} web.APIError "Can not find ID" -// @Security ApiKeyAuth -// @Security BasicAuth -// @Security OAuth2Application[write] -// @Security OAuth2Implicit[read, admin] -// @Security OAuth2AccessCode[read] -// @Security OAuth2Password[admin] -// @Router /testapi/get-struct-array-by-string/{some_id} [get] -func GetStructArrayByString(c *gin.Context) { - //write your code -} - -// @Summary Upload file -// @Description Upload file -// @ID file.upload -// @Accept multipart/form-data -// @Produce json -// @Param file formData file true "this is a test file" -// @Success 200 {string} string "ok" -// @Failure 400 {object} web.APIError "We need ID!!" -// @Failure 401 {array} string -// @Failure 404 {object} web.APIError "Can not find ID" -// @Router /file/upload [post] -func Upload(ctx *gin.Context) { - //write your code -} - -// @Summary use Anonymous field -// @Success 200 {object} web.RevValue "ok" -func AnonymousField() { - -} - -// @Summary use pet2 -// @Success 200 {object} web.Pet2 "ok" -func Pet2() { - -} - -// @Summary Use IndirectRecursiveTest -// @Success 200 {object} web.IndirectRecursiveTest -func IndirectRecursiveTest() { -} - -// @Summary Use Tags -// @Success 200 {object} web.Tags -func Tags() { -} - -// @Summary Use CrossAlias -// @Success 200 {object} web.CrossAlias -func CrossAlias() { -} - -// @Summary Use AnonymousStructArray -// @Success 200 {object} web.AnonymousStructArray -func AnonymousStructArray() { -} - -type Pet3 struct { - ID int `json:"id"` -} - -// @Success 200 {object} web.Pet5a "ok" -func GetPet5a() { - -} - -// @Success 200 {object} web.Pet5b "ok" -func GetPet5b() { - -} - -// @Success 200 {object} web.Pet5c "ok" -func GetPet5c() { - -} - -type SwagReturn []map[string]string - -// @Success 200 {object} api.SwagReturn "ok" -func GetPet6MapString() { - } diff --git a/testdata/simple_cgo/cross/test.go b/testdata/simple_cgo/cross/test.go deleted file mode 100644 index 540e53f4f..000000000 --- a/testdata/simple_cgo/cross/test.go +++ /dev/null @@ -1,6 +0,0 @@ -package cross - -type Cross struct { - Array []string - String string -} diff --git a/testdata/simple_cgo/main.go b/testdata/simple_cgo/main.go index 13b15c02d..3368deb68 100644 --- a/testdata/simple_cgo/main.go +++ b/testdata/simple_cgo/main.go @@ -60,7 +60,5 @@ func main() { r := gin.New() r.GET("/testapi/get-string-by-int/:some_id", api.GetStringByInt) - r.GET("/testapi/get-struct-array-by-string/:some_id", api.GetStructArrayByString) - r.POST("/testapi/upload", api.Upload) r.Run() } diff --git a/testdata/simple_cgo/web/handler.go b/testdata/simple_cgo/web/handler.go deleted file mode 100644 index ece1628dc..000000000 --- a/testdata/simple_cgo/web/handler.go +++ /dev/null @@ -1,97 +0,0 @@ -package web - -import ( - "time" - - "github.com/satori/go.uuid" - "github.com/shopspring/decimal" - "github.com/swaggo/swag/testdata/simple/cross" -) - -type Pet struct { - ID int `json:"id" example:"1" format:"int64" readonly:"true"` - Category struct { - ID int `json:"id" example:"1"` - Name string `json:"name" example:"category_name"` - PhotoUrls []string `json:"photo_urls" example:"http://test/image/1.jpg,http://test/image/2.jpg" format:"url"` - SmallCategory struct { - ID int `json:"id" example:"1"` - Name string `json:"name" example:"detail_category_name" binding:"required" minLength:"4" maxLength:"16"` - PhotoUrls []string `json:"photo_urls" example:"http://test/image/1.jpg,http://test/image/2.jpg"` - } `json:"small_category"` - } `json:"category"` - Name string `json:"name" example:"poti" binding:"required"` - PhotoUrls []string `json:"photo_urls" example:"http://test/image/1.jpg,http://test/image/2.jpg" binding:"required"` - Tags []Tag `json:"tags"` - Pets *[]Pet2 `json:"pets"` - Pets2 []*Pet2 `json:"pets2"` - Status string `json:"status" enums:"healthy,ill"` - Price float32 `json:"price" example:"3.25" minimum:"1.0" maximum:"1000"` - IsAlive bool `json:"is_alive" example:"true" default:"true"` - Data interface{} `json:"data"` - Hidden string `json:"-"` - UUID uuid.UUID `json:"uuid"` - Decimal decimal.Decimal `json:"decimal"` - IntArray []int `json:"int_array" example:"1,2"` - EnumArray []int `json:"enum_array" enums:"1,2,3,5,7"` -} - -type Tag struct { - ID int `json:"id" format:"int64"` - Name string `json:"name"` - Pets []Pet `json:"pets"` -} - -type Tags []*Tag - -type AnonymousStructArray []struct { - Foo string `json:"foo"` -} - -type CrossAlias cross.Cross - -type Pet2 struct { - ID int `json:"id"` - MiddleName *string `json:"middlename" extensions:"x-nullable,x-abc=def"` - DeletedAt *time.Time `json:"deleted_at"` -} - -type IndirectRecursiveTest struct { - Tags []Tag -} - -type APIError struct { - ErrorCode int - ErrorMessage string - CreatedAt time.Time -} - -type RevValueBase struct { - Status bool `json:"Status"` - - Err int32 `json:"Err,omitempty"` -} -type RevValue struct { - RevValueBase `json:"rev_value_base"` - - Data int `json:"Data"` - Cross cross.Cross `json:"cross"` - Crosses []cross.Cross `json:"crosses"` -} - -// Below we have Pet5b as base type and Pet5a and Pet5c both have Pet5b as anonymous field, inheriting it's properties -// By using these names we ensure that our test will fill if the order of parsing matters at all - -type Pet5a struct { - *Pet5b - Odd bool `json:"odd" binding:"required"` -} - -type Pet5b struct { - Name string `json:"name" binding:"required"` -} - -type Pet5c struct { - *Pet5b - Odd bool `json:"odd" binding:"required"` -} From 719cfd8cfce1792ec79a6ca3617e74a6aaad7572 Mon Sep 17 00:00:00 2001 From: Breezewish Date: Mon, 20 Apr 2020 11:19:58 +0800 Subject: [PATCH 69/84] Sort query parameters generated from object --- operation.go | 9 ++++++++- parser_test.go | 39 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/operation.go b/operation.go index dfc529c1f..af3f530f6 100644 --- a/operation.go +++ b/operation.go @@ -9,6 +9,7 @@ import ( "net/http" "os" "regexp" + "sort" "strconv" "strings" @@ -208,7 +209,13 @@ func (operation *Operation) ParseParamComment(commentLine string, astFile *ast.F } return false } - for name, prop := range schema.Properties { + orderedNames := make([]string, 0, len(schema.Properties)) + for k, _ := range schema.Properties { + orderedNames = append(orderedNames, k) + } + sort.Strings(orderedNames) + for _, name := range orderedNames { + prop := schema.Properties[name] if len(prop.Type) == 0 { continue } diff --git a/parser_test.go b/parser_test.go index 1bf371bee..717e5aef8 100644 --- a/parser_test.go +++ b/parser_test.go @@ -3056,6 +3056,42 @@ func Fun() { } ` + expected := `{ + "info": { + "contact": {}, + "license": {} + }, + "paths": { + "/test": { + "get": { + "parameters": [ + { + "type": "integer", + "name": "age", + "in": "query" + }, + { + "type": "string", + "name": "name", + "in": "query" + }, + { + "type": "array", + "items": { + "type": "string" + }, + "name": "teachers", + "in": "query" + } + ], + "responses": { + "200": {} + } + } + } + } +}` + f, err := goparser.ParseFile(token.NewFileSet(), "", src, goparser.ParseComments) assert.NoError(t, err) @@ -3064,7 +3100,8 @@ func Fun() { err = p.ParseRouterAPIInfo("", f) assert.NoError(t, err) - assert.Equal(t, 3, len(p.swagger.Paths.Paths["/test"].Get.Parameters)) + b, _ := json.MarshalIndent(p.swagger, "", " ") + assert.Equal(t, expected, string(b)) } func TestParseRenamedStructDefinition(t *testing.T) { From 593555a5b1fe97720797fe93d41cf28fe8f27cf5 Mon Sep 17 00:00:00 2001 From: sdghchj Date: Mon, 20 Apr 2020 21:09:56 +0800 Subject: [PATCH 70/84] Overriding deep-level fields in response object (#672) * overriding deep fields in response * format * fix * support map in response; fix test. Co-authored-by: Bogdan U Co-authored-by: Eason Lin --- README.md | 17 +- operation.go | 242 ++++++++++++---------- operation_test.go | 155 ++++++++++++++ parser_test.go | 30 +-- testdata/non_exported_json_fields/main.go | 4 +- 5 files changed, 307 insertions(+), 141 deletions(-) diff --git a/README.md b/README.md index 4fb00cf6a..a36b1015f 100644 --- a/README.md +++ b/README.md @@ -510,13 +510,14 @@ type Account struct { ```go type JSONResult struct { - Code int `json:"code" ` - Message string `json:"message"` - Data interface{} `json:"data"` + Code int `json:"code" ` + Message string `json:"message"` + Data interface{} `json:"data"` } type Order struct { //in `proto` package - ... + Id uint `json:"id"` + Data interface{} `json:"data"` } ``` @@ -531,7 +532,13 @@ type Order struct { //in `proto` package ```go @success 200 {object} jsonresult.JSONResult{data1=string,data2=[]string,data3=proto.Order,data4=[]proto.Order} "desc" ``` - +- overriding deep-level fields +```go +type DeepObject struct { //in `proto` package + ... +} +@success 200 {object} jsonresult.JSONResult{data1=proto.Order{data=proto.DeepObject},data2=[]proto.Order{data=[]proto.DeepObject}} "desc" +``` ### Add a headers in response ```go diff --git a/operation.go b/operation.go index dfc529c1f..d7f82129c 100644 --- a/operation.go +++ b/operation.go @@ -632,54 +632,152 @@ func findTypeDef(importPath, typeName string) (*ast.TypeSpec, error) { var responsePattern = regexp.MustCompile(`([\d]+)[\s]+([\w\{\}]+)[\s]+([\w\-\.\/\{\}=,\[\]]+)[^"]*(.*)?`) -type nestedField struct { - Name string - Type string - IsArray bool - Ref spec.Ref -} +//RepsonseType{data1=Type1,data2=Type2} +var combinedPattern = regexp.MustCompile(`^([\w\-\.\/\[\]]+)\{(.*)\}$`) -func (nested *nestedField) getSchema() *spec.Schema { - if IsPrimitiveType(nested.Type) { - return &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{nested.Type}}} +func (operation *Operation) parseResponseObjectSchema(refType string, astFile *ast.File) (*spec.Schema, error) { + switch { + case refType == "interface{}": + return &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{"object"}}}, nil + case IsGolangPrimitiveType(refType): + refType = TransToValidSchemeType(refType) + return &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{refType}}}, nil + case IsPrimitiveType(refType): + return &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{refType}}}, nil + case strings.HasPrefix(refType, "[]"): + schema, err := operation.parseResponseObjectSchema(refType[2:], astFile) + if err != nil { + return nil, err + } + return &spec.Schema{SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{Schema: schema}}, + }, nil + case strings.HasPrefix(refType, "map["): + //ignore key type + idx := strings.Index(refType, "]") + if idx < 0 { + return nil, fmt.Errorf("invalid type: %s", refType) + } + refType = refType[idx+1:] + var valueSchema spec.SchemaOrBool + if refType == "interface{}" { + valueSchema.Allows = true + } else { + schema, err := operation.parseResponseObjectSchema(refType, astFile) + if err != nil { + return &spec.Schema{}, err + } + valueSchema.Schema = schema + } + return &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + AdditionalProperties: &valueSchema, + }, + }, nil + case strings.Contains(refType, "{"): + return operation.parseResponseCombinedObjectSchema(refType, astFile) + default: + if operation.parser != nil { // checking refType has existing in 'TypeDefinitions' + refNewType, typeSpec, err := operation.registerSchemaType(refType, astFile) + if err != nil { + return nil, err + } + refType = TypeDocName(refNewType, typeSpec) + } + return &spec.Schema{SchemaProps: spec.SchemaProps{Ref: spec.Ref{ + Ref: jsonreference.MustCreateRef("#/definitions/" + refType), + }}}, nil } - - return &spec.Schema{SchemaProps: spec.SchemaProps{Ref: nested.Ref}} } -//RepsonseType{data1=Type1,data2=Type2} -var nestedPattern = regexp.MustCompile(`^([\w\-\.\/]+)\{(.*)\}$`) - -func (operation *Operation) tryExtractNestedFields(specStr string, astFile *ast.File) (refType string, nestedFields []*nestedField, err error) { - matches := nestedPattern.FindStringSubmatch(specStr) +func (operation *Operation) parseResponseCombinedObjectSchema(refType string, astFile *ast.File) (*spec.Schema, error) { + matches := combinedPattern.FindStringSubmatch(refType) if len(matches) != 3 { - return specStr, nil, nil + return nil, fmt.Errorf("invalid type: %s", refType) } refType = matches[1] - fields := strings.Split(matches[2], ",") - for _, field := range fields { - if matches := strings.Split(field, "="); len(matches) == 2 { - nested := &nestedField{Name: matches[0], Type: matches[1], IsArray: strings.HasPrefix(matches[1], "[]")} - if nested.IsArray { - nested.Type = nested.Type[2:] + schema, err := operation.parseResponseObjectSchema(refType, astFile) + if err != nil { + return nil, err + } + + parseFields := func(s string) []string { + n := 0 + return strings.FieldsFunc(s, func(r rune) bool { + if r == '{' { + n++ + return false + } else if r == '}' { + n-- + return false } - nested.Type = TransToValidSchemeType(nested.Type) - if !IsPrimitiveType(nested.Type) { - if operation.parser != nil { // checking refType has existing in 'TypeDefinitions' - refType, typeSpec, err := operation.registerSchemaType(nested.Type, astFile) - if err != nil { - return specStr, nil, err - } + return r == ',' && n == 0 + }) + } - nested.Ref = spec.Ref{ - Ref: jsonreference.MustCreateRef("#/definitions/" + TypeDocName(refType, typeSpec)), - } + fields := parseFields(matches[2]) + props := map[string]spec.Schema{} + for _, field := range fields { + if matches := strings.SplitN(field, "=", 2); len(matches) == 2 { + if strings.HasPrefix(matches[1], "[]") { + itemSchema, err := operation.parseResponseObjectSchema(matches[1][2:], astFile) + if err != nil { + return nil, err + } + props[matches[0]] = spec.Schema{SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{Schema: itemSchema}}, + } + } else { + schema, err := operation.parseResponseObjectSchema(matches[1], astFile) + if err != nil { + return nil, err } + props[matches[0]] = *schema } - nestedFields = append(nestedFields, nested) } } - return + + if len(props) == 0 { + return schema, nil + } + return &spec.Schema{ + SchemaProps: spec.SchemaProps{ + AllOf: []spec.Schema{ + *schema, + { + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: props, + }, + }, + }, + }, + }, nil +} + +func (operation *Operation) parseResponseSchema(schemaType, refType string, astFile *ast.File) (*spec.Schema, error) { + switch schemaType { + case "object": + if !strings.HasPrefix(refType, "[]") { + return operation.parseResponseObjectSchema(refType, astFile) + } + refType = refType[2:] + fallthrough + case "array": + schema, err := operation.parseResponseObjectSchema(refType, astFile) + if err != nil { + return nil, err + } + return &spec.Schema{SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{Schema: schema}}, + }, nil + default: + return &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{schemaType}}}, nil + } } // ParseResponseComment parses comment for given `response` comment string. @@ -694,87 +792,20 @@ func (operation *Operation) ParseResponseComment(commentLine string, astFile *as return err } - response := spec.Response{} - code, _ := strconv.Atoi(matches[1]) responseDescription := strings.Trim(matches[4], "\"") if responseDescription == "" { responseDescription = http.StatusText(code) } - response.Description = responseDescription schemaType := strings.Trim(matches[2], "{}") refType := matches[3] - - refType, nestedFields, err := operation.tryExtractNestedFields(refType, astFile) + schema, err := operation.parseResponseSchema(schemaType, refType, astFile) if err != nil { return err } - var typeSpec *ast.TypeSpec - if !IsGolangPrimitiveType(refType) { - if operation.parser != nil { // checking refType has existing in 'TypeDefinitions' - var err error - if refType, typeSpec, err = operation.registerSchemaType(refType, astFile); err != nil { - return err - } - } - } - - // so we have to know all type in app - response.Schema = &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{schemaType}}} - - if schemaType == "object" { - response.Schema.SchemaProps = spec.SchemaProps{} - ref := spec.Ref{ - Ref: jsonreference.MustCreateRef("#/definitions/" + TypeDocName(refType, typeSpec)), - } - - if nestedFields == nil { - response.Schema.Ref = ref - } else { - props := make(map[string]spec.Schema) - for _, nested := range nestedFields { - if nested.IsArray { - props[nested.Name] = spec.Schema{SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, - Items: &spec.SchemaOrArray{Schema: nested.getSchema()}, - }} - } else { - props[nested.Name] = *nested.getSchema() - } - } - nestedSpec := spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: props, - }, - } - response.Schema.AllOf = []spec.Schema{{SchemaProps: spec.SchemaProps{Ref: ref}}, nestedSpec} - } - - } else if schemaType == "array" { - refType = TransToValidSchemeType(refType) - if IsPrimitiveType(refType) { - response.Schema.Items = &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: spec.StringOrArray{refType}, - }, - }, - } - } else { - response.Schema.Items = &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Ref: spec.Ref{Ref: jsonreference.MustCreateRef("#/definitions/" + TypeDocName(refType, typeSpec))}, - }, - }, - } - } - } - if operation.Responses == nil { operation.Responses = &spec.Responses{ ResponsesProps: spec.ResponsesProps{ @@ -783,8 +814,9 @@ func (operation *Operation) ParseResponseComment(commentLine string, astFile *as } } - operation.Responses.StatusCodeResponses[code] = response - + operation.Responses.StatusCodeResponses[code] = spec.Response{ + ResponseProps: spec.ResponseProps{Schema: schema, Description: responseDescription}, + } return nil } diff --git a/operation_test.go b/operation_test.go index c85cc5584..1367ecdb4 100644 --- a/operation_test.go +++ b/operation_test.go @@ -437,6 +437,161 @@ func TestParseResponseCommentWithNestedFields(t *testing.T) { assert.Equal(t, expected, string(b)) } +func TestParseResponseCommentWithDeepNestedFields(t *testing.T) { + comment := `@Success 200 {object} model.CommonHeader{data1=int,data2=[]int,data3=model.Payload{data1=int,data2=model.DeepPayload},data4=[]model.Payload{data1=[]int,data2=[]model.DeepPayload}} "Error message, if code != 200` + operation := NewOperation() + operation.parser = New() + + operation.parser.TypeDefinitions["model"] = make(map[string]*ast.TypeSpec) + operation.parser.TypeDefinitions["model"]["CommonHeader"] = &ast.TypeSpec{} + operation.parser.TypeDefinitions["model"]["Payload"] = &ast.TypeSpec{} + operation.parser.TypeDefinitions["model"]["DeepPayload"] = &ast.TypeSpec{} + + err := operation.ParseComment(comment, nil) + assert.NoError(t, err) + + response := operation.Responses.StatusCodeResponses[200] + assert.Equal(t, `Error message, if code != 200`, response.Description) + + b, _ := json.MarshalIndent(operation, "", " ") + expected := `{ + "responses": { + "200": { + "description": "Error message, if code != 200", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/model.CommonHeader" + }, + { + "type": "object", + "properties": { + "data1": { + "type": "integer" + }, + "data2": { + "type": "array", + "items": { + "type": "integer" + } + }, + "data3": { + "allOf": [ + { + "$ref": "#/definitions/model.Payload" + }, + { + "type": "object", + "properties": { + "data1": { + "type": "integer" + }, + "data2": { + "$ref": "#/definitions/model.DeepPayload" + } + } + } + ] + }, + "data4": { + "type": "array", + "items": { + "allOf": [ + { + "$ref": "#/definitions/model.Payload" + }, + { + "type": "object", + "properties": { + "data1": { + "type": "array", + "items": { + "type": "integer" + } + }, + "data2": { + "type": "array", + "items": { + "$ref": "#/definitions/model.DeepPayload" + } + } + } + } + ] + } + } + } + } + ] + } + } + } +}` + assert.Equal(t, expected, string(b)) +} + +func TestParseResponseCommentWithNestedArrayMapFields(t *testing.T) { + comment := `@Success 200 {object} []map[string]model.CommonHeader{data1=[]map[string]model.Payload,data2=map[string][]int} "Error message, if code != 200` + operation := NewOperation() + operation.parser = New() + + operation.parser.TypeDefinitions["model"] = make(map[string]*ast.TypeSpec) + operation.parser.TypeDefinitions["model"]["CommonHeader"] = &ast.TypeSpec{} + operation.parser.TypeDefinitions["model"]["Payload"] = &ast.TypeSpec{} + + err := operation.ParseComment(comment, nil) + assert.NoError(t, err) + + response := operation.Responses.StatusCodeResponses[200] + assert.Equal(t, `Error message, if code != 200`, response.Description) + + b, _ := json.MarshalIndent(operation, "", " ") + expected := `{ + "responses": { + "200": { + "description": "Error message, if code != 200", + "schema": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": { + "allOf": [ + { + "$ref": "#/definitions/model.CommonHeader" + }, + { + "type": "object", + "properties": { + "data1": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/model.Payload" + } + } + }, + "data2": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "integer" + } + } + } + } + } + ] + } + } + } + } + } +}` + assert.Equal(t, expected, string(b)) +} + func TestParseResponseCommentWithObjectTypeInSameFile(t *testing.T) { comment := `@Success 200 {object} testOwner "Error message, if code != 200"` operation := NewOperation() diff --git a/parser_test.go b/parser_test.go index 1bf371bee..0b9fcb337 100644 --- a/parser_test.go +++ b/parser_test.go @@ -1907,34 +1907,6 @@ func TestParseStructComment(t *testing.T) { "type": "integer" } } - }, - "web.Post": { - "type": "object", - "properties": { - "data": { - "description": "Post data", - "type": "object", - "properties": { - "name": { - "description": "Post tag", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "id": { - "type": "integer", - "format": "int64", - "example": 1 - }, - "name": { - "description": "Post name", - "type": "string", - "example": "poti" - } - } } } }` @@ -1974,7 +1946,7 @@ func TestParseNonExportedJSONFields(t *testing.T) { "200": { "description": "OK", "schema": { - "type": "string" + "$ref": "#/definitions/main.MyStruct" } } } diff --git a/testdata/non_exported_json_fields/main.go b/testdata/non_exported_json_fields/main.go index ed641c38d..9d59f7a81 100644 --- a/testdata/non_exported_json_fields/main.go +++ b/testdata/non_exported_json_fields/main.go @@ -26,7 +26,7 @@ type MyStruct struct { // @Description Does something, but internal (non-exported) fields inside a struct won't be marshaled into JSON // @Accept json // @Produce json -// @Success 200 {string} MyStruct +// @Success 200 {object} MyStruct // @Router /so-something [get] func DoSomething(c *gin.Context) { //write your code @@ -39,6 +39,6 @@ func DoSomething(c *gin.Context) { // @basePath /api func main() { r := gin.New() - r.GET("/do-something", api.DoSomething) + r.GET("/do-something", DoSomething) r.Run() } From ca3ddd81f2c186b072be0a2478e892fbd875f395 Mon Sep 17 00:00:00 2001 From: sdghchj Date: Fri, 8 May 2020 16:54:32 +0800 Subject: [PATCH 71/84] code optimization --- parser.go | 42 ++++++++++-------------------------------- 1 file changed, 10 insertions(+), 32 deletions(-) diff --git a/parser.go b/parser.go index 7647a1bfd..dfa576fa6 100644 --- a/parser.go +++ b/parser.go @@ -846,17 +846,15 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st properties := map[string]spec.Schema{} if field.Names == nil { - fullTypeName, err := getFieldType(field.Type) + propertyName, err := getPropertyName(pkgName, field.Type, parser) if err != nil { return properties, []string{}, nil } - typeName := fullTypeName - - if splits := strings.Split(fullTypeName, "."); len(splits) > 1 { - pkgName = splits[0] - typeName = splits[1] + if propertyName.CrossPkg != "" { + pkgName = propertyName.CrossPkg } + typeName := propertyName.SchemaType typeSpec := parser.TypeDefinitions[pkgName][typeName] if typeSpec == nil { @@ -882,8 +880,12 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st switch schemaType { case "object": - for k, v := range schema.SchemaProps.Properties { - properties[k] = v + if len(schema.SchemaProps.Properties) > 0 { + for k, v := range schema.SchemaProps.Properties { + properties[k] = v + } + } else if schema.AdditionalProperties != nil { + properties[typeName] = *schema } case "array": properties[typeName] = *schema @@ -1131,30 +1133,6 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st return properties, nil, nil } -func getFieldType(field interface{}) (string, error) { - - switch ftype := field.(type) { - case *ast.Ident: - return ftype.Name, nil - - case *ast.SelectorExpr: - packageName, err := getFieldType(ftype.X) - if err != nil { - return "", err - } - return fmt.Sprintf("%s.%s", packageName, ftype.Sel.Name), nil - - case *ast.StarExpr: - fullName, err := getFieldType(ftype.X) - if err != nil { - return "", err - } - return fullName, nil - - } - return "", fmt.Errorf("unknown field type %#v", field) -} - func (parser *Parser) parseField(pkgName string, field *ast.Field) (*structField, error) { prop, err := getPropertyName(pkgName, field.Type, parser) if err != nil { From a7e01807cb5b9d7a56a74bddfa40dd0b969e4105 Mon Sep 17 00:00:00 2001 From: sdghchj Date: Fri, 8 May 2020 17:19:56 +0800 Subject: [PATCH 72/84] Update parser.go --- parser.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/parser.go b/parser.go index dfa576fa6..bb25fd6b6 100644 --- a/parser.go +++ b/parser.go @@ -880,12 +880,8 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st switch schemaType { case "object": - if len(schema.SchemaProps.Properties) > 0 { - for k, v := range schema.SchemaProps.Properties { - properties[k] = v - } - } else if schema.AdditionalProperties != nil { - properties[typeName] = *schema + for k, v := range schema.SchemaProps.Properties { + properties[k] = v } case "array": properties[typeName] = *schema From c9d332d95e443708da89d49d379890bdd3da4d82 Mon Sep 17 00:00:00 2001 From: sdghchj Date: Fri, 8 May 2020 17:33:51 +0800 Subject: [PATCH 73/84] Update parser.go --- parser.go | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/parser.go b/parser.go index bb25fd6b6..7647a1bfd 100644 --- a/parser.go +++ b/parser.go @@ -846,15 +846,17 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st properties := map[string]spec.Schema{} if field.Names == nil { - propertyName, err := getPropertyName(pkgName, field.Type, parser) + fullTypeName, err := getFieldType(field.Type) if err != nil { return properties, []string{}, nil } - if propertyName.CrossPkg != "" { - pkgName = propertyName.CrossPkg + typeName := fullTypeName + + if splits := strings.Split(fullTypeName, "."); len(splits) > 1 { + pkgName = splits[0] + typeName = splits[1] } - typeName := propertyName.SchemaType typeSpec := parser.TypeDefinitions[pkgName][typeName] if typeSpec == nil { @@ -1129,6 +1131,30 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st return properties, nil, nil } +func getFieldType(field interface{}) (string, error) { + + switch ftype := field.(type) { + case *ast.Ident: + return ftype.Name, nil + + case *ast.SelectorExpr: + packageName, err := getFieldType(ftype.X) + if err != nil { + return "", err + } + return fmt.Sprintf("%s.%s", packageName, ftype.Sel.Name), nil + + case *ast.StarExpr: + fullName, err := getFieldType(ftype.X) + if err != nil { + return "", err + } + return fullName, nil + + } + return "", fmt.Errorf("unknown field type %#v", field) +} + func (parser *Parser) parseField(pkgName string, field *ast.Field) (*structField, error) { prop, err := getPropertyName(pkgName, field.Type, parser) if err != nil { From 38f619b3079333cd52f744e8f403f0683d522f40 Mon Sep 17 00:00:00 2001 From: sugoi Date: Sat, 9 May 2020 17:17:23 +0800 Subject: [PATCH 74/84] Add Chinese translation of readme #695 --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index a36b1015f..880f9bbed 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # swag +🌍 *[English](README.md) ∙ [简体中文](README_zh-CN.md)* + [![Travis Status](https://img.shields.io/travis/swaggo/swag/master.svg)](https://travis-ci.org/swaggo/swag) From 0937fc43cfd46c91a6723228f86a50383e494bff Mon Sep 17 00:00:00 2001 From: sugoi Date: Sat, 9 May 2020 17:40:12 +0800 Subject: [PATCH 75/84] Add Chinese translation of readme #695 --- README_zh-CN.md | 739 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 739 insertions(+) create mode 100644 README_zh-CN.md diff --git a/README_zh-CN.md b/README_zh-CN.md new file mode 100644 index 000000000..1f5819b68 --- /dev/null +++ b/README_zh-CN.md @@ -0,0 +1,739 @@ +# swag + +🌍 *[English](README.md) ∙ [简体中文](README_zh-CN.md)* + + + +[![Travis Status](https://img.shields.io/travis/swaggo/swag/master.svg)](https://travis-ci.org/swaggo/swag) +[![Coverage Status](https://img.shields.io/codecov/c/github/swaggo/swag/master.svg)](https://codecov.io/gh/swaggo/swag) +[![Go Report Card](https://goreportcard.com/badge/github.com/swaggo/swag)](https://goreportcard.com/report/github.com/swaggo/swag) +[![codebeat badge](https://codebeat.co/badges/71e2f5e5-9e6b-405d-baf9-7cc8b5037330)](https://codebeat.co/projects/github-com-swaggo-swag-master) +[![Go Doc](https://godoc.org/github.com/swaggo/swagg?status.svg)](https://godoc.org/github.com/swaggo/swag) +[![Backers on Open Collective](https://opencollective.com/swag/backers/badge.svg)](#backers) +[![Sponsors on Open Collective](https://opencollective.com/swag/sponsors/badge.svg)](#sponsors) [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fswaggo%2Fswag.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fswaggo%2Fswag?ref=badge_shield) +[![Release](https://img.shields.io/github/release/swaggo/swag.svg?style=flat-square)](https://github.com/swaggo/swag/releases) + +Swag将Go的注释转换为Swagger2.0文档。我们为流行的 [Go Web Framework](#支持的Web框架) 创建了各种插件,这样可以与现有Go项目快速集成(使用Swagger UI)。 + +## 目录 + +- [快速开始](#快速开始) +- [支持的Web框架](#支持的web框架) +- [如何与Gin集成](#如何与gin集成) +- [开发现状](#开发现状) +- [声明式注释格式](#声明式注释格式) + - [通用API信息](#通用api信息) + - [API操作](#api操作) + - [安全性](#安全性) +- [样例](#样例) + - [多行的描述](#多行的描述) + - [用户自定义的具有数组类型的结构](#用户自定义的具有数组类型的结构) + - [响应中的模型组成](#响应中的模型组成) + - [在响应中增加头字段](#在响应中增加头字段) + - [使用多路径参数](#使用多路径参数) + - [结构体的示例值](#结构体的示例值) + - [结构体描述](#结构体描述) + - [在被支持的自定义类型中使用`swaggertype`标签](#在被支持的自定义类型中使用swaggertype标签) + - [使用`swaggerignore`标签排除字段](#使用swaggerignore标签排除字段) + - [将扩展信息添加到结构字段](#将扩展信息添加到结构字段) + - [对展示的模型重命名](#对展示的模型重命名) + - [如何使用安全性注释](#如何使用安全性注释) +- [项目相关](#项目相关) + +## 快速开始 + +1. 将注释添加到API源代码中,请参阅声明性注释格式。 +2. 使用如下命令下载swag: + +```bash +go get -u github.com/swaggo/swag/cmd/swag +``` + +从源码开始构建的话,需要有Go环境(1.9及以上版本)。 + +或者从github的release页面下载预编译好的二进制文件。 + +3. 在包含`main.go`文件的项目根目录运行`swag init`。这将会解析注释并生成需要的文件(`docs`文件夹和`docs/docs.go`)。 + +```bash +swag init +``` + +确保导入了生成的`docs/docs.go`文件,这样特定的配置文件才会被初始化。如果通用API指数没有写在`main.go`中,可以使用`-g`标识符来告知swag。 + +```bash +swag init -g http/api.go +``` + +## swag cli + +```bash +swag init -h +NAME: + swag init - Create docs.go + +USAGE: + swag init [command options] [arguments...] + +OPTIONS: + --generalInfo value, -g value Go file path in which 'swagger general API Info' is written (default: "main.go") + --dir value, -d value Directory you want to parse (default: "./") + --propertyStrategy value, -p value Property Naming Strategy like snakecase,camelcase,pascalcase (default: "camelcase") + --output value, -o value Output directory for all the generated files(swagger.json, swagger.yaml and doc.go) (default: "./docs") + --parseVendor Parse go files in 'vendor' folder, disabled by default + --parseDependency Parse go files in outside dependency folder, disabled by default + --markdownFiles value, --md value Parse folder containing markdown files to use as description, disabled by default + --generatedTime Generate timestamp at the top of docs.go, true by default +``` + +## 支持的Web框架 + +- [gin](http://github.com/swaggo/gin-swagger) +- [echo](http://github.com/swaggo/echo-swagger) +- [buffalo](https://github.com/swaggo/buffalo-swagger) +- [net/http](https://github.com/swaggo/http-swagger) + +## 如何与Gin集成 + +[点击此处](https://github.com/swaggo/swag/tree/master/example/celler)查看示例源代码。 + +1. 使用`swag init`生成Swagger2.0文档后,导入如下代码包: + +```go +import "github.com/swaggo/gin-swagger" // gin-swagger middleware +import "github.com/swaggo/files" // swagger embed files +``` + +2. 在`main.go`源代码中添加通用的API注释: + +```bash +// @title Swagger Example API +// @version 1.0 +// @description This is a sample server celler server. +// @termsOfService http://swagger.io/terms/ + +// @contact.name API Support +// @contact.url http://www.swagger.io/support +// @contact.email support@swagger.io + +// @license.name Apache 2.0 +// @license.url http://www.apache.org/licenses/LICENSE-2.0.html + +// @host localhost:8080 +// @BasePath /api/v1 +// @query.collection.format multi + +// @securityDefinitions.basic BasicAuth + +// @securityDefinitions.apikey ApiKeyAuth +// @in header +// @name Authorization + +// @securitydefinitions.oauth2.application OAuth2Application +// @tokenUrl https://example.com/oauth/token +// @scope.write Grants write access +// @scope.admin Grants read and write access to administrative information + +// @securitydefinitions.oauth2.implicit OAuth2Implicit +// @authorizationurl https://example.com/oauth/authorize +// @scope.write Grants write access +// @scope.admin Grants read and write access to administrative information + +// @securitydefinitions.oauth2.password OAuth2Password +// @tokenUrl https://example.com/oauth/token +// @scope.read Grants read access +// @scope.write Grants write access +// @scope.admin Grants read and write access to administrative information + +// @securitydefinitions.oauth2.accessCode OAuth2AccessCode +// @tokenUrl https://example.com/oauth/token +// @authorizationurl https://example.com/oauth/authorize +// @scope.admin Grants read and write access to administrative information + +// @x-extension-openapi {"example": "value on a json format"} + +func main() { + r := gin.Default() + + c := controller.NewController() + + v1 := r.Group("/api/v1") + { + accounts := v1.Group("/accounts") + { + accounts.GET(":id", c.ShowAccount) + accounts.GET("", c.ListAccounts) + accounts.POST("", c.AddAccount) + accounts.DELETE(":id", c.DeleteAccount) + accounts.PATCH(":id", c.UpdateAccount) + accounts.POST(":id/images", c.UploadAccountImage) + } + //... + } + r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) + r.Run(":8080") +} +//... +``` + +此外,可以动态设置一些通用的API信息。生成的代码包`docs`到处`SwaggerInfo`变量,使用该变量可以通过编码的方式设置标题、描述、版本、主机和基础路径。使用Gin的示例: + +```go +package main + +import ( + "github.com/gin-gonic/gin" + "github.com/swaggo/files" + "github.com/swaggo/gin-swagger" + + "./docs" // docs is generated by Swag CLI, you have to import it. +) + +// @contact.name API Support +// @contact.url http://www.swagger.io/support +// @contact.email support@swagger.io + +// @license.name Apache 2.0 +// @license.url http://www.apache.org/licenses/LICENSE-2.0.html + +// @termsOfService http://swagger.io/terms/ + +func main() { + + // programatically set swagger info + docs.SwaggerInfo.Title = "Swagger Example API" + docs.SwaggerInfo.Description = "This is a sample server Petstore server." + docs.SwaggerInfo.Version = "1.0" + docs.SwaggerInfo.Host = "petstore.swagger.io" + docs.SwaggerInfo.BasePath = "/v2" + docs.SwaggerInfo.Schemes = []string{"http", "https"} + + r := gin.New() + + // use ginSwagger middleware to serve the API docs + r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) + + r.Run() +} +``` + +3. 在`controller`代码中添加API操作注释: + +```go +package controller + +import ( + "fmt" + "net/http" + "strconv" + + "github.com/gin-gonic/gin" + "github.com/swaggo/swag/example/celler/httputil" + "github.com/swaggo/swag/example/celler/model" +) + +// ShowAccount godoc +// @Summary Show a account +// @Description get string by ID +// @ID get-string-by-int +// @Accept json +// @Produce json +// @Param id path int true "Account ID" +// @Success 200 {object} model.Account +// @Header 200 {string} Token "qwerty" +// @Failure 400 {object} httputil.HTTPError +// @Failure 404 {object} httputil.HTTPError +// @Failure 500 {object} httputil.HTTPError +// @Router /accounts/{id} [get] +func (c *Controller) ShowAccount(ctx *gin.Context) { + id := ctx.Param("id") + aid, err := strconv.Atoi(id) + if err != nil { + httputil.NewError(ctx, http.StatusBadRequest, err) + return + } + account, err := model.AccountOne(aid) + if err != nil { + httputil.NewError(ctx, http.StatusNotFound, err) + return + } + ctx.JSON(http.StatusOK, account) +} + +// ListAccounts godoc +// @Summary List accounts +// @Description get accounts +// @Accept json +// @Produce json +// @Param q query string false "name search by q" +// @Success 200 {array} model.Account +// @Header 200 {string} Token "qwerty" +// @Failure 400 {object} httputil.HTTPError +// @Failure 404 {object} httputil.HTTPError +// @Failure 500 {object} httputil.HTTPError +// @Router /accounts [get] +func (c *Controller) ListAccounts(ctx *gin.Context) { + q := ctx.Request.URL.Query().Get("q") + accounts, err := model.AccountsAll(q) + if err != nil { + httputil.NewError(ctx, http.StatusNotFound, err) + return + } + ctx.JSON(http.StatusOK, accounts) +} + +//... +``` + +```bash +swag init +``` + +4. 运行程序,然后在浏览器中访问 http://localhost:8080/swagger/index.html。将看到Swagger 2.0 Api文档,如下所示: + +![swagger_index.html](https://raw.githubusercontent.com/swaggo/swag/master/assets/swagger-image.png) + +## 开发现状 + +[Swagger 2.0 文档](https://swagger.io/docs/specification/2-0/basic-structure/) + +- [x] Basic Structure +- [x] API Host and Base Path +- [x] Paths and Operations +- [x] Describing Parameters +- [x] Describing Request Body +- [x] Describing Responses +- [x] MIME Types +- [x] Authentication + - [x] Basic Authentication + - [x] API Keys +- [x] Adding Examples +- [x] File Upload +- [x] Enums +- [x] Grouping Operations With Tags +- [ ] Swagger Extensions + +## 声明式注释格式 + +## 通用API信息 + +**示例** [`celler/main.go`](https://github.com/swaggo/swag/blob/master/example/celler/main.go) + +| 注释 | 说明 | 示例 | +| ----------------------- | ----------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | +| title | **必填** 应用程序的名称。 | // @title Swagger Example API | +| version | **必填** 提供应用程序API的版本。 | // @version 1.0 | +| description | 应用程序的简短描述。 | // @description This is a sample server celler server. | +| tag.name | 标签的名称。 | // @tag.name This is the name of the tag | +| tag.description | 标签的描述。 | // @tag.description Cool Description | +| tag.docs.url | 标签的外部文档的URL。 | // @tag.docs.url https://example.com | +| tag.docs.description | 标签的外部文档说明。 | // @tag.docs.description Best example documentation | +| termsOfService | API的服务条款。 | // @termsOfService http://swagger.io/terms/ | +| contact.name | 公开的API的联系信息。 | // @contact.name API Support | +| contact.url | 联系信息的URL。 必须采用网址格式。 | // @contact.url http://www.swagger.io/support | +| contact.email | 联系人/组织的电子邮件地址。 必须采用电子邮件地址的格式。 | // @contact.email support@swagger.io | +| license.name | **必填** 用于API的许可证名称。 | // @license.name Apache 2.0 | +| license.url | 用于API的许可证的URL。 必须采用网址格式。 | // @license.url http://www.apache.org/licenses/LICENSE-2.0.html | +| host | 运行API的主机(主机名或IP地址)。 | // @host localhost:8080 | +| BasePath | 运行API的基本路径。 | // @BasePath /api/v1 | +| query.collection.format | 查询或枚举中的默认集合(数组)参数格式:csv,multi,pipes,tsv,ssv。 如果未设置,则默认为csv。 | // @query.collection.format multi | +| schemes | 用空格分隔的请求的传输协议。 | // @schemes http https | +| x-name | 扩展的键必须以x-开头,并且只能使用json值 | // @x-example-key {"key": "value"} | + +### 使用Markdown描述 + +如果文档中的短字符串不足以完整表达,或者需要展示图片,代码示例等类似的内容,则可能需要使用Markdown描述。要使用Markdown描述,请使用一下注释。 + +| 注释 | 说明 | 示例 | +| ------------------------ | ------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------- | +| title | **必填** 应用程序的名称。 | // @title Swagger Example API | +| version | **必填** 提供应用程序API的版本。 | // @version 1.0 | +| description.markdown | 应用程序的简短描述。 从`api.md`文件中解析。 这是`@description`的替代用法。 | // @description.markdown No value needed, this parses the description from api.md | +| tag.name | 标签的名称。 | // @tag.name This is the name of the tag | +| tag.description.markdown | 标签说明,这是`tag.description`的替代用法。 该描述将从名为`tagname.md的`文件中读取。 | // @tag.description.markdown | + +## API操作 + +Example [celler/controller](https://github.com/swaggo/swag/tree/master/example/celler/controller) + +| 注释 | 描述 | | +| -------------------- | ------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | +| description | 操作行为的详细说明。 | +| description.markdown | 应用程序的简短描述。该描述将从名为`endpointname.md`的文件中读取。 | // @description.file endpoint.description.markdown | +| id | 用于标识操作的唯一字符串。在所有API操作中必须唯一。 | +| tags | 每个API操作的标签列表,以逗号分隔。 | +| summary | 该操作的简短摘要。 | +| accept | API可以使用的MIME类型的列表。值必须如“[Mime类型](#mime-types)”中所述。 | +| produce | API可以生成的MIME类型的列表。值必须如“[Mime类型](#mime-types)”中所述。 | +| param | 用空格分隔的参数。`param name`,`param type`,`data type`,`is mandatory?`,`comment` `attribute(optional)` | +| security | 每个API操作的[安全性](#security)。 | +| success | 以空格分隔的成功响应。`return code`,`{param type}`,`data type`,`comment` | +| failure | 以空格分隔的故障响应。`return code`,`{param type}`,`data type`,`comment` | +| header | 以空格分隔的头字段。 `return code`,`{param type}`,`data type`,`comment` | +| router | 以空格分隔的路径定义。 `path`,`[httpMethod]` | +| x-name | 扩展字段必须以`x-`开头,并且只能使用json值。 | + +## Mime类型 + +`swag` g接受所有格式正确的MIME类型, 即使匹配 `*/*`。除此之外,`swag`还接受某些MIME类型的别名,如下所示: + +| Alias | MIME Type | +| --------------------- | --------------------------------- | +| json | application/json | +| xml | text/xml | +| plain | text/plain | +| html | text/html | +| mpfd | multipart/form-data | +| x-www-form-urlencoded | application/x-www-form-urlencoded | +| json-api | application/vnd.api+json | +| json-stream | application/x-json-stream | +| octet-stream | application/octet-stream | +| png | image/png | +| jpeg | image/jpeg | +| gif | image/gif | + +## 参数类型 + +- query +- path +- header +- body +- formData + +## 数据类型 + +- string (string) +- integer (int, uint, uint32, uint64) +- number (float32) +- boolean (bool) +- user defined struct + +## 安全性 + +| 注释 | 描述 | 参数 | 示例 | +| -------------------------------------- | --------------------------------------------------------------------------------------------- | --------------------------------- | ------------------------------------------------------------ | +| securitydefinitions.basic | [Basic](https://swagger.io/docs/specification/2-0/authentication/basic-authentication/) auth. | | // @securityDefinitions.basic BasicAuth | +| securitydefinitions.apikey | [API key](https://swagger.io/docs/specification/2-0/authentication/api-keys/) auth. | in, name | // @securityDefinitions.apikey ApiKeyAuth | +| securitydefinitions.oauth2.application | [OAuth2 application](https://swagger.io/docs/specification/authentication/oauth2/) auth. | tokenUrl, scope | // @securitydefinitions.oauth2.application OAuth2Application | +| securitydefinitions.oauth2.implicit | [OAuth2 implicit](https://swagger.io/docs/specification/authentication/oauth2/) auth. | authorizationUrl, scope | // @securitydefinitions.oauth2.implicit OAuth2Implicit | +| securitydefinitions.oauth2.password | [OAuth2 password](https://swagger.io/docs/specification/authentication/oauth2/) auth. | tokenUrl, scope | // @securitydefinitions.oauth2.password OAuth2Password | +| securitydefinitions.oauth2.accessCode | [OAuth2 access code](https://swagger.io/docs/specification/authentication/oauth2/) auth. | tokenUrl, authorizationUrl, scope | // @securitydefinitions.oauth2.accessCode OAuth2AccessCode | + +| 参数注释 | 示例 | +| ---------------- | -------------------------------------------------------- | +| in | // @in header | +| name | // @name Authorization | +| tokenUrl | // @tokenUrl https://example.com/oauth/token | +| authorizationurl | // @authorizationurl https://example.com/oauth/authorize | +| scope.hoge | // @scope.write Grants write access | + +## 属性 + +```go +// @Param enumstring query string false "string enums" Enums(A, B, C) +// @Param enumint query int false "int enums" Enums(1, 2, 3) +// @Param enumnumber query number false "int enums" Enums(1.1, 1.2, 1.3) +// @Param string query string false "string valid" minlength(5) maxlength(10) +// @Param int query int false "int valid" mininum(1) maxinum(10) +// @Param default query string false "string default" default(A) +// @Param collection query []string false "string collection" collectionFormat(multi) +``` + +也适用于结构体字段: + +```go +type Foo struct { + Bar string `minLength:"4" maxLength:"16"` + Baz int `minimum:"10" maximum:"20" default:"15"` + Qux []string `enums:"foo,bar,baz"` +} +``` + +### 当前可用的 + +| 字段名 | 类型 | 描述 | +| ---------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| default | * | 声明如果未提供任何参数,则服务器将使用的默认参数值,例如,如果请求中的客户端未提供该参数,则用于控制每页结果数的“计数”可能默认为100。 (注意:“default”对于必需的参数没有意义)。参看 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-6.2。 与JSON模式不同,此值务必符合此参数的定义[类型](#parameterType)。 | +| maximum | `number` | 参看 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.2. | +| minimum | `number` | 参看 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.3. | +| maxLength | `integer` | 参看 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.1. | +| minLength | `integer` | 参看 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.2. | +| enums | [\*] | 参看 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.5.1. | +| format | `string` | 上面提到的[类型](#parameterType)的扩展格式。有关更多详细信息,请参见[数据类型格式](https://swagger.io/specification/v2/#dataTypeFormat)。 | +| collectionFormat | `string` | 如果使用类型数组,则确定数组的格式。 可能的值为:
  • `csv` - 逗号分隔值 `foo,bar`.
  • `ssv` - 空格分隔值 `foo bar`.
  • `tsv` - 制表符分隔值 `foo\tbar`.
  • `pipes` - 管道符分隔值 foo|bar.
  • `multi` - 对应于多个参数实例,而不是单个实例 `foo=bar&foo=baz` 的多个值。这仅对“`query`”或“`formData`”中的参数有效。
默认值是 `csv`。 | + +### 进一步的 + +| 字段名 | 类型 | 描述 | +| ----------- | :-------: | ---------------------------------------------------------------------------------- | +| multipleOf | `number` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.1. | +| pattern | `string` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.3. | +| maxItems | `integer` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.3.2. | +| minItems | `integer` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.3.3. | +| uniqueItems | `boolean` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.3.4. | + +## 样例 + +### 多行的描述 + +可以在常规api描述或路由定义中添加跨越多行的描述,如下所示: + +```go +// @description This is the first line +// @description This is the second line +// @description And so forth. +``` + +### 用户自定义的具有数组类型的结构 + +```go +// @Success 200 {array} model.Account <-- This is a user defined struct. +``` + +```go +package model + +type Account struct { + ID int `json:"id" example:"1"` + Name string `json:"name" example:"account name"` +} +``` + +### 响应中的模型组成 + +```go +// JSONResult's data field will be overridden by the specific type proto.Order +@success 200 {object} jsonresult.JSONResult{data=proto.Order} "desc" +``` + +```go +type JSONResult struct { + Code int `json:"code" ` + Message string `json:"message"` + Data interface{} `json:"data"` +} + +type Order struct { //in `proto` package + ... +} +``` + +- 还支持对象数组和原始类型作为嵌套响应 + +```go +@success 200 {object} jsonresult.JSONResult{data=[]proto.Order} "desc" +@success 200 {object} jsonresult.JSONResult{data=string} "desc" +@success 200 {object} jsonresult.JSONResult{data=[]string} "desc" +``` + +- 覆盖多个字段。如果不存在,将添加字段。 + +```go +@success 200 {object} jsonresult.JSONResult{data1=string,data2=[]string,data3=proto.Order,data4=[]proto.Order} "desc" +``` + +### 在响应中增加头字段 + +```go +// @Success 200 {string} string "ok" +// @Header 200 {string} Location "/entity/1" +// @Header 200 {string} Token "qwerty" +``` + +### 使用多路径参数 + +```go +/// ... +// @Param group_id path int true "Group ID" +// @Param account_id path int true "Account ID" +// ... +// @Router /examples/groups/{group_id}/accounts/{account_id} [get] +``` + +### 结构体的示例值 + +```go +type Account struct { + ID int `json:"id" example:"1"` + Name string `json:"name" example:"account name"` + PhotoUrls []string `json:"photo_urls" example:"http://test/image/1.jpg,http://test/image/2.jpg"` +} +``` + +### 结构体描述 + +```go +type Account struct { + // ID this is userid + ID int `json:"id"` + Name string `json:"name"` // This is Name +} +``` + +### 在被支持的自定义类型中使用`swaggertype`标签 + +[#201](https://github.com/swaggo/swag/issues/201#issuecomment-475479409) + +```go +type TimestampTime struct { + time.Time +} + +///implement encoding.JSON.Marshaler interface +func (t *TimestampTime) MarshalJSON() ([]byte, error) { + bin := make([]byte, 16) + bin = strconv.AppendInt(bin[:0], t.Time.Unix(), 10) + return bin, nil +} + +func (t *TimestampTime) UnmarshalJSON(bin []byte) error { + v, err := strconv.ParseInt(string(bin), 10, 64) + if err != nil { + return err + } + t.Time = time.Unix(v, 0) + return nil +} +/// + +type Account struct { + // Override primitive type by simply specifying it via `swaggertype` tag + ID sql.NullInt64 `json:"id" swaggertype:"integer"` + + // Override struct type to a primitive type 'integer' by specifying it via `swaggertype` tag + RegisterTime TimestampTime `json:"register_time" swaggertype:"primitive,integer"` + + // Array types can be overridden using "array," format + Coeffs []big.Float `json:"coeffs" swaggertype:"array,number"` +} +``` + +[#379](https://github.com/swaggo/swag/issues/379) + +```go +type CerticateKeyPair struct { + Crt []byte `json:"crt" swaggertype:"string" format:"base64" example:"U3dhZ2dlciByb2Nrcw=="` + Key []byte `json:"key" swaggertype:"string" format:"base64" example:"U3dhZ2dlciByb2Nrcw=="` +} +``` + +生成的swagger文档如下: + +```go +"api.MyBinding": { + "type":"object", + "properties":{ + "crt":{ + "type":"string", + "format":"base64", + "example":"U3dhZ2dlciByb2Nrcw==" + }, + "key":{ + "type":"string", + "format":"base64", + "example":"U3dhZ2dlciByb2Nrcw==" + } + } +} +``` + +### 使用`swaggerignore`标签排除字段 + +```go +type Account struct { + ID string `json:"id"` + Name string `json:"name"` + Ignored int `swaggerignore:"true"` +} +``` + +### 将扩展信息添加到结构字段 + +```go +type Account struct { + ID string `json:"id" extensions:"x-nullable,x-abc=def"` // extensions fields must start with "x-" +} +``` + +生成swagger文档,如下所示: + +```go +"Account": { + "type": "object", + "properties": { + "id": { + "type": "string", + "x-nullable": true, + "x-abc": "def" + } + } +} +``` + +### 对展示的模型重命名 + +```go +type Resp struct { + Code int +}//@name Response +``` + +### 如何使用安全性注释 + +通用API信息。 + +```go +// @securityDefinitions.basic BasicAuth + +// @securitydefinitions.oauth2.application OAuth2Application +// @tokenUrl https://example.com/oauth/token +// @scope.write Grants write access +// @scope.admin Grants read and write access to administrative information +``` + +每个API操作。 + +```go +// @Security ApiKeyAuth +``` + +使用AND条件。 + +```go +// @Security ApiKeyAuth +// @Security OAuth2Application[write, admin] +``` + +## 项目相关 + +This project was inspired by [yvasiyarov/swagger](https://github.com/yvasiyarov/swagger) but we simplified the usage and added support a variety of [web frameworks](#supported-web-frameworks). Gopher image source is [tenntenn/gopher-stickers](https://github.com/tenntenn/gopher-stickers). It has licenses [creative commons licensing](http://creativecommons.org/licenses/by/3.0/deed.en). + +## 贡献者 + +This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)]. + + +## 支持者 + +Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/swag#backer)] + + + +## 赞助商 + +Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/swag#sponsor)] + + + + + + + + + + + + +## License + +[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fswaggo%2Fswag.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fswaggo%2Fswag?ref=badge_large) From 366d3931701a9f6848bf76a9611348f08cf14f7a Mon Sep 17 00:00:00 2001 From: sdghchj Date: Sat, 9 May 2020 18:59:24 +0800 Subject: [PATCH 76/84] Update README_zh-CN.md --- README_zh-CN.md | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/README_zh-CN.md b/README_zh-CN.md index 1f5819b68..955ce6208 100644 --- a/README_zh-CN.md +++ b/README_zh-CN.md @@ -28,12 +28,12 @@ Swag将Go的注释转换为Swagger2.0文档。我们为流行的 [Go Web Framewo - [样例](#样例) - [多行的描述](#多行的描述) - [用户自定义的具有数组类型的结构](#用户自定义的具有数组类型的结构) - - [响应中的模型组成](#响应中的模型组成) + - [响应对象中的模型组合](#响应对象中的模型组合) - [在响应中增加头字段](#在响应中增加头字段) - [使用多路径参数](#使用多路径参数) - [结构体的示例值](#结构体的示例值) - [结构体描述](#结构体描述) - - [在被支持的自定义类型中使用`swaggertype`标签](#在被支持的自定义类型中使用swaggertype标签) + - [使用`swaggertype`标签更改字段类型](#使用`swaggertype`标签更改字段类型) - [使用`swaggerignore`标签排除字段](#使用swaggerignore标签排除字段) - [将扩展信息添加到结构字段](#将扩展信息添加到结构字段) - [对展示的模型重命名](#对展示的模型重命名) @@ -76,14 +76,14 @@ USAGE: swag init [command options] [arguments...] OPTIONS: - --generalInfo value, -g value Go file path in which 'swagger general API Info' is written (default: "main.go") - --dir value, -d value Directory you want to parse (default: "./") - --propertyStrategy value, -p value Property Naming Strategy like snakecase,camelcase,pascalcase (default: "camelcase") - --output value, -o value Output directory for all the generated files(swagger.json, swagger.yaml and doc.go) (default: "./docs") - --parseVendor Parse go files in 'vendor' folder, disabled by default - --parseDependency Parse go files in outside dependency folder, disabled by default - --markdownFiles value, --md value Parse folder containing markdown files to use as description, disabled by default - --generatedTime Generate timestamp at the top of docs.go, true by default + --generalInfo value, -g value API通用信息所在的go源文件路径,如果是相对路径则基于API解析目录 (默认: "main.go") + --dir value, -d value API解析目录 (默认: "./") + --propertyStrategy value, -p value 结构体字段命名规则,三种:snakecase,camelcase,pascalcase (默认: "camelcase") + --output value, -o value 文件(swagger.json, swagger.yaml and doc.go)输出目录 (默认: "./docs") + --parseVendor 是否解析vendor目录里的go源文件,默认不 + --parseDependency 是否解析依赖目录中的go源文件,默认不 + --markdownFiles value, --md value 指定API的描述信息所使用的markdown文件所在的目录 + --generatedTime 是否输出时间到输出文件docs.go的顶部,默认是 ``` ## 支持的Web框架 @@ -336,7 +336,7 @@ swag init | license.url | 用于API的许可证的URL。 必须采用网址格式。 | // @license.url http://www.apache.org/licenses/LICENSE-2.0.html | | host | 运行API的主机(主机名或IP地址)。 | // @host localhost:8080 | | BasePath | 运行API的基本路径。 | // @BasePath /api/v1 | -| query.collection.format | 查询或枚举中的默认集合(数组)参数格式:csv,multi,pipes,tsv,ssv。 如果未设置,则默认为csv。 | // @query.collection.format multi | +| query.collection.format | 请求URI query里数组参数的默认格式:csv,multi,pipes,tsv,ssv。 如果未设置,则默认为csv。 | // @query.collection.format multi | | schemes | 用空格分隔的请求的传输协议。 | // @schemes http https | | x-name | 扩展的键必须以x-开头,并且只能使用json值 | // @x-example-key {"key": "value"} | @@ -460,7 +460,7 @@ type Foo struct { | minLength | `integer` | 参看 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.2. | | enums | [\*] | 参看 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.5.1. | | format | `string` | 上面提到的[类型](#parameterType)的扩展格式。有关更多详细信息,请参见[数据类型格式](https://swagger.io/specification/v2/#dataTypeFormat)。 | -| collectionFormat | `string` | 如果使用类型数组,则确定数组的格式。 可能的值为:
  • `csv` - 逗号分隔值 `foo,bar`.
  • `ssv` - 空格分隔值 `foo bar`.
  • `tsv` - 制表符分隔值 `foo\tbar`.
  • `pipes` - 管道符分隔值 foo|bar.
  • `multi` - 对应于多个参数实例,而不是单个实例 `foo=bar&foo=baz` 的多个值。这仅对“`query`”或“`formData`”中的参数有效。
默认值是 `csv`。 | +| collectionFormat | `string` | 指定query数组参数的格式。 可能的值为:
  • `csv` - 逗号分隔值 `foo,bar`.
  • `ssv` - 空格分隔值 `foo bar`.
  • `tsv` - 制表符分隔值 `foo\tbar`.
  • `pipes` - 管道符分隔值 foo|bar.
  • `multi` - 对应于多个参数实例,而不是单个实例 `foo=bar&foo=baz` 的多个值。这仅对“`query`”或“`formData`”中的参数有效。
默认值是 `csv`。 | ### 进一步的 @@ -499,10 +499,10 @@ type Account struct { } ``` -### 响应中的模型组成 +### 响应对象中的模型组合 ```go -// JSONResult's data field will be overridden by the specific type proto.Order +// JSONResult的data字段类型将被proto.Order类型替换 @success 200 {object} jsonresult.JSONResult{data=proto.Order} "desc" ``` @@ -526,7 +526,7 @@ type Order struct { //in `proto` package @success 200 {object} jsonresult.JSONResult{data=[]string} "desc" ``` -- 覆盖多个字段。如果不存在,将添加字段。 +- 替换多个字段的类型。如果某字段不存在,将添加该字段。 ```go @success 200 {object} jsonresult.JSONResult{data1=string,data2=[]string,data3=proto.Order,data4=[]proto.Order} "desc" @@ -570,7 +570,7 @@ type Account struct { } ``` -### 在被支持的自定义类型中使用`swaggertype`标签 +### 使用`swaggertype`标签更改字段类型 [#201](https://github.com/swaggo/swag/issues/201#issuecomment-475479409) @@ -579,13 +579,14 @@ type TimestampTime struct { time.Time } -///implement encoding.JSON.Marshaler interface +///实现encoding.JSON.Marshaler接口 func (t *TimestampTime) MarshalJSON() ([]byte, error) { bin := make([]byte, 16) bin = strconv.AppendInt(bin[:0], t.Time.Unix(), 10) return bin, nil } +///实现encoding.JSON.Unmarshaler接口 func (t *TimestampTime) UnmarshalJSON(bin []byte) error { v, err := strconv.ParseInt(string(bin), 10, 64) if err != nil { @@ -597,10 +598,10 @@ func (t *TimestampTime) UnmarshalJSON(bin []byte) error { /// type Account struct { - // Override primitive type by simply specifying it via `swaggertype` tag + // 使用`swaggertype`标签将别名类型更改为内置类型integer ID sql.NullInt64 `json:"id" swaggertype:"integer"` - // Override struct type to a primitive type 'integer' by specifying it via `swaggertype` tag + // 使用`swaggertype`标签更改struct类型为内置类型integer RegisterTime TimestampTime `json:"register_time" swaggertype:"primitive,integer"` // Array types can be overridden using "array," format @@ -651,7 +652,7 @@ type Account struct { ```go type Account struct { - ID string `json:"id" extensions:"x-nullable,x-abc=def"` // extensions fields must start with "x-" + ID string `json:"id" extensions:"x-nullable,x-abc=def"` // 扩展字段必须以"x-"开头 } ``` From c0c2ff81fde1d28ac79ed310c576aa69cb149ec2 Mon Sep 17 00:00:00 2001 From: Turing Zhu Date: Mon, 18 May 2020 09:24:32 +0800 Subject: [PATCH 77/84] Correct translation --- README_zh-CN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README_zh-CN.md b/README_zh-CN.md index 955ce6208..d14f49851 100644 --- a/README_zh-CN.md +++ b/README_zh-CN.md @@ -176,7 +176,7 @@ func main() { //... ``` -此外,可以动态设置一些通用的API信息。生成的代码包`docs`到处`SwaggerInfo`变量,使用该变量可以通过编码的方式设置标题、描述、版本、主机和基础路径。使用Gin的示例: +此外,可以动态设置一些通用的API信息。生成的代码包`docs`导出`SwaggerInfo`变量,使用该变量可以通过编码的方式设置标题、描述、版本、主机和基础路径。使用Gin的示例: ```go package main From 450806293c4e98eec1fc6f85a422a87e152d451c Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Sun, 24 May 2020 12:43:28 +0800 Subject: [PATCH 78/84] chore: drop go1.11&1.12 ci (#710) --- .github/workflows/ci.yml | 4 ++-- .travis.yml | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4abd3fc28..ef9d51a7e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,7 +3,7 @@ jobs: test: strategy: matrix: - go: [ '1.11.x', '1.12.x' ] + go: [ '1.13.x', '1.14.x' ] platform: [ubuntu-latest, macos-latest] runs-on: ${{ matrix.platform }} steps: @@ -37,4 +37,4 @@ jobs: working-directory: ./src/github.com/${{ github.repository }} run: make test env: - GOPATH: ${{ runner.workspace }} \ No newline at end of file + GOPATH: ${{ runner.workspace }} diff --git a/.travis.yml b/.travis.yml index d2ac67355..36a51fd72 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,6 @@ language: go go: - - 1.11.x - - 1.12.x - 1.13.x - 1.14.x From d8a6d4a2e86e5554cb4f805ee1fc9a08e0d5bdb7 Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Sun, 24 May 2020 21:46:41 +0800 Subject: [PATCH 79/84] chore: jump to v1.6.6 (#704) --- version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.go b/version.go index 60c01d518..a1e481131 100644 --- a/version.go +++ b/version.go @@ -1,4 +1,4 @@ package swag // Version of swag -const Version = "v1.6.5" +const Version = "v1.6.6" From 271cf8fec2c14e365fcb013c465e23a75881ca58 Mon Sep 17 00:00:00 2001 From: easonlin404 Date: Sun, 24 May 2020 22:19:40 +0800 Subject: [PATCH 80/84] fix: short aliase flag is not working --- cmd/swag/main.go | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/cmd/swag/main.go b/cmd/swag/main.go index 5ff7d79f3..a6c595186 100644 --- a/cmd/swag/main.go +++ b/cmd/swag/main.go @@ -24,28 +24,32 @@ const ( var initFlags = []cli.Flag{ &cli.StringFlag{ - Name: generalInfoFlag + ", g", - Value: "main.go", - Usage: "Go file path in which 'swagger general API Info' is written", + Name: generalInfoFlag, + Aliases: []string{"g"}, + Value: "main.go", + Usage: "Go file path in which 'swagger general API Info' is written", }, &cli.StringFlag{ - Name: searchDirFlag + ", d", - Value: "./", - Usage: "Directory you want to parse", + Name: searchDirFlag, + Aliases: []string{"d"}, + Value: "./", + Usage: "Directory you want to parse", }, &cli.StringFlag{ Name: excludeFlag, Usage: "exclude directories and files when searching, comma separated", }, &cli.StringFlag{ - Name: propertyStrategyFlag + ", p", - Value: "camelcase", - Usage: "Property Naming Strategy like snakecase,camelcase,pascalcase", + Name: propertyStrategyFlag, + Aliases: []string{"p"}, + Value: "camelcase", + Usage: "Property Naming Strategy like snakecase,camelcase,pascalcase", }, &cli.StringFlag{ - Name: outputFlag + ", o", - Value: "./docs", - Usage: "Output directory for all the generated files(swagger.json, swagger.yaml and doc.go)", + Name: outputFlag, + Aliases: []string{"o"}, + Value: "./docs", + Usage: "Output directory for all the generated files(swagger.json, swagger.yaml and doc.go)", }, &cli.BoolFlag{ Name: parseVendorFlag, @@ -56,9 +60,10 @@ var initFlags = []cli.Flag{ Usage: "Parse go files in outside dependency folder, disabled by default", }, &cli.StringFlag{ - Name: markdownFilesFlag + ", md", - Value: "", - Usage: "Parse folder containing markdown files to use as description, disabled by default", + Name: markdownFilesFlag, + Aliases: []string{"md"}, + Value: "", + Usage: "Parse folder containing markdown files to use as description, disabled by default", }, &cli.BoolFlag{ Name: "generatedTime", From 3f543760b5f5c7e305d91a771095653431379a65 Mon Sep 17 00:00:00 2001 From: Hongzhou Chen Date: Mon, 25 May 2020 13:32:13 +0800 Subject: [PATCH 81/84] Override deep-level fields in request body object --- operation.go | 43 ++++++--------------- operation_test.go | 97 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 31 deletions(-) diff --git a/operation.go b/operation.go index d7f82129c..32f2d0218 100644 --- a/operation.go +++ b/operation.go @@ -253,33 +253,14 @@ func (operation *Operation) ParseParamComment(commentLine string, astFile *ast.F case "primitive": param.Schema.Type = spec.StringOrArray{refType} case "array": - param.Schema.Type = spec.StringOrArray{objectType} - param.Schema.Items = &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{}, - }, - } - // Array of Primitive or Object - if IsPrimitiveType(refType) { - param.Schema.Items.Schema.Type = spec.StringOrArray{refType} - } else { - refType, typeSpec, err := operation.registerSchemaType(refType, astFile) - if err != nil { - return err - } - param.Schema.Items.Schema.Ref = spec.Ref{ - Ref: jsonreference.MustCreateRef("#/definitions/" + TypeDocName(refType, typeSpec)), - } - } + refType = "[]" + refType + fallthrough case "object": - refType, typeSpec, err := operation.registerSchemaType(refType, astFile) + schema, err := operation.parseObjectSchema(refType, astFile) if err != nil { return err } - param.Schema.Type = []string{} - param.Schema.Ref = spec.Ref{ - Ref: jsonreference.MustCreateRef("#/definitions/" + TypeDocName(refType, typeSpec)), - } + param.Schema = schema } default: return fmt.Errorf("%s is not supported paramType", paramType) @@ -635,7 +616,7 @@ var responsePattern = regexp.MustCompile(`([\d]+)[\s]+([\w\{\}]+)[\s]+([\w\-\.\/ //RepsonseType{data1=Type1,data2=Type2} var combinedPattern = regexp.MustCompile(`^([\w\-\.\/\[\]]+)\{(.*)\}$`) -func (operation *Operation) parseResponseObjectSchema(refType string, astFile *ast.File) (*spec.Schema, error) { +func (operation *Operation) parseObjectSchema(refType string, astFile *ast.File) (*spec.Schema, error) { switch { case refType == "interface{}": return &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{"object"}}}, nil @@ -645,7 +626,7 @@ func (operation *Operation) parseResponseObjectSchema(refType string, astFile *a case IsPrimitiveType(refType): return &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{refType}}}, nil case strings.HasPrefix(refType, "[]"): - schema, err := operation.parseResponseObjectSchema(refType[2:], astFile) + schema, err := operation.parseObjectSchema(refType[2:], astFile) if err != nil { return nil, err } @@ -664,7 +645,7 @@ func (operation *Operation) parseResponseObjectSchema(refType string, astFile *a if refType == "interface{}" { valueSchema.Allows = true } else { - schema, err := operation.parseResponseObjectSchema(refType, astFile) + schema, err := operation.parseObjectSchema(refType, astFile) if err != nil { return &spec.Schema{}, err } @@ -698,7 +679,7 @@ func (operation *Operation) parseResponseCombinedObjectSchema(refType string, as return nil, fmt.Errorf("invalid type: %s", refType) } refType = matches[1] - schema, err := operation.parseResponseObjectSchema(refType, astFile) + schema, err := operation.parseObjectSchema(refType, astFile) if err != nil { return nil, err } @@ -722,7 +703,7 @@ func (operation *Operation) parseResponseCombinedObjectSchema(refType string, as for _, field := range fields { if matches := strings.SplitN(field, "=", 2); len(matches) == 2 { if strings.HasPrefix(matches[1], "[]") { - itemSchema, err := operation.parseResponseObjectSchema(matches[1][2:], astFile) + itemSchema, err := operation.parseObjectSchema(matches[1][2:], astFile) if err != nil { return nil, err } @@ -731,7 +712,7 @@ func (operation *Operation) parseResponseCombinedObjectSchema(refType string, as Items: &spec.SchemaOrArray{Schema: itemSchema}}, } } else { - schema, err := operation.parseResponseObjectSchema(matches[1], astFile) + schema, err := operation.parseObjectSchema(matches[1], astFile) if err != nil { return nil, err } @@ -762,12 +743,12 @@ func (operation *Operation) parseResponseSchema(schemaType, refType string, astF switch schemaType { case "object": if !strings.HasPrefix(refType, "[]") { - return operation.parseResponseObjectSchema(refType, astFile) + return operation.parseObjectSchema(refType, astFile) } refType = refType[2:] fallthrough case "array": - schema, err := operation.parseResponseObjectSchema(refType, astFile) + schema, err := operation.parseObjectSchema(refType, astFile) if err != nil { return nil, err } diff --git a/operation_test.go b/operation_test.go index 1367ecdb4..29f3a2c6c 100644 --- a/operation_test.go +++ b/operation_test.go @@ -938,6 +938,53 @@ func TestParseParamCommentByBodyType(t *testing.T) { assert.Equal(t, expected, string(b)) } +func TestParseParamCommentByBodyTypeWithDeepNestedFields(t *testing.T) { + comment := `@Param body body model.CommonHeader{data=string,data2=int} true "test deep"` + operation := NewOperation() + operation.parser = New() + + operation.parser.TypeDefinitions["model"] = make(map[string]*ast.TypeSpec) + operation.parser.TypeDefinitions["model"]["CommonHeader"] = &ast.TypeSpec{} + + err := operation.ParseComment(comment, nil) + assert.NoError(t, err) + assert.Len(t, operation.Parameters, 1) + assert.Equal(t, "test deep", operation.Parameters[0].Description) + assert.True(t, operation.Parameters[0].Required) + + b, err := json.MarshalIndent(operation, "", " ") + assert.NoError(t, err) + expected := `{ + "parameters": [ + { + "description": "test deep", + "name": "body", + "in": "body", + "required": true, + "schema": { + "allOf": [ + { + "$ref": "#/definitions/model.CommonHeader" + }, + { + "type": "object", + "properties": { + "data": { + "type": "string" + }, + "data2": { + "type": "integer" + } + } + } + ] + } + } + ] +}` + assert.Equal(t, expected, string(b)) +} + func TestParseParamCommentByBodyTypeArrayOfPrimitiveGo(t *testing.T) { comment := `@Param some_id body []int true "Some ID"` operation := NewOperation() @@ -965,6 +1012,56 @@ func TestParseParamCommentByBodyTypeArrayOfPrimitiveGo(t *testing.T) { assert.Equal(t, expected, string(b)) } +func TestParseParamCommentByBodyTypeArrayOfPrimitiveGoWithDeepNestedFields(t *testing.T) { + comment := `@Param body body []model.CommonHeader{data=string,data2=int} true "test deep"` + operation := NewOperation() + operation.parser = New() + + operation.parser.TypeDefinitions["model"] = make(map[string]*ast.TypeSpec) + operation.parser.TypeDefinitions["model"]["CommonHeader"] = &ast.TypeSpec{} + + err := operation.ParseComment(comment, nil) + assert.NoError(t, err) + assert.Len(t, operation.Parameters, 1) + assert.Equal(t, "test deep", operation.Parameters[0].Description) + assert.True(t, operation.Parameters[0].Required) + + b, err := json.MarshalIndent(operation, "", " ") + assert.NoError(t, err) + expected := `{ + "parameters": [ + { + "description": "test deep", + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "array", + "items": { + "allOf": [ + { + "$ref": "#/definitions/model.CommonHeader" + }, + { + "type": "object", + "properties": { + "data": { + "type": "string" + }, + "data2": { + "type": "integer" + } + } + } + ] + } + } + } + ] +}` + assert.Equal(t, expected, string(b)) +} + func TestParseParamCommentByBodyTypeErr(t *testing.T) { comment := `@Param some_id body model.OrderRow true "Some ID"` operation := NewOperation() From 51088c1da1ca7d779fbf1006181a9fff4d68d710 Mon Sep 17 00:00:00 2001 From: Scott Dowding <62896133+sdowding-koho@users.noreply.github.com> Date: Thu, 28 May 2020 22:56:52 -0600 Subject: [PATCH 82/84] chore(README): Add required docs to README.md (#703) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 880f9bbed..71932bf3c 100644 --- a/README.md +++ b/README.md @@ -458,6 +458,7 @@ type Foo struct { Field Name | Type | Description ---|:---:|--- +validate | `string` | Determines the validation for the parameter. Possible values are: `required`. default | * | Declares the value of the parameter that the server will use if none is provided, for example a "count" to control the number of results per page might default to 100 if not supplied by the client in the request. (Note: "default" has no meaning for required parameters.) See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-6.2. Unlike JSON Schema this value MUST conform to the defined [`type`](#parameterType) for this parameter. maximum | `number` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.2. minimum | `number` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.3. From 16c21c4daa9721311c8cdc1f04e759e54ff27751 Mon Sep 17 00:00:00 2001 From: sdghchj Date: Fri, 29 May 2020 14:15:34 +0800 Subject: [PATCH 83/84] fix code checking error and fix #717 fix code checking error and fix #717 --- operation.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/operation.go b/operation.go index cf7c11583..6c47d6fa5 100644 --- a/operation.go +++ b/operation.go @@ -210,7 +210,7 @@ func (operation *Operation) ParseParamComment(commentLine string, astFile *ast.F return false } orderedNames := make([]string, 0, len(schema.Properties)) - for k, _ := range schema.Properties { + for k = range schema.Properties { orderedNames = append(orderedNames, k) } sort.Strings(orderedNames) @@ -239,6 +239,11 @@ func (operation *Operation) ParseParamComment(commentLine string, astFile *ast.F Println(fmt.Sprintf("skip field [%s] in %s is not supported type for %s", name, refType, paramType)) continue } + param.Nullable = prop.Nullable + param.Format = prop.Format + param.Default = prop.Default + param.Example = prop.Example + param.Extensions = prop.Extensions param.CommonValidations.Maximum = prop.Maximum param.CommonValidations.Minimum = prop.Minimum param.CommonValidations.ExclusiveMaximum = prop.ExclusiveMaximum From c03e213acd1f3d85a304ccb71d2de6bf76ce572c Mon Sep 17 00:00:00 2001 From: sdghchj Date: Fri, 29 May 2020 14:21:10 +0800 Subject: [PATCH 84/84] fix code checking --- operation.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/operation.go b/operation.go index 399732dc4..4450bf697 100644 --- a/operation.go +++ b/operation.go @@ -210,7 +210,7 @@ func (operation *Operation) ParseParamComment(commentLine string, astFile *ast.F return false } orderedNames := make([]string, 0, len(schema.Properties)) - for k = range schema.Properties { + for k := range schema.Properties { orderedNames = append(orderedNames, k) } sort.Strings(orderedNames)