Recovering operational

master
Paulo Simão 2021-09-06 09:54:09 -03:00
parent 45e64c198b
commit ac8771aac4
21 changed files with 293 additions and 359 deletions

2
go.mod
View File

@ -1,6 +1,6 @@
module go.digitalcircle.com.br/tools/apigen module go.digitalcircle.com.br/tools/apigen
go 1.16 go 1.17
require ( require (
github.com/alecthomas/kong v0.2.15 github.com/alecthomas/kong v0.2.15

View File

@ -1,4 +1,4 @@
package main package lib
import ( import (
"go/ast" "go/ast"

View File

@ -1,4 +1,4 @@
package main package lib
//type Config struct { //type Config struct {
// Gofname string `yaml:"gofname"` // Gofname string `yaml:"gofname"`

View File

@ -1,4 +1,4 @@
package main package lib
import ( import (
"fmt" "fmt"

View File

@ -1,4 +1,4 @@
package main package lib
import ( import (
"github.com/alecthomas/kong" "github.com/alecthomas/kong"
@ -18,9 +18,9 @@ var CLI struct {
Goserver struct { Goserver struct {
Src string `arg help:"Source Dir"` Src string `arg help:"Source Dir"`
} `cmd help:"Gens GO Server impl"` } `cmd help:"Gens GO Server impl"`
Gin struct { //Gin struct {
Src string `arg help:"Source Dir"` // Src string `arg help:"Source Dir"`
} `cmd help:"Gens Gin Server impl"` //} `cmd help:"Gens Gin Server impl"`
Gocli struct { Gocli struct {
Src string `arg help:"Source Dir"` Src string `arg help:"Source Dir"`
Dst string `arg help:"Dst file"` Dst string `arg help:"Dst file"`
@ -39,7 +39,7 @@ var CLI struct {
} `cmd help:"Gens Http call impl"` } `cmd help:"Gens Http call impl"`
} }
func main() { func Run() {
var processor func() error var processor func() error
kong.ConfigureHelp(kong.HelpOptions{ kong.ConfigureHelp(kong.HelpOptions{
@ -65,12 +65,12 @@ func main() {
processor = func() error { processor = func() error {
return processGoServerOutput(CLI.Goserver.Src + "/apigen.go") return processGoServerOutput(CLI.Goserver.Src + "/apigen.go")
} }
case "gin <src>": //case "gin <src>":
log.Printf("Gen Gin Server") // log.Printf("Gen Gin Server")
src = CLI.Gin.Src // src = CLI.Gin.Src
processor = func() error { // processor = func() error {
return processGinServerOutput(CLI.Gin.Src + "/apigen.go") // return processGinServerOutput(CLI.Gin.Src + "/apigen.go")
} // }
case "gocli <src> <dst>": case "gocli <src> <dst>":
log.Printf("Gen GO Client") log.Printf("Gen GO Client")
src = CLI.Gocli.Src src = CLI.Gocli.Src

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

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

View File

@ -1,4 +1,4 @@
package main package lib
import ( import (
"bytes" "bytes"

View File

@ -1,4 +1,4 @@
package main package lib
import ( import (
"bytes" "bytes"

View File

@ -1,4 +1,4 @@
package main package lib
import ( import (
"bytes" "bytes"

View File

@ -1,4 +1,4 @@
package main package lib
import ( import (
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"

View File

@ -1,4 +1,4 @@
package main package lib
type API struct { type API struct {
BasePath string `yaml:"basepath,omitempty"` BasePath string `yaml:"basepath,omitempty"`
@ -66,6 +66,7 @@ type APIMethod struct {
Verb string `yaml:"verb"` Verb string `yaml:"verb"`
Path string `yaml:"path"` Path string `yaml:"path"`
Perm string `yaml:perm` Perm string `yaml:perm`
Raw bool `yaml:"raw"`
ReqType *APIParamType ReqType *APIParamType
ResType *APIParamType ResType *APIParamType
} }

View File

@ -1,4 +1,4 @@
package main package lib
import ( import (
"log" "log"
@ -17,4 +17,3 @@ func Debug(s string, p ...interface{}) {
func Log(s string, p ...interface{}) { func Log(s string, p ...interface{}) {
log.Printf("LOG: "+s, p...) log.Printf("LOG: "+s, p...)
} }

7
main.go 100644
View File

@ -0,0 +1,7 @@
package main
import "go.digitalcircle.com.br/tools/apigen/lib"
func main() {
lib.Run()
}

View File

@ -1,24 +0,0 @@
package main
import (
"bytes"
_ "embed"
"os"
"text/template"
)
//go:embed templates/goserver-gin.gotmpl
var ginServerTemplate string
func processGinServerOutput(f string) error {
tmpl, err := template.New("gin").Parse(ginServerTemplate)
if err != nil {
return err
}
buf := &bytes.Buffer{}
err = tmpl.Execute(buf, api)
if err != nil {
return err
}
return os.WriteFile(f, buf.Bytes(), 0600)
}

View File

@ -1,27 +0,0 @@
package main
import (
"bytes"
)
import (
_ "embed"
"os"
"text/template"
)
//go:embed templates/goclient.gotmpl
var goCliTemplate string
func processGoClientOutput(f string) error {
tmpl, err := template.New("gin").Parse(goCliTemplate)
if err != nil {
return err
}
buf := &bytes.Buffer{}
err = tmpl.Execute(buf, api)
if err != nil {
return err
}
return os.WriteFile(f, buf.Bytes(), 0600)
}

View File

@ -1,24 +0,0 @@
package main
import (
"bytes"
_ "embed"
"os"
"text/template"
)
//go:embed templates/goserver.gotmpl
var goServerTemplate string
func processGoServerOutput(f string) error {
tmpl, err := template.New("go").Parse(goServerTemplate)
if err != nil {
return err
}
buf := &bytes.Buffer{}
err = tmpl.Execute(buf, api)
if err != nil {
return err
}
return os.WriteFile(f, buf.Bytes(), 0600)
}

View File

@ -1,74 +0,0 @@
package {{.Namespace}}
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
}
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)
return ret, nil
}
{{range $typename,$type := .Types}}
type {{$typename}} struct{
{{range $fieldname, $field:= $type.Fields}}
{{$fieldname}} {{ if $field.Array}}[]{{end}}{{if $field.Map}}map[{{$field.Mapkey}}]{{$field.Mapval}}{{else}}{{$field.Type}}{{end}}
{{end}}
}
{{end}}
{{range $methodname,$method :=.Methods}}
func {{$methodname}}(req {{if $method.ReqType.IsArray}}[]{{end}}{{if $method.ReqType.Ispointer}}*{{end}}{{$method.ReqType.Typename}}) (res {{if $method.ResType.IsArray}}[]{{end}}{{if $method.ResType.Ispointer}}*{{end}}{{$method.ResType.Typename}}, err error){
var dec *json.Decoder
dec, err = invoke("{{$method.Verb}}", "{{$method.Path}}", req)
if err!=nil{
return
}
var ret {{if $method.ResType.IsArray}}[]{{end}}{{if $method.ResType.Ispointer}}*{{end}}{{$method.ResType.Typename}} {{if $method.ResType.Ispointer}} {{if ne $method.ResType.IsArray true}}=&{{$method.ResType.Typename}}{}{{end}}{{end}}
err = dec.Decode({{if $method.ResType.IsArray}}&{{end}} {{if ne $method.ResType.Ispointer true}}&{{end}}ret)
return ret, err
}
{{end}}

View File

@ -1,55 +0,0 @@
package {{.Namespace}}
import (
"github.com/gin-gonic/gin"
)
{{range $impalias, $impname := .UsedImportsFunctions}}
{{if and ( ne $impname "context") (ne $impname "json") (ne $impname "strings") (ne $impname "net/http") -}}
import "{{.}}"
{{- end}}
{{end}}
var perms map[string]string
func init(){
perms=make(map[string]string)
{{range .Methods -}}
{{if .Perm}}
perms["{{.Verb}}_{{.Path}}"]="{{.Perm}}"
{{- end}}
{{end}}
}
func GetPerm(c *gin.Context) string {
perm, ok := perms[c.Request.Method+"_"+c.Request.URL.Path]
if !ok {
return ""
}
return perm
}
func Build(r *gin.Engine) *gin.Engine {
{{range $pathname,$path:= .SortedPaths -}}
{{range $verbname,$verb :=.SortedVerbs -}}
r.{{$verb.Verb}}("{{$path.Path}}", func(c *gin.Context) {
var req {{if $verb.Method.ReqType.IsArray -}}[]{{ end -}}
{{- if $verb.Method.ReqType.Ispointer}} *{{ end -}}
{{- $verb.Method.ReqType.Typename}}
{{- if or $verb.Method.ReqType.IsArray $verb.Method.ReqType.Ispointer}}
{{- if $verb.Method.ReqType.Ispointer}} = &{{- $verb.Method.ReqType.Typename}}{}{{ end}}
c.BindJSON(req)
{{else}}
c.BindJSON(&req)
{{end -}}
res,err:= {{$verb.Method.Name}}(c.Request.Context(),req)
if err!=nil{
c.Error(err)
}
c.JSON(200,res)
})
{{end}}
{{end -}}
return r
}

View File

@ -1,38 +0,0 @@
package {{.Package}}
import (
"context"
"encoding/json"
"strings"
"net/http"
)
{{range .Imports}}
import {{.}}
{{end}}
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),
}
{{range .Methods}}
{{if .Perm}}
ret.Perms["{{.Verb}}_{{.Path}}"]="{{.Perm}}"
{{end}}
{{end}}
}

View File

@ -1,95 +0,0 @@
package {{.Namespace}}
import (
"context"
"encoding/json"
"strings"
"net/http"
)
{{range $impalias, $impname := .UsedImportsFunctions}}
{{if and ( ne $impname "context") (ne $impname "json") (ne $impname "strings") (ne $impname "net/http") -}}
import "{{.}}"
{{- end}}
{{end}}
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),
}
{{range .Methods -}}
{{if .Perm -}}
ret.Perms["{{.Verb}}_{{.Path}}"]="{{.Perm}}"
{{- end}}
{{end}}
{{range .SortedPaths}}
mux.HandleFunc("{{.Path}}",func(w http.ResponseWriter, r *http.Request) {
switch r.Method{
{{range .SortedVerbs -}}
case "{{.Verb}}":
{{if .Method.Raw -}}
{{.Method.Name}}(w , r)
{{else -}}
h_{{.Method.Name}}(w , r)
{{end -}}
{{end -}}
default:
http.Error(w,"Method not allowed",500)
}
})
{{end}}
return &ret
}
{{range $MethodName, $Method := .Methods}}
func h_{{$MethodName}}(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx = context.WithValue(r.Context(), "REQ", r)
ctx = context.WithValue(ctx, "RES", w)
var req {{if $Method.ReqType.IsArray}}[]{{end}}{{if $Method.ReqType.Ispointer}}*{{end}}{{$Method.ReqType.Typename}}
if r.Method!=http.MethodGet && r.Method!=http.MethodHead {
{{if or $Method.ReqType.IsArray $Method.ReqType.Ispointer}}
err := json.NewDecoder(r.Body).Decode(req)
{{else}}
err := json.NewDecoder(r.Body).Decode(&req)
{{end}}
if err != nil {
http.Error(w, err.Error(), 500)
return
}
}
res, err := {{$MethodName}}(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
}
}
{{end}}