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"
2014-06-23 14:00:57 +00:00
"reflect"
2014-06-21 13:27:12 +00:00
"regexp"
2017-02-21 08:30:55 +00:00
"runtime"
2018-03-12 07:52:00 +00:00
"sort"
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
2019-01-07 18:04:40 +00:00
yaml "gopkg.in/yaml.v2"
2016-09-14 21:09:54 +00:00
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"
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
)
2018-10-06 20:32:51 +00:00
const (
astTypeArray = "array"
astTypeObject = "object"
astTypeMap = "map"
)
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 modelsList map [ string ] map [ string ] swagger . Schema
var rootapi swagger . Swagger
2017-08-01 12:33:45 +00:00
var astPkgs [ ] * ast . Package
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
2018-10-06 20:32:51 +00:00
"time.Time" : "string:datetime" ,
"json.RawMessage" : "object:" ,
2017-03-01 10:42:14 +00:00
}
var stdlibObject = map [ string ] string {
2018-10-06 20:32:51 +00:00
"&{time Time}" : "time.Time" ,
"&{json RawMessage}" : "json.RawMessage" ,
2016-09-24 22:34:26 +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 )
2016-08-01 06:40:43 +00:00
modelsList = make ( map [ string ] map [ string ] swagger . Schema )
2017-08-01 12:33:45 +00:00
astPkgs = make ( [ ] * ast . Package , 0 )
2016-09-24 22:34:26 +00:00
}
2018-03-12 07:52:00 +00:00
// ParsePackagesFromDir parses packages from a given directory
2017-03-06 23:58:53 +00:00
func ParsePackagesFromDir ( dirpath string ) {
2016-11-20 10:46:42 +00:00
c := make ( chan error )
go func ( ) {
filepath . Walk ( dirpath , func ( fpath string , fileInfo os . FileInfo , err error ) error {
if err != nil {
return nil
}
if ! fileInfo . IsDir ( ) {
return nil
}
2017-10-04 13:54:05 +00:00
// skip folder if it's a 'vendor' folder within dirpath or its child,
// all 'tests' folders and dot folders wihin dirpath
d , _ := filepath . Rel ( dirpath , fpath )
if ! ( d == "vendor" || strings . HasPrefix ( d , "vendor" + string ( os . PathSeparator ) ) ) &&
2019-02-12 13:23:17 +00:00
! strings . Contains ( d , "tests" ) &&
2017-10-04 13:54:05 +00:00
! ( d [ 0 ] == '.' ) {
2016-11-20 10:46:42 +00:00
err = parsePackageFromDir ( fpath )
if err != nil {
// Send the error to through the channel and continue walking
2019-01-07 18:04:40 +00:00
c <- fmt . Errorf ( "error while parsing directory: %s" , err . Error ( ) )
2016-11-20 10:46:42 +00:00
return nil
}
}
return nil
} )
close ( c )
} ( )
for err := range c {
2017-03-06 23:58:53 +00:00
beeLogger . Log . Warnf ( "%s" , err )
2016-09-24 22:34:26 +00:00
}
}
2016-11-20 10:46:42 +00:00
func parsePackageFromDir ( path string ) error {
2016-09-24 22:34:26 +00:00
fileSet := token . NewFileSet ( )
folderPkgs , err := parser . ParseDir ( fileSet , path , func ( info os . FileInfo ) bool {
name := info . Name ( )
return ! info . IsDir ( ) && ! strings . HasPrefix ( name , "." ) && strings . HasSuffix ( name , ".go" )
} , parser . ParseComments )
if err != nil {
2016-11-20 10:46:42 +00:00
return err
2016-09-24 22:34:26 +00:00
}
2016-11-13 14:14:48 +00:00
2017-08-01 12:33:45 +00:00
for _ , v := range folderPkgs {
astPkgs = append ( astPkgs , v )
2016-09-24 22:34:26 +00:00
}
2016-11-20 10:46:42 +00:00
return nil
2014-06-18 04:19:03 +00:00
}
2018-03-12 07:52:00 +00:00
// GenerateDocs generates documentations for a given path.
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 {
2018-01-09 08:49:57 +00:00
// Analyze NewNamespace, it will return version and the subfunction
selExpr , selOK := v . Fun . ( * ast . SelectorExpr )
if ! selOK || selExpr . Sel . Name != "NewNamespace" {
2016-08-18 15:31:09 +00:00
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
}
}
}
}
}
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 {
2018-07-22 03:59:24 +00:00
var x * ast . SelectorExpr
var p1 interface { } = p
if ident , ok := p1 . ( * ast . Ident ) ; ok {
if assign , ok := ident . Obj . Decl . ( * ast . AssignStmt ) ; ok {
if len ( assign . Rhs ) > 0 {
p1 = assign . Rhs [ 0 ] . ( * ast . UnaryExpr )
}
}
}
if _ , ok := p1 . ( * ast . UnaryExpr ) ; ok {
x = p1 . ( * ast . UnaryExpr ) . X . ( * ast . CompositeLit ) . Type . ( * ast . SelectorExpr )
} else {
beeLogger . Log . Warnf ( "Couldn't determine type\n" )
continue
}
2014-06-18 04:19:03 +00:00
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
}
2020-06-25 14:29:27 +00:00
2014-06-18 04:19:03 +00:00
pkgRealpath := ""
2020-07-11 05:02:25 +00:00
if bu . IsGOMODULE ( ) {
2020-06-25 14:29:27 +00:00
pkgRealpath = filepath . Join ( bu . GetBeeWorkPath ( ) , ".." , pkgpath )
2017-04-13 16:10:00 +00:00
} else {
2020-06-25 14:29:27 +00:00
gopaths := bu . GetGOPATHs ( )
if len ( gopaths ) == 0 {
beeLogger . Log . Fatal ( "GOPATH environment variable is not set or empty" )
}
wg , _ := filepath . EvalSymlinks ( filepath . Join ( vendorPath , pkgpath ) )
if utils . FileExists ( wg ) {
pkgRealpath = wg
} else {
wgopath := gopaths
for _ , wg := range wgopath {
wg , _ = filepath . EvalSymlinks ( filepath . Join ( wg , "src" , pkgpath ) )
if utils . FileExists ( wg ) {
pkgRealpath = wg
break
}
2017-04-13 16:10:00 +00:00
}
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
}
2020-06-25 14:29:27 +00:00
2014-06-18 04:19:03 +00:00
for _ , pkg := range astPkgs {
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 {
2019-01-07 18:07:11 +00:00
t := strings . TrimSpace ( strings . TrimPrefix ( c . Text , "//" ) )
2014-06-18 04:19:03 +00:00
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 {
2016-09-24 22:34:26 +00:00
m , mod , realTypes := getModel ( schemaName )
2016-08-20 07:12:15 +00:00
schema . Ref = "#/definitions/" + m
2016-08-19 16:14:39 +00:00
if _ , ok := modelsList [ pkgpath + controllerName ] ; ! ok {
2017-03-06 23:58:53 +00:00
modelsList [ pkgpath + controllerName ] = make ( map [ string ] swagger . Schema )
2016-08-19 16:14:39 +00:00
}
2016-08-20 07:12:15 +00:00
modelsList [ pkgpath + controllerName ] [ schemaName ] = mod
2016-09-24 22:34:26 +00:00
appendModels ( pkgpath , controllerName , realTypes )
2016-08-19 16:14:39 +00:00
}
2016-08-20 07:12:15 +00:00
if isArray {
rs . Schema = & swagger . Schema {
2018-10-06 20:32:51 +00:00
Type : astTypeArray ,
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-07-19 06:42:46 +00:00
isArray := false
2017-07-19 07:38:04 +00:00
if p [ 1 ] == "body" && strings . HasPrefix ( p [ 2 ] , "[]" ) {
2017-07-19 06:42:46 +00:00
p [ 2 ] = p [ 2 ] [ 2 : ]
isArray = true
}
2016-09-24 22:34:26 +00:00
m , mod , realTypes := getModel ( p [ 2 ] )
2017-07-19 06:42:46 +00:00
if isArray {
para . Schema = & swagger . Schema {
2018-10-06 20:32:51 +00:00
Type : astTypeArray ,
2017-07-19 06:42:46 +00:00
Items : & swagger . Schema {
Ref : "#/definitions/" + m ,
} ,
}
} else {
para . Schema = & swagger . Schema {
Ref : "#/definitions/" + m ,
}
2016-08-13 06:45:12 +00:00
}
2017-07-19 06:42:46 +00:00
2016-08-17 15:48:38 +00:00
if _ , ok := modelsList [ pkgpath + controllerName ] ; ! ok {
2017-03-06 23:58:53 +00:00
modelsList [ pkgpath + controllerName ] = make ( map [ string ] swagger . Schema )
2016-08-17 15:48:38 +00:00
}
modelsList [ pkgpath + controllerName ] [ typ ] = mod
2016-09-24 22:34:26 +00:00
appendModels ( pkgpath , controllerName , realTypes )
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
}
}
}
2019-01-08 04:04:02 +00:00
routerPath = urlReplace ( routerPath )
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" ||
2018-10-06 20:32:51 +00:00
typ == astTypeArray || typ == "file" {
2017-04-25 21:10:03 +00:00
paraType = typ
2019-04-10 15:32:55 +00:00
if para . In == "body" {
para . Schema = & swagger . Schema {
Type : paraType ,
}
}
2017-04-25 21:10:03 +00:00
} else if sType , ok := basicTypes [ typ ] ; ok {
typeFormat := strings . Split ( sType , ":" )
paraType = typeFormat [ 0 ]
paraFormat = typeFormat [ 1 ]
2019-04-10 15:32:55 +00:00
if para . In == "body" {
para . Schema = & swagger . Schema {
2020-06-25 14:29:27 +00:00
Type : paraType ,
2019-04-10 15:32:55 +00:00
Format : paraFormat ,
}
}
2017-04-25 21:10:03 +00:00
} else {
m , mod , realTypes := getModel ( typ )
para . Schema = & swagger . Schema {
Ref : "#/definitions/" + m ,
}
if _ , ok := modelsList [ pkgpath + controllerName ] ; ! ok {
modelsList [ pkgpath + controllerName ] = make ( map [ string ] swagger . Schema )
}
modelsList [ pkgpath + controllerName ] [ typ ] = mod
appendModels ( pkgpath , controllerName , realTypes )
}
if isArray {
2017-07-19 07:49:54 +00:00
if para . In == "body" {
para . Schema = & swagger . Schema {
2018-10-06 20:32:51 +00:00
Type : astTypeArray ,
2017-07-19 07:49:54 +00:00
Items : & swagger . Schema {
Type : paraType ,
Format : paraFormat ,
} ,
}
} else {
2018-10-06 20:32:51 +00:00
para . Type = astTypeArray
2017-07-19 07:49:54 +00:00
para . Items = & swagger . ParameterItems {
2017-07-19 06:42:46 +00:00
Type : paraType ,
Format : paraFormat ,
2017-07-19 07:49:54 +00:00
}
2017-04-25 21:10:03 +00:00
}
} 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
2018-10-06 20:32:51 +00:00
func getModel ( str string ) ( definitionName string , m swagger . Schema , realTypes [ ] string ) {
2014-06-19 08:40:55 +00:00
strs := strings . Split ( str , "." )
2018-10-06 20:32:51 +00:00
// strs = [packageName].[objectName]
packageName := strs [ 0 ]
objectname := strs [ len ( strs ) - 1 ]
// Default all swagger schemas to object, if no other type is found
m . Type = astTypeObject
L :
2014-06-19 08:40:55 +00:00
for _ , pkg := range astPkgs {
2017-08-01 12:33:45 +00:00
if strs [ 0 ] == pkg . Name {
for _ , fl := range pkg . Files {
for k , d := range fl . Scope . Objects {
if d . Kind == ast . Typ {
if k != objectname {
2018-10-06 20:32:51 +00:00
// Still searching for the right object
2017-08-01 12:33:45 +00:00
continue
}
2018-10-06 20:32:51 +00:00
parseObject ( d , k , & m , & realTypes , astPkgs , packageName )
// When we've found the correct object, we can stop searching
break L
2014-06-19 08:40:55 +00:00
}
2016-08-24 22:42:23 +00:00
}
}
}
}
2018-10-06 20:32:51 +00:00
2016-08-24 22:42:23 +00:00
if m . Title == "" {
2018-10-06 20:32:51 +00:00
// Don't log when error has already been logged
if _ , found := rootapi . Definitions [ str ] ; ! found {
beeLogger . Log . Warnf ( "Cannot find the object: %s" , str )
}
m . Title = objectname
2016-08-24 22:42:23 +00:00
// TODO remove when all type have been supported
}
if len ( rootapi . Definitions ) == 0 {
rootapi . Definitions = make ( map [ string ] swagger . Schema )
}
2018-10-06 20:32:51 +00:00
rootapi . Definitions [ str ] = m
return str , m , realTypes
2016-08-24 22:42:23 +00:00
}
2017-08-01 12:33:45 +00:00
func parseObject ( d * ast . Object , k string , m * swagger . Schema , realTypes * [ ] string , astPkgs [ ] * ast . Package , packageName string ) {
2016-08-24 22:42:23 +00:00
ts , ok := d . Decl . ( * ast . TypeSpec )
if ! ok {
2018-10-06 20:32:51 +00:00
beeLogger . Log . Fatalf ( "Unknown type without TypeSec: %v" , d )
2016-08-24 22:42:23 +00:00
}
2018-10-06 20:32:51 +00:00
// TODO support other types, such as `MapType`, `InterfaceType` etc...
2017-11-13 06:29:44 +00:00
switch t := ts . Type . ( type ) {
2018-03-16 05:34:54 +00:00
case * ast . ArrayType :
m . Title = k
2018-10-06 20:32:51 +00:00
m . Type = astTypeArray
2018-03-16 05:34:54 +00:00
if isBasicType ( fmt . Sprint ( t . Elt ) ) {
typeFormat := strings . Split ( basicTypes [ fmt . Sprint ( t . Elt ) ] , ":" )
m . Format = typeFormat [ 0 ]
} else {
objectName := packageName + "." + fmt . Sprint ( t . Elt )
if _ , ok := rootapi . Definitions [ objectName ] ; ! ok {
objectName , _ , _ = getModel ( objectName )
}
m . Items = & swagger . Schema {
Ref : "#/definitions/" + objectName ,
}
}
2017-11-13 06:29:44 +00:00
case * ast . Ident :
2018-03-10 09:35:59 +00:00
parseIdent ( t , k , m , astPkgs )
2017-11-13 06:29:44 +00:00
case * ast . StructType :
parseStruct ( t , k , m , realTypes , astPkgs , packageName )
2016-08-24 22:42:23 +00:00
}
2017-11-13 06:29:44 +00:00
}
2018-03-12 07:52:00 +00:00
// parse as enum, in the package, find out all consts with the same type
2018-03-10 09:35:59 +00:00
func parseIdent ( st * ast . Ident , k string , m * swagger . Schema , astPkgs [ ] * ast . Package ) {
2017-11-13 06:29:44 +00:00
m . Title = k
2018-03-10 09:35:59 +00:00
basicType := fmt . Sprint ( st )
if object , isStdLibObject := stdlibObject [ basicType ] ; isStdLibObject {
basicType = object
}
2018-10-06 20:32:51 +00:00
if t , ok := basicTypes [ basicType ] ; ok {
typeFormat := strings . Split ( t , ":" )
2018-03-10 09:35:59 +00:00
m . Type = typeFormat [ 0 ]
m . Format = typeFormat [ 1 ]
}
2018-03-12 07:52:00 +00:00
enums := make ( map [ int ] string )
2018-03-15 10:05:37 +00:00
enumValues := make ( map [ int ] interface { } )
2017-11-13 06:29:44 +00:00
for _ , pkg := range astPkgs {
for _ , fl := range pkg . Files {
for _ , obj := range fl . Scope . Objects {
if obj . Kind == ast . Con {
vs , ok := obj . Decl . ( * ast . ValueSpec )
if ! ok {
2018-10-06 20:32:51 +00:00
beeLogger . Log . Fatalf ( "Unknown type without ValueSpec: %v" , vs )
2017-11-13 06:29:44 +00:00
}
2018-03-12 07:52:00 +00:00
ti , ok := vs . Type . ( * ast . Ident )
if ! ok {
// TODO type inference, iota not support yet
continue
}
// Only add the enums that are defined by the current identifier
if ti . Name != k {
continue
}
// For all names and values, aggregate them by it's position so that we can sort them later.
for i , val := range vs . Values {
v , ok := val . ( * ast . BasicLit )
if ! ok {
2018-10-06 20:32:51 +00:00
beeLogger . Log . Warnf ( "Unknown type without BasicLit: %v" , v )
2018-03-12 07:52:00 +00:00
continue
}
enums [ int ( val . Pos ( ) ) ] = fmt . Sprintf ( "%s = %s" , vs . Names [ i ] . Name , v . Value )
2018-03-15 10:05:37 +00:00
switch v . Kind {
case token . INT :
vv , err := strconv . Atoi ( v . Value )
if err != nil {
2018-10-06 20:32:51 +00:00
beeLogger . Log . Warnf ( "Unknown type with BasicLit to int: %v" , v . Value )
2018-03-15 10:05:37 +00:00
continue
}
enumValues [ int ( val . Pos ( ) ) ] = vv
case token . FLOAT :
vv , err := strconv . ParseFloat ( v . Value , 64 )
if err != nil {
2018-10-06 20:32:51 +00:00
beeLogger . Log . Warnf ( "Unknown type with BasicLit to int: %v" , v . Value )
2018-03-15 10:05:37 +00:00
continue
}
enumValues [ int ( val . Pos ( ) ) ] = vv
default :
enumValues [ int ( val . Pos ( ) ) ] = strings . Trim ( v . Value , ` " ` )
}
2017-11-13 06:29:44 +00:00
}
}
}
}
}
2018-03-12 07:52:00 +00:00
// Sort the enums by position
if len ( enums ) > 0 {
var keys [ ] int
for k := range enums {
keys = append ( keys , k )
}
sort . Ints ( keys )
for _ , k := range keys {
m . Enum = append ( m . Enum , enums [ k ] )
}
// Automatically use the first enum value as the example.
m . Example = enumValues [ keys [ 0 ] ]
}
2017-11-13 06:29:44 +00:00
}
func parseStruct ( st * ast . StructType , k string , m * swagger . Schema , realTypes * [ ] string , astPkgs [ ] * ast . Package , packageName string ) {
2016-08-24 22:42:23 +00:00
m . Title = k
if st . Fields . List != nil {
m . Properties = make ( map [ string ] swagger . Propertie )
for _ , field := range st . Fields . List {
isSlice , realType , sType := typeAnalyser ( field )
2018-10-06 20:32:51 +00:00
if ( isSlice && isBasicType ( realType ) ) || sType == astTypeObject {
2016-09-24 22:34:26 +00:00
if len ( strings . Split ( realType , " " ) ) > 1 {
realType = strings . Replace ( realType , " " , "." , - 1 )
realType = strings . Replace ( realType , "&" , "" , - 1 )
realType = strings . Replace ( realType , "{" , "" , - 1 )
realType = strings . Replace ( realType , "}" , "" , - 1 )
} else {
realType = packageName + "." + realType
}
}
2016-08-24 22:42:23 +00:00
* realTypes = append ( * realTypes , realType )
mp := swagger . Propertie { }
2018-03-10 09:35:59 +00:00
isObject := false
2016-08-24 22:42:23 +00:00
if isSlice {
2018-10-06 20:32:51 +00:00
mp . Type = astTypeArray
if t , ok := basicTypes [ ( strings . Replace ( realType , "[]" , "" , - 1 ) ) ] ; ok {
typeFormat := strings . Split ( t , ":" )
2016-08-24 22:42:23 +00:00
mp . Items = & swagger . Propertie {
Type : typeFormat [ 0 ] ,
Format : typeFormat [ 1 ] ,
2014-06-19 08:40:55 +00:00
}
2016-08-24 22:42:23 +00:00
} else {
mp . Items = & swagger . Propertie {
Ref : "#/definitions/" + realType ,
2014-06-19 08:40:55 +00:00
}
2016-08-24 22:42:23 +00:00
}
} else {
2018-10-06 20:32:51 +00:00
if sType == astTypeObject {
2018-03-10 09:35:59 +00:00
isObject = true
2016-09-22 15:04:58 +00:00
mp . Ref = "#/definitions/" + realType
} else if isBasicType ( realType ) {
2016-08-24 22:42:23 +00:00
typeFormat := strings . Split ( sType , ":" )
mp . Type = typeFormat [ 0 ]
mp . Format = typeFormat [ 1 ]
2018-10-06 20:32:51 +00:00
} else if realType == astTypeMap {
2016-09-22 15:04:58 +00:00
typeFormat := strings . Split ( sType , ":" )
mp . AdditionalProperties = & swagger . Propertie {
Type : typeFormat [ 0 ] ,
Format : typeFormat [ 1 ] ,
}
2016-08-24 22:42:23 +00:00
}
}
if field . Names != nil {
2014-10-22 14:45:26 +00:00
2016-08-24 22:42:23 +00:00
// set property name as field name
var name = field . Names [ 0 ] . Name
2014-10-22 14:45:26 +00:00
2016-08-24 22:42:23 +00:00
// if no tag skip tag processing
if field . Tag == nil {
m . Properties [ name ] = mp
continue
}
2014-10-22 14:45:26 +00:00
2016-08-24 22:42:23 +00:00
var tagValues [ ] string
2016-10-29 14:36:41 +00:00
2016-08-24 22:42:23 +00:00
stag := reflect . StructTag ( strings . Trim ( field . Tag . Value , "`" ) )
2016-10-29 14:36:41 +00:00
2016-10-08 08:35:26 +00:00
defaultValue := stag . Get ( "doc" )
2016-10-29 14:36:41 +00:00
if defaultValue != "" {
2016-10-08 08:35:26 +00:00
r , _ := regexp . Compile ( ` default\((.*)\) ` )
if r . MatchString ( defaultValue ) {
res := r . FindStringSubmatch ( defaultValue )
2016-10-29 14:36:41 +00:00
mp . Default = str2RealType ( res [ 1 ] , realType )
2016-10-10 14:36:51 +00:00
2016-10-29 14:36:41 +00:00
} else {
2017-03-15 16:44:29 +00:00
beeLogger . Log . Warnf ( "Invalid default value: %s" , defaultValue )
2016-10-08 08:35:26 +00:00
}
}
2016-10-29 14:36:41 +00:00
2016-08-24 22:42:23 +00:00
tag := stag . Get ( "json" )
if tag != "" {
tagValues = strings . Split ( tag , "," )
}
2014-10-22 14:45:26 +00:00
2016-08-24 22:42:23 +00:00
// dont add property if json tag first value is "-"
if len ( tagValues ) == 0 || tagValues [ 0 ] != "-" {
2014-10-22 14:45:26 +00:00
2016-08-24 22:42:23 +00:00
// set property name to the left most json tag value only if is not omitempty
if len ( tagValues ) > 0 && tagValues [ 0 ] != "omitempty" {
name = tagValues [ 0 ]
}
2014-10-22 14:45:26 +00:00
2016-08-24 22:42:23 +00:00
if thrifttag := stag . Get ( "thrift" ) ; thrifttag != "" {
ts := strings . Split ( thrifttag , "," )
if ts [ 0 ] != "" {
name = ts [ 0 ]
}
}
if required := stag . Get ( "required" ) ; required != "" {
m . Required = append ( m . Required , name )
}
if desc := stag . Get ( "description" ) ; desc != "" {
mp . Description = desc
}
2014-10-22 14:45:26 +00:00
2018-03-10 09:35:59 +00:00
if example := stag . Get ( "example" ) ; example != "" && ! isObject && ! isSlice {
mp . Example = str2RealType ( example , realType )
}
2020-07-29 08:29:06 +00:00
if len ( tagValues ) == 2 && tagValues [ 1 ] == "string" {
mp . Type = "string"
}
2016-08-24 22:42:23 +00:00
m . Properties [ name ] = mp
}
if ignore := stag . Get ( "ignore" ) ; ignore != "" {
continue
}
} else {
2017-11-20 08:32:19 +00:00
// only parse case of when embedded field is TypeName
// cases of *TypeName and Interface are not handled, maybe useless for swagger spec
tag := ""
if field . Tag != nil {
stag := reflect . StructTag ( strings . Trim ( field . Tag . Value , "`" ) )
tag = stag . Get ( "json" )
}
if tag != "" {
tagValues := strings . Split ( tag , "," )
if tagValues [ 0 ] == "-" {
//if json tag is "-", omit
continue
} else {
//if json tag is "something", output: something #definition/pkgname.Type
m . Properties [ tagValues [ 0 ] ] = mp
continue
}
} else {
//if no json tag, expand all fields of the type here
nm := & swagger . Schema { }
for _ , pkg := range astPkgs {
for _ , fl := range pkg . Files {
for nameOfObj , obj := range fl . Scope . Objects {
2019-01-08 04:04:02 +00:00
if pkg . Name + "." + obj . Name == realType {
2017-11-20 08:32:19 +00:00
parseObject ( obj , nameOfObj , nm , realTypes , astPkgs , pkg . Name )
}
2014-06-20 04:03:42 +00:00
}
2014-06-19 08:40:55 +00:00
}
}
2017-11-20 08:32:19 +00:00
for name , p := range nm . Properties {
m . Properties [ name ] = p
}
continue
2014-06-19 08:40:55 +00:00
}
}
}
}
}
2014-06-21 13:27:12 +00:00
2016-08-16 15:32:13 +00:00
func typeAnalyser ( f * ast . Field ) ( isSlice bool , realType , swaggerType string ) {
2014-06-23 13:25:12 +00:00
if arr , ok := f . Type . ( * ast . ArrayType ) ; ok {
2014-06-23 13:38:19 +00:00
if isBasicType ( fmt . Sprint ( arr . Elt ) ) {
2017-05-05 03:31:03 +00:00
return true , fmt . Sprintf ( "[]%v" , arr . Elt ) , basicTypes [ fmt . Sprint ( arr . Elt ) ]
2014-06-23 13:25:12 +00:00
}
2014-06-23 14:00:57 +00:00
if mp , ok := arr . Elt . ( * ast . MapType ) ; ok {
2018-10-06 20:32:51 +00:00
return false , fmt . Sprintf ( "map[%v][%v]" , mp . Key , mp . Value ) , astTypeObject
2014-06-23 13:25:12 +00:00
}
if star , ok := arr . Elt . ( * ast . StarExpr ) ; ok {
2018-10-06 20:32:51 +00:00
return true , fmt . Sprint ( star . X ) , astTypeObject
2014-06-24 04:06:45 +00:00
}
2018-10-06 20:32:51 +00:00
return true , fmt . Sprint ( arr . Elt ) , astTypeObject
2016-07-22 23:05:01 +00:00
}
switch t := f . Type . ( type ) {
case * ast . StarExpr :
2017-05-08 00:38:06 +00:00
basicType := fmt . Sprint ( t . X )
2018-05-10 06:37:03 +00:00
if object , isStdLibObject := stdlibObject [ basicType ] ; isStdLibObject {
basicType = object
}
2017-05-08 00:38:06 +00:00
if k , ok := basicTypes [ basicType ] ; ok {
return false , basicType , k
}
2018-10-06 20:32:51 +00:00
return false , basicType , astTypeObject
2016-08-16 15:32:13 +00:00
case * ast . MapType :
2016-09-22 15:04:58 +00:00
val := fmt . Sprintf ( "%v" , t . Value )
if isBasicType ( val ) {
2018-10-06 20:32:51 +00:00
return false , astTypeMap , basicTypes [ val ]
2016-09-22 15:04:58 +00:00
}
2018-10-06 20:32:51 +00:00
return false , val , astTypeObject
2016-08-16 15:32:13 +00:00
}
2017-03-01 10:42:14 +00:00
basicType := fmt . Sprint ( f . Type )
if object , isStdLibObject := stdlibObject [ basicType ] ; isStdLibObject {
basicType = object
}
if k , ok := basicTypes [ basicType ] ; ok {
return false , basicType , k
2014-06-21 13:27:12 +00:00
}
2018-10-06 20:32:51 +00:00
return false , basicType , astTypeObject
2014-06-21 13:27:12 +00:00
}
func isBasicType ( Type string ) bool {
2016-08-16 15:32:13 +00:00
if _ , ok := basicTypes [ Type ] ; ok {
return true
2014-06-21 13:27:12 +00:00
}
return false
}
// append models
2016-09-24 22:34:26 +00:00
func appendModels ( pkgpath , controllerName string , realTypes [ ] string ) {
2014-06-21 13:27:12 +00:00
for _ , realType := range realTypes {
2014-06-24 04:06:45 +00:00
if realType != "" && ! isBasicType ( strings . TrimLeft ( realType , "[]" ) ) &&
2018-10-06 20:32:51 +00:00
! strings . HasPrefix ( realType , astTypeMap ) && ! strings . HasPrefix ( realType , "&" ) {
2016-09-24 22:34:26 +00:00
if _ , ok := modelsList [ pkgpath + controllerName ] [ realType ] ; ok {
2014-08-01 08:04:09 +00:00
continue
2014-06-23 13:25:12 +00:00
}
2016-09-24 22:34:26 +00:00
_ , mod , newRealTypes := getModel ( realType )
modelsList [ pkgpath + controllerName ] [ realType ] = mod
appendModels ( pkgpath , controllerName , newRealTypes )
2014-06-21 13:27:12 +00:00
}
}
}
2016-08-16 15:32:13 +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 : ] + "}"
}
2018-03-23 06:29:39 +00:00
if pt [ i ] [ 0 ] == '{' && strings . Contains ( pt [ i ] , ":" ) {
pt [ i ] = pt [ i ] [ : strings . Index ( pt [ i ] , ":" ) ] + "}"
} else if pt [ i ] [ 0 ] == '{' && strings . Contains ( pt [ i ] , "(" ) {
pt [ i ] = pt [ i ] [ : strings . Index ( pt [ i ] , "(" ) ] + "}"
}
2016-08-16 15:32:13 +00:00
}
}
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 )
2017-12-27 16:47:14 +00:00
case "uint" , "uint64" , "uint32" , "uint16" , "uint8" :
ret , err = strconv . ParseUint ( s , 10 , 0 )
2016-10-29 14:36:41 +00:00
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
}