multiple improvements

master
Paulo Simão 2021-01-30 06:53:34 -03:00
parent 517aa43668
commit fa16e16e9b
19 changed files with 993 additions and 403 deletions

13
go.mod 100644
View File

@ -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
)

22
go.sum 100644
View File

@ -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=

View File

@ -1,25 +1,19 @@
package main package main
import ( //type Config struct {
"flag" // Gofname string `yaml:"gofname"`
"gopkg.in/yaml.v2" // Goimpldir string `yaml:"goimpldir"`
"io/ioutil" // Tsfname string `json:"tsfname"`
) // Goclifname string `json:"goclifname"`
//}
type Config struct { //
Gofname string `yaml:"gofname"` //var config Config
Goimpldir string `yaml:"goimpldir"` //
Tsfname string `json:"tsfname"` //func loadConfig() {
Goclifname string `json:"goclifname"` // fname := flag.String("f", ".apigen.yaml", "File with config to load - defaults to '.apigen'")
} // flag.Parse()
// bs, err := ioutil.ReadFile(*fname)
var config Config // Err(err)
// err = yaml.Unmarshal(bs, &config)
func loadConfig() { // Err(err)
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)
}

265
src/loader.go 100644
View File

@ -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
}

View File

@ -1,282 +1,102 @@
package main package main
import ( import (
"go/ast" "github.com/alecthomas/kong"
"go/parser" "github.com/pkg/errors"
"go/token"
"log" "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 knownMethods map[string]bool = make(map[string]bool)
var httpMapper map[string]map[string]string = make(map[string]map[string]string) var httpMapper map[string]map[string]string = make(map[string]map[string]string)
var packageName string = "main" var packageName string = "main"
func addStruct(a *ast.GenDecl) { var CLI struct {
md := manageComments(a.Doc) Yaml struct {
if md["API"] == "" { Src string `arg help:"Source Dir"`
return Fname string `arg help:"File to be generated"`
} } `cmd help:"Gens YAML metamodel"`
tp := APIType{ Goserver struct {
Name: "", Src string `arg help:"Source Dir"`
Desc: "", } `cmd help:"Gens GO Server impl"`
Fields: make(map[string]*APIField), Gocli struct {
Col: md["COL"], Src string `arg help:"Source Dir"`
} Dst string `arg help:"Dst file"`
tp.Name = a.Specs[0].(*ast.TypeSpec).Name.Name } `cmd help:"Gens Go Cli impl"`
log.Printf("Type:" + tp.Name) Ts struct {
for _, v := range a.Specs[0].(*ast.TypeSpec).Type.(*ast.StructType).Fields.List { Src string `arg help:"Source Dir"`
tp.Fields[v.Names[0].Name] = &APIField{} Dst string `arg help:"Dst file"`
switch x := v.Type.(type) { } `cmd help:"Gens Typescript Cli impl"`
case *ast.Ident: Http struct {
tp.Fields[v.Names[0].Name].Type = x.Name Src string `arg help:"Source Dir"`
case *ast.ArrayType: Dst string `arg help:"Dst file"`
switch z := x.Elt.(type) { } `cmd help:"Gens Http call impl"`
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
})
}
} }
func main() { 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 <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 "gocli <src> <dst>":
log.Printf("Gen GO Client")
src = CLI.Gocli.Src
processor = func() error {
return processGoClientOutput(CLI.Gocli.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")
}
exceptionaltypemapper["[]byte"] = "string" if err != nil {
panic(err)
}
err = load(src)
tstypemapper["time.Time"] = "Date" if err != nil {
tstypemapper["primitive.ObjectID"] = "string" panic(err)
tstypemapper["time.Duration"] = "Date" }
tstypemapper["int"] = "number" err = processor()
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) if err != nil {
load() panic(err)
process(&api) }
//loadConfig()
//
//os.Remove(config.Gofname)
//process(&api)
} }

View File

@ -7,9 +7,9 @@ import (
"strings" "strings"
) )
func processGoClientOutput(api *API) { func processGoClientOutput(f string) error {
b := bytes.Buffer{} b := bytes.Buffer{}
f := config.Goclifname
fparts := strings.Split(f, "/") fparts := strings.Split(f, "/")
pkg := fparts[len(fparts)-2] 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) err := ioutil.WriteFile(f, b.Bytes(), 0600)
if err != nil { return err
panic(err)
}
} }

