diff --git a/README.md b/README.md index d282dbf68..1fd76932c 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ Swag converts Go annotations to Swagger Documentation 2.0. We've created a varie - [Rename model to display](#rename-model-to-display) - [How to use security annotations](#how-to-use-security-annotations) - [Add a description for enum items](#add-a-description-for-enum-items) + - [Generate only specific docs file types](#generate-only-specific-docs-file-types) - [About the Project](#about-the-project) ## Getting started @@ -86,18 +87,20 @@ 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: "./") + --dir value, -d value Directories you want to parse,comma separated and general-info file must be in the first one (default: "./") --exclude value Exclude directories and files when searching, 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") + --output value, -o value Output directory for all the generated files(swagger.json, swagger.yaml and docs.go) (default: "./docs") + --outputTypes value, --ot value Output types of generated files (docs.go, swagger.json, swagger.yaml) like go,json,yaml (default: "go,json,yaml") --parseVendor Parse go files in 'vendor' folder, disabled by default (default: false) - --parseDependency Parse go files in outside dependency folder, disabled by default (default: false) + --parseDependency, --pd Parse go files inside dependency folder, disabled by default (default: false) --markdownFiles value, --md value Parse folder containing markdown files to use as description, disabled by default --codeExampleFiles value, --cef value Parse folder containing code example files to use for the x-codeSamples extension, disabled by default --parseInternal Parse go files in internal packages, disabled by default (default: false) --generatedTime Generate timestamp at the top of docs.go, disabled by default (default: false) --parseDepth value Dependency parse depth (default: 100) - --instanceName value Set the swagger document instance name (default: "swagger") + --instanceName value This parameter can be used to name different swagger document instances. It is optional. + --overridesFile value File to read global type overrides from. (default: ".swaggo") --help, -h show help (default: false) ``` @@ -797,6 +800,15 @@ type Example struct { } ``` +### Generate only specific docs file types + +By default `swag` command generates Swagger specification in three different files/file types: +- docs.go +- swagger.json +- swagger.yaml + +If you would like to limit a set of file types which should be generated you can use `--outputTypes` (short `-ot`) flag. Default value is `go,json,yaml` - output types separated with comma. To limit output only to `go` and `yaml` files, you would write `go,yaml`. With complete command that would be `swag init --outputTypes go,yaml`. + ## About the Project 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). ## Contributors diff --git a/cmd/swag/main.go b/cmd/swag/main.go index 229894d04..0a18900f0 100644 --- a/cmd/swag/main.go +++ b/cmd/swag/main.go @@ -4,6 +4,7 @@ import ( "fmt" "log" "os" + "strings" "github.com/urfave/cli/v2" @@ -18,6 +19,7 @@ const ( generalInfoFlag = "generalInfo" propertyStrategyFlag = "propertyStrategy" outputFlag = "output" + outputTypesFlag = "outputTypes" parseVendorFlag = "parseVendor" parseDependencyFlag = "parseDependency" markdownFilesFlag = "markdownFiles" @@ -56,7 +58,13 @@ var initFlags = []cli.Flag{ Name: outputFlag, Aliases: []string{"o"}, Value: "./docs", - Usage: "Output directory for all the generated files(swagger.json, swagger.yaml and doc.go)", + Usage: "Output directory for all the generated files(swagger.json, swagger.yaml and docs.go)", + }, + &cli.StringFlag{ + Name: outputTypesFlag, + Aliases: []string{"ot"}, + Value: "go,json,yaml", + Usage: "Output types of generated files (docs.go, swagger.json, swagger.yaml) like go,json,yaml", }, &cli.BoolFlag{ Name: parseVendorFlag, @@ -113,12 +121,18 @@ func initAction(c *cli.Context) error { return fmt.Errorf("not supported %s propertyStrategy", strategy) } + outputTypes := strings.Split(c.String(outputTypesFlag), ",") + if len(outputTypes) == 0 { + return fmt.Errorf("no output types specified") + } + return gen.New().Build(&gen.Config{ SearchDir: c.String(searchDirFlag), Excludes: c.String(excludeFlag), MainAPIFile: c.String(generalInfoFlag), PropNamingStrategy: strategy, OutputDir: c.String(outputFlag), + OutputTypes: outputTypes, ParseVendor: c.Bool(parseVendorFlag), ParseDependency: c.Bool(parseDependencyFlag), MarkdownFilesDir: c.String(markdownFilesFlag), diff --git a/gen/gen.go b/gen/gen.go index 0fbb25951..105b89c6f 100644 --- a/gen/gen.go +++ b/gen/gen.go @@ -9,6 +9,7 @@ import ( "io" "log" "os" + "path" "path/filepath" "strings" "text/template" @@ -24,20 +25,36 @@ var open = os.Open // DefaultOverridesFile is the location swaggo will look for type overrides. const DefaultOverridesFile = ".swaggo" +type genTypeWriter func(*Config, *spec.Swagger) error + // Gen presents a generate tool for swag. type Gen struct { - jsonIndent func(data interface{}) ([]byte, error) - jsonToYAML func(data []byte) ([]byte, error) + json func(data interface{}) ([]byte, error) + jsonIndent func(data interface{}) ([]byte, error) + jsonToYAML func(data []byte) ([]byte, error) + outputTypeMap map[string]genTypeWriter } // New creates a new Gen. func New() *Gen { - return &Gen{ + gen := &Gen{ + json: func(data interface{}) ([]byte, error) { + return json.Marshal(data) + }, jsonIndent: func(data interface{}) ([]byte, error) { return json.MarshalIndent(data, "", " ") }, jsonToYAML: yaml.JSONToYAML, } + + gen.outputTypeMap = map[string]genTypeWriter{ + "go": gen.writeDocSwagger, + "json": gen.writeJSONSwagger, + "yaml": gen.writeYAMLSwagger, + "yml": gen.writeYAMLSwagger, + } + + return gen } // Config presents Gen configurations. @@ -51,6 +68,9 @@ type Config struct { // OutputDir represents the output directory for all the generated files OutputDir string + // OutputTypes define types of files which should be generated + OutputTypes []string + // MainAPIFile the Go file path in which 'swagger general API Info' is written MainAPIFile string @@ -137,23 +157,32 @@ func (g *Gen) Build(config *Config) error { } swagger := p.GetSwagger() - b, err := g.jsonIndent(swagger) - if err != nil { + if err := os.MkdirAll(config.OutputDir, os.ModePerm); err != nil { return err } - if err := os.MkdirAll(config.OutputDir, os.ModePerm); err != nil { - return err + for _, outputType := range config.OutputTypes { + outputType = strings.ToLower(strings.TrimSpace(outputType)) + if typeWriter, ok := g.outputTypeMap[outputType]; ok { + if err := typeWriter(config, swagger); err != nil { + return err + } + } else { + log.Printf("output type '%s' not supported", outputType) + } } + return nil +} + +func (g *Gen) writeDocSwagger(config *Config, swagger *spec.Swagger) error { + docFileName := path.Join(config.OutputDir, "docs.go") + absOutputDir, err := filepath.Abs(config.OutputDir) if err != nil { return err } packageName := filepath.Base(absOutputDir) - docFileName := filepath.Join(config.OutputDir, "docs.go") - jsonFileName := filepath.Join(config.OutputDir, "swagger.json") - yamlFileName := filepath.Join(config.OutputDir, "swagger.yaml") docs, err := os.Create(docFileName) if err != nil { @@ -161,31 +190,52 @@ func (g *Gen) Build(config *Config) error { } defer docs.Close() - err = g.writeFile(b, jsonFileName) + // Write doc + err = g.writeGoDoc(packageName, docs, swagger, config) if err != nil { return err } - y, err := g.jsonToYAML(b) + log.Printf("create docs.go at %+v", docFileName) + return nil +} + +func (g *Gen) writeJSONSwagger(config *Config, swagger *spec.Swagger) error { + jsonFileName := path.Join(config.OutputDir, "swagger.json") + + b, err := g.jsonIndent(swagger) if err != nil { - return fmt.Errorf("cannot convert json to yaml error: %s", err) + return err } - err = g.writeFile(y, yamlFileName) + err = g.writeFile(b, jsonFileName) if err != nil { return err } - // Write doc - err = g.writeGoDoc(packageName, docs, swagger, config) + log.Printf("create swagger.json at %+v", jsonFileName) + return nil +} + +func (g *Gen) writeYAMLSwagger(config *Config, swagger *spec.Swagger) error { + yamlFileName := path.Join(config.OutputDir, "swagger.yaml") + + b, err := g.json(swagger) 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) + y, err := g.jsonToYAML(b) + if err != nil { + return fmt.Errorf("cannot covert json to yaml error: %s", err) + } + + err = g.writeFile(y, yamlFileName) + if err != nil { + return err + } + log.Printf("create swagger.yaml at %+v", yamlFileName) return nil } diff --git a/gen/gen_test.go b/gen/gen_test.go index 7b2653052..c44b32b8f 100644 --- a/gen/gen_test.go +++ b/gen/gen_test.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "os" "os/exec" + "path" "path/filepath" "plugin" "strings" @@ -20,11 +21,14 @@ import ( const searchDir = "../testdata/simple" +var outputTypes = []string{"go", "json", "yaml"} + func TestGen_Build(t *testing.T) { config := &Config{ SearchDir: searchDir, MainAPIFile: "./main.go", OutputDir: "../testdata/simple/docs", + OutputTypes: outputTypes, PropNamingStrategy: "", } assert.NoError(t, New().Build(config)) @@ -42,11 +46,44 @@ func TestGen_Build(t *testing.T) { } } +func TestGen_SpecificOutputTypes(t *testing.T) { + config := &Config{ + SearchDir: searchDir, + MainAPIFile: "./main.go", + OutputDir: "../testdata/simple/docs", + OutputTypes: []string{"go", "unknownType"}, + PropNamingStrategy: "", + } + assert.NoError(t, New().Build(config)) + + tt := []struct { + expectedFile string + shouldExist bool + }{ + {filepath.Join(config.OutputDir, "docs.go"), true}, + {filepath.Join(config.OutputDir, "swagger.json"), false}, + {filepath.Join(config.OutputDir, "swagger.yaml"), false}, + } + for _, tc := range tt { + _, err := os.Stat(tc.expectedFile) + if tc.shouldExist { + if os.IsNotExist(err) { + require.NoError(t, err) + } + } else { + require.Error(t, err) + require.True(t, errors.Is(err, os.ErrNotExist)) + } + _ = os.Remove(tc.expectedFile) + } +} + func TestGen_BuildInstanceName(t *testing.T) { config := &Config{ SearchDir: searchDir, MainAPIFile: "./main.go", OutputDir: "../testdata/simple/docs", + OutputTypes: outputTypes, PropNamingStrategy: "", } assert.NoError(t, New().Build(config)) @@ -92,6 +129,7 @@ func TestGen_BuildSnakeCase(t *testing.T) { SearchDir: "../testdata/simple2", MainAPIFile: "./main.go", OutputDir: "../testdata/simple2/docs", + OutputTypes: outputTypes, PropNamingStrategy: swag.SnakeCase, } @@ -115,6 +153,7 @@ func TestGen_BuildLowerCamelcase(t *testing.T) { SearchDir: "../testdata/simple3", MainAPIFile: "./main.go", OutputDir: "../testdata/simple3/docs", + OutputTypes: outputTypes, PropNamingStrategy: "", } @@ -138,6 +177,7 @@ func TestGen_BuildDescriptionWithQuotes(t *testing.T) { SearchDir: "../testdata/quotes", MainAPIFile: "./main.go", OutputDir: "../testdata/quotes/docs", + OutputTypes: outputTypes, MarkdownFilesDir: "../testdata/quotes", } @@ -186,6 +226,7 @@ func TestGen_jsonIndent(t *testing.T) { SearchDir: searchDir, MainAPIFile: "./main.go", OutputDir: "../testdata/simple/docs", + OutputTypes: outputTypes, PropNamingStrategy: "", } gen := New() @@ -200,6 +241,7 @@ func TestGen_jsonToYAML(t *testing.T) { SearchDir: searchDir, MainAPIFile: "./main.go", OutputDir: "../testdata/simple/docs", + OutputTypes: outputTypes, PropNamingStrategy: "", } gen := New() @@ -226,6 +268,7 @@ func TestGen_SearchDirIsNotExist(t *testing.T) { SearchDir: "../isNotExistDir", MainAPIFile: "./main.go", OutputDir: swaggerConfDir, + OutputTypes: outputTypes, PropNamingStrategy: propNamingStrategy, } assert.EqualError(t, New().Build(config), "dir: ../isNotExistDir does not exist") @@ -237,6 +280,7 @@ func TestGen_MainAPiNotExist(t *testing.T) { SearchDir: searchDir, MainAPIFile: "./notExists.go", OutputDir: swaggerConfDir, + OutputTypes: outputTypes, PropNamingStrategy: propNamingStrategy, } assert.Error(t, New().Build(config)) @@ -248,6 +292,7 @@ func TestGen_OutputIsNotExist(t *testing.T) { SearchDir: searchDir, MainAPIFile: "./main.go", OutputDir: "/dev/null", + OutputTypes: outputTypes, PropNamingStrategy: propNamingStrategy, } assert.Error(t, New().Build(config)) @@ -255,12 +300,14 @@ func TestGen_OutputIsNotExist(t *testing.T) { func TestGen_FailToWrite(t *testing.T) { outputDir := filepath.Join(os.TempDir(), "swagg", "test") + outputTypes := []string{"go", "json", "yaml"} var propNamingStrategy string config := &Config{ SearchDir: searchDir, MainAPIFile: "./main.go", OutputDir: outputDir, + OutputTypes: outputTypes, PropNamingStrategy: propNamingStrategy, } @@ -302,6 +349,7 @@ func TestGen_configWithOutputDir(t *testing.T) { SearchDir: searchDir, MainAPIFile: "./main.go", OutputDir: "../testdata/simple/docs", + OutputTypes: outputTypes, PropNamingStrategy: "", } @@ -320,6 +368,64 @@ func TestGen_configWithOutputDir(t *testing.T) { } } +func TestGen_configWithOutputTypesAll(t *testing.T) { + searchDir := "../testdata/simple" + outputTypes := []string{"go", "json", "yaml"} + + config := &Config{ + SearchDir: searchDir, + MainAPIFile: "./main.go", + OutputDir: "../testdata/simple/docs", + OutputTypes: outputTypes, + PropNamingStrategy: "", + } + + 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) + } +} + +func TestGen_configWithOutputTypesSingle(t *testing.T) { + searchDir := "../testdata/simple" + outputTypes := []string{"go", "json", "yaml"} + + for _, outputType := range outputTypes { + config := &Config{ + SearchDir: searchDir, + MainAPIFile: "./main.go", + OutputDir: "../testdata/simple/docs", + OutputTypes: []string{outputType}, + PropNamingStrategy: "", + } + + assert.NoError(t, New().Build(config)) + + outFileName := "swagger" + if outputType == "go" { + outFileName = "docs" + } + expectedFiles := []string{ + path.Join(config.OutputDir, outFileName+"."+outputType), + } + for _, expectedFile := range expectedFiles { + if _, err := os.Stat(expectedFile); os.IsNotExist(err) { + t.Fatal(err) + } + os.Remove(expectedFile) + } + } +} + func TestGen_formatSource(t *testing.T) { src := `package main @@ -398,6 +504,7 @@ func TestGen_GeneratedDoc(t *testing.T) { SearchDir: searchDir, MainAPIFile: "./main.go", OutputDir: "../testdata/simple/docs", + OutputTypes: outputTypes, PropNamingStrategy: "", } @@ -429,6 +536,7 @@ func TestGen_cgoImports(t *testing.T) { SearchDir: "../testdata/simple_cgo", MainAPIFile: "./main.go", OutputDir: "../testdata/simple_cgo/docs", + OutputTypes: outputTypes, PropNamingStrategy: "", ParseDependency: true, }