331 lines
8.0 KiB
Go
331 lines
8.0 KiB
Go
package lib
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"github.com/fatih/structtag"
|
|
"go/ast"
|
|
"go/parser"
|
|
"go/token"
|
|
"log"
|
|
"net/http"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
var api API
|
|
|
|
func llog(s string, p ...interface{}) {
|
|
fmt.Printf(s+"\n", p...)
|
|
}
|
|
|
|
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
|
|
llog("Adding type: %s => %#v", tp.Name, md)
|
|
for _, v := range a.Specs[0].(*ast.TypeSpec).Type.(*ast.StructType).Fields.List {
|
|
|
|
if len(v.Names) < 1 {
|
|
panic("Does not support Composition: " + tp.Name)
|
|
}
|
|
tp.Fields[v.Names[0].Name] = &APIField{}
|
|
tp.Fields[v.Names[0].Name].Tags = make(map[string]APIFieldTag)
|
|
|
|
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:
|
|
api.UsedImportsTypes[z.X.(*ast.Ident).Name] = api.Imports[z.X.(*ast.Ident).Name]
|
|
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:
|
|
api.UsedImportsTypes[z.Name] = api.Imports[z.Name]
|
|
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:
|
|
api.UsedImportsTypes[z.Name] = api.Imports[z.Name]
|
|
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)
|
|
}
|
|
if v.Tag != nil {
|
|
tgstr := strings.ReplaceAll(v.Tag.Value, "`", "")
|
|
tg, err := structtag.Parse(tgstr)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
for _, tgv := range tg.Keys() {
|
|
atg, err := tg.Get(tgv)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
tp.Fields[v.Names[0].Name].Tags[tgv] = APIFieldTag{
|
|
Key: tgv,
|
|
Name: atg.Name,
|
|
Opts: atg.Options,
|
|
}
|
|
}
|
|
log.Printf("#%v", tg)
|
|
}
|
|
|
|
}
|
|
api.Types[tp.Name] = &tp
|
|
}
|
|
|
|
func addFunction(a *ast.FuncDecl) error {
|
|
md := manageComments(a.Doc)
|
|
|
|
if md["API"] == "" {
|
|
return nil
|
|
}
|
|
|
|
llog("Adding Fuction: %s => %#v", a.Name, md)
|
|
reqType := &APIParamType{}
|
|
resType := &APIParamType{}
|
|
|
|
if len(a.Type.Params.List) != 2 {
|
|
return errors.New(fmt.Sprintf("Function %s does not have 2 IN parameters (context and pointer to req struct)", a.Name.Name))
|
|
}
|
|
|
|
if len(a.Type.Results.List) != 2 {
|
|
return errors.New(fmt.Sprintf("Function %s does not have 2 OUT parameters (pointer to res struct and err)", a.Name.Name))
|
|
}
|
|
if a.Type.Results.List[1].Type.(*ast.Ident).Name != "error" {
|
|
return errors.New(fmt.Sprintf("Function %s does not have error as 2nd OUT parameter", a.Name.Name))
|
|
}
|
|
|
|
p0tp := a.Type.Params.List[0].Type.(*ast.SelectorExpr)
|
|
if p0tp.X.(*ast.Ident).Name != "context" && p0tp.Sel.Name != "Context" {
|
|
return errors.New(fmt.Sprintf("Function %s, param 1 is not of type context.Context", a.Name))
|
|
}
|
|
|
|
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:
|
|
api.UsedImportsFunctions[y.X.(*ast.Ident).Name] = api.Imports[y.X.(*ast.Ident).Name]
|
|
reqType.Typename = y.X.(*ast.Ident).Name + "." + y.Sel.Name
|
|
}
|
|
default:
|
|
return errors.New(fmt.Sprintf("Function %s does not have 2 IN parameters (context and pointer to req struct)", a.Name.Name))
|
|
|
|
}
|
|
|
|
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:
|
|
api.UsedImportsFunctions[y.X.(*ast.Ident).Name] = api.Imports[y.X.(*ast.Ident).Name]
|
|
resType.Typename = y.X.(*ast.Ident).Name + "." + y.Sel.Name
|
|
}
|
|
default:
|
|
return errors.New(fmt.Sprintf("Function %s does not have 2 OUT parameters (pointer to res struct and err)", a.Name.Name))
|
|
|
|
}
|
|
|
|
if md["RAW"] == "true" {
|
|
reqType.Typename = md["REQ"]
|
|
resType.Typename = md["RES"]
|
|
}
|
|
|
|
verb := md["VERB"]
|
|
if verb == "" {
|
|
verb = http.MethodPost
|
|
}
|
|
fn := APIMethod{
|
|
Name: a.Name.Name,
|
|
Desc: a.Name.Name,
|
|
Verb: verb,
|
|
Path: md["PATH"],
|
|
Perm: md["PERM"],
|
|
ReqType: reqType,
|
|
ResType: resType,
|
|
}
|
|
|
|
if fn.Path == "" {
|
|
fn.Path = "/" + strings.Replace(strings.ToLower(a.Name.Name), "_", "/", -1)
|
|
}
|
|
|
|
api.Methods[a.Name.Name] = &fn
|
|
|
|
return nil
|
|
}
|
|
|
|
// load is responsible for loading the package for AST Parsing
|
|
func load(src string) error {
|
|
|
|
api.Types = (make(map[string]*APIType))
|
|
api.Methods = (make(map[string]*APIMethod))
|
|
api.Imports = make(map[string]string)
|
|
api.UsedImportsTypes = make(map[string]string)
|
|
api.UsedImportsFunctions = make(map[string]string)
|
|
api.Paths = make(map[string]*APIPath)
|
|
api.SortedPaths = make([]*APIPath, 0)
|
|
|
|
fset := token.NewFileSet() // positions are relative to fset
|
|
|
|
f, err := parser.ParseDir(fset, src, nil, parser.ParseComments)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
//iterate over packages
|
|
for _, v := range f {
|
|
|
|
llog("Loading Package: %s", v.Name)
|
|
// Print the AST.
|
|
ast.Inspect(v, func(n ast.Node) bool {
|
|
|
|
//tp := reflect.TypeOf(n)
|
|
//if tp != nil {
|
|
// log.Printf("Type: %#v", tp.String())
|
|
//}
|
|
|
|
switch x := n.(type) {
|
|
|
|
case *ast.GenDecl:
|
|
if x.Tok == token.TYPE {
|
|
addStruct(x)
|
|
} else {
|
|
return true
|
|
}
|
|
case *ast.ImportSpec:
|
|
var impkey = ""
|
|
var impval = ""
|
|
impval = strings.Replace(x.Path.Value, "\"", "", -1)
|
|
impval = strings.Replace(impval, "'", "", -1)
|
|
if x.Name != nil && x.Name.Name != "" {
|
|
impkey = x.Name.Name
|
|
} else {
|
|
parts := strings.Split(impval, "/")
|
|
impkey = parts[len(parts)-1]
|
|
}
|
|
api.Imports[impkey] = impval
|
|
case *ast.File:
|
|
manageCommentsGroups(x.Comments)
|
|
case *ast.FuncDecl:
|
|
err = addFunction(x)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
llog("Adding fn: %s", x.Name)
|
|
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
|
|
})
|
|
}
|
|
|
|
for k, v := range api.Methods {
|
|
path, ok := api.Paths[v.Path]
|
|
if !ok {
|
|
path = &APIPath{
|
|
Path: v.Path,
|
|
MapVerbs: make(map[string]*APIVerb),
|
|
SortedVerbs: make([]*APIVerb, 0),
|
|
}
|
|
api.Paths[v.Path] = path
|
|
}
|
|
path.MapVerbs[v.Verb] = &APIVerb{
|
|
Verb: v.Verb,
|
|
Method: v,
|
|
}
|
|
pathmap, ok := httpMapper[v.Path]
|
|
if !ok {
|
|
httpMapper[v.Path] = make(map[string]string)
|
|
pathmap = httpMapper[v.Path]
|
|
}
|
|
pathmap[v.Verb] = k
|
|
}
|
|
|
|
pathNames := make([]string, 0)
|
|
for k, v := range api.Paths {
|
|
verbs := make([]string, 0)
|
|
for k, _ := range v.MapVerbs {
|
|
verbs = append(verbs, k)
|
|
}
|
|
sort.Strings(verbs)
|
|
|
|
for _, sv := range verbs {
|
|
v.SortedVerbs = append(v.SortedVerbs, v.MapVerbs[sv])
|
|
}
|
|
pathNames = append(pathNames, k)
|
|
}
|
|
sort.Strings(pathNames)
|
|
for _, p := range pathNames {
|
|
api.SortedPaths = append(api.SortedPaths, api.Paths[p])
|
|
}
|
|
|
|
api.Namespace = packageName
|
|
|
|
return err
|
|
}
|