View File

@ -7,15 +7,26 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"os/exec" "os/exec"
"sort"
"strings" "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{} b := bytes.Buffer{}
f := config.Gofname
os.Remove(f) os.Remove(f)
b.WriteString(fmt.Sprintf(`package %s b.WriteString(fmt.Sprintf(`package %s
@ -47,7 +58,8 @@ func Init() API{
Perms: make(map[string]string), Perms: make(map[string]string),
} }
`) `)
for _, m := range api.Methods { for _, k := range strMKeys {
m := api.Methods[k]
if m.Perm != "" { if m.Perm != "" {
b.WriteString(fmt.Sprintf(` b.WriteString(fmt.Sprintf(`
ret.Perms["%s_%s"]="%s" ret.Perms["%s_%s"]="%s"
@ -57,12 +69,25 @@ ret.Perms["%s_%s"]="%s"
b.WriteString("\n\n") b.WriteString("\n\n")
for p, mv := range httpMapper { sortedMapper := make([]string, 0)
for k, _ := range httpMapper {
b.WriteString(fmt.Sprintf(" mux.HandleFunc(\"%s\",func(w http.ResponseWriter, r *http.Request) {\n", strings.Replace(p, "//", "/", -1))) 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") 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)) b.WriteString(fmt.Sprintf(" case \"%s\":", v))
if api.Methods[id].Raw { if api.Methods[id].Raw {
@ -116,10 +141,11 @@ ret.Perms["%s_%s"]="%s"
err := ioutil.WriteFile(f, b.Bytes(), 0600) err := ioutil.WriteFile(f, b.Bytes(), 0600)
if err != nil { if err != nil {
panic(err) return err
} }
cmd := exec.Command("/bin/sh", "-c", "go fmt "+f) cmd := exec.Command("/bin/sh", "-c", "go fmt "+f)
bs, err := cmd.Output() bs, err := cmd.Output()
//dc.Err(err) //dc.Err(err)
dc.Log(string(bs)) dc.Log(string(bs))
return err
} }

View File

@ -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=<MYCOOKIE>\n\n")
b.WriteString(tj)
b.WriteString("\n\n")
}
err := ioutil.WriteFile(f, b.Bytes(), 0600)
return err
}

View File

@ -8,7 +8,27 @@ import (
"strings" "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 { _typeName := func(m *APIParamType) string {
if m.IsArray { if m.IsArray {
@ -18,9 +38,6 @@ func processTSClientOutput(f string, api *API) {
} }
b := bytes.Buffer{} b := bytes.Buffer{}
if f == "" {
f = config.Tsfname
}
b.WriteString("//#region Base\n") b.WriteString("//#region Base\n")
b.WriteString(fmt.Sprintf(` b.WriteString(fmt.Sprintf(`
@ -166,7 +183,5 @@ async function InvokeOk(path: string, method: HTMLMethod, body?: any): Promise<b
b.WriteString("//#endregion\n") b.WriteString("//#endregion\n")
err := ioutil.WriteFile(f, b.Bytes(), 0600) err := ioutil.WriteFile(f, b.Bytes(), 0600)
if err != nil { return err
panic(err)
}
} }

15
src/processYaml.go 100644
View File

@ -0,0 +1,15 @@
package main
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
}

View File

@ -7,6 +7,11 @@ type API struct {
Methods map[string]*APIMethod `yaml:"methods"` Methods map[string]*APIMethod `yaml:"methods"`
Namespace string Namespace string
} }
type APIFieldTag struct {
Key string
Name string
Opts []string
}
type APIField struct { type APIField struct {
Type string `yaml:"type"` Type string `yaml:"type"`
Array bool `yaml:"array"` Array bool `yaml:"array"`
@ -14,6 +19,7 @@ type APIField struct {
Map bool `yaml:"map"` Map bool `yaml:"map"`
Mapkey string `yaml:"mapkey"` Mapkey string `yaml:"mapkey"`
Mapval string `yaml:"mapval"` Mapval string `yaml:"mapval"`
Tags map[string]APIFieldTag
} }
func (a *APIField) String() string { func (a *APIField) String() string {
@ -25,7 +31,7 @@ func (a *APIField) String() string {
} }
type APIType struct { type APIType struct {
Name string Name string `yaml:"name"`
Desc string `yaml:"desc"` Desc string `yaml:"desc"`
Fields map[string]*APIField `yaml:"fields"` Fields map[string]*APIField `yaml:"fields"`
Col string `yaml:"col"` Col string `yaml:"col"`

View File

@ -1,6 +1,8 @@
package main package main
import "log" import (
"log"
)
func Err(e error) { func Err(e error) {
if e != nil { if e != nil {

66
test/api.yaml 100644
View File

@ -0,0 +1,66 @@
basepath: ""
host: ""
types:
AStr:
name: AStr
desc: ""
fields:
A:
type: string
array: false
desc: ""
map: false
mapkey: ""
mapval: ""
B:
type: string
array: true
desc: ""
map: false
mapkey: ""
mapval: ""
col: ""
methods:
SomeAPI:
desc: SomeAPI
verb: PUT
path: /someapi
perm: ASD
reqtype:
typename: AStr
ispointer: true
isarray: false
restype:
typename: AStr
ispointer: true
isarray: true
raw: false
SomeAPI2:
desc: SomeAPI2
verb: POST
path: /someapi
perm: ""
reqtype:
typename: AStr
ispointer: true
isarray: false
restype:
typename: AStr
ispointer: true
isarray: true
raw: false
SomeAPI3:
desc: SomeAPI3
verb: POST
path: /someapi3
perm: ""
reqtype:
typename: AStr
ispointer: true
isarray: false
restype:
typename: AStr
ispointer: true
isarray: true
raw: false
namespace: ""

View File

@ -1,63 +0,0 @@
package goapi
import (
"context"
"encoding/json"
"net/http"
"strings"
)
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]]
}
func Init() API {
mux := &http.ServeMux{}
ret := API{
Mux: mux,
Perms: make(map[string]string),
}
mux.HandleFunc("/someapi", func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "POST":
h_SomeAPI2(w, r)
default:
http.Error(w, "Method not allowed", 500)
}
})
return ret
}
func h_SomeAPI2(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx = context.WithValue(r.Context(), "REQ", r)
ctx = context.WithValue(ctx, "RES", w)
req := &AStr{}
if r.Method != http.MethodGet && r.Method != http.MethodHead {
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
}
res, err := SomeAPI2(ctx, req)
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
}
}

View File

@ -0,0 +1,127 @@
package goapi
import (
"context"
"encoding/json"
"net/http"
"strings"
)
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]]
}
func Init() API {
mux := &http.ServeMux{}
ret := API{
Mux: mux,
Perms: make(map[string]string),
}
mux.HandleFunc("/someapi", func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "POST":
h_SomeAPI2(w, r)
case "PUT":
h_SomeAPI(w, r)
default:
http.Error(w, "Method not allowed", 500)
}
})
mux.HandleFunc("/someapi3", func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "POST":
h_SomeAPI3(w, r)
default:
http.Error(w, "Method not allowed", 500)
}
})
return ret
}
func h_SomeAPI(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx = context.WithValue(r.Context(), "REQ", r)
ctx = context.WithValue(ctx, "RES", w)
req := &Noop{}
if r.Method != http.MethodGet && r.Method != http.MethodHead {
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
}
res, err := SomeAPI(ctx, req)
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
}
}
func h_SomeAPI2(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx = context.WithValue(r.Context(), "REQ", r)
ctx = context.WithValue(ctx, "RES", w)
req := &AStr2{}
if r.Method != http.MethodGet && r.Method != http.MethodHead {
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
}
res, err := SomeAPI2(ctx, req)
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
}
}
func h_SomeAPI3(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx = context.WithValue(r.Context(), "REQ", r)
ctx = context.WithValue(ctx, "RES", w)
req := &AStr{}
if r.Method != http.MethodGet && r.Method != http.MethodHead {
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
}
res, err := SomeAPI3(ctx, req)
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
}
}

