diff --git a/.replit b/.replit new file mode 100644 index 0000000..b312b17 --- /dev/null +++ b/.replit @@ -0,0 +1,2 @@ +language = "go" +run = "" \ No newline at end of file diff --git a/README.md b/README.md index 197860c..b617cf5 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,11 @@ # goas The project is based on - [yvasiyarov/swagger](https://github.com/yvasiyarov/swagger) repository. -- [uudashr/go-module](https://github.com/uudashr/go-module) repository. +- [uudashr/go-module](https://github.com/uudashr/go-module) repository. (currently deprecated) +- [mikunalpha/goas](https://github.com/mikunalpha/goas) repository + +The reason for the fork was [#10](https://github.com/mikunalpha/goas/issues/10) and [#11](https://github.com/mikunalpha/goas/issues/11) + Generate [OpenAPI Specification](https://swagger.io/specification) json file with comments in Go. @@ -12,7 +16,7 @@ Generate [OpenAPI Specification](https://swagger.io/specification) json file wit ## Install ``` -go get -u github.com/mikunalpha/goas +go get -u github.com/nicocesar/goas ``` ## Usage @@ -146,10 +150,10 @@ func PostUser() { #### Response ``` -@Success {stauts} {jsonType} {goType} {description} +@Success {status} {jsonType} {goType} {description} @Success 200 object UsersResponse "UsersResponse JSON" -@Failure {stauts} {jsonType} {goType} {description} +@Failure {status} {jsonType} {goType} {description} @Failure 400 object ErrorResponse "ErrorResponse JSON" ``` - {status}: The HTTP status code. diff --git a/go.mod b/go.mod index be5a5b6..fb3ef16 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,9 @@ -module github.com/mikunalpha/goas +module github.com/nicocesar/goas go 1.12 require ( github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 - github.com/mikunalpha/go-module v0.0.0-20190521120234-12aa2dc244ca github.com/urfave/cli v1.20.0 - github.com/uudashr/go-module v0.0.0-20180827225833-c0ca9c3a4966 // indirect + golang.org/x/mod v0.3.0 ) diff --git a/go.sum b/go.sum index e943754..9c53bc8 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,18 @@ github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 h1:i462o439ZjprVSFSZLZxcsoAe592sZB1rci2Z8j4wdk= github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA= -github.com/mikunalpha/go-module v0.0.0-20190507143600-b54b56b95307 h1:G3hp0pjy0Ur4fa+W6urYfOcIzi0DMoLtx4fDGQGseUY= -github.com/mikunalpha/go-module v0.0.0-20190507143600-b54b56b95307/go.mod h1:ZA+ZzsD/oj1v6Flm0iUci0TQr48OCj4gPCg1JA0zyh0= -github.com/mikunalpha/go-module v0.0.0-20190521120234-12aa2dc244ca h1:5s3kB2GTEN8/X591x/Qz7o3oNHtgF+cS/360aiVy71k= -github.com/mikunalpha/go-module v0.0.0-20190521120234-12aa2dc244ca/go.mod h1:ZA+ZzsD/oj1v6Flm0iUci0TQr48OCj4gPCg1JA0zyh0= 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/uudashr/go-module v0.0.0-20180827225833-c0ca9c3a4966 h1:7dS/ZO0dIwrtj/FGTt9I6urVpx7LEHzucegv4ORYK3M= -github.com/uudashr/go-module v0.0.0-20180827225833-c0ca9c3a4966/go.mod h1:P6Nk1sQWL6jcdBIxnLVlqCsOl0arao7gg7sPoM6gx4A= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/parser.go b/parser.go index 402eb57..84f91b4 100644 --- a/parser.go +++ b/parser.go @@ -19,7 +19,7 @@ import ( "unicode" "github.com/iancoleman/orderedmap" - "github.com/mikunalpha/go-module" + module "golang.org/x/mod/modfile" ) type parser struct { @@ -400,19 +400,22 @@ func (p *parser) parseModule() error { filepath.Walk(p.ModulePath, walker) return nil } +func fixer(path, version string) (string, error) { + return version, nil +} func (p *parser) parseGoMod() error { b, err := ioutil.ReadFile(p.GoModFilePath) if err != nil { return err } - goMod, err := module.Parse(b) + goMod, err := module.ParseLax(p.GoModFilePath, b, fixer) if err != nil { return err } - for i := range goMod.Requires { + for i := range goMod.Require { pathRunes := []rune{} - for _, v := range goMod.Requires[i].Path { + for _, v := range goMod.Require[i].Mod.Path { if !unicode.IsUpper(v) { pathRunes = append(pathRunes, v) continue @@ -420,8 +423,8 @@ func (p *parser) parseGoMod() error { pathRunes = append(pathRunes, '!') pathRunes = append(pathRunes, unicode.ToLower(v)) } - pkgName := goMod.Requires[i].Path - pkgPath := filepath.Join(p.GoModCachePath, string(pathRunes)+"@"+goMod.Requires[i].Version) + pkgName := goMod.Require[i].Mod.Path + pkgPath := filepath.Join(p.GoModCachePath, string(pathRunes)+"@"+goMod.Require[i].Mod.Version) pkgName = filepath.ToSlash(pkgName) p.KnownPkgs = append(p.KnownPkgs, pkg{ Name: pkgName, @@ -647,7 +650,8 @@ func (p *parser) parseOperation(pkgPath, pkgName string, astComments []*ast.Comm for _, astComment := range astComments { comment := strings.TrimSpace(strings.TrimLeft(astComment.Text, "/")) if len(comment) == 0 { - return nil + // ignore empty lines + continue } attribute := strings.Fields(comment)[0] switch strings.ToLower(attribute) { @@ -700,7 +704,7 @@ func (p *parser) parseParamComment(pkgPath, pkgName string, operation *Operation description := matches[5] // `file`, `form` - if in == "file" || in == "form" { + if in == "file" || in == "files" || in == "form" { if operation.RequestBody == nil { operation.RequestBody = &RequestBodyObject{ Content: map[string]*MediaTypeObject{ @@ -720,6 +724,15 @@ func (p *parser) parseParamComment(pkgPath, pkgName string, operation *Operation Format: "binary", Description: description, }) + } else if in == "files" { + operation.RequestBody.Content[ContentTypeForm].Schema.Properties.Set(name, &SchemaObject{ + Type: "array", + Items: &SchemaObject{ + Type: "string", + Format: "binary", + }, + Description: description, + }) } else if isGoTypeOASType(goType) { operation.RequestBody.Content[ContentTypeForm].Schema.Properties.Set(name, &SchemaObject{ Type: goTypesOASTypes[goType], @@ -800,53 +813,70 @@ func (p *parser) parseParamComment(pkgPath, pkgName string, operation *Operation func (p *parser) parseResponseComment(pkgPath, pkgName string, operation *OperationObject, comment string) error { // {status} {jsonType} {goType} {description} // 201 object models.User "User Model" - re := regexp.MustCompile(`([\d]+)[\s]+([\w\{\}]+)[\s]+([\w\-\.\/\[\]]+)[^"]*(.*)?`) + // if 204 or something else without empty return payload + // 204 "User Model" + re := regexp.MustCompile(`(?P[\d]+)[\s]*(?P[\w\{\}]+)?[\s]+(?P[\w\-\.\/\[\]]+)?[^"]*(?P.*)?`) matches := re.FindStringSubmatch(comment) - if len(matches) != 5 { - return fmt.Errorf("parseResponseComment can not parse response comment \"%s\"", comment) + + paramsMap := make(map[string]string) + for i, name := range re.SubexpNames() { + if i > 0 && i <= len(matches) { + paramsMap[name] = matches[i] + } } - status := matches[1] - _, err := strconv.Atoi(matches[1]) + if len(matches) <= 2 { + return fmt.Errorf("parseResponseComment can not parse response comment \"%s\", matches: %v", comment, matches) + } + + status := paramsMap["status"] + _, err := strconv.Atoi(status) if err != nil { return fmt.Errorf("parseResponseComment: http status must be int, but got %s", status) } - switch matches[2] { - case "object", "array", "{object}", "{array}": - default: - return fmt.Errorf("parseResponseComment: invalid jsonType %s", matches[2]) + + // ignore type if not set + if jsonType := paramsMap["jsonType"]; jsonType != "" { + switch jsonType { + case "object", "array", "{object}", "{array}": + default: + return fmt.Errorf("parseResponseComment: invalid jsonType \"%s\"", paramsMap["jsonType"]) + } } + responseObject := &ResponseObject{ Content: map[string]*MediaTypeObject{}, } - responseObject.Description = strings.Trim(matches[4], "\"") + responseObject.Description = strings.Trim(paramsMap["description"], "\"") - re = regexp.MustCompile(`\[\w*\]`) - goType := re.ReplaceAllString(matches[3], "[]") - if strings.HasPrefix(goType, "[]") || strings.HasPrefix(goType, "map[]") { - schema, err := p.parseSchemaObject(pkgPath, pkgName, goType) - if err != nil { - p.debug("parseResponseComment cannot parse goType", goType) - } - responseObject.Content[ContentTypeJson] = &MediaTypeObject{ - Schema: *schema, - } - } else { - typeName, err := p.registerType(pkgPath, pkgName, matches[3]) - if err != nil { - return err - } - if isBasicGoType(typeName) { - responseObject.Content[ContentTypeText] = &MediaTypeObject{ - Schema: SchemaObject{ - Type: "string", - }, + if goTypeRaw := paramsMap["goType"]; goTypeRaw != "" { + re = regexp.MustCompile(`\[\w*\]`) + goType := re.ReplaceAllString(goTypeRaw, "[]") + if strings.HasPrefix(goType, "[]") || strings.HasPrefix(goType, "map[]") { + schema, err := p.parseSchemaObject(pkgPath, pkgName, goType) + if err != nil { + p.debug("parseResponseComment: cannot parse goType", goType) } - } else { responseObject.Content[ContentTypeJson] = &MediaTypeObject{ - Schema: SchemaObject{ - Ref: addSchemaRefLinkPrefix(typeName), - }, + Schema: *schema, + } + } else { + typeName, err := p.registerType(pkgPath, pkgName, matches[3]) + if err != nil { + return err + } + if isBasicGoType(typeName) { + responseObject.Content[ContentTypeText] = &MediaTypeObject{ + Schema: SchemaObject{ + Type: "string", + }, + } + } else { + responseObject.Content[ContentTypeJson] = &MediaTypeObject{ + Schema: SchemaObject{ + Ref: addSchemaRefLinkPrefix(typeName), + }, + } } } } @@ -964,7 +994,17 @@ func (p *parser) parseSchemaObject(pkgPath, pkgName, typeName string) (*SchemaOb if len(typeNameParts) == 1 { typeSpec, exist = p.getTypeSpec(pkgPath, pkgName, typeName) if !exist { - log.Fatalf("Can not find definition of %s ast.TypeSpec. Current package %s", typeName, pkgName) + for _, value := range p.KnownNamePkg { + typeSpec, exist = p.getTypeSpec(value.Path, value.Name, typeName) + if exist { + pkgPath = value.Path + pkgName = value.Name + break + } + } + if !exist { + log.Fatalf("Can not find definition of %s ast.TypeSpec. Current package %s", typeName, pkgName) + } } schemaObject.PkgName = pkgName schemaObject.ID = genSchemeaObjectID(pkgName, typeName)