package main import ( "go/ast" "go/parser" "go/token" "log" "net/http" "os" "strings" ) var api API //var httptestdir string var tstypemapper map[string]string = make(map[string]string) var knownMethods map[string]bool = make(map[string]bool) var httpMapper map[string]map[string]string = make(map[string]map[string]string) var packageName string = "main" func addStruct(a *ast.GenDecl) { md := manageComments(a.Doc) if md["API"] == "" { return } tp := APIType{ Name: "", Desc: "", Fields: make(map[string]*APIField), Col: md["COL"], } tp.Name = a.Specs[0].(*ast.TypeSpec).Name.Name log.Printf("Type:" + tp.Name) for _, v := range a.Specs[0].(*ast.TypeSpec).Type.(*ast.StructType).Fields.List { tp.Fields[v.Names[0].Name] = &APIField{} switch x := v.Type.(type) { case *ast.Ident: tp.Fields[v.Names[0].Name].Type = x.Name case *ast.ArrayType: switch z := x.Elt.(type) { case *ast.Ident: tp.Fields[v.Names[0].Name].Type = z.Name tp.Fields[v.Names[0].Name].Array = true case *ast.InterfaceType: tp.Fields[v.Names[0].Name].Type = "interface{}" tp.Fields[v.Names[0].Name].Array = true case *ast.SelectorExpr: tp.Fields[v.Names[0].Name].Type = z.X.(*ast.Ident).Name + "." + z.Sel.Name tp.Fields[v.Names[0].Name].Array = true } case *ast.StarExpr: switch y := x.X.(type) { case *ast.Ident: tp.Fields[v.Names[0].Name].Type = y.Name case *ast.SelectorExpr: switch z := y.X.(type) { case *ast.Ident: tp.Fields[v.Names[0].Name].Type = z.Name + "." + y.Sel.Name } } case *ast.InterfaceType: tp.Fields[v.Names[0].Name].Type = "interface{}" case *ast.SelectorExpr: switch z := x.X.(type) { case *ast.Ident: tp.Fields[v.Names[0].Name].Type = z.Name + "." + x.Sel.Name } case *ast.MapType: switch z := x.Value.(type) { case *ast.Ident: tp.Fields[v.Names[0].Name].Type = "" tp.Fields[v.Names[0].Name].Mapkey = x.Key.(*ast.Ident).Name tp.Fields[v.Names[0].Name].Mapval = z.Name tp.Fields[v.Names[0].Name].Map = true case *ast.InterfaceType: tp.Fields[v.Names[0].Name].Type = "interface{}" tp.Fields[v.Names[0].Name].Array = true } default: log.Printf("%#v", x) } } api.Types[tp.Name] = &tp } func addFunction(a *ast.FuncDecl) { md := manageComments(a.Doc) if md["API"] == "" { return } reqType := &APIParamType{} resType := &APIParamType{} if len(a.Type.Params.List) > 1 { switch x := a.Type.Params.List[1].Type.(type) { case *ast.StarExpr: reqType.Ispointer = true switch y := x.X.(type) { case *ast.Ident: reqType.Typename = y.Name case *ast.SelectorExpr: reqType.Typename = y.X.(*ast.Ident).Name + "." + y.Sel.Name } case *ast.Ident: reqType.Typename = x.Name } } if a.Type.Results != nil && len(a.Type.Results.List) > 0 { switch x := a.Type.Results.List[0].Type.(type) { case *ast.StarExpr: resType.Ispointer = true switch y := x.X.(type) { case *ast.Ident: resType.Typename = y.Name case *ast.SelectorExpr: resType.Typename = y.X.(*ast.Ident).Name + "." + y.Sel.Name } case *ast.Ident: resType.Typename = x.Name } } if md["RAW"] == "true" { reqType.Typename = md["REQ"] resType.Typename = md["RES"] } verb := md["VERB"] if verb == "" { verb = http.MethodPost } fn := APIMethod{ Desc: a.Name.Name, Verb: verb, Path: md["PATH"], Perm: md["PERM"], ReqType: reqType, ResType: resType, Raw: md["RAW"] == "true", } api.Methods[a.Name.Name] = &fn } //func processHTTPTestOutput(f string, api *API) { // // for k, m := range api.Methods { // fname := path.Join(httptestdir, k+".http") // if dc.FileExists(fname) { // continue // } // b := bytes.Buffer{} // b.WriteString(fmt.Sprintf(`%s{{BASEURL}}%s //Content-Type: application/json //Cookie: dc={{COOKIE}} // //{} // //### //`, m.Verb, api.BasePath+m.Path)) // // err := ioutil.WriteFile(fname, b.Bytes(), 0600) // dc.Err(err) // } //} func mapHttp(api *API) { for k, v := range api.Methods { pathmap, ok := httpMapper[v.Path] if !ok { httpMapper[v.Path] = make(map[string]string) pathmap = httpMapper[v.Path] } pathmap[v.Verb] = k } } func process(api *API) { mapHttp(api) processGoServerOutput(api) processTSClientOutput("", api) processGoClientOutput(api) } func load() { api.Types = (make(map[string]*APIType)) api.Methods = (make(map[string]*APIMethod)) fset := token.NewFileSet() // positions are relative to fset f, err := parser.ParseDir(fset, config.Goimpldir, nil, parser.ParseComments) if err != nil { panic(err) } for _, v := range f { // Print the AST. ast.Inspect(v, func(n ast.Node) bool { switch x := n.(type) { case *ast.GenDecl: if x.Tok == token.TYPE { addStruct(x) } else { return true } case *ast.File: c := manageCommentsGroups(x.Comments) log.Printf("%+v", c) case *ast.FuncDecl: addFunction(x) case *ast.ValueSpec: if x.Names[0].Name == "BASEPATH" { api.BasePath = strings.Replace(x.Values[0].(*ast.BasicLit).Value, "\"", "", -1) } if x.Names[0].Name == "NAMESPACE" { api.Namespace = strings.Replace(x.Values[0].(*ast.BasicLit).Value, "\"", "", -1) } log.Printf("%#v", x) case *ast.Package: packageName = x.Name default: //log.Printf("%#v", x) return true } return true }) } } func main() { loadConfig() tstypemapper["time.Time"] = "Date" tstypemapper["primitive.ObjectID"] = "string" tstypemapper["time.Duration"] = "Date" tstypemapper["int"] = "number" tstypemapper["int32"] = "number" tstypemapper["int64"] = "number" tstypemapper["float"] = "number" tstypemapper["float64"] = "number" tstypemapper["uint8"] = "number" tstypemapper["uint16"] = "number" tstypemapper["uint32"] = "number" tstypemapper["error"] = "Error" tstypemapper["bool"] = "boolean" tstypemapper["interface{}"] = "any" tstypemapper["bson.M"] = "any" os.Remove(config.Gofname) load() process(&api) }