View File

@ -2,42 +2,68 @@ package goapi
import ( import (
"context" "context"
"time"
) )
// //
///*@API*/ /*@API*/
//type ARequestStruct struct { type ARequestStruct struct {
// A *string `json:"a"` A *string `json:"SUPERCALIFRAGILISPEALIDOUX"`
// B int64 B int64 `json:"bcd"`
// C time.Time C time.Time
// D *string D *string
//} }
//
///*@API*/ /*@API*/
//type AResponseStruct struct { type AResponseStruct struct {
// A string A string
// B int64 B int64
// C time.Time C time.Time
// D *string D *string
//} }
//
///*
//@API
//@PATH: /someapi
//*/
//func SomeAPI(ctx context.Context, a *ARequestStruct) (*AResponseStruct, error) {
// return nil, nil
//}
/*@API*/ /*@API*/
type AStr struct { type AStr struct {
A string Country string
City string
HouseNumber int64
IsCondo bool
SomeWeirdTest string `json:"SUPERCALIFRAGILISPEALIDOUX"`
}
/*@API*/
type AStr2 struct {
Firstname string `json:"firstname"`
Lastname string `json:"lastname"`
Products []string
Addresses map[string]AStr
}
/*@API*/
type Noop struct{}
/*
@API
@PATH: /someapi
@PERM: ASD
@VERB: PUT
*/
func SomeAPI(ctx context.Context, a *Noop) ([]*AStr, error) {
return nil, nil
} }
/* /*
@API @API
@PATH: /someapi @PATH: /someapi
*/ */
func SomeAPI2(ctx context.Context, a *AStr) ([]*AStr, error) { func SomeAPI2(ctx context.Context, a *AStr2) ([]*AStr, error) {
return nil, nil
}
/*
@API
@PATH: /someapi3
*/
func SomeAPI3(ctx context.Context, a *AStr) ([]*AStr, error) {
return nil, nil return nil, nil
} }

