Recovering operational

This commit is contained in:
2021-09-06 09:54:09 -03:00
parent 45e64c198b
commit ac8771aac4
21 changed files with 293 additions and 359 deletions
+68
View File
@@ -0,0 +1,68 @@
package lib
import (
"go/ast"
"regexp"
"strings"
)
func manageComments(a *ast.CommentGroup) map[string]string {
ret := make(map[string]string)
if a == nil {
return ret
}
var myExp = regexp.MustCompile(`\/?\*?@(?P<k>.*)\s*:\s*(?P<v>.*)\*?\/?`)
var myExp2 = regexp.MustCompile(`\/?\*?@(?P<k>.*?)\*?\/?$`)
for _, v := range a.List {
lines := strings.Split(v.Text, "\n")
for _, l := range lines {
m := myExp.FindStringSubmatch(l)
if m != nil {
ret[m[1]] = m[2]
} else {
m := myExp2.FindStringSubmatch(l)
if m != nil {
ret[m[1]] = "OK"
}
}
}
}
return ret
}
func manageCommentsGroups(as []*ast.CommentGroup) map[string]string {
ret := make(map[string]string)
if as == nil {
return ret
}
for _, a := range as {
var myExp = regexp.MustCompile(`\/?\*?@(?P<k>.*)\s*:\s*(?P<v>.*)\*?\/?`)
var myExp2 = regexp.MustCompile(`\/?\*?@(?P<k>.*?)\*?\/?$`)
for _, v := range a.List {
lines := strings.Split(v.Text, "\n")
for _, l := range lines {
m := myExp.FindStringSubmatch(l)
if m != nil {
ret[m[1]] = m[2]
} else {
m := myExp2.FindStringSubmatch(l)
if m != nil {
ret[m[1]] = "OK"
}
}
}
}
}
return ret
}
+19
View File
@@ -0,0 +1,19 @@
package lib
//type Config struct {
// Gofname string `yaml:"gofname"`
// Goimpldir string `yaml:"goimpldir"`
// Tsfname string `json:"tsfname"`
// Goclifname string `json:"goclifname"`
//}
//
//var config Config
//
//func loadConfig() {
// fname := flag.String("f", ".apigen.yaml", "File with config to load - defaults to '.apigen'")
// flag.Parse()
// bs, err := ioutil.ReadFile(*fname)
// Err(err)
// err = yaml.Unmarshal(bs, &config)
// Err(err)
//}
+345
View File
@@ -0,0 +1,345 @@
package lib
import (
"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 {
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) {
md := manageComments(a.Doc)
if md["API"] == "" {
return
}
llog("Adding Fuction: %s => %#v", a.Name, md)
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:
api.UsedImportsFunctions[y.X.(*ast.Ident).Name] = api.Imports[y.X.(*ast.Ident).Name]
reqType.Typename = y.X.(*ast.Ident).Name + "." + y.Sel.Name
}
case *ast.ArrayType:
reqType.IsArray = true
switch y := x.Elt.(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
case *ast.StarExpr:
reqType.Ispointer = true
switch z := y.X.(type) {
case *ast.Ident:
reqType.Typename = z.Name
case *ast.SelectorExpr:
api.UsedImportsFunctions[z.X.(*ast.Ident).Name] = api.Imports[z.X.(*ast.Ident).Name]
reqType.Typename = z.X.(*ast.Ident).Name + "." + z.Sel.Name
}
}
case *ast.Ident:
reqType.Typename = x.Name
case *ast.SelectorExpr:
api.UsedImportsFunctions[x.X.(*ast.Ident).Name] = api.Imports[x.X.(*ast.Ident).Name]
reqType.Typename = x.X.(*ast.Ident).Name + "." + x.Sel.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:
api.UsedImportsFunctions[y.X.(*ast.Ident).Name] = api.Imports[y.X.(*ast.Ident).Name]
resType.Typename = y.X.(*ast.Ident).Name + "." + y.Sel.Name
}
case *ast.ArrayType:
resType.IsArray = true
switch y := x.Elt.(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
case *ast.StarExpr:
resType.Ispointer = true
switch z := y.X.(type) {
case *ast.Ident:
resType.Typename = z.Name
case *ast.SelectorExpr:
api.UsedImportsFunctions[z.X.(*ast.Ident).Name] = api.Imports[z.X.(*ast.Ident).Name]
resType.Typename = z.X.(*ast.Ident).Name + "." + z.Sel.Name
}
}
case *ast.Ident:
resType.Typename = x.Name
case *ast.SelectorExpr:
api.UsedImportsFunctions[x.X.(*ast.Ident).Name] = api.Imports[x.X.(*ast.Ident).Name]
resType.Typename = x.X.(*ast.Ident).Name + "." + x.Sel.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
}
}
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
}
for _, v := range f {
llog("Loading Package: %s", v.Name)
// 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.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:
addFunction(x)
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 nil
}
+121
View File
@@ -0,0 +1,121 @@
package lib
import (
"github.com/alecthomas/kong"
"github.com/pkg/errors"
"log"
)
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"
var CLI struct {
Yaml struct {
Src string `arg help:"Source Dir"`
Fname string `arg help:"File to be generated"`
} `cmd help:"Gens YAML metamodel"`
Goserver struct {
Src string `arg help:"Source Dir"`
} `cmd help:"Gens GO Server impl"`
//Gin struct {
// Src string `arg help:"Source Dir"`
//} `cmd help:"Gens Gin Server impl"`
Gocli struct {
Src string `arg help:"Source Dir"`
Dst string `arg help:"Dst file"`
} `cmd help:"Gens Go Cli impl"`
Pycli struct {
Src string `arg help:"Source Dir"`
Dst string `arg help:"Dst file"`
} `cmd help:"Gens Python Cli impl"`
Ts struct {
Src string `arg help:"Source Dir"`
Dst string `arg help:"Dst file"`
} `cmd help:"Gens Typescript Cli impl"`
Http struct {
Src string `arg help:"Source Dir"`
Dst string `arg help:"Dst file"`
} `cmd help:"Gens Http call impl"`
}
func Run() {
var processor func() error
kong.ConfigureHelp(kong.HelpOptions{
NoAppSummary: false,
Summary: true,
Compact: true,
Tree: true,
Indenter: nil,
})
ctx := kong.Parse(&CLI)
var err error
var src string
switch ctx.Command() {
case "yaml <src> <fname>":
log.Printf("Gens YAML")
src = CLI.Yaml.Src
processor = func() error {
return processYaml(CLI.Yaml.Fname, nil)
}
case "goserver <src>":
log.Printf("Gen GO Server")
src = CLI.Goserver.Src
processor = func() error {
return processGoServerOutput(CLI.Goserver.Src + "/apigen.go")
}
//case "gin <src>":
// log.Printf("Gen Gin Server")
// src = CLI.Gin.Src
// processor = func() error {
// return processGinServerOutput(CLI.Gin.Src + "/apigen.go")
// }
case "gocli <src> <dst>":
log.Printf("Gen GO Client")
src = CLI.Gocli.Src
processor = func() error {
return processGoClientOutput(CLI.Gocli.Dst)
}
case "pycli <src> <dst>":
log.Printf("Gen Python Client")
src = CLI.Pycli.Src
processor = func() error {
return processPyClientOutput(CLI.Pycli.Dst)
}
case "ts <src> <dst>":
log.Printf("Gen TS Client")
src = CLI.Ts.Src
processor = func() error {
return processTSClientOutput(CLI.Ts.Dst)
}
case "http <src> <dst>":
log.Printf("Gen Http Client")
src = CLI.Http.Src
processor = func() error {
return processHttpCallOut(CLI.Http.Dst)
}
default:
err = errors.New("unknown option")
}
if err != nil {
panic(err)
}
err = load(src)
if err != nil {
panic(err)
}
err = processor()
if err != nil {
panic(err)
}
//loadConfig()
//
//os.Remove(config.Gofname)
//process(&api)
}
+141
View File
@@ -0,0 +1,141 @@
package lib
import (
"bytes"
"fmt"
"os"
)
import (
_ "embed"
)
func processGoClientOutput(f string) error {
buf := &bytes.Buffer{}
W := func(s string, p ...interface{}) {
buf.WriteString(fmt.Sprintf(s, p...))
}
WNL := func(s string, p ...interface{}) {
buf.WriteString(fmt.Sprintf(s+"\n", p...))
}
ResDecType := func(v *APIParamType) string {
ret := ""
if v.IsArray {
ret = ret + "[]"
}
if v.Ispointer {
ret = ret + "*"
}
ret += " " + v.Typename
return ret
}
ResImplType := func(v *APIParamType) string {
ret := ""
if v.IsArray {
ret = ret + "[]"
}
if v.Ispointer {
ret = ret + "*"
}
ret += v.Typename
ret += " = "
if !v.IsArray || v.Ispointer {
ret = ret + "&"
}
ret += v.Typename + "{}"
return ret
}
WNL("package %s", api.Namespace)
WNL(`import (
"bytes"
"errors"
"io/ioutil"
"encoding/json"
"net/http"
"time"
)
var Basepath string = ""
var Host string = ""
var ExtraHeaders map[string]string = make(map[string]string)
func invoke(m string, path string, bodyo interface{}) (*json.Decoder, error) {
b := &bytes.Buffer{}
err := json.NewEncoder(b).Encode(bodyo)
if err != nil {
return nil, err
}
body := bytes.NewReader(b.Bytes())
req, err := http.NewRequest(m, Host+Basepath+path, body)
if err != nil {
return nil, err
}
req.Header.Set("Content-type", "application/json")
for k, v := range ExtraHeaders {
req.Header.Set(k, v)
}
cli := http.Client{}
res, err := cli.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode >= 400 {
bs, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil,err
}
return nil, errors.New(string(bs))
}
ret := json.NewDecoder(res.Body)
return ret, nil
}
`)
for k, v := range api.Types {
WNL(`type %s struct {`, k)
for fn, f := range v.Fields {
W(` %s `, fn)
if f.Array {
W("[]")
}
if f.Map {
W("map[%s]%s", f.Mapkey, f.Mapval)
} else {
WNL(f.Type)
}
}
WNL(`}`)
}
for k, v := range api.Methods {
WNL(`func %s(req %s) (res %s, err error){`, k, ResDecType(v.ReqType), ResDecType(v.ResType))
WNL(` var dec *json.Decoder
dec, err = invoke("%s", "%s", res)
if err!=nil{
return
}
var ret %s`, v.Verb, v.Path, ResImplType(v.ResType))
W(` err = dec.Decode(`)
if v.ResType.IsArray || !v.ResType.Ispointer {
W("&")
}
WNL(`ret)
return ret, err
}`)
}
return os.WriteFile(f, buf.Bytes(), 0600)
}
+123
View File
@@ -0,0 +1,123 @@
package lib
import (
"bytes"
_ "embed"
"fmt"
"os"
)
func processGoServerOutput(f string) error {
buf := &bytes.Buffer{}
W := func(s string, p ...interface{}) {
buf.WriteString(fmt.Sprintf(s, p...))
}
WNL := func(s string, p ...interface{}) {
buf.WriteString(fmt.Sprintf(s+"\n", p...))
}
WNL("package %s", api.Namespace)
WNL(`import (
"context"
"encoding/json"
"strings"
"net/http"
)`)
for k := range api.UsedImportsFunctions {
W(`import "%s"`, k)
}
WNL(`type API struct {
Mux *http.ServeMux
Perms map[string]string
}
func (a *API) GetPerm(r *http.Request) string {
return a.Perms[r.Method+"_"+strings.Split(r.RequestURI, "?")[0]]
}
`)
WNL(`func Init() *API{
mux := &http.ServeMux{}
ret := &API{
Mux: mux,
Perms: make(map[string]string),
}`)
for _, v := range api.Methods {
if v.Perm != "" {
WNL(` ret.Perms["%s_%s"]="%s"`, v.Verb, v.Path, v.Perm)
}
}
for _, v := range api.SortedPaths {
WNL(` mux.HandleFunc("%s",func(w http.ResponseWriter, r *http.Request) {
switch r.Method {`, v.Path)
for _, v1 := range v.SortedVerbs {
WNL(` case "%s":`, v1.Method.Verb)
if v1.Method.Raw {
WNL(` %s(w,r)`, v1.Method.Name)
} else {
WNL(` h_%s(w,r)`, v1.Method.Name)
}
WNL(` default:
http.Error(w,"Method not allowed",500)`)
}
WNL(` }`)
WNL(` })
return ret
}`)
}
for _, v := range api.Methods {
WNL(`func h_%s(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx = context.WithValue(r.Context(), "REQ", r)
ctx = context.WithValue(ctx, "RES", w)`, v.Name)
W(" var req ")
if v.ReqType.IsArray {
W("[]")
}
if v.ReqType.Ispointer {
W("*")
}
WNL(v.ReqType.Typename)
WNL(` if r.Method!=http.MethodGet && r.Method!=http.MethodHead {`)
if v.ReqType.Ispointer || v.ReqType.IsArray {
WNL(" err := json.NewDecoder(r.Body).Decode(req)")
} else {
WNL(" err := json.NewDecoder(r.Body).Decode(&req)")
}
WNL(` if err != nil {
http.Error(w, err.Error(), 500)
return
}
}`)
WNL(` res, err := %s(ctx,req)`, v.Name)
WNL(` if err != nil {
http.Error(w, err.Error(), 500)
return
}
w.Header().Add("Content-Type","Application/json")
err=json.NewEncoder(w).Encode(res)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
}`)
}
return os.WriteFile(f, buf.Bytes(), 0600)
}
+99
View File
@@ -0,0 +1,99 @@
package lib
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"sort"
"strings"
)
func typeToJson(tn string) interface{} {
rootmap := make(map[string]interface{})
td, ok := api.Types[tn]
if !ok {
if tn == "string" {
return "A STRING VALUE"
}
if tn == "bool" {
return true
}
if strings.HasPrefix(strings.ToLower(tn), "int") {
return 123456
}
if strings.HasPrefix(strings.ToLower(tn), "float") {
return 123.456
}
}
for k, v := range td.Fields {
tg, ok := v.Tags["json"]
fname := strings.ToLower(k)
if ok {
fname = tg.Name
}
if v.Map {
submapval := typeToJson(v.Mapval)
submap := make(map[string]interface{})
submap["a"] = submapval
submap["b"] = submapval
submap["c"] = submapval
rootmap[fname] = submap
} else {
submapval := typeToJson(v.Type)
if v.Array {
submap := make([]interface{}, 0)
submap = append(submap, submapval)
submap = append(submap, submapval)
submap = append(submap, submapval)
rootmap[fname] = submap
} else {
rootmap[fname] = submapval
}
}
}
return rootmap
}
func typeToJsonStr(tn string) string {
o := typeToJson(tn)
bs, err := json.MarshalIndent(o, "", "\t")
if err != nil {
panic(err)
}
return string(bs)
}
func processHttpCallOut(f string) error {
b := bytes.Buffer{}
sortedMethods := make([]string, 0)
for k, _ := range api.Methods {
sortedMethods = append(sortedMethods, k)
}
sort.Strings(sortedMethods)
for _, k := range sortedMethods {
m := api.Methods[k]
tj := typeToJsonStr(m.ReqType.Typename)
b.WriteString("###\n")
if m.Desc != "" {
b.WriteString(fmt.Sprintf("#%s", strings.Replace(m.Desc, "\n", "\n#", -1)))
}
b.WriteString(fmt.Sprintf("\n"))
b.WriteString(fmt.Sprintf(m.Verb + " https://host/basepath" + m.Path + "\n"))
b.WriteString("Content-Type: application/json\n")
b.WriteString("Cookie: dc=<MYCOOKIE>\n\n")
b.WriteString(tj)
b.WriteString("\n\n")
}
err := ioutil.WriteFile(f, b.Bytes(), 0600)
return err
}
+157
View File
@@ -0,0 +1,157 @@
package lib
import (
"bytes"
"fmt"
"io/ioutil"
"log"
"strings"
)
func processPyClientOutput(f string) error {
var tstypemapper map[string]string = make(map[string]string)
var exceptionaltypemapper map[string]string = make(map[string]string)
exceptionaltypemapper["[]byte"] = "str"
exceptionaltypemapper["[]string"] = "List[str]"
tstypemapper["string"] = "str"
tstypemapper["time.Time"] = "int"
tstypemapper["primitive.ObjectID"] = "str"
tstypemapper["time.Duration"] = "int"
tstypemapper["int"] = "int"
tstypemapper["int32"] = "int"
tstypemapper["int64"] = "int"
tstypemapper["float"] = "float"
tstypemapper["float64"] = "float"
tstypemapper["uint8"] = "int"
tstypemapper["uint16"] = "int"
tstypemapper["uint32"] = "int"
tstypemapper["error"] = "Exception"
tstypemapper["bool"] = "bool"
tstypemapper["interface{}"] = "dict"
tstypemapper["bson.M"] = "dict"
_typeName := func(m *APIParamType) string {
if m.IsArray {
return "List[" + m.Typename + "]"
}
return m.Typename
}
b := bytes.Buffer{}
b.WriteString("from dataclasses import dataclass\n")
b.WriteString("import requests\n")
b.WriteString("import json\n")
b.WriteString("from typing import List\n")
b.WriteString("#region Base\n")
b.WriteString(fmt.Sprintf(`
__ctx = {"apibase":"%s"}
def SetAPIBase(s: str):
__ctx["apibase"] = s
def GetAPIBase() -> str:
return __ctx["apibase"]
def SetCookie(s: str):
__ctx["cookie"] = s
def GetCookie() -> str:
return __ctx["cookie"]
def InvokeTxt(path: str, method: str, body) -> str:
headers = {"Content-type": "application/json", "Cookie": "dc="+GetCookie()}
fpath = GetAPIBase() + path
r = requests.request(method, fpath, json=body, headers=headers)
return r.text
def InvokeJSON(path: str, method: str, body) -> dict:
d = body.__dict__
return json.loads(InvokeTxt(path, method, d))
`, api.BasePath))
b.WriteString("#endregion\n\n")
b.WriteString("#region Types\n")
for k, v := range api.Types {
if v.Desc != "" {
//b.WriteString(fmt.Sprintf("/**\n%s*/\n", v.Desc))
}
if len(v.Fields) < 1 {
b.WriteString(fmt.Sprintf("@ dataclass\nclass %s :\n\tpass\n\n", k))
} else {
b.WriteString(fmt.Sprintf("@ dataclass\nclass %s :\n", k))
var ftype string
var ok bool
for kf, f := range v.Fields {
ftype, ok = exceptionaltypemapper[f.String()]
if ok {
log.Printf("Mapped exceptional type: %s ==> %s", f.String(), ftype)
}
if !ok {
if f.Array {
ftype, ok = tstypemapper["[]"+f.Type]
} else {
ftype, ok = tstypemapper[f.Type]
}
}
if !ok {
ftype = f.Type
}
if f.Map {
//fm, ok := tstypemapper[f.Mapkey]
//if !ok {
// fm = f.Mapkey
//}
//fv, ok := tstypemapper[f.Mapval]
//if !ok {
// fv = f.Mapval
//}
//ftype = "{[s:" + fm + "]:" + fv + "}"
ftype = "dict"
}
if f.Desc != "" {
//b.WriteString(fmt.Sprintf("\t/**\n%s*/\n", f.Desc))
}
b.WriteString(fmt.Sprintf("\t%s: %s\n", strings.ToLower(kf), ftype))
}
b.WriteString(fmt.Sprintf("\n\n"))
}
}
b.WriteString("#endregion\n\n")
b.WriteString("#region Methods\n")
for k, m := range api.Methods {
if m.Desc != "" {
//b.WriteString(fmt.Sprintf("/**\n%s*/\n", m.Desc))
}
rettype := _typeName(m.ResType)
if rettype != "" {
b.WriteString(fmt.Sprintf("def %s(req:%s)-> %s:\n", k, _typeName(m.ReqType), rettype))
} else {
b.WriteString(fmt.Sprintf("def %s(req:%s):\n", k, _typeName(m.ReqType)))
}
b.WriteString(fmt.Sprintf("\treturn InvokeJSON(\"%s\",\"%s\",req)\n", m.Path, m.Verb))
b.WriteString(fmt.Sprintf("\n\n"))
//}
}
b.WriteString("#endregion\n")
err := ioutil.WriteFile(f, b.Bytes(), 0600)
return err
}
+187
View File
@@ -0,0 +1,187 @@
package lib
import (
"bytes"
"fmt"
"io/ioutil"
"log"
"strings"
)
func processTSClientOutput(f string) error {
var tstypemapper map[string]string = make(map[string]string)
var exceptionaltypemapper map[string]string = make(map[string]string)
exceptionaltypemapper["[]byte"] = "string"
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"
_typeName := func(m *APIParamType) string {
if m.IsArray {
return m.Typename + "[]"
}
return m.Typename
}
b := bytes.Buffer{}
b.WriteString("//#region Base\n")
b.WriteString(fmt.Sprintf(`
var apibase="%s";
export function SetAPIBase(s:string){
apibase=s;
}
export function GetAPIBase(): string{
return apibase;
}
let REGEX_DATE = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)(Z|([+\-])(\d{2}):(\d{2}))$/
type HTMLMethod = "GET" | "POST" | "PUT" | "DELETE" | "HEAD" | "TRACE"
async function Invoke(path: string, method: HTMLMethod, body?: any): Promise<Response> {
let jbody = undefined
let init = {method: method, mode: "cors", credentials: "include", withCredentials: true}
if (!!body) {
let jbody = JSON.stringify(body)
//@ts-ignore
init.body = jbody
}
if (apibase.endsWith("/") && path.startsWith("/")) {
path = path.substr(1, path.length)
}
let fpath = (apibase + path)
//@ts-ignore
let res = await fetch(fpath, init)
return res
}
async function InvokeJSON(path: string, method: HTMLMethod, body?: any): Promise<any> {
let txt = await InvokeTxt(path, method, body)
if (txt == "") {
txt = "{}"
}
let ret = JSON.parse(txt, (k: string, v: string) => {
if (REGEX_DATE.exec(v)) {
return new Date(v)
}
return v
})
return ret
}
async function InvokeTxt(path: string, method: HTMLMethod, body?: any): Promise<string> {
//@ts-ignore
let res = await Invoke(path, method, body)
let txt = await res.text()
if (res.status < 200 || res.status >= 400) {
// webix.alert("API Error:" + res.status + "\n" + txt)
console.error("API Error:" + res.status + "\n" + txt)
let e = new Error(txt)
throw e
}
return txt
}
async function InvokeOk(path: string, method: HTMLMethod, body?: any): Promise<boolean> {
//@ts-ignore
let res = await Invoke(path, method, body)
let txt = await res.text()
if (res.status >= 400) {
console.error("API Error:" + res.status + "\n" + txt)
return false
}
return true
}
`, api.BasePath))
b.WriteString("//#endregion\n\n")
b.WriteString("//#region Types\n")
for k, v := range api.Types {
if v.Desc != "" {
b.WriteString(fmt.Sprintf("/**\n%s*/\n", v.Desc))
}
b.WriteString(fmt.Sprintf("export interface %s {\n", k))
var ftype string
var ok bool
for kf, f := range v.Fields {
ftype, ok = exceptionaltypemapper[f.String()]
if ok {
log.Printf("Mapped exceptional type: %s ==> %s", f.String(), ftype)
}
if !ok {
if f.Array {
ftype, ok = tstypemapper["[]"+f.Type]
} else {
ftype, ok = tstypemapper[f.Type]
}
}
if !ok {
ftype = f.Type
}
if f.Map {
fm, ok := tstypemapper[f.Mapkey]
if !ok {
fm = f.Mapkey
}
fv, ok := tstypemapper[f.Mapval]
if !ok {
fv = f.Mapval
}
ftype = "{[s:" + fm + "]:" + fv + "}"
}
if f.Desc != "" {
b.WriteString(fmt.Sprintf("\t/**\n%s*/\n", f.Desc))
}
b.WriteString(fmt.Sprintf("\t%s ?: %s\n", strings.ToLower(kf), ftype))
}
b.WriteString(fmt.Sprintf("}\n\n"))
}
b.WriteString("//#endregion\n\n")
b.WriteString("//#region Methods\n")
for k, m := range api.Methods {
if m.Desc != "" {
b.WriteString(fmt.Sprintf("/**\n%s*/\n", m.Desc))
}
b.WriteString(fmt.Sprintf("export async function %s(req:%s):Promise<%s>{\n", k, _typeName(m.ReqType), _typeName(m.ResType)))
b.WriteString(fmt.Sprintf("\treturn InvokeJSON(\"%s\",\"%s\",req)\n", m.Path, m.Verb))
b.WriteString(fmt.Sprintf("}\n\n"))
//}
}
b.WriteString("//#endregion\n")
err := ioutil.WriteFile(f, b.Bytes(), 0600)
return err
}
+15
View File
@@ -0,0 +1,15 @@
package lib
import (
"gopkg.in/yaml.v2"
"io/ioutil"
)
func processYaml(dst string, opts interface{}) error {
bs, err := yaml.Marshal(api)
if err != nil {
return err
}
err = ioutil.WriteFile(dst, bs, 0600)
return err
}
+113
View File
@@ -0,0 +1,113 @@
package lib
type API struct {
BasePath string `yaml:"basepath,omitempty"`
Host string `yaml:"host,omitempty"`
Types map[string]*APIType `yaml:"types,omitempty"`
Methods map[string]*APIMethod `yaml:"methods,omitempty"`
Namespace string `yaml:"namespace"`
Imports map[string]string `yaml:"imports"`
UsedImportsTypes map[string]string `yaml:"used_imports_types"`
UsedImportsFunctions map[string]string `yaml:"used_imports_functions"`
SortedPaths []*APIPath `yaml:"sorted_paths"`
Paths map[string]*APIPath `yaml:"paths"`
}
type APIPath struct {
Path string `yaml:"path"`
MapVerbs map[string]*APIVerb `yaml:"map_verbs"`
SortedVerbs []*APIVerb `yaml:"sorted_verbs"`
}
type APIVerb struct {
Verb string `yaml:"verb"`
Method *APIMethod `yaml:"method"`
}
type APIFieldTag struct {
Key string `yaml:"key"`
Name string `yaml:"name"`
Opts []string `yaml:"opts"`
}
type APIField struct {
Type string `yaml:"type,omitempty"`
Array bool `yaml:"array,omitempty"`
Desc string `yaml:"desc,omitempty"`
Map bool `yaml:"map,omitempty"`
Mapkey string `yaml:"mapkey,omitempty"`
Mapval string `yaml:"mapval,omitempty"`
Tags map[string]APIFieldTag `yaml:"tags,omitempty"`
}
func (a *APIField) String() string {
if a.Array {
return "[]" + a.Type
} else {
return a.Type
}
}
type APIType struct {
Name string `yaml:"name,omitempty"`
Desc string `yaml:"desc,omitempty"`
Fields map[string]*APIField `yaml:"fields,omitempty"`
Col string `yaml:"col,omitempty"`
TypeDef string `yaml:"-"`
}
type APIParamType struct {
Typename string
Ispointer bool
IsArray bool
}
type APIMethod struct {
Name string `yaml:"name"`
Desc string `yaml:"desc"`
Verb string `yaml:"verb"`
Path string `yaml:"path"`
Perm string `yaml:perm`
Raw bool `yaml:"raw"`
ReqType *APIParamType
ResType *APIParamType
}
func APIParamTypeToString(t *APIParamType) string {
ret := ""
if t.IsArray {
ret = "[]"
if t.Ispointer {
ret = ret + "*"
}
ret = ret + t.Typename
return ret
}
if t.Ispointer {
ret = ret + "*"
}
ret = ret + t.Typename
return ret
}
func APIParamTypeDecToString(t *APIParamType) string {
ret := ""
if t.IsArray {
ret = "[]"
if t.Ispointer {
ret = ret + "*"
}
ret = ret + t.Typename
return ret
}
if t.Ispointer {
ret = ret + "*"
}
ret = ret + t.Typename
return ret
}
func APIParamTypeUseRef(t *APIParamType) string {
if t.IsArray || !t.Ispointer {
return "&"
}
return ""
}
+19
View File
@@ -0,0 +1,19 @@
package lib
import (
"log"
)
func Err(e error) {
if e != nil {
panic(e)
}
}
func Debug(s string, p ...interface{}) {
log.Printf("DEBUG: "+s, p...)
}
func Log(s string, p ...interface{}) {
log.Printf("LOG: "+s, p...)
}