Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support colletionFormat for array params in query; Exclude directories and files #642

Merged
merged 13 commits into from
Mar 22, 2020
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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"} |

Expand Down
6 changes: 6 additions & 0 deletions cmd/swag/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

const (
searchDirFlag = "dir"
excludeFlag = "exclude"
generalInfoFlag = "generalInfo"
propertyStrategyFlag = "propertyStrategy"
outputFlag = "output"
Expand All @@ -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",
Expand Down Expand Up @@ -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),
Expand Down
6 changes: 5 additions & 1 deletion gen/gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
11 changes: 10 additions & 1 deletion operation.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,10 +147,18 @@ func (operation *Operation) ParseParamComment(commentLine string, astFile *ast.F

// Detect refType
objectType := "object"
if strings.HasPrefix(refType, "[]") == true {
var collectionFormat string
if strings.HasPrefix(refType, "[]") {
objectType = "array"
refType = strings.TrimPrefix(refType, "[]")
refType = TransToValidSchemeType(refType)
if operation.parser != nil {
collectionFormat = TransToValidCollectionFormat(operation.parser.collectionFormatInQuery)
}
} else if pos := strings.IndexRune(refType, ']'); pos > 0 && strings.HasPrefix(refType, "[") { //for example: [csv]int
objectType = "array"
collectionFormat = TransToValidCollectionFormat(refType[1:pos])
refType = TransToValidSchemeType(refType[pos+1:])
} else if IsPrimitiveType(refType) ||
paramType == "formData" && refType == "file" {
objectType = "primitive"
Expand All @@ -175,6 +183,7 @@ 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"
param.SimpleSchema.CollectionFormat = collectionFormat
param.SimpleSchema.Items = &spec.Items{
SimpleSchema: spec.SimpleSchema{
Type: refType,
Expand Down
26 changes: 26 additions & 0 deletions operation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 [multi]string true "Users List"`
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()
Expand Down
46 changes: 32 additions & 14 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"net/http"
"os"
"os/exec"
"path"
"path/filepath"
"reflect"
"sort"
Expand Down Expand Up @@ -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.
Expand All @@ -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 {
Expand All @@ -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)
Expand All @@ -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
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}

Expand Down
10 changes: 10 additions & 0 deletions schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down