Skip to content

Commit

Permalink
Merge branch 'master' into nested_schema
Browse files Browse the repository at this point in the history
  • Loading branch information
sdghchj committed Mar 22, 2020
2 parents e1b41ed + 2f74ff2 commit 132eace
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 27 deletions.
6 changes: 5 additions & 1 deletion 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 Expand Up @@ -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:
Expand All @@ -458,6 +462,7 @@ Field Name | Type | Description
<a name="parameterMinLength"></a>minLength | `integer` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.2.
<a name="parameterEnums"></a>enums | [\*] | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.5.1.
<a name="parameterFormat"></a>format | `string` | The extending format for the previously mentioned [`type`](#parameterType). See [Data Type Formats](https://swagger.io/specification/v2/#dataTypeFormat) for further details.
<a name="parameterCollectionFormat"></a>collectionFormat | `string` |Determines the format of the array if type array is used. Possible values are: <ul><li>`csv` - comma separated values `foo,bar`. <li>`ssv` - space separated values `foo bar`. <li>`tsv` - tab separated values `foo\tbar`. <li>`pipes` - pipe separated values <code>foo&#124;bar</code>. <li>`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". </ul> Default value is `csv`.

### Future

Expand All @@ -468,7 +473,6 @@ Field Name | Type | Description
<a name="parameterMaxItems"></a>maxItems | `integer` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.3.2.
<a name="parameterMinItems"></a>minItems | `integer` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.3.3.
<a name="parameterUniqueItems"></a>uniqueItems | `boolean` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.3.4.
<a name="parameterCollectionFormat"></a>collectionFormat | `string` | Determines the format of the array if type array is used. Possible values are: <ul><li>`csv` - comma separated values `foo,bar`. <li>`ssv` - space separated values `foo bar`. <li>`tsv` - tab separated values `foo\tbar`. <li>`pipes` - pipe separated values <code>foo&#124;bar</code>. <li>`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". </ul> Default value is `csv`.

## Examples

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
42 changes: 31 additions & 11 deletions operation.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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],
Expand Down Expand Up @@ -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, &param); err != nil {
if err := operation.parseAndExtractionParamAttribute(commentLine, objectType, refType, &param); err != nil {
return err
}
operation.Operation.Parameters = append(operation.Operation.Parameters, param)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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)
Expand Down
26 changes: 26 additions & 0 deletions operation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,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()
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

0 comments on commit 132eace

Please sign in to comment.