2014-06-19 13:09:17 +00:00
// Copyright 2013 bee authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
2017-03-06 23:58:53 +00:00
package swaggergen
2014-06-18 04:19:03 +00:00
import (
"encoding/json"
"errors"
"fmt"
"go/ast"
"go/parser"
"go/token"
"os"
"path"
"path/filepath"
2017-02-21 08:30:55 +00:00
"runtime"
2014-06-18 04:19:03 +00:00
"strconv"
"strings"
2014-06-20 11:47:03 +00:00
"unicode"
2014-06-18 04:19:03 +00:00
2016-09-14 21:09:54 +00:00
"gopkg.in/yaml.v2"
2014-06-18 04:19:03 +00:00
"github.com/astaxie/beego/swagger"
"github.com/astaxie/beego/utils"
2017-03-06 23:58:53 +00:00
beeLogger "github.com/beego/bee/logger"
2017-04-24 12:10:52 +00:00
bu "github.com/beego/bee/utils"
2017-08-14 17:30:26 +00:00
"github.com/go-openapi/spec"
"github.com/wy-z/tspec/tspec"
2014-06-18 04:19:03 +00:00
)
2014-06-20 11:47:03 +00:00
const (
ajson = "application/json"
axml = "application/xml"
aplain = "text/plain"
ahtml = "text/html"
2017-08-16 10:17:07 +00:00
aform = "multipart/form-data"
2014-06-20 11:47:03 +00:00
)
2016-08-01 06:40:43 +00:00
var pkgCache map [ string ] struct { } //pkg:controller:function:comments comments: key:value
2014-06-18 04:19:03 +00:00
var controllerComments map [ string ] string
var importlist map [ string ] string
2016-08-08 08:44:49 +00:00
var controllerList map [ string ] map [ string ] * swagger . Item //controllername Paths items
2016-08-01 06:40:43 +00:00
var rootapi swagger . Swagger
2017-08-14 17:30:26 +00:00
var tparser * tspec . Parser
var controllerPkg * ast . Package
func convertSpecDefinitions ( specDefs spec . Definitions ) ( defs map [ string ] swagger . Schema , err error ) {
bytes , err := json . Marshal ( specDefs )
if err != nil {
return
}
defs = make ( map [ string ] swagger . Schema )
err = json . Unmarshal ( bytes , & defs )
if err != nil {
return
}
return
}
2017-08-19 02:42:22 +00:00
func parseModel ( pkg * ast . Package , typeStr string ) ( typeTitle string , err error ) {
2017-08-14 17:30:26 +00:00
if pkg == nil {
panic ( "pkg can not be nil" )
}
schema , err := tparser . Parse ( pkg , typeStr )
if err != nil {
return
}
2017-08-19 02:42:22 +00:00
typeTitle = schema . Title
2017-08-14 17:30:26 +00:00
return
}
2016-09-24 22:34:26 +00:00
// refer to builtin.go
var basicTypes = map [ string ] string {
2017-03-01 10:42:14 +00:00
"bool" : "boolean:" ,
"uint" : "integer:int32" ,
"uint8" : "integer:int32" ,
"uint16" : "integer:int32" ,
"uint32" : "integer:int32" ,
"uint64" : "integer:int64" ,
"int" : "integer:int64" ,
"int8" : "integer:int32" ,
"int16" : "integer:int32" ,
"int32" : "integer:int32" ,
"int64" : "integer:int64" ,
"uintptr" : "integer:int64" ,
"float32" : "number:float" ,
"float64" : "number:double" ,
"string" : "string:" ,
"complex64" : "number:float" ,
"complex128" : "number:double" ,
"byte" : "string:byte" ,
"rune" : "string:byte" ,
// builtin golang objects
2017-03-06 23:58:53 +00:00
"time.Time" : "string:string" ,
2017-03-01 10:42:14 +00:00
}
2014-06-18 04:19:03 +00:00
func init ( ) {
2016-08-01 06:40:43 +00:00
pkgCache = make ( map [ string ] struct { } )
2014-06-18 04:19:03 +00:00
controllerComments = make ( map [ string ] string )
importlist = make ( map [ string ] string )
2016-08-08 08:44:49 +00:00
controllerList = make ( map [ string ] map [ string ] * swagger . Item )
2017-08-14 17:30:26 +00:00
tparser = tspec . NewParser ( )
2014-06-18 04:19:03 +00:00
}
2017-08-14 17:30:26 +00:00
// GenerateDocs ...
2017-03-06 23:58:53 +00:00
func GenerateDocs ( curpath string ) {
2014-06-18 04:19:03 +00:00
fset := token . NewFileSet ( )
2017-04-24 12:10:52 +00:00
f , err := parser . ParseFile ( fset , filepath . Join ( curpath , "routers" , "router.go" ) , nil , parser . ParseComments )
2014-06-18 04:19:03 +00:00
if err != nil {
2017-03-15 16:44:29 +00:00
beeLogger . Log . Fatalf ( "Error while parsing router.go: %s" , err )
2014-06-18 04:19:03 +00:00
}
2016-08-01 06:40:43 +00:00
rootapi . Infos = swagger . Information { }
rootapi . SwaggerVersion = "2.0"
2016-11-13 14:14:48 +00:00
// Analyse API comments
2014-06-18 04:19:03 +00:00
if f . Comments != nil {
for _ , c := range f . Comments {
for _ , s := range strings . Split ( c . Text ( ) , "\n" ) {
if strings . HasPrefix ( s , "@APIVersion" ) {
2016-08-01 06:40:43 +00:00
rootapi . Infos . Version = strings . TrimSpace ( s [ len ( "@APIVersion" ) : ] )
2014-06-18 04:19:03 +00:00
} else if strings . HasPrefix ( s , "@Title" ) {
2016-08-01 06:40:43 +00:00
rootapi . Infos . Title = strings . TrimSpace ( s [ len ( "@Title" ) : ] )
2014-06-18 04:19:03 +00:00
} else if strings . HasPrefix ( s , "@Description" ) {
2016-08-01 06:40:43 +00:00
rootapi . Infos . Description = strings . TrimSpace ( s [ len ( "@Description" ) : ] )
2014-06-18 04:19:03 +00:00
} else if strings . HasPrefix ( s , "@TermsOfServiceUrl" ) {
2016-08-13 06:45:12 +00:00
rootapi . Infos . TermsOfService = strings . TrimSpace ( s [ len ( "@TermsOfServiceUrl" ) : ] )
2014-06-18 04:19:03 +00:00
} else if strings . HasPrefix ( s , "@Contact" ) {
2016-08-01 06:40:43 +00:00
rootapi . Infos . Contact . EMail = strings . TrimSpace ( s [ len ( "@Contact" ) : ] )
2016-09-14 21:09:54 +00:00
} else if strings . HasPrefix ( s , "@Name" ) {
rootapi . Infos . Contact . Name = strings . TrimSpace ( s [ len ( "@Name" ) : ] )
} else if strings . HasPrefix ( s , "@URL" ) {
rootapi . Infos . Contact . URL = strings . TrimSpace ( s [ len ( "@URL" ) : ] )
2016-11-14 11:07:22 +00:00
} else if strings . HasPrefix ( s , "@LicenseUrl" ) {
2016-09-22 14:26:39 +00:00
if rootapi . Infos . License == nil {
2016-11-20 10:46:42 +00:00
rootapi . Infos . License = & swagger . License { URL : strings . TrimSpace ( s [ len ( "@LicenseUrl" ) : ] ) }
2016-09-22 14:26:39 +00:00
} else {
2016-11-20 10:46:42 +00:00
rootapi . Infos . License . URL = strings . TrimSpace ( s [ len ( "@LicenseUrl" ) : ] )
2016-09-22 14:26:39 +00:00
}
2016-11-14 11:07:22 +00:00
} else if strings . HasPrefix ( s , "@License" ) {
2016-09-22 14:26:39 +00:00
if rootapi . Infos . License == nil {
2016-11-20 10:46:42 +00:00
rootapi . Infos . License = & swagger . License { Name : strings . TrimSpace ( s [ len ( "@License" ) : ] ) }
2016-09-22 14:26:39 +00:00
} else {
2016-11-14 11:07:22 +00:00
rootapi . Infos . License . Name = strings . TrimSpace ( s [ len ( "@License" ) : ] )
2016-09-22 14:26:39 +00:00
}
2016-09-14 21:09:54 +00:00
} else if strings . HasPrefix ( s , "@Schemes" ) {
rootapi . Schemes = strings . Split ( strings . TrimSpace ( s [ len ( "@Schemes" ) : ] ) , "," )
} else if strings . HasPrefix ( s , "@Host" ) {
rootapi . Host = strings . TrimSpace ( s [ len ( "@Host" ) : ] )
2017-05-13 22:35:18 +00:00
} else if strings . HasPrefix ( s , "@SecurityDefinition" ) {
if len ( rootapi . SecurityDefinitions ) == 0 {
rootapi . SecurityDefinitions = make ( map [ string ] swagger . Security )
}
var out swagger . Security
p := getparams ( strings . TrimSpace ( s [ len ( "@SecurityDefinition" ) : ] ) )
if len ( p ) < 2 {
beeLogger . Log . Fatalf ( "Not enough params for security: %d\n" , len ( p ) )
}
out . Type = p [ 1 ]
switch out . Type {
case "oauth2" :
if len ( p ) < 6 {
beeLogger . Log . Fatalf ( "Not enough params for oauth2: %d\n" , len ( p ) )
}
if ! ( p [ 3 ] == "implicit" || p [ 3 ] == "password" || p [ 3 ] == "application" || p [ 3 ] == "accessCode" ) {
beeLogger . Log . Fatalf ( "Unknown flow type: %s. Possible values are `implicit`, `password`, `application` or `accessCode`.\n" , p [ 1 ] )
}
out . AuthorizationURL = p [ 2 ]
out . Flow = p [ 3 ]
if len ( p ) % 2 != 0 {
out . Description = strings . Trim ( p [ len ( p ) - 1 ] , ` " ` )
}
out . Scopes = make ( map [ string ] string )
for i := 4 ; i < len ( p ) - 1 ; i += 2 {
out . Scopes [ p [ i ] ] = strings . Trim ( p [ i + 1 ] , ` " ` )
}
case "apiKey" :
if len ( p ) < 4 {
beeLogger . Log . Fatalf ( "Not enough params for apiKey: %d\n" , len ( p ) )
}
if ! ( p [ 3 ] == "header" || p [ 3 ] == "query" ) {
beeLogger . Log . Fatalf ( "Unknown in type: %s. Possible values are `query` or `header`.\n" , p [ 4 ] )
}
out . Name = p [ 2 ]
out . In = p [ 3 ]
if len ( p ) > 4 {
out . Description = strings . Trim ( p [ 4 ] , ` " ` )
}
case "basic" :
if len ( p ) > 2 {
out . Description = strings . Trim ( p [ 2 ] , ` " ` )
}
default :
beeLogger . Log . Fatalf ( "Unknown security type: %s. Possible values are `oauth2`, `apiKey` or `basic`.\n" , p [ 1 ] )
}
rootapi . SecurityDefinitions [ p [ 0 ] ] = out
} else if strings . HasPrefix ( s , "@Security" ) {
if len ( rootapi . Security ) == 0 {
rootapi . Security = make ( [ ] map [ string ] [ ] string , 0 )
}
rootapi . Security = append ( rootapi . Security , getSecurity ( s ) )
2014-06-18 04:19:03 +00:00
}
}
}
}
2016-11-13 14:14:48 +00:00
// Analyse controller package
2014-06-18 04:19:03 +00:00
for _ , im := range f . Imports {
2015-05-14 07:56:19 +00:00
localName := ""
if im . Name != nil {
localName = im . Name . Name
}
2017-04-13 16:10:00 +00:00
analyseControllerPkg ( path . Join ( curpath , "vendor" ) , localName , im . Path . Value )
2014-06-18 04:19:03 +00:00
}
for _ , d := range f . Decls {
switch specDecl := d . ( type ) {
case * ast . FuncDecl :
for _ , l := range specDecl . Body . List {
2016-08-08 08:44:49 +00:00
switch stmt := l . ( type ) {
2014-06-18 04:19:03 +00:00
case * ast . AssignStmt :
2016-08-08 08:44:49 +00:00
for _ , l := range stmt . Rhs {
2014-08-01 15:40:48 +00:00
if v , ok := l . ( * ast . CallExpr ) ; ok {
2016-11-13 14:14:48 +00:00
// Analyse NewNamespace, it will return version and the subfunction
2016-08-18 15:31:09 +00:00
if selName := v . Fun . ( * ast . SelectorExpr ) . Sel . String ( ) ; selName != "NewNamespace" {
continue
}
2016-11-13 14:14:48 +00:00
version , params := analyseNewNamespace ( v )
2016-08-18 15:31:09 +00:00
if rootapi . BasePath == "" && version != "" {
rootapi . BasePath = version
}
2014-08-01 15:40:48 +00:00
for _ , p := range params {
switch pp := p . ( type ) {
case * ast . CallExpr :
2017-04-28 14:53:38 +00:00
var controllerName string
2014-08-01 15:40:48 +00:00
if selname := pp . Fun . ( * ast . SelectorExpr ) . Sel . String ( ) ; selname == "NSNamespace" {
2016-11-13 14:14:48 +00:00
s , params := analyseNewNamespace ( pp )
2014-08-01 15:40:48 +00:00
for _ , sp := range params {
switch pp := sp . ( type ) {
case * ast . CallExpr :
if pp . Fun . ( * ast . SelectorExpr ) . Sel . String ( ) == "NSInclude" {
2016-11-13 14:14:48 +00:00
controllerName = analyseNSInclude ( s , pp )
2016-08-08 08:44:49 +00:00
if v , ok := controllerComments [ controllerName ] ; ok {
rootapi . Tags = append ( rootapi . Tags , swagger . Tag {
2016-08-16 15:32:13 +00:00
Name : strings . Trim ( s , "/" ) ,
2016-08-08 08:44:49 +00:00
Description : v ,
} )
}
2014-08-01 15:40:48 +00:00
}
2014-06-18 04:19:03 +00:00
}
}
2016-08-08 08:44:49 +00:00
} else if selname == "NSInclude" {
2016-11-13 14:14:48 +00:00
controllerName = analyseNSInclude ( "" , pp )
2014-08-01 15:40:48 +00:00
if v , ok := controllerComments [ controllerName ] ; ok {
2016-08-08 08:44:49 +00:00
rootapi . Tags = append ( rootapi . Tags , swagger . Tag {
Name : controllerName , // if the NSInclude has no prefix, we use the controllername as the tag
Description : v ,
} )
2014-08-01 15:40:48 +00:00
}
2014-06-18 04:19:03 +00:00
}
}
}
}
2014-08-01 15:40:48 +00:00
2014-06-18 04:19:03 +00:00
}
}
}
}
}
2017-08-14 17:30:26 +00:00
defs , err := convertSpecDefinitions ( tparser . Definitions ( ) )
if err != nil {
panic ( err )
}
rootapi . Definitions = defs
2016-08-01 06:40:43 +00:00
os . Mkdir ( path . Join ( curpath , "swagger" ) , 0755 )
fd , err := os . Create ( path . Join ( curpath , "swagger" , "swagger.json" ) )
2017-03-06 23:58:53 +00:00
if err != nil {
panic ( err )
}
2016-09-14 21:09:54 +00:00
fdyml , err := os . Create ( path . Join ( curpath , "swagger" , "swagger.yml" ) )
2014-06-18 04:19:03 +00:00
if err != nil {
panic ( err )
}
2016-09-14 21:09:54 +00:00
defer fdyml . Close ( )
2016-08-01 06:40:43 +00:00
defer fd . Close ( )
2016-08-13 06:45:12 +00:00
dt , err := json . MarshalIndent ( rootapi , "" , " " )
2016-09-14 21:09:54 +00:00
dtyml , erryml := yaml . Marshal ( rootapi )
if err != nil || erryml != nil {
2016-08-13 06:45:12 +00:00
panic ( err )
}
_ , err = fd . Write ( dt )
2016-09-14 21:09:54 +00:00
_ , erryml = fdyml . Write ( dtyml )
if err != nil || erryml != nil {
2014-06-18 04:19:03 +00:00
panic ( err )
}
}
2016-11-13 14:14:48 +00:00
// analyseNewNamespace returns version and the others params
func analyseNewNamespace ( ce * ast . CallExpr ) ( first string , others [ ] ast . Expr ) {
2014-06-18 04:19:03 +00:00
for i , p := range ce . Args {
if i == 0 {
switch pp := p . ( type ) {
case * ast . BasicLit :
first = strings . Trim ( pp . Value , ` " ` )
}
continue
}
others = append ( others , p )
}
return
}
2016-11-13 14:14:48 +00:00
func analyseNSInclude ( baseurl string , ce * ast . CallExpr ) string {
2014-06-18 04:19:03 +00:00
cname := ""
for _ , p := range ce . Args {
x := p . ( * ast . UnaryExpr ) . X . ( * ast . CompositeLit ) . Type . ( * ast . SelectorExpr )
if v , ok := importlist [ fmt . Sprint ( x . X ) ] ; ok {
cname = v + x . Sel . Name
}
if apis , ok := controllerList [ cname ] ; ok {
2016-08-08 08:44:49 +00:00
for rt , item := range apis {
2017-04-28 14:53:38 +00:00
tag := cname
2016-08-08 08:44:49 +00:00
if baseurl != "" {
2016-08-13 06:45:12 +00:00
rt = baseurl + rt
2016-08-16 15:32:13 +00:00
tag = strings . Trim ( baseurl , "/" )
2016-08-08 08:44:49 +00:00
}
if item . Get != nil {
item . Get . Tags = [ ] string { tag }
}
if item . Post != nil {
item . Post . Tags = [ ] string { tag }
}
if item . Put != nil {
item . Put . Tags = [ ] string { tag }
}
if item . Patch != nil {
item . Patch . Tags = [ ] string { tag }
}
if item . Head != nil {
item . Head . Tags = [ ] string { tag }
}
if item . Delete != nil {
item . Delete . Tags = [ ] string { tag }
}
if item . Options != nil {
item . Options . Tags = [ ] string { tag }
}
2016-08-08 12:48:51 +00:00
if len ( rootapi . Paths ) == 0 {
rootapi . Paths = make ( map [ string ] * swagger . Item )
}
2016-08-17 00:02:21 +00:00
rt = urlReplace ( rt )
2016-08-08 08:44:49 +00:00
rootapi . Paths [ rt ] = item
2014-06-19 08:40:55 +00:00
}
}
2014-06-18 04:19:03 +00:00
}
return cname
}
2017-04-13 16:10:00 +00:00
func analyseControllerPkg ( vendorPath , localName , pkgpath string ) {
2014-06-18 04:19:03 +00:00
pkgpath = strings . Trim ( pkgpath , "\"" )
2014-09-04 14:35:30 +00:00
if isSystemPackage ( pkgpath ) {
return
}
2016-08-01 06:40:43 +00:00
if pkgpath == "github.com/astaxie/beego" {
return
}
2015-05-14 07:56:19 +00:00
if localName != "" {
importlist [ localName ] = pkgpath
} else {
pps := strings . Split ( pkgpath , "/" )
importlist [ pps [ len ( pps ) - 1 ] ] = pkgpath
}
2017-04-24 12:10:52 +00:00
gopaths := bu . GetGOPATHs ( )
if len ( gopaths ) == 0 {
2017-03-15 16:44:29 +00:00
beeLogger . Log . Fatal ( "GOPATH environment variable is not set or empty" )
2014-06-18 04:19:03 +00:00
}
pkgRealpath := ""
2017-04-13 16:10:00 +00:00
wg , _ := filepath . EvalSymlinks ( filepath . Join ( vendorPath , pkgpath ) )
if utils . FileExists ( wg ) {
pkgRealpath = wg
} else {
2017-04-24 12:10:52 +00:00
wgopath := gopaths
2017-04-13 16:10:00 +00:00
for _ , wg := range wgopath {
wg , _ = filepath . EvalSymlinks ( filepath . Join ( wg , "src" , pkgpath ) )
if utils . FileExists ( wg ) {
pkgRealpath = wg
break
}
2014-06-18 04:19:03 +00:00
}
}
if pkgRealpath != "" {
if _ , ok := pkgCache [ pkgpath ] ; ok {
return
}
2016-08-08 08:44:49 +00:00
pkgCache [ pkgpath ] = struct { } { }
2014-06-18 04:19:03 +00:00
} else {
2017-04-13 16:10:00 +00:00
beeLogger . Log . Fatalf ( "Package '%s' does not exist in the GOPATH or vendor path" , pkgpath )
2014-06-18 04:19:03 +00:00
}
2016-11-13 14:14:48 +00:00
2014-06-18 04:19:03 +00:00
fileSet := token . NewFileSet ( )
astPkgs , err := parser . ParseDir ( fileSet , pkgRealpath , func ( info os . FileInfo ) bool {
name := info . Name ( )
return ! info . IsDir ( ) && ! strings . HasPrefix ( name , "." ) && strings . HasSuffix ( name , ".go" )
} , parser . ParseComments )
if err != nil {
2017-03-15 16:44:29 +00:00
beeLogger . Log . Fatalf ( "Error while parsing dir at '%s': %s" , pkgpath , err )
2014-06-18 04:19:03 +00:00
}
for _ , pkg := range astPkgs {
2017-08-14 17:30:26 +00:00
if pkg . Name == "controllers" {
controllerPkg = pkg
}
2014-06-18 04:19:03 +00:00
for _ , fl := range pkg . Files {
for _ , d := range fl . Decls {
switch specDecl := d . ( type ) {
case * ast . FuncDecl :
if specDecl . Recv != nil && len ( specDecl . Recv . List ) > 0 {
if t , ok := specDecl . Recv . List [ 0 ] . Type . ( * ast . StarExpr ) ; ok {
2016-11-13 14:14:48 +00:00
// Parse controller method
2017-04-25 21:10:03 +00:00
parserComments ( specDecl , fmt . Sprint ( t . X ) , pkgpath )
2014-06-18 04:19:03 +00:00
}
}
case * ast . GenDecl :
2016-08-01 06:40:43 +00:00
if specDecl . Tok == token . TYPE {
2014-06-18 04:19:03 +00:00
for _ , s := range specDecl . Specs {
switch tp := s . ( * ast . TypeSpec ) . Type . ( type ) {
case * ast . StructType :
_ = tp . Struct
2016-11-13 14:14:48 +00:00
// Parse controller definition comments
2016-08-08 08:44:49 +00:00
if strings . TrimSpace ( specDecl . Doc . Text ( ) ) != "" {
controllerComments [ pkgpath + s . ( * ast . TypeSpec ) . Name . String ( ) ] = specDecl . Doc . Text ( )
}
2014-06-18 04:19:03 +00:00
}
}
}
}
}
}
}
}
2014-09-04 14:35:30 +00:00
func isSystemPackage ( pkgpath string ) bool {
2016-11-13 14:14:48 +00:00
goroot := os . Getenv ( "GOROOT" )
2017-02-21 08:30:55 +00:00
if goroot == "" {
goroot = runtime . GOROOT ( )
}
2014-09-04 14:35:30 +00:00
if goroot == "" {
2017-03-15 16:44:29 +00:00
beeLogger . Log . Fatalf ( "GOROOT environment variable is not set or empty" )
2014-09-04 14:35:30 +00:00
}
2016-11-13 14:14:48 +00:00
2014-09-04 14:35:30 +00:00
wg , _ := filepath . EvalSymlinks ( filepath . Join ( goroot , "src" , "pkg" , pkgpath ) )
if utils . FileExists ( wg ) {
return true
}
2015-01-03 13:17:59 +00:00
//TODO(zh):support go1.4
wg , _ = filepath . EvalSymlinks ( filepath . Join ( goroot , "src" , pkgpath ) )
2017-03-06 23:58:53 +00:00
return utils . FileExists ( wg )
2014-09-04 14:35:30 +00:00
}
2016-08-20 07:12:15 +00:00
func peekNextSplitString ( ss string ) ( s string , spacePos int ) {
spacePos = strings . IndexFunc ( ss , unicode . IsSpace )
if spacePos < 0 {
s = ss
spacePos = len ( ss )
} else {
s = strings . TrimSpace ( ss [ : spacePos ] )
}
return
}
2014-06-18 04:19:03 +00:00
// parse the func comments
2017-04-25 21:10:03 +00:00
func parserComments ( f * ast . FuncDecl , controllerName , pkgpath string ) error {
2016-08-08 08:44:49 +00:00
var routerPath string
var HTTPMethod string
2016-08-08 12:48:51 +00:00
opts := swagger . Operation {
Responses : make ( map [ string ] swagger . Response ) ,
}
2017-04-25 21:10:03 +00:00
funcName := f . Name . String ( )
comments := f . Doc
funcParamMap := buildParamMap ( f . Type . Params )
//TODO: resultMap := buildParamMap(f.Type.Results)
2014-06-18 04:19:03 +00:00
if comments != nil && comments . List != nil {
for _ , c := range comments . List {
t := strings . TrimSpace ( strings . TrimLeft ( c . Text , "//" ) )
if strings . HasPrefix ( t , "@router" ) {
2014-06-20 11:49:43 +00:00
elements := strings . TrimSpace ( t [ len ( "@router" ) : ] )
2014-06-18 04:19:03 +00:00
e1 := strings . SplitN ( elements , " " , 2 )
if len ( e1 ) < 1 {
return errors . New ( "you should has router infomation" )
}
2016-08-17 00:02:21 +00:00
routerPath = e1 [ 0 ]
2014-06-18 04:19:03 +00:00
if len ( e1 ) == 2 && e1 [ 1 ] != "" {
e1 = strings . SplitN ( e1 [ 1 ] , " " , 2 )
2016-08-08 08:44:49 +00:00
HTTPMethod = strings . ToUpper ( strings . Trim ( e1 [ 0 ] , "[]" ) )
2014-06-18 04:19:03 +00:00
} else {
2016-08-08 08:44:49 +00:00
HTTPMethod = "GET"
2014-06-18 04:19:03 +00:00
}
} else if strings . HasPrefix ( t , "@Title" ) {
2016-08-08 08:44:49 +00:00
opts . OperationID = controllerName + "." + strings . TrimSpace ( t [ len ( "@Title" ) : ] )
2014-06-18 04:19:03 +00:00
} else if strings . HasPrefix ( t , "@Description" ) {
2016-08-25 16:32:39 +00:00
opts . Description = strings . TrimSpace ( t [ len ( "@Description" ) : ] )
} else if strings . HasPrefix ( t , "@Summary" ) {
opts . Summary = strings . TrimSpace ( t [ len ( "@Summary" ) : ] )
2014-06-18 04:19:03 +00:00
} else if strings . HasPrefix ( t , "@Success" ) {
2014-06-20 11:47:03 +00:00
ss := strings . TrimSpace ( t [ len ( "@Success" ) : ] )
2016-08-08 08:44:49 +00:00
rs := swagger . Response { }
2016-08-20 07:12:15 +00:00
respCode , pos := peekNextSplitString ( ss )
ss = strings . TrimSpace ( ss [ pos : ] )
respType , pos := peekNextSplitString ( ss )
if respType == "{object}" || respType == "{array}" {
isArray := respType == "{array}"
ss = strings . TrimSpace ( ss [ pos : ] )
schemaName , pos := peekNextSplitString ( ss )
if schemaName == "" {
2017-03-15 16:44:29 +00:00
beeLogger . Log . Fatalf ( "[%s.%s] Schema must follow {object} or {array}" , controllerName , funcName )
2014-06-20 11:47:03 +00:00
}
2016-08-20 07:12:15 +00:00
if strings . HasPrefix ( schemaName , "[]" ) {
schemaName = schemaName [ 2 : ]
isArray = true
2014-06-19 08:40:55 +00:00
}
2016-08-20 07:12:15 +00:00
schema := swagger . Schema { }
if sType , ok := basicTypes [ schemaName ] ; ok {
2016-08-19 16:14:39 +00:00
typeFormat := strings . Split ( sType , ":" )
2016-08-20 07:12:15 +00:00
schema . Type = typeFormat [ 0 ]
schema . Format = typeFormat [ 1 ]
2016-08-19 16:14:39 +00:00
} else {
2017-08-19 02:42:22 +00:00
typeTitle , err := parseModel ( controllerPkg , schemaName )
2017-08-14 17:30:26 +00:00
if err != nil {
beeLogger . Log . Fatalf ( "failed to parse model %s: %s" , schemaName , err )
2016-08-19 16:14:39 +00:00
}
2017-08-19 02:42:22 +00:00
schema . Ref = "#/definitions/" + typeTitle
2016-08-19 16:14:39 +00:00
}
2016-08-20 07:12:15 +00:00
if isArray {
rs . Schema = & swagger . Schema {
2016-08-24 03:07:57 +00:00
Type : "array" ,
2016-08-20 07:12:15 +00:00
Items : & schema ,
}
2016-08-24 03:07:57 +00:00
} else {
2016-08-20 07:12:15 +00:00
rs . Schema = & schema
}
rs . Description = strings . TrimSpace ( ss [ pos : ] )
} else {
rs . Description = strings . TrimSpace ( ss )
2014-06-19 08:40:55 +00:00
}
2016-08-20 07:12:15 +00:00
opts . Responses [ respCode ] = rs
2014-06-18 04:19:03 +00:00
} else if strings . HasPrefix ( t , "@Param" ) {
para := swagger . Parameter { }
2014-06-18 10:50:08 +00:00
p := getparams ( strings . TrimSpace ( t [ len ( "@Param " ) : ] ) )
2014-06-20 11:47:03 +00:00
if len ( p ) < 4 {
2017-03-15 16:44:29 +00:00
beeLogger . Log . Fatal ( controllerName + "_" + funcName + "'s comments @Param should have at least 4 params" )
2014-06-20 11:47:03 +00:00
}
2017-04-25 21:10:03 +00:00
paramNames := strings . SplitN ( p [ 0 ] , "=>" , 2 )
para . Name = paramNames [ 0 ]
funcParamName := para . Name
if len ( paramNames ) > 1 {
funcParamName = paramNames [ 1 ]
}
paramType , ok := funcParamMap [ funcParamName ]
if ok {
delete ( funcParamMap , funcParamName )
}
2016-08-18 10:21:23 +00:00
switch p [ 1 ] {
case "query" :
fallthrough
case "header" :
fallthrough
case "path" :
fallthrough
case "formData" :
fallthrough
case "body" :
2016-08-18 13:03:27 +00:00
break
2016-08-18 10:21:23 +00:00
default :
2017-03-15 16:44:29 +00:00
beeLogger . Log . Warnf ( "[%s.%s] Unknown param location: %s. Possible values are `query`, `header`, `path`, `formData` or `body`.\n" , controllerName , funcName , p [ 1 ] )
2016-08-18 10:21:23 +00:00
}
2016-08-08 08:44:49 +00:00
para . In = p [ 1 ]
2014-06-24 04:06:45 +00:00
pp := strings . Split ( p [ 2 ] , "." )
2016-08-13 06:45:12 +00:00
typ := pp [ len ( pp ) - 1 ]
if len ( pp ) >= 2 {
2017-08-19 02:42:22 +00:00
typeTitle , err := parseModel ( controllerPkg , p [ 2 ] )
2017-08-14 17:30:26 +00:00
if err != nil {
beeLogger . Log . Fatalf ( "failed to parse model %s: %s" , p [ 2 ] , err )
2016-08-13 06:45:12 +00:00
}
2017-08-14 17:30:26 +00:00
para . Schema = & swagger . Schema {
2017-08-19 02:42:22 +00:00
Ref : "#/definitions/" + typeTitle ,
2016-08-17 15:48:38 +00:00
}
2016-08-13 06:45:12 +00:00
} else {
2017-04-25 21:10:03 +00:00
if typ == "auto" {
typ = paramType
2016-08-13 06:45:12 +00:00
}
2017-04-25 21:10:03 +00:00
setParamType ( & para , typ , pkgpath , controllerName )
2016-08-13 06:45:12 +00:00
}
2016-10-29 14:36:41 +00:00
switch len ( p ) {
case 5 :
2014-06-18 04:19:03 +00:00
para . Required , _ = strconv . ParseBool ( p [ 3 ] )
2016-08-13 06:45:12 +00:00
para . Description = strings . Trim ( p [ 4 ] , ` " ` )
2016-10-29 14:36:41 +00:00
case 6 :
para . Default = str2RealType ( p [ 3 ] , para . Type )
para . Required , _ = strconv . ParseBool ( p [ 4 ] )
para . Description = strings . Trim ( p [ 5 ] , ` " ` )
default :
2016-08-13 06:45:12 +00:00
para . Description = strings . Trim ( p [ 3 ] , ` " ` )
2014-06-18 04:19:03 +00:00
}
opts . Parameters = append ( opts . Parameters , para )
} else if strings . HasPrefix ( t , "@Failure" ) {
2016-08-08 08:44:49 +00:00
rs := swagger . Response { }
2014-06-20 11:47:03 +00:00
st := strings . TrimSpace ( t [ len ( "@Failure" ) : ] )
2014-06-18 04:19:03 +00:00
var cd [ ] rune
2014-06-20 11:47:03 +00:00
var start bool
2014-06-18 04:19:03 +00:00
for i , s := range st {
2014-06-20 11:47:03 +00:00
if unicode . IsSpace ( s ) {
if start {
2016-08-08 08:44:49 +00:00
rs . Description = strings . TrimSpace ( st [ i + 1 : ] )
2014-06-20 11:47:03 +00:00
break
} else {
continue
}
2014-06-18 04:19:03 +00:00
}
2014-06-20 11:47:03 +00:00
start = true
2014-06-18 04:19:03 +00:00
cd = append ( cd , s )
}
2016-08-08 08:44:49 +00:00
opts . Responses [ string ( cd ) ] = rs
} else if strings . HasPrefix ( t , "@Deprecated" ) {
opts . Deprecated , _ = strconv . ParseBool ( strings . TrimSpace ( t [ len ( "@Deprecated" ) : ] ) )
2014-06-20 11:47:03 +00:00
} else if strings . HasPrefix ( t , "@Accept" ) {
accepts := strings . Split ( strings . TrimSpace ( strings . TrimSpace ( t [ len ( "@Accept" ) : ] ) ) , "," )
for _ , a := range accepts {
switch a {
case "json" :
opts . Consumes = append ( opts . Consumes , ajson )
opts . Produces = append ( opts . Produces , ajson )
case "xml" :
opts . Consumes = append ( opts . Consumes , axml )
opts . Produces = append ( opts . Produces , axml )
case "plain" :
opts . Consumes = append ( opts . Consumes , aplain )
opts . Produces = append ( opts . Produces , aplain )
case "html" :
opts . Consumes = append ( opts . Consumes , ahtml )
opts . Produces = append ( opts . Produces , ahtml )
2017-08-16 10:17:07 +00:00
case "form" :
opts . Consumes = append ( opts . Consumes , aform )
2014-06-20 11:47:03 +00:00
}
}
2017-05-13 22:35:18 +00:00
} else if strings . HasPrefix ( t , "@Security" ) {
if len ( opts . Security ) == 0 {
opts . Security = make ( [ ] map [ string ] [ ] string , 0 )
}
opts . Security = append ( opts . Security , getSecurity ( t ) )
2014-06-18 04:19:03 +00:00
}
}
}
2017-04-25 21:10:03 +00:00
2016-08-08 08:44:49 +00:00
if routerPath != "" {
2017-04-25 21:10:03 +00:00
//Go over function parameters which were not mapped and create swagger params for them
for name , typ := range funcParamMap {
para := swagger . Parameter { }
para . Name = name
setParamType ( & para , typ , pkgpath , controllerName )
if paramInPath ( name , routerPath ) {
para . In = "path"
} else {
para . In = "query"
}
opts . Parameters = append ( opts . Parameters , para )
}
2016-08-08 08:44:49 +00:00
var item * swagger . Item
if itemList , ok := controllerList [ pkgpath + controllerName ] ; ok {
if it , ok := itemList [ routerPath ] ; ! ok {
item = & swagger . Item { }
} else {
item = it
}
2014-06-18 04:19:03 +00:00
} else {
2016-08-08 08:44:49 +00:00
controllerList [ pkgpath + controllerName ] = make ( map [ string ] * swagger . Item )
item = & swagger . Item { }
}
2017-06-05 15:19:45 +00:00
for _ , hm := range strings . Split ( HTTPMethod , "," ) {
switch hm {
case "GET" :
item . Get = & opts
case "POST" :
item . Post = & opts
case "PUT" :
item . Put = & opts
case "PATCH" :
item . Patch = & opts
case "DELETE" :
item . Delete = & opts
case "HEAD" :
item . Head = & opts
case "OPTIONS" :
item . Options = & opts
}
2014-06-18 04:19:03 +00:00
}
2016-08-08 08:44:49 +00:00
controllerList [ pkgpath + controllerName ] [ routerPath ] = item
2014-06-18 04:19:03 +00:00
}
return nil
}
2017-04-25 21:10:03 +00:00
func setParamType ( para * swagger . Parameter , typ string , pkgpath , controllerName string ) {
isArray := false
paraType := ""
paraFormat := ""
if strings . HasPrefix ( typ , "[]" ) {
typ = typ [ 2 : ]
isArray = true
}
if typ == "string" || typ == "number" || typ == "integer" || typ == "boolean" ||
typ == "array" || typ == "file" {
paraType = typ
} else if sType , ok := basicTypes [ typ ] ; ok {
typeFormat := strings . Split ( sType , ":" )
paraType = typeFormat [ 0 ]
paraFormat = typeFormat [ 1 ]
} else {
2017-08-19 02:42:22 +00:00
typeTitle , err := parseModel ( controllerPkg , typ )
2017-08-14 17:30:26 +00:00
if err != nil {
beeLogger . Log . Fatalf ( "failed to parse model %s: %s" , typ , err )
2017-04-25 21:10:03 +00:00
}
2017-08-14 17:30:26 +00:00
para . Schema = & swagger . Schema {
2017-08-19 02:42:22 +00:00
Ref : "#/definitions/" + typeTitle ,
2017-04-25 21:10:03 +00:00
}
}
if isArray {
para . Type = "array"
para . Items = & swagger . ParameterItems {
Type : paraType ,
Format : paraFormat ,
}
} else {
para . Type = paraType
para . Format = paraFormat
}
}
func paramInPath ( name , route string ) bool {
return strings . HasSuffix ( route , ":" + name ) ||
strings . Contains ( route , ":" + name + "/" )
}
func getFunctionParamType ( t ast . Expr ) string {
switch paramType := t . ( type ) {
case * ast . Ident :
return paramType . Name
// case *ast.Ellipsis:
// result := getFunctionParamType(paramType.Elt)
// result.array = true
// return result
case * ast . ArrayType :
return "[]" + getFunctionParamType ( paramType . Elt )
case * ast . StarExpr :
return getFunctionParamType ( paramType . X )
case * ast . SelectorExpr :
return getFunctionParamType ( paramType . X ) + "." + paramType . Sel . Name
default :
return ""
}
}
func buildParamMap ( list * ast . FieldList ) map [ string ] string {
i := 0
result := map [ string ] string { }
if list != nil {
funcParams := list . List
for _ , fparam := range funcParams {
param := getFunctionParamType ( fparam . Type )
var paramName string
if len ( fparam . Names ) > 0 {
paramName = fparam . Names [ 0 ] . Name
} else {
paramName = fmt . Sprint ( i )
i ++
}
result [ paramName ] = param
}
}
return result
}
2014-06-18 04:19:03 +00:00
// analisys params return []string
// @Param query form string true "The email for login"
// [query form string true "The email for login"]
func getparams ( str string ) [ ] string {
2014-06-20 11:47:03 +00:00
var s [ ] rune
2014-06-18 04:19:03 +00:00
var j int
var start bool
var r [ ] string
2016-10-29 14:36:41 +00:00
var quoted int8
2017-05-19 01:41:47 +00:00
for _ , c := range str {
2016-10-29 14:36:41 +00:00
if unicode . IsSpace ( c ) && quoted == 0 {
2014-06-18 04:19:03 +00:00
if ! start {
continue
} else {
start = false
j ++
r = append ( r , string ( s ) )
2014-06-20 11:47:03 +00:00
s = make ( [ ] rune , 0 )
2014-06-18 04:19:03 +00:00
continue
}
}
2016-10-29 14:36:41 +00:00
2014-06-18 04:19:03 +00:00
start = true
2016-10-29 14:36:41 +00:00
if c == '"' {
quoted ^ = 1
continue
}
2014-06-18 04:19:03 +00:00
s = append ( s , c )
}
2016-10-29 14:36:41 +00:00
if len ( s ) > 0 {
r = append ( r , string ( s ) )
}
2014-06-18 04:19:03 +00:00
return r
}
2014-06-19 08:40:55 +00:00
2017-05-13 22:35:18 +00:00
func getSecurity ( t string ) ( security map [ string ] [ ] string ) {
security = make ( map [ string ] [ ] string )
p := getparams ( strings . TrimSpace ( t [ len ( "@Security" ) : ] ) )
if len ( p ) == 0 {
beeLogger . Log . Fatalf ( "No params for security specified\n" )
}
security [ p [ 0 ] ] = make ( [ ] string , 0 )
for i := 1 ; i < len ( p ) ; i ++ {
security [ p [ 0 ] ] = append ( security [ p [ 0 ] ] , p [ i ] )
}
return
}
2016-08-16 15:32:13 +00:00
func urlReplace ( src string ) string {
pt := strings . Split ( src , "/" )
for i , p := range pt {
if len ( p ) > 0 {
if p [ 0 ] == ':' {
pt [ i ] = "{" + p [ 1 : ] + "}"
} else if p [ 0 ] == '?' && p [ 1 ] == ':' {
pt [ i ] = "{" + p [ 2 : ] + "}"
}
}
}
return strings . Join ( pt , "/" )
}
2016-10-29 14:36:41 +00:00
func str2RealType ( s string , typ string ) interface { } {
var err error
var ret interface { }
switch typ {
case "int" , "int64" , "int32" , "int16" , "int8" :
ret , err = strconv . Atoi ( s )
case "bool" :
ret , err = strconv . ParseBool ( s )
case "float64" :
ret , err = strconv . ParseFloat ( s , 64 )
case "float32" :
ret , err = strconv . ParseFloat ( s , 32 )
default :
return s
}
if err != nil {
2017-03-15 16:44:29 +00:00
beeLogger . Log . Warnf ( "Invalid default value type '%s': %s" , typ , s )
2016-10-29 14:36:41 +00:00
return s
}
return ret
}