Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .replit
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
language = "go"
run = ""
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.

Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand Down
5 changes: 2 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
@@ -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
)
20 changes: 14 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
@@ -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=
126 changes: 83 additions & 43 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import (
"unicode"

"github.com/iancoleman/orderedmap"
"github.com/mikunalpha/go-module"
module "golang.org/x/mod/modfile"
)

type parser struct {
Expand Down Expand Up @@ -400,28 +400,31 @@ 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
}
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,
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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{
Expand All @@ -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],
Expand Down Expand Up @@ -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<status>[\d]+)[\s]*(?P<jsonType>[\w\{\}]+)?[\s]+(?P<goType>[\w\-\.\/\[\]]+)?[^"]*(?P<description>.*)?`)
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),
},
}
}
}
}
Expand Down Expand Up @@ -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)
Expand Down