From 76031216231b1c6a0869f15c944394a3013767bb Mon Sep 17 00:00:00 2001 From: Ivan Volkov Date: Mon, 18 Dec 2023 11:46:16 +0300 Subject: [PATCH] Add flag state #1628 (#1629) * add state flag --- README.md | 1 + cmd/swag/main.go | 7 + gen/gen.go | 35 ++- gen/gen_test.go | 78 ++++++ operation.go | 8 + parser.go | 16 ++ testdata/state/admin_expected.json | 396 +++++++++++++++++++++++++++++ testdata/state/api/api.go | 76 ++++++ testdata/state/api/api_user.go | 76 ++++++ testdata/state/main.go | 38 +++ testdata/state/user_expected.json | 396 +++++++++++++++++++++++++++++ testdata/state/web/handler.go | 64 +++++ 12 files changed, 1186 insertions(+), 5 deletions(-) create mode 100644 testdata/state/admin_expected.json create mode 100644 testdata/state/api/api.go create mode 100644 testdata/state/api/api_user.go create mode 100644 testdata/state/main.go create mode 100644 testdata/state/user_expected.json create mode 100644 testdata/state/web/handler.go diff --git a/README.md b/README.md index 802016240..2cbce944c 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,7 @@ OPTIONS: --tags value, -t value A comma-separated list of tags to filter the APIs for which the documentation is generated.Special case if the tag is prefixed with the '!' character then the APIs with that tag will be excluded --templateDelims value, --td value Provide custom delimeters for Go template generation. The format is leftDelim,rightDelim. For example: "[[,]]" --collectionFormat value, --cf value Set default collection format (default: "csv") + --state value Initial state for the state machine (default: ""), @HostState in root file, @State in other files --help, -h show help (default: false) ``` diff --git a/cmd/swag/main.go b/cmd/swag/main.go index 1d55b7ff6..bebeb6cb8 100644 --- a/cmd/swag/main.go +++ b/cmd/swag/main.go @@ -40,6 +40,7 @@ const ( packageName = "packageName" collectionFormatFlag = "collectionFormat" packagePrefixFlag = "packagePrefix" + stateFlag = "state" ) var initFlags = []cli.Flag{ @@ -173,6 +174,11 @@ var initFlags = []cli.Flag{ Value: "", Usage: "Parse only packages whose import path match the given prefix, comma separated", }, + &cli.StringFlag{ + Name: stateFlag, + Value: "", + Usage: "Set host state for swagger.json", + }, } func initAction(ctx *cli.Context) error { @@ -242,6 +248,7 @@ func initAction(ctx *cli.Context) error { Debugger: logger, CollectionFormat: collectionFormat, PackagePrefix: ctx.String(packagePrefixFlag), + State: ctx.String(stateFlag), }) } diff --git a/gen/gen.go b/gen/gen.go index 5cbd942a6..43cf73ed8 100644 --- a/gen/gen.go +++ b/gen/gen.go @@ -17,6 +17,8 @@ import ( "github.com/go-openapi/spec" "github.com/swaggo/swag" + "golang.org/x/text/cases" + "golang.org/x/text/language" "sigs.k8s.io/yaml" ) @@ -141,6 +143,9 @@ type Config struct { // Parse only packages whose import path match the given prefix, comma separated PackagePrefix string + + // State set host state + State string } // Build builds swagger json file for given searchDir and mainAPIFile. Returns json. @@ -207,6 +212,7 @@ func (g *Gen) Build(config *Config) error { p.ParseVendor = config.ParseVendor p.ParseInternal = config.ParseInternal p.RequiredByDefault = config.RequiredByDefault + p.HostState = config.State if err := p.ParseAPIMultiSearchDir(searchDirs, config.MainAPIFile, config.ParseDepth); err != nil { return err @@ -235,6 +241,10 @@ func (g *Gen) Build(config *Config) error { func (g *Gen) writeDocSwagger(config *Config, swagger *spec.Swagger) error { var filename = "docs.go" + if config.State != "" { + filename = config.State + "_" + filename + } + if config.InstanceName != swag.Name { filename = config.InstanceName + "_" + filename } @@ -274,6 +284,10 @@ func (g *Gen) writeDocSwagger(config *Config, swagger *spec.Swagger) error { func (g *Gen) writeJSONSwagger(config *Config, swagger *spec.Swagger) error { var filename = "swagger.json" + if config.State != "" { + filename = config.State + "_" + filename + } + if config.InstanceName != swag.Name { filename = config.InstanceName + "_" + filename } @@ -298,6 +312,10 @@ func (g *Gen) writeJSONSwagger(config *Config, swagger *spec.Swagger) error { func (g *Gen) writeYAMLSwagger(config *Config, swagger *spec.Swagger) error { var filename = "swagger.yaml" + if config.State != "" { + filename = config.State + "_" + filename + } + if config.InstanceName != swag.Name { filename = config.InstanceName + "_" + filename } @@ -441,6 +459,11 @@ func (g *Gen) writeGoDoc(packageName string, output io.Writer, swagger *spec.Swa return err } + state := "" + if len(config.State) > 0 { + state = cases.Title(language.English).String(strings.ToLower(config.State)) + } + buffer := &bytes.Buffer{} err = generator.Execute(buffer, struct { @@ -452,6 +475,7 @@ func (g *Gen) writeGoDoc(packageName string, output io.Writer, swagger *spec.Swa Title string Description string Version string + State string InstanceName string Schemes []string GeneratedTime bool @@ -468,6 +492,7 @@ func (g *Gen) writeGoDoc(packageName string, output io.Writer, swagger *spec.Swa Title: swagger.Info.Title, Description: swagger.Info.Description, Version: swagger.Info.Version, + State: state, InstanceName: config.InstanceName, LeftTemplateDelim: config.LeftTemplateDelim, RightTemplateDelim: config.RightTemplateDelim, @@ -489,10 +514,10 @@ package {{.PackageName}} import "github.com/swaggo/swag" -const docTemplate{{ if ne .InstanceName "swagger" }}{{ .InstanceName }} {{- end }} = ` + "`{{ printDoc .Doc}}`" + ` +const docTemplate{{ if ne .InstanceName "swagger" }}{{ .InstanceName }} {{- end }}{{ .State }} = ` + "`{{ printDoc .Doc}}`" + ` -// SwaggerInfo{{ if ne .InstanceName "swagger" }}{{ .InstanceName }} {{- end }} holds exported Swagger Info so clients can modify it -var SwaggerInfo{{ if ne .InstanceName "swagger" }}{{ .InstanceName }} {{- end }} = &swag.Spec{ +// Swagger{{ .State }}Info{{ if ne .InstanceName "swagger" }}{{ .InstanceName }} {{- end }} holds exported Swagger Info so clients can modify it +var Swagger{{ .State }}Info{{ if ne .InstanceName "swagger" }}{{ .InstanceName }} {{- end }} = &swag.Spec{ Version: {{ printf "%q" .Version}}, Host: {{ printf "%q" .Host}}, BasePath: {{ printf "%q" .BasePath}}, @@ -500,12 +525,12 @@ var SwaggerInfo{{ if ne .InstanceName "swagger" }}{{ .InstanceName }} {{- end }} Title: {{ printf "%q" .Title}}, Description: {{ printf "%q" .Description}}, InfoInstanceName: {{ printf "%q" .InstanceName }}, - SwaggerTemplate: docTemplate{{ if ne .InstanceName "swagger" }}{{ .InstanceName }} {{- end }}, + SwaggerTemplate: docTemplate{{ if ne .InstanceName "swagger" }}{{ .InstanceName }} {{- end }}{{ .State }}, LeftDelim: {{ printf "%q" .LeftTemplateDelim}}, RightDelim: {{ printf "%q" .RightTemplateDelim}}, } func init() { - swag.Register(SwaggerInfo{{ if ne .InstanceName "swagger" }}{{ .InstanceName }} {{- end }}.InstanceName(), SwaggerInfo{{ if ne .InstanceName "swagger" }}{{ .InstanceName }} {{- end }}) + swag.Register(Swagger{{ .State }}Info{{ if ne .InstanceName "swagger" }}{{ .InstanceName }} {{- end }}.InstanceName(), Swagger{{ .State }}Info{{ if ne .InstanceName "swagger" }}{{ .InstanceName }} {{- end }}) } ` diff --git a/gen/gen_test.go b/gen/gen_test.go index 3d213186b..97c0cdef4 100644 --- a/gen/gen_test.go +++ b/gen/gen_test.go @@ -895,3 +895,81 @@ func TestGen_ErrorAndInterface(t *testing.T) { assert.JSONEq(t, string(expectedJSON), string(jsonOutput)) } + +func TestGen_StateAdmin(t *testing.T) { + config := &Config{ + SearchDir: "../testdata/state", + MainAPIFile: "./main.go", + OutputDir: "../testdata/state/docs", + OutputTypes: outputTypes, + PropNamingStrategy: "", + State: "admin", + } + + assert.NoError(t, New().Build(config)) + + expectedFiles := []string{ + filepath.Join(config.OutputDir, "admin_docs.go"), + filepath.Join(config.OutputDir, "admin_swagger.json"), + filepath.Join(config.OutputDir, "admin_swagger.yaml"), + } + t.Cleanup(func() { + for _, expectedFile := range expectedFiles { + _ = os.Remove(expectedFile) + } + }) + + // check files + for _, expectedFile := range expectedFiles { + if _, err := os.Stat(expectedFile); os.IsNotExist(err) { + require.NoError(t, err) + } + } + + // check content + jsonOutput, err := os.ReadFile(filepath.Join(config.OutputDir, "admin_swagger.json")) + require.NoError(t, err) + expectedJSON, err := os.ReadFile(filepath.Join(config.SearchDir, "admin_expected.json")) + require.NoError(t, err) + + assert.JSONEq(t, string(expectedJSON), string(jsonOutput)) +} + +func TestGen_StateUser(t *testing.T) { + config := &Config{ + SearchDir: "../testdata/state", + MainAPIFile: "./main.go", + OutputDir: "../testdata/state/docs", + OutputTypes: outputTypes, + PropNamingStrategy: "", + State: "user", + } + + assert.NoError(t, New().Build(config)) + + expectedFiles := []string{ + filepath.Join(config.OutputDir, "user_docs.go"), + filepath.Join(config.OutputDir, "user_swagger.json"), + filepath.Join(config.OutputDir, "user_swagger.yaml"), + } + t.Cleanup(func() { + for _, expectedFile := range expectedFiles { + _ = os.Remove(expectedFile) + } + }) + + // check files + for _, expectedFile := range expectedFiles { + if _, err := os.Stat(expectedFile); os.IsNotExist(err) { + require.NoError(t, err) + } + } + + // check content + jsonOutput, err := os.ReadFile(filepath.Join(config.OutputDir, "user_swagger.json")) + require.NoError(t, err) + expectedJSON, err := os.ReadFile(filepath.Join(config.SearchDir, "user_expected.json")) + require.NoError(t, err) + + assert.JSONEq(t, string(expectedJSON), string(jsonOutput)) +} diff --git a/operation.go b/operation.go index 9a02c0fd4..3664fd9af 100644 --- a/operation.go +++ b/operation.go @@ -30,6 +30,7 @@ type Operation struct { codeExampleFilesDir string spec.Operation RouterProperties []RouteProperties + State string } var mimeTypeAliases = map[string]string{ @@ -118,6 +119,8 @@ func (operation *Operation) ParseComment(comment string, astFile *ast.File) erro lineRemainder = fields[1] } switch lowerAttribute { + case stateAttr: + operation.ParseStateComment(lineRemainder) case descriptionAttr: operation.ParseDescriptionComment(lineRemainder) case descriptionMarkdownAttr: @@ -183,6 +186,11 @@ func (operation *Operation) ParseCodeSample(attribute, _, lineRemainder string) return operation.ParseMetadata(attribute, strings.ToLower(attribute), lineRemainder) } +// ParseDescriptionComment godoc. +func (operation *Operation) ParseStateComment(lineRemainder string) { + operation.State = lineRemainder +} + // ParseDescriptionComment godoc. func (operation *Operation) ParseDescriptionComment(lineRemainder string) { if operation.Description == "" { diff --git a/parser.go b/parser.go index 6bf991e92..a2bafa1f9 100644 --- a/parser.go +++ b/parser.go @@ -66,6 +66,7 @@ const ( extDocsURLAttr = "@externaldocs.url" xCodeSamplesAttr = "@x-codesamples" scopeAttrPrefix = "@scope." + stateAttr = "@state" ) // ParseFlag determine what to parse @@ -174,6 +175,9 @@ type Parser struct { // tags to filter the APIs after tags map[string]struct{} + + // HostState is the state of the host + HostState string } // FieldParserFactory create FieldParser. @@ -541,6 +545,14 @@ func parseGeneralAPIInfo(parser *Parser, comments []string) error { case "@host": parser.swagger.Host = value + case "@hoststate": + fields = FieldsByAnySpace(commentLine, 3) + if len(fields) != 3 { + return fmt.Errorf("%s needs 3 arguments", attribute) + } + if parser.HostState == fields[1] { + parser.swagger.Host = fields[2] + } case "@basepath": parser.swagger.BasePath = value @@ -977,6 +989,7 @@ func matchExtension(extensionToMatch string, comments []*ast.Comment) (match boo // ParseRouterAPIInfo parses router api info for given astFile. func (parser *Parser) ParseRouterAPIInfo(fileInfo *AstFileInfo) error { +DeclsLoop: for _, astDescription := range fileInfo.File.Decls { if (fileInfo.ParseFlag & ParseOperations) == ParseNone { continue @@ -992,6 +1005,9 @@ func (parser *Parser) ParseRouterAPIInfo(fileInfo *AstFileInfo) error { if err != nil { return fmt.Errorf("ParseComment error in file %s :%+v", fileInfo.Path, err) } + if operation.State != "" && operation.State != parser.HostState { + continue DeclsLoop + } } err := processRouterOperation(parser, operation) if err != nil { diff --git a/testdata/state/admin_expected.json b/testdata/state/admin_expected.json new file mode 100644 index 000000000..d5f3a3da5 --- /dev/null +++ b/testdata/state/admin_expected.json @@ -0,0 +1,396 @@ +{ + "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-admin.swagger.io", + "basePath": "/v3", + "paths": { + "/admin/file/upload": { + "post": { + "description": "Upload file", + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "summary": "Upload file", + "operationId": "admin.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" + } + } + } + } + }, + "/admin/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": "admin.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": { + "$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" + } + } + } + } + }, + "/admin/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": "admin.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", + "items": { + "type": "string", + "format": "url" + }, + "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": {}, + "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", + "multipleOf": 0.01, + "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" + } + } + } + } + } +} diff --git a/testdata/state/api/api.go b/testdata/state/api/api.go new file mode 100644 index 000000000..71718e804 --- /dev/null +++ b/testdata/state/api/api.go @@ -0,0 +1,76 @@ +package api + +import "net/http" + +// @State admin +// @Summary Add a new pet to the store +// @Description get string by ID +// @ID admin.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 /admin/testapi/get-string-by-int/{some_id} [get] +func GetStringByInt(w http.ResponseWriter, r *http.Request) { + //write your code +} + +// @State admin +// @Description get struct array by ID +// @ID admin.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" Minimum(0) default(0) +// @Param limit query int true "Limit" Maximum(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 /admin/testapi/get-struct-array-by-string/{some_id} [get] +func GetStructArrayByString(w http.ResponseWriter, r *http.Request) { + //write your code +} + +// @State admin +// @Summary Upload file +// @Description Upload file +// @ID admin.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 404 {object} web.APIError "Can not find ID" +// @Router /admin/file/upload [post] +func Upload(w http.ResponseWriter, r *http.Request) { + //write your code +} + +// @State admin +// @Summary use Anonymous field +// @Success 200 {object} web.RevValue "ok" +func AnonymousField() { + +} + +// @State admin +// @Summary use pet2 +// @Success 200 {object} web.Pet2 "ok" +func Pet2() { + +} + +type Pet3 struct { + ID int `json:"id"` +} diff --git a/testdata/state/api/api_user.go b/testdata/state/api/api_user.go new file mode 100644 index 000000000..014541998 --- /dev/null +++ b/testdata/state/api/api_user.go @@ -0,0 +1,76 @@ +package api + +import "net/http" + +// @State user +// @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 GetStringByIntUser(w http.ResponseWriter, r *http.Request) { + //write your code +} + +// @State user +// @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" Minimum(0) default(0) +// @Param limit query int true "Limit" Maximum(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 GetStructArrayByStringUser(w http.ResponseWriter, r *http.Request) { + //write your code +} + +// @State user +// @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 404 {object} web.APIError "Can not find ID" +// @Router /file/upload [post] +func UploadUser(w http.ResponseWriter, r *http.Request) { + //write your code +} + +// @State user +// @Summary use Anonymous field +// @Success 200 {object} web.RevValue "ok" +func AnonymousFieldUser() { + +} + +// @State user +// @Summary use pet2 +// @Success 200 {object} web.Pet2 "ok" +func Pet2User() { + +} + +type Pet3User struct { + ID int `json:"id"` +} diff --git a/testdata/state/main.go b/testdata/state/main.go new file mode 100644 index 000000000..97cdd6666 --- /dev/null +++ b/testdata/state/main.go @@ -0,0 +1,38 @@ +package main + +import ( + "net/http" + + "github.com/swaggo/swag/testdata/state/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 + +// @hostState admin petstore-admin.swagger.io +// @hostState user petstore-user.swagger.io +// @BasePath /v3 +func main() { + state := "admin" // "admin" or "user" + switch state { + case "admin": + http.HandleFunc("/admin/testapi/get-string-by-int/", api.GetStringByInt) + http.HandleFunc("/admin/testapi/get-struct-array-by-string/", api.GetStructArrayByString) + http.HandleFunc("/admin/testapi/upload", api.Upload) + http.ListenAndServe(":8080", nil) + case "user": + http.HandleFunc("/testapi/get-string-by-int/", api.GetStringByIntUser) + http.HandleFunc("/testapi/get-struct-array-by-string/", api.GetStructArrayByStringUser) + http.HandleFunc("/testapi/upload", api.UploadUser) + http.ListenAndServe(":8080", nil) + } +} diff --git a/testdata/state/user_expected.json b/testdata/state/user_expected.json new file mode 100644 index 000000000..18431c031 --- /dev/null +++ b/testdata/state/user_expected.json @@ -0,0 +1,396 @@ +{ + "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-user.swagger.io", + "basePath": "/v3", + "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": { + "$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", + "items": { + "type": "string", + "format": "url" + }, + "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": {}, + "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", + "multipleOf": 0.01, + "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" + } + } + } + } + } +} diff --git a/testdata/state/web/handler.go b/testdata/state/web/handler.go new file mode 100644 index 000000000..61da78c50 --- /dev/null +++ b/testdata/state/web/handler.go @@ -0,0 +1,64 @@ +package web + +import ( + "time" + + uuid "github.com/gofrs/uuid" + "github.com/shopspring/decimal" +) + +type Pet struct { + ID int `example:"1" format:"int64"` + Category struct { + ID int `example:"1"` + Name string `example:"category_name"` + PhotoURLs []string `example:"http://test/image/1.jpg,http://test/image/2.jpg" format:"url"` + SmallCategory struct { + ID int `example:"1"` + Name string `example:"detail_category_name"` + PhotoURLs []string `example:"http://test/image/1.jpg,http://test/image/2.jpg"` + } + } + Name string `example:"poti"` + PhotoURLs []string `example:"http://test/image/1.jpg,http://test/image/2.jpg"` + Tags []Tag + Pets *[]Pet2 + Pets2 []*Pet2 + Status string + Price float32 `example:"3.25" multipleOf:"0.01"` + IsAlive bool `example:"true"` + Data interface{} + Hidden string `json:"-"` + UUID uuid.UUID + Decimal decimal.Decimal + Function func() +} + +type Tag struct { + ID int `format:"int64"` + Name string + Pets []Pet +} + +type Pet2 struct { + ID int + MiddleName *string + DeletedAt *time.Time +} + +type APIError struct { + ErrorCode int + ErrorMessage string + CreatedAt time.Time +} + +type RevValueBase struct { + Status bool + + Err int32 +} +type RevValue struct { + RevValueBase + + Data int +}