123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547 |
- package main
- import (
- "encoding/json"
- "errors"
- "flag"
- "fmt"
- "go/ast"
- "go/parser"
- "go/token"
- "os"
- "path"
- "path/filepath"
- "reflect"
- "runtime"
- "strings"
- )
- // gloabl var.
- var (
- ErrParams = errors.New("err params")
- _gopath = filepath.SplitList(os.Getenv("GOPATH"))
- )
- var (
- dir string
- pkgs = make(map[string]*ast.Package)
- rlpkgs = make(map[string]*ast.Package)
- definitions = make(map[string]*Schema)
- swagger = Swagger{
- Definitions: make(map[string]*Schema),
- Paths: make(map[string]*Item),
- SwaggerVersion: "2.0",
- Infos: Information{
- Title: "go-common api",
- Description: "api",
- Version: "1.0",
- Contact: Contact{
- EMail: "lintanghui@bilibili.com",
- },
- License: &License{
- Name: "Apache 2.0",
- URL: "http://www.apache.org/licenses/LICENSE-2.0.html",
- },
- },
- }
- stdlibObject = map[string]string{
- "&{time Time}": "time.Time",
- }
- )
- // refer to builtin.go
- var basicTypes = map[string]string{
- "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
- "time.Time": "string:string",
- }
- func main() {
- flag.StringVar(&dir, "d", "./", "specific project dir")
- flag.Parse()
- err := ParseFromDir(dir)
- if err != nil {
- panic(err)
- }
- parseModel(pkgs)
- parseModel(rlpkgs)
- parseRouter()
- fd, err := os.Create(path.Join(dir, "swagger.json"))
- if err != nil {
- panic(err)
- }
- b, _ := json.MarshalIndent(swagger, "", " ")
- fd.Write(b)
- }
- // ParseFromDir parse ast pkg from dir.
- func ParseFromDir(dir string) (err error) {
- filepath.Walk(dir, func(fpath string, fileInfo os.FileInfo, err error) error {
- if err != nil {
- return nil
- }
- if !fileInfo.IsDir() {
- return nil
- }
- err = parseFromDir(fpath)
- return err
- })
- return
- }
- func parseFromDir(dir string) (err error) {
- fset := token.NewFileSet()
- pkgFolder, err := parser.ParseDir(fset, dir, func(info os.FileInfo) bool {
- name := info.Name()
- return !info.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go")
- }, parser.ParseComments)
- if err != nil {
- return
- }
- for k, p := range pkgFolder {
- pkgs[k] = p
- }
- return
- }
- func parseImport(dir string) (err error) {
- fset := token.NewFileSet()
- pkgFolder, err := parser.ParseDir(fset, dir, func(info os.FileInfo) bool {
- name := info.Name()
- return !info.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go")
- }, parser.ParseComments)
- if err != nil {
- return
- }
- for k, p := range pkgFolder {
- rlpkgs[k] = p
- }
- return
- }
- func parseModel(pkgs map[string]*ast.Package) {
- for _, p := range pkgs {
- for _, f := range p.Files {
- for _, im := range f.Imports {
- if !isSystemPackage(im.Path.Value) {
- for _, gp := range _gopath {
- path := gp + "/src/" + strings.Trim(im.Path.Value, "\"")
- if isExist(path) {
- parseImport(path)
- }
- }
- }
- }
- scom := parseStructComment(f)
- for _, obj := range f.Scope.Objects {
- if obj.Kind == ast.Typ {
- objName := obj.Name
- schema := &Schema{
- Title: objName,
- Type: "object",
- }
- ts, ok := obj.Decl.(*ast.TypeSpec)
- if !ok {
- fmt.Printf("obj type error %v ", obj.Kind)
- }
- st, ok := ts.Type.(*ast.StructType)
- if !ok {
- continue
- }
- properites := make(map[string]*Propertie)
- for _, fd := range st.Fields.List {
- if len(fd.Names) == 0 {
- continue
- }
- name, required, omit, desc := parseFieldTag(fd)
- if omit {
- continue
- }
- isSlice, realType, sType := typeAnalyser(fd)
- if (isSlice && isBasicType(realType)) || sType == "object" {
- 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)
- }
- }
- mp := &Propertie{}
- if isSlice {
- mp.Type = "array"
- if isBasicType(strings.Replace(realType, "[]", "", -1)) {
- typeFormat := strings.Split(sType, ":")
- mp.Items = &Propertie{
- Type: typeFormat[0],
- Format: typeFormat[1],
- }
- } else {
- ss := strings.Split(realType, ".")
- mp.RefImport = ss[len(ss)-1]
- mp.Type = "array"
- mp.Items = &Propertie{
- Ref: "#/definitions/" + mp.RefImport,
- Type: sType,
- }
- }
- } else {
- if sType == "object" {
- ss := strings.Split(realType, ".")
- mp.RefImport = ss[len(ss)-1]
- mp.Type = sType
- mp.Ref = "#/definitions/" + mp.RefImport
- } else if isBasicType(realType) {
- typeFormat := strings.Split(sType, ":")
- mp.Type = typeFormat[0]
- mp.Format = typeFormat[1]
- } else if realType == "map" {
- typeFormat := strings.Split(sType, ":")
- mp.AdditionalProperties = &Propertie{
- Type: typeFormat[0],
- Format: typeFormat[1],
- }
- }
- }
- if name == "" {
- name = fd.Names[0].Name
- }
- if required {
- schema.Required = append(schema.Required, name)
- }
- mp.Description = desc
- if scm, ok := scom[obj.Name]; ok {
- if cm, ok := scm.field[fd.Names[0].Name]; ok {
- mp.Description = cm + desc
- }
- }
- properites[name] = mp
- }
- if scm, ok := scom[obj.Name]; ok {
- schema.Description = scm.comment
- }
- schema.Properties = properites
- definitions[schema.Title] = schema
- }
- }
- }
- }
- }
- func parseFieldTag(field *ast.Field) (name string, required, omit bool, tagDes string) {
- if field.Tag == nil {
- return
- }
- tag := reflect.StructTag(strings.Trim(field.Tag.Value, "`"))
- param := tag.Get("form")
- if param != "" {
- params := strings.Split(param, ",")
- if len(params) > 0 {
- name = params[0]
- }
- if len(params) == 2 && params[1] == "split" {
- tagDes = "数组,按逗号分隔"
- }
- }
- if def := tag.Get("default"); def != "" {
- tagDes = fmt.Sprintf("%s 默认值 %s", tagDes, def)
- }
- validate := tag.Get("validate")
- if validate != "" {
- params := strings.Split(validate, ",")
- for _, param := range params {
- switch {
- case param == "required":
- required = true
- case strings.HasPrefix(param, "min"):
- tagDes = fmt.Sprintf("%s 最小值 %s", tagDes, strings.Split(param, "=")[1])
- case strings.HasPrefix(param, "max"):
- tagDes = fmt.Sprintf("%s 最大值 %s", tagDes, strings.Split(param, "=")[1])
- }
- }
- }
- // parse json response.
- json := tag.Get("json")
- if json != "" {
- jsons := strings.Split(json, ",")
- if len(jsons) > 0 {
- if jsons[0] == "-" {
- omit = true
- return
- }
- }
- }
- return
- }
- func parseRouter() {
- for _, p := range pkgs {
- if p.Name != "http" {
- continue
- }
- fmt.Printf("开始解析生成swagger文档\n")
- for _, f := range p.Files {
- for _, decl := range f.Decls {
- if fdecl, ok := decl.(*ast.FuncDecl); ok {
- if fdecl.Doc != nil {
- path, req, resp, item, err := parseFuncDoc(fdecl.Doc)
- if err != nil {
- fmt.Printf("解析失败 注解错误 %v\n", err)
- continue
- }
- if path != "" && err == nil {
- fmt.Printf("解析 %s 完成 请求参数为 %s 返回结构为 %s\n", path, req, resp)
- swagger.Paths[path] = item
- }
- }
- }
- }
- }
- }
- }
- func parseFuncDoc(f *ast.CommentGroup) (path, reqObj, respObj string, item *Item, err error) {
- item = new(Item)
- op := new(Operation)
- params := make([]*Parameter, 0)
- response := make(map[string]*Response)
- for _, d := range f.List {
- t := strings.TrimSpace(strings.TrimPrefix(d.Text, "//"))
- content := strings.Split(t, " ")
- switch content[0] {
- case "@params":
- if len(content) < 2 {
- err = fmt.Errorf("err params %s", content)
- return
- }
- reqObj = content[1]
- if model, ok := definitions[content[1]]; ok {
- for n, p := range model.Properties {
- param := &Parameter{
- In: "query",
- Name: n,
- Description: p.Description,
- Type: p.Type,
- Format: p.Format,
- }
- for _, p := range model.Required {
- if p == n {
- param.Required = true
- }
- }
- params = append(params, param)
- }
- } else {
- err = ErrParams
- return
- }
- case "@router":
- if len(content) != 3 {
- err = ErrParams
- return
- }
- switch content[1] {
- case "get":
- item.Get = op
- case "post":
- item.Post = op
- }
- path = content[2]
- op.OperationID = path
- case "@response":
- if len(content) < 2 {
- err = fmt.Errorf("err response %s", content)
- return
- }
- var (
- isarray bool
- ismap bool
- )
- if strings.HasPrefix(content[1], "[]") {
- isarray = true
- respObj = content[1][2:]
- } else if strings.HasPrefix(content[1], "map[]") {
- ismap = true
- respObj = content[1][5:]
- } else {
- respObj = content[1]
- }
- defini, ok := definitions[respObj]
- if !ok {
- err = ErrParams
- return
- }
- var resp *Propertie
- if isarray {
- resp = &Propertie{
- Type: "array",
- Items: &Propertie{
- Type: "object",
- Ref: "#/definitions/" + respObj,
- },
- }
- } else if ismap {
- resp = &Propertie{
- Type: "object",
- AdditionalProperties: &Propertie{
- Ref: "#/definitions/" + respObj,
- },
- }
- } else {
- resp = &Propertie{
- Type: "object",
- Ref: "#/definitions/" + respObj,
- }
- }
- response["200"] = &Response{
- Schema: &Schema{
- Type: "object",
- Properties: map[string]*Propertie{
- "code": &Propertie{
- Type: "integer",
- Description: "错误码描述",
- },
- "data": resp,
- "message": &Propertie{
- Type: "string",
- Description: "错误码文本描述",
- },
- "ttl": &Propertie{
- Type: "integer",
- Format: "int64",
- Description: "客户端限速时间",
- },
- },
- },
- Description: "服务成功响应内容",
- }
- op.Responses = response
- for _, rl := range defini.Properties {
- if rl.RefImport != "" {
- swagger.Definitions[rl.RefImport] = definitions[rl.RefImport]
- }
- }
- swagger.Definitions[respObj] = defini
- case "@description":
- op.Description = content[1]
- }
- }
- op.Parameters = params
- return
- }
- type structComment struct {
- comment string
- field map[string]string
- }
- func parseStructComment(f *ast.File) (scom map[string]structComment) {
- scom = make(map[string]structComment)
- for _, d := range f.Decls {
- switch specDecl := d.(type) {
- case *ast.GenDecl:
- if specDecl.Tok == token.TYPE {
- for _, s := range specDecl.Specs {
- switch tp := s.(*ast.TypeSpec).Type.(type) {
- case *ast.StructType:
- fcom := make(map[string]string)
- for _, fd := range tp.Fields.List {
- if len(fd.Names) == 0 {
- continue
- }
- if len(fd.Comment.Text()) > 0 {
- fcom[fd.Names[0].Name] = strings.TrimSuffix(fd.Comment.Text(), "\n")
- }
- }
- sspec := s.(*ast.TypeSpec)
- scom[sspec.Name.String()] = structComment{comment: strings.TrimSuffix(specDecl.Doc.Text(), "\n"), field: fcom}
- }
- }
- }
- }
- }
- return
- }
- func isBasicType(Type string) bool {
- if _, ok := basicTypes[Type]; ok {
- return true
- }
- return false
- }
- func typeAnalyser(f *ast.Field) (isSlice bool, realType, swaggerType string) {
- if arr, ok := f.Type.(*ast.ArrayType); ok {
- if isBasicType(fmt.Sprint(arr.Elt)) {
- return true, fmt.Sprintf("[]%v", arr.Elt), basicTypes[fmt.Sprint(arr.Elt)]
- }
- if mp, ok := arr.Elt.(*ast.MapType); ok {
- return false, fmt.Sprintf("map[%v][%v]", mp.Key, mp.Value), "object"
- }
- if star, ok := arr.Elt.(*ast.StarExpr); ok {
- return true, fmt.Sprint(star.X), "object"
- }
- basicType := fmt.Sprint(arr.Elt)
- if object, isStdLibObject := stdlibObject[basicType]; isStdLibObject {
- basicType = object
- }
- if k, ok := basicTypes[basicType]; ok {
- return true, basicType, k
- }
- return true, fmt.Sprint(arr.Elt), "object"
- }
- switch t := f.Type.(type) {
- case *ast.StarExpr:
- basicType := fmt.Sprint(t.X)
- if k, ok := basicTypes[basicType]; ok {
- return false, basicType, k
- }
- return false, basicType, "object"
- case *ast.MapType:
- val := fmt.Sprintf("%v", t.Value)
- if isBasicType(val) {
- return false, "map", basicTypes[val]
- }
- return false, val, "object"
- }
- basicType := fmt.Sprint(f.Type)
- if object, isStdLibObject := stdlibObject[basicType]; isStdLibObject {
- basicType = object
- }
- if k, ok := basicTypes[basicType]; ok {
- return false, basicType, k
- }
- return false, basicType, "object"
- }
- func isSystemPackage(pkgpath string) bool {
- goroot := os.Getenv("GOROOT")
- if goroot == "" {
- goroot = runtime.GOROOT()
- }
- wg, _ := filepath.EvalSymlinks(filepath.Join(goroot, "src", "pkg", pkgpath))
- if isExist(wg) {
- return true
- }
- wg, _ = filepath.EvalSymlinks(filepath.Join(goroot, "src", pkgpath))
- return isExist(wg)
- }
- func isExist(path string) bool {
- _, err := os.Stat(path)
- return err == nil || os.IsExist(err)
- }
|