From fa16e16e9bcfa4de67f38c734aeede1b8315a8ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Sima=CC=83o?= Date: Sat, 30 Jan 2021 06:53:34 -0300 Subject: [PATCH] multiple improvements --- go.mod | 13 ++ go.sum | 22 +++ src/config.go | 40 ++-- src/loader.go | 265 ++++++++++++++++++++++++++ src/main.go | 348 +++++++++-------------------------- src/processGoClientOutput.go | 8 +- src/processGoServerOutput.go | 44 ++++- src/processHTTPCallOutput.go | 99 ++++++++++ src/processTSClientOutput.go | 29 ++- src/processYaml.go | 15 ++ src/types.go | 8 +- src/util.go | 4 +- test/api.yaml | 66 +++++++ test/goapi/api.go | 63 ------- test/goapi/apigen.go | 127 +++++++++++++ test/goapi/test.go | 76 +++++--- test/gocli/apicli.go | 74 +++++++- test/test.http | 61 ++++++ test/tscli/api.ts | 34 ++++ 19 files changed, 993 insertions(+), 403 deletions(-) create mode 100644 go.mod create mode 100644 go.sum create mode 100644 src/loader.go create mode 100644 src/processHTTPCallOutput.go create mode 100644 src/processYaml.go create mode 100644 test/api.yaml delete mode 100644 test/goapi/api.go create mode 100644 test/goapi/apigen.go create mode 100644 test/test.http diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..bb89f0a --- /dev/null +++ b/go.mod @@ -0,0 +1,13 @@ +module go.digitalcircle.com.br/tools/apigen + +go 1.15 + +require ( + github.com/alecthomas/kong v0.2.12 + github.com/bxcodec/faker/v3 v3.5.0 // indirect + github.com/fatih/structtag v1.2.0 + github.com/pkg/errors v0.9.1 + github.com/yuin/stagparser v0.0.0-20181218160030-e10a81132760 + go.digitalcircle.com.br/golib/base v0.0.0-20210124165830-341c1d300435 + gopkg.in/yaml.v2 v2.4.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..7dc2059 --- /dev/null +++ b/go.sum @@ -0,0 +1,22 @@ +github.com/alecthomas/kong v0.2.12 h1:X3kkCOXGUNzLmiu+nQtoxWqj4U2a39MpSJR3QdQXOwI= +github.com/alecthomas/kong v0.2.12/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE= +github.com/bxcodec/faker v1.5.0 h1:RIWOeAcM3ZHye1i8bQtHU2LfNOaLmHuRiCo60mNMOcQ= +github.com/bxcodec/faker v2.0.1+incompatible h1:P0KUpUw5w6WJXwrPfv35oc91i4d8nf40Nwln+M/+faA= +github.com/bxcodec/faker/v3 v3.5.0 h1:Rahy6dwbd6up0wbwbV7dFyQb+jmdC51kpATuUdnzfMg= +github.com/bxcodec/faker/v3 v3.5.0/go.mod h1:gF31YgnMSMKgkvl+fyEo1xuSMbEuieyqfeslGYFjneM= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= +github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/yuin/stagparser v0.0.0-20181218160030-e10a81132760 h1:i+m5+M2renk/OmDHvzRjlBQXm+5X6WR6xPjWfHNzvcM= +github.com/yuin/stagparser v0.0.0-20181218160030-e10a81132760/go.mod h1:+qbo7cNcx8dT/77C41x4MZbasDrLuUDI/04ZGR/7IqM= +go.digitalcircle.com.br/golib/base v0.0.0-20210124165830-341c1d300435 h1:XVnDH9egiX/iTc59738ZozUZHGahb494y52oxmLJYN4= +go.digitalcircle.com.br/golib/base v0.0.0-20210124165830-341c1d300435/go.mod h1:8BeArwDJW81pf5Fhchrs/Hh2YSg0FMnNeaV+j1kUEyM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/src/config.go b/src/config.go index 47c3b9e..cb7b4b2 100644 --- a/src/config.go +++ b/src/config.go @@ -1,25 +1,19 @@ package main -import ( - "flag" - "gopkg.in/yaml.v2" - "io/ioutil" -) - -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) -} +//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) +//} diff --git a/src/loader.go b/src/loader.go new file mode 100644 index 0000000..bfe55d1 --- /dev/null +++ b/src/loader.go @@ -0,0 +1,265 @@ +package main + +import ( + "github.com/fatih/structtag" + "go/token" + "net/http" +) + +import ( + "go/ast" + "go/parser" + "log" + "strings" +) + +var api API + +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{} + 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: + 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) + } + 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 + } + 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.ArrayType: + reqType.IsArray = true + switch y := x.Elt.(type) { + case *ast.Ident: + reqType.Typename = y.Name + case *ast.SelectorExpr: + 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: + reqType.Typename = z.X.(*ast.Ident).Name + "." + z.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.ArrayType: + resType.IsArray = true + switch y := x.Elt.(type) { + case *ast.Ident: + resType.Typename = y.Name + case *ast.SelectorExpr: + 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: + resType.Typename = z.X.(*ast.Ident).Name + "." + z.Sel.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 load(src string) error { + + 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, src, nil, parser.ParseComments) + + if err != nil { + return 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 + }) + } + + 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 + } + + return nil +} diff --git a/src/main.go b/src/main.go index 9de1a2f..1225445 100644 --- a/src/main.go +++ b/src/main.go @@ -1,282 +1,102 @@ package main import ( - "go/ast" - "go/parser" - "go/token" + "github.com/alecthomas/kong" + "github.com/pkg/errors" "log" - "net/http" - "os" - "strings" ) -var api API - -//var httptestdir string -var tstypemapper map[string]string = make(map[string]string) -var exceptionaltypemapper 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.ArrayType: - reqType.IsArray = true - switch y := x.Elt.(type) { - case *ast.Ident: - reqType.Typename = y.Name - case *ast.SelectorExpr: - 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: - reqType.Typename = z.X.(*ast.Ident).Name + "." + z.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.ArrayType: - resType.IsArray = true - switch y := x.Elt.(type) { - case *ast.Ident: - resType.Typename = y.Name - case *ast.SelectorExpr: - 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: - resType.Typename = z.X.(*ast.Ident).Name + "." + z.Sel.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 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 - }) - } - +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"` + Gocli struct { + Src string `arg help:"Source Dir"` + Dst string `arg help:"Dst file"` + } `cmd help:"Gens Go 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 main() { - loadConfig() + 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 ": + log.Printf("Gens YAML") + src = CLI.Yaml.Src + processor = func() error { + return processYaml(CLI.Yaml.Fname, nil) + } + case "goserver ": + log.Printf("Gen GO Server") + src = CLI.Goserver.Src + processor = func() error { + return processGoServerOutput(CLI.Goserver.Src + "/apigen.go") + } + case "gocli ": + log.Printf("Gen GO Client") + src = CLI.Gocli.Src + processor = func() error { + return processGoClientOutput(CLI.Gocli.Dst) + } + case "ts ": + log.Printf("Gen TS Client") + src = CLI.Ts.Src + processor = func() error { + return processTSClientOutput(CLI.Ts.Dst) + } + case "http ": + log.Printf("Gen Http Client") + src = CLI.Http.Src + processor = func() error { + return processHttpCallOut(CLI.Http.Dst) + } + default: + err = errors.New("unknown option") + } - exceptionaltypemapper["[]byte"] = "string" + if err != nil { + panic(err) + } + err = load(src) - 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" + if err != nil { + panic(err) + } + err = processor() - os.Remove(config.Gofname) - load() - process(&api) + if err != nil { + panic(err) + } + //loadConfig() + // + + //os.Remove(config.Gofname) + + //process(&api) } diff --git a/src/processGoClientOutput.go b/src/processGoClientOutput.go index 80e2952..2cd3e95 100644 --- a/src/processGoClientOutput.go +++ b/src/processGoClientOutput.go @@ -7,9 +7,9 @@ import ( "strings" ) -func processGoClientOutput(api *API) { +func processGoClientOutput(f string) error { b := bytes.Buffer{} - f := config.Goclifname + fparts := strings.Split(f, "/") pkg := fparts[len(fparts)-2] @@ -109,7 +109,5 @@ func invoke(m string, path string, bodyo interface{}) (*json.Decoder, error) { } err := ioutil.WriteFile(f, b.Bytes(), 0600) - if err != nil { - panic(err) - } + return err } diff --git a/src/processGoServerOutput.go b/src/processGoServerOutput.go index 2ad368c..fd19918 100644 --- a/src/processGoServerOutput.go +++ b/src/processGoServerOutput.go @@ -7,15 +7,26 @@ import ( "io/ioutil" "os" "os/exec" + "sort" "strings" ) -func processGoServerOutput(api *API) { +func processGoServerOutput(f string) error { + + strKeys := make([]string, 0) + for k, _ := range api.Types { + strKeys = append(strKeys, k) + } + sort.Strings(strKeys) + + strMKeys := make([]string, 0) + for k, _ := range api.Methods { + strKeys = append(strMKeys, k) + } + sort.Strings(strMKeys) b := bytes.Buffer{} - f := config.Gofname - os.Remove(f) b.WriteString(fmt.Sprintf(`package %s @@ -47,7 +58,8 @@ func Init() API{ Perms: make(map[string]string), } `) - for _, m := range api.Methods { + for _, k := range strMKeys { + m := api.Methods[k] if m.Perm != "" { b.WriteString(fmt.Sprintf(` ret.Perms["%s_%s"]="%s" @@ -57,12 +69,25 @@ ret.Perms["%s_%s"]="%s" b.WriteString("\n\n") - for p, mv := range httpMapper { - - b.WriteString(fmt.Sprintf(" mux.HandleFunc(\"%s\",func(w http.ResponseWriter, r *http.Request) {\n", strings.Replace(p, "//", "/", -1))) + sortedMapper := make([]string, 0) + for k, _ := range httpMapper { + sortedMapper = append(sortedMapper, k) + } + sort.Strings(sortedMapper) + for _, p := range sortedMapper { + mv := httpMapper[p] + b.WriteString(fmt.Sprintf(" mux.HandleFunc(\"%s\",func(w http.ResponseWriter, r *http.Request) {\n", + strings.Replace(p, "//", "/", -1))) b.WriteString(" switch r.Method{\n") - for v, id := range mv { + sorteVerbs := make([]string, 0) + for k, _ := range mv { + sorteVerbs = append(sorteVerbs, k) + } + sort.Strings(sorteVerbs) + + for _, v := range sorteVerbs { + id := mv[v] b.WriteString(fmt.Sprintf(" case \"%s\":", v)) if api.Methods[id].Raw { @@ -116,10 +141,11 @@ ret.Perms["%s_%s"]="%s" err := ioutil.WriteFile(f, b.Bytes(), 0600) if err != nil { - panic(err) + return err } cmd := exec.Command("/bin/sh", "-c", "go fmt "+f) bs, err := cmd.Output() //dc.Err(err) dc.Log(string(bs)) + return err } diff --git a/src/processHTTPCallOutput.go b/src/processHTTPCallOutput.go new file mode 100644 index 0000000..618a4f2 --- /dev/null +++ b/src/processHTTPCallOutput.go @@ -0,0 +1,99 @@ +package main + +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=\n\n") + b.WriteString(tj) + b.WriteString("\n\n") + + } + + err := ioutil.WriteFile(f, b.Bytes(), 0600) + return err +} diff --git a/src/processTSClientOutput.go b/src/processTSClientOutput.go index 9f289df..f47c662 100644 --- a/src/processTSClientOutput.go +++ b/src/processTSClientOutput.go @@ -8,7 +8,27 @@ import ( "strings" ) -func processTSClientOutput(f string, api *API) { +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 { @@ -18,9 +38,6 @@ func processTSClientOutput(f string, api *API) { } b := bytes.Buffer{} - if f == "" { - f = config.Tsfname - } b.WriteString("//#region Base\n") b.WriteString(fmt.Sprintf(` @@ -166,7 +183,5 @@ async function InvokeOk(path: string, method: HTMLMethod, body?: any): Promise= 400 { + bs, err := ioutil.ReadAll(res.Body) + if err != nil { + panic(err) + } + return nil, errors.New(string(bs)) + } + ret := json.NewDecoder(res.Body) return ret, nil } -type AStr struct { - A string `json:"a"` +type ARequestStruct struct { + C time.Time `json:"c"` + D string `json:"d"` + A string `json:"a"` + B int64 `json:"b"` } -func SomeAPI2(req *AStr) (res []*AStr, err error) { - dec, err := invoke("POST", "/someapi", req) +type AResponseStruct struct { + A string `json:"a"` + B int64 `json:"b"` + C time.Time `json:"c"` + D string `json:"d"` +} + +type AStr struct { + A string `json:"a"` + B []string `json:"b"` +} + +type AStr2 struct { + X string `json:"x"` + Y []string `json:"y"` + Z []AStr `json:"z"` + W map[string]AStr `json:"w"` +} + +func SomeAPI3(req *AStr) (res []*AStr, err error) { + var dec *json.Decoder + dec, err = invoke("POST", "/someapi3", req) + if err != nil { + return + } ret := []*AStr{} - err = dec.Decode(ret) + err = dec.Decode(&ret) + if err != nil { + return nil, err + } + return ret, err +} +func SomeAPI(req *AStr) (res []*AStr, err error) { + var dec *json.Decoder + dec, err = invoke("PUT", "/someapi", req) + if err != nil { + return + } + ret := []*AStr{} + err = dec.Decode(&ret) + if err != nil { + return nil, err + } + return ret, err +} +func SomeAPI2(req *AStr) (res []*AStr, err error) { + var dec *json.Decoder + dec, err = invoke("POST", "/someapi", req) + if err != nil { + return + } + ret := []*AStr{} + err = dec.Decode(&ret) if err != nil { return nil, err } diff --git a/test/test.http b/test/test.http new file mode 100644 index 0000000..de94254 --- /dev/null +++ b/test/test.http @@ -0,0 +1,61 @@ +### +#SomeAPI +PUT https://host/basepath/someapi +Content-Type: application/json +Cookie: dc= + +{} + +### +#SomeAPI2 +POST https://host/basepath/someapi +Content-Type: application/json +Cookie: dc= + +{ + "addresses": { + "a": { + "SUPERCALIFRAGILISPEALIDOUX": "A STRING VALUE", + "city": "A STRING VALUE", + "country": "A STRING VALUE", + "housenumber": 123456, + "iscondo": true + }, + "b": { + "SUPERCALIFRAGILISPEALIDOUX": "A STRING VALUE", + "city": "A STRING VALUE", + "country": "A STRING VALUE", + "housenumber": 123456, + "iscondo": true + }, + "c": { + "SUPERCALIFRAGILISPEALIDOUX": "A STRING VALUE", + "city": "A STRING VALUE", + "country": "A STRING VALUE", + "housenumber": 123456, + "iscondo": true + } + }, + "firstname": "A STRING VALUE", + "lastname": "A STRING VALUE", + "products": [ + "A STRING VALUE", + "A STRING VALUE", + "A STRING VALUE" + ] +} + +### +#SomeAPI3 +POST https://host/basepath/someapi3 +Content-Type: application/json +Cookie: dc= + +{ + "SUPERCALIFRAGILISPEALIDOUX": "A STRING VALUE", + "city": "A STRING VALUE", + "country": "A STRING VALUE", + "housenumber": 123456, + "iscondo": true +} + diff --git a/test/tscli/api.ts b/test/tscli/api.ts index f57492c..7e83835 100644 --- a/test/tscli/api.ts +++ b/test/tscli/api.ts @@ -83,15 +83,49 @@ async function InvokeOk(path: string, method: HTMLMethod, body?: any): Promise{ + return InvokeJSON("/someapi","PUT",req) +} + /** SomeAPI2*/ export async function SomeAPI2(req:AStr):Promise{ return InvokeJSON("/someapi","POST",req) } +/** +SomeAPI3*/ +export async function SomeAPI3(req:AStr):Promise{ + return InvokeJSON("/someapi3","POST",req) +} + //#endregion