View File

@ -3,7 +3,10 @@ package gocli
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"errors"
"io/ioutil"
"net/http" "net/http"
"time"
) )
var Basepath string = "" var Basepath string = ""
@ -33,18 +36,79 @@ func invoke(m string, path string, bodyo interface{}) (*json.Decoder, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if res.StatusCode >= 400 {
bs, err := ioutil.ReadAll(res.Body)
if err != nil {
panic(err)
}
return nil, errors.New(string(bs))
}
ret := json.NewDecoder(res.Body) ret := json.NewDecoder(res.Body)
return ret, nil return ret, nil
} }
type AStr struct { type ARequestStruct struct {
C time.Time `json:"c"`
D string `json:"d"`
A string `json:"a"` A string `json:"a"`
B int64 `json:"b"`
} }
func SomeAPI2(req *AStr) (res []*AStr, err error) { type AResponseStruct struct {
dec, err := invoke("POST", "/someapi", req) 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{} 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 { if err != nil {
return nil, err return nil, err
} }

61
test/test.http 100644
View File

@ -0,0 +1,61 @@
###
#SomeAPI
PUT https://host/basepath/someapi
Content-Type: application/json
Cookie: dc=<MYCOOKIE>
{}
###
#SomeAPI2
POST https://host/basepath/someapi
Content-Type: application/json
Cookie: dc=<MYCOOKIE>
{
"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=<MYCOOKIE>
{
"SUPERCALIFRAGILISPEALIDOUX": "A STRING VALUE",
"city": "A STRING VALUE",
"country": "A STRING VALUE",
"housenumber": 123456,
"iscondo": true
}

View File

@ -83,15 +83,49 @@ async function InvokeOk(path: string, method: HTMLMethod, body?: any): Promise<b
//#region Types //#region Types
export interface AStr { export interface AStr {
a:string a:string
b:string
}
export interface AStr2 {
z:AStr
w:{[s:string]:AStr}
x:string
y:string
}
export interface ARequestStruct {
b:number
c:Date
d:string
a:string
}
export interface AResponseStruct {
a:string
b:number
c:Date
d:string
} }
//#endregion //#endregion
//#region Methods //#region Methods
/**
SomeAPI*/
export async function SomeAPI(req:AStr):Promise<AStr[]>{
return InvokeJSON("/someapi","PUT",req)
}
/** /**
SomeAPI2*/ SomeAPI2*/
export async function SomeAPI2(req:AStr):Promise<AStr[]>{ export async function SomeAPI2(req:AStr):Promise<AStr[]>{
return InvokeJSON("/someapi","POST",req) return InvokeJSON("/someapi","POST",req)
} }
/**
SomeAPI3*/
export async function SomeAPI3(req:AStr):Promise<AStr[]>{
return InvokeJSON("/someapi3","POST",req)
}
//#endregion //#endregion