From 984592b7fd61a56e9f453f755a46212ee181b38e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Sima=CC=83o?= Date: Sun, 3 Oct 2021 11:39:42 -0300 Subject: [PATCH] Simplified --- go.mod | 2 +- go.sum | 5 + lib/loader.go | 182 ++++++++++++++----------------- lib/processGoServerOutput.go | 188 +++++++++++++++++++++++++------- test/demo/api.py | 58 ++++++++++ test/demo/api.ts | 101 ++++++++++++++++++ test/demo/api/apigen.go | 201 +++++++++++++++++++++++++++++++++++ test/demo/api/bizrules.go | 40 +++++++ test/demo/cli/cli.go | 90 ++++++++++++++++ test/demo/cmd/main.go | 21 ++++ test/demo/server/main.go | 11 ++ test/demo/test.http | 3 + test/goapi/test.go | 2 +- test/gocli/cli.go | 79 ++++---------- 14 files changed, 784 insertions(+), 199 deletions(-) create mode 100644 test/demo/api.py create mode 100644 test/demo/api.ts create mode 100644 test/demo/api/apigen.go create mode 100644 test/demo/api/bizrules.go create mode 100644 test/demo/cli/cli.go create mode 100644 test/demo/cmd/main.go create mode 100644 test/demo/server/main.go create mode 100644 test/demo/test.http diff --git a/go.mod b/go.mod index 3a1191f..5154cd6 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module go.digitalcircle.com.br/tools/apigen go 1.17 require ( - github.com/alecthomas/kong v0.2.15 + github.com/alecthomas/kong v0.2.17 github.com/fatih/structtag v1.2.0 github.com/pkg/errors v0.9.1 gopkg.in/yaml.v2 v2.4.0 diff --git a/go.sum b/go.sum index 8ee6cef..b9bb659 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/alecthomas/kong v0.2.15 h1:HP3K1XuFn0wGSWFGVW67V+65tXw/Ht8FDYiLNAuX2Ug= github.com/alecthomas/kong v0.2.15/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE= +github.com/alecthomas/kong v0.2.17 h1:URDISCI96MIgcIlQyoCAlhOmrSw6pZScBNkctg8r0W0= +github.com/alecthomas/kong v0.2.17/go.mod h1:ka3VZ8GZNPXv9Ov+j4YNLkI8mTuhXyr/0ktSlqIydQQ= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -40,6 +42,7 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= @@ -54,3 +57,5 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/lib/loader.go b/lib/loader.go index 92a88c3..d6a6908 100644 --- a/lib/loader.go +++ b/lib/loader.go @@ -1,6 +1,7 @@ package lib import ( + "errors" "fmt" "github.com/fatih/structtag" "go/ast" @@ -117,118 +118,89 @@ func addStruct(a *ast.GenDecl) { api.Types[tp.Name] = &tp } -func addFunction(a *ast.FuncDecl) { +func addFunction(a *ast.FuncDecl) error { md := manageComments(a.Doc) if md["API"] == "" { - return + return nil } 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 len(a.Type.Params.List) != 2 { + return errors.New(fmt.Sprintf("Function %s does not have 2 IN parameters (context and pointer to req struct)", a.Name.Name)) } - if 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 + if len(a.Type.Results.List) != 2 { + return errors.New(fmt.Sprintf("Function %s does not have 2 OUT parameters (pointer to res struct and err)", a.Name.Name)) + } + if a.Type.Results.List[1].Type.(*ast.Ident).Name != "error" { + return errors.New(fmt.Sprintf("Function %s does not have error as 2nd OUT parameter", a.Name.Name)) } + p0tp := a.Type.Params.List[0].Type.(*ast.SelectorExpr) + if p0tp.X.(*ast.Ident).Name != "context" && p0tp.Sel.Name != "Context" { + return errors.New(fmt.Sprintf("Function %s, param 1 is not of type context.Context", a.Name)) + } + + switch x := a.Type.Params.List[1].Type.(type) { + case *ast.StarExpr: + reqType.Ispointer = true + switch y := x.X.(type) { + case *ast.Ident: + reqType.Typename = y.Name + case *ast.SelectorExpr: + api.UsedImportsFunctions[y.X.(*ast.Ident).Name] = api.Imports[y.X.(*ast.Ident).Name] + reqType.Typename = y.X.(*ast.Ident).Name + "." + y.Sel.Name + } + default: + return errors.New(fmt.Sprintf("Function %s does not have 2 IN parameters (context and pointer to req struct)", a.Name.Name)) + + } + + switch x := a.Type.Results.List[0].Type.(type) { + case *ast.StarExpr: + resType.Ispointer = true + switch y := x.X.(type) { + case *ast.Ident: + resType.Typename = y.Name + case *ast.SelectorExpr: + api.UsedImportsFunctions[y.X.(*ast.Ident).Name] = api.Imports[y.X.(*ast.Ident).Name] + resType.Typename = y.X.(*ast.Ident).Name + "." + y.Sel.Name + } + default: + return errors.New(fmt.Sprintf("Function %s does not have 2 OUT parameters (pointer to res struct and err)", a.Name.Name)) + + } + + if md["RAW"] == "true" { + reqType.Typename = md["REQ"] + resType.Typename = md["RES"] + } + + verb := md["VERB"] + if verb == "" { + verb = http.MethodPost + } + fn := APIMethod{ + Name: a.Name.Name, + Desc: a.Name.Name, + Verb: verb, + Path: md["PATH"], + Perm: md["PERM"], + ReqType: reqType, + ResType: resType, + } + + if fn.Path == "" { + fn.Path = "/" + strings.Replace(strings.ToLower(a.Name.Name), "_", "/", -1) + } + + api.Methods[a.Name.Name] = &fn + + return nil } func load(src string) error { @@ -255,6 +227,11 @@ func load(src string) error { // Print the AST. ast.Inspect(v, func(n ast.Node) bool { + //tp := reflect.TypeOf(n) + //if tp != nil { + // log.Printf("Type: %#v", tp.String()) + //} + switch x := n.(type) { case *ast.GenDecl: @@ -278,7 +255,10 @@ func load(src string) error { case *ast.File: manageCommentsGroups(x.Comments) case *ast.FuncDecl: - addFunction(x) + err = addFunction(x) + if err != nil { + return false + } llog("Adding fn: %s", x.Name) case *ast.ValueSpec: if x.Names[0].Name == "BASEPATH" { @@ -341,5 +321,5 @@ func load(src string) error { api.Namespace = packageName - return nil + return err } diff --git a/lib/processGoServerOutput.go b/lib/processGoServerOutput.go index 4f1ce23..1874f41 100644 --- a/lib/processGoServerOutput.go +++ b/lib/processGoServerOutput.go @@ -27,16 +27,117 @@ func processGoServerOutput(f string) error { WNL("package %s", api.Namespace) WNL(`import ( - "context" - "encoding/json" - "strings" - "net/http" - )`) + "context" + "encoding/json" + "strings" + "net/http" + "errors" + "reflect" + "strconv" +)`) for k := range api.UsedImportsFunctions { WNL(`import "%s"`, k) } + WNL(`func handleTag(s string, r *http.Request) string { + parts := strings.Split(s, ":") + where := "Q" + key := "" + if len(parts) == 1 { + key = parts[0] + } else { + where = parts[0] + key = parts[1] + } + + switch where { + case "Q": + return r.URL.Query().Get(key) + case "H": + return r.Header.Get(key) + case "P": + switch key { + case "*": + return r.URL.Path + case "last": + pps := strings.Split(r.URL.Path, "/") + return pps[len(pps)-1] + case "len": + pps := strings.Split(r.URL.Path, "/") + return strconv.Itoa(len(pps)) + default: + pps := strings.Split(r.URL.Path, "/") + n, _ := strconv.Atoi(key) + if n < len(pps) { + return pps[n] + } + return "" + } + } + return "" +} + +func convert(s string, tpname string) interface{} { + switch tpname { + case "string": + return s + + case "int": + v, _ := strconv.Atoi(s) + return v + case "int8": + v, _ := strconv.Atoi(s) + return int8(v) + case "int16": + v, _ := strconv.Atoi(s) + return int16(v) + case "int32": + v, _ := strconv.Atoi(s) + return int32(v) + case "int64": + v, _ := strconv.Atoi(s) + return int64(v) + case "uint": + v, _ := strconv.Atoi(s) + return uint(v) + case "float32": + v, _ := strconv.Atoi(s) + return float32(v) + case "float64": + v, _ := strconv.Atoi(s) + return float64(v) + case "bool": + return s == "true" || s == "1" || s == "Y" + } + return nil +} + +func Map(r *http.Request, in interface{}) error { + + tp := reflect.TypeOf(in) + vl := reflect.ValueOf(in) + if tp.Kind() == reflect.Ptr { + tp = tp.Elem() + vl = vl.Elem() + } + if tp.Kind() != reflect.Struct { + return errors.New("Type is not struct") + } + + for i := 0; i < tp.NumField(); i++ { + k, ok := tp.Field(i).Tag.Lookup("in") + if ok { + str := handleTag(k, r) + v := convert(str, tp.Field(i).Type.Name()) + strv := reflect.ValueOf(v) + vl.Field(i).Set(strv) + } + } + return nil +} +`) + WNL(`type API struct { Mux *http.ServeMux Perms map[string]string @@ -47,41 +148,6 @@ func (a *API) GetPerm(r *http.Request) string { } `) - 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() @@ -90,6 +156,12 @@ func (a *API) GetPerm(r *http.Request) string { WNL(" req := %s", ResImplType(v.ReqType)) + WNL(` err:=Map(r,req) + if err != nil { + http.Error(w, err.Error(), 500) + return + }`) + WNL(` if r.Method!=http.MethodGet && r.Method!=http.MethodHead {`) if v.ReqType.Ispointer || v.ReqType.IsArray { @@ -119,5 +191,41 @@ func (a *API) GetPerm(r *http.Request) string { } + 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 +}`) + return os.WriteFile(f, buf.Bytes(), 0600) } diff --git a/test/demo/api.py b/test/demo/api.py new file mode 100644 index 0000000..c5954da --- /dev/null +++ b/test/demo/api.py @@ -0,0 +1,58 @@ +from dataclasses import dataclass +import requests +import json +from typing import List +#region Base + + +__ctx = {"apibase":""} + + +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)) + +#endregion + +#region Types +@ dataclass +class SomeReq : + fielda: str + + +@ dataclass +class SomeRes : + msg: str + + +#endregion + +#region Methods +def Dosome(req:SomeReq)-> SomeRes: + return InvokeJSON("/some","POST",req) + + +#endregion diff --git a/test/demo/api.ts b/test/demo/api.ts new file mode 100644 index 0000000..cf20a98 --- /dev/null +++ b/test/demo/api.ts @@ -0,0 +1,101 @@ +//#region Base + + +var apibase=""; + +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 { + 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 { + + 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 { + //@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 { + + //@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 +} + +//#endregion + +//#region Types +export interface SomeReq { + fielda ?: string +} + +export interface SomeRes { + msg ?: string +} + +//#endregion + +//#region Methods +/** +Dosome*/ +export async function Dosome(req:SomeReq):Promise{ + return InvokeJSON("/some","POST",req) +} + +//#endregion diff --git a/test/demo/api/apigen.go b/test/demo/api/apigen.go new file mode 100644 index 0000000..ee57122 --- /dev/null +++ b/test/demo/api/apigen.go @@ -0,0 +1,201 @@ +package api + +import ( + "context" + "encoding/json" + "errors" + "net/http" + "reflect" + "strconv" + "strings" +) + +func handleTag(s string, r *http.Request) string { + parts := strings.Split(s, ":") + where := "Q" + key := "" + if len(parts) == 1 { + key = parts[0] + } else { + where = parts[0] + key = parts[1] + } + + switch where { + case "Q": + return r.URL.Query().Get(key) + case "H": + return r.Header.Get(key) + case "P": + switch key { + case "*": + return r.URL.Path + case "last": + pps := strings.Split(r.URL.Path, "/") + return pps[len(pps)-1] + case "len": + pps := strings.Split(r.URL.Path, "/") + return strconv.Itoa(len(pps)) + default: + pps := strings.Split(r.URL.Path, "/") + n, _ := strconv.Atoi(key) + if n < len(pps) { + return pps[n] + } + return "" + } + } + return "" +} + +func convert(s string, tpname string) interface{} { + switch tpname { + case "string": + return s + + case "int": + v, _ := strconv.Atoi(s) + return v + case "int8": + v, _ := strconv.Atoi(s) + return int8(v) + case "int16": + v, _ := strconv.Atoi(s) + return int16(v) + case "int32": + v, _ := strconv.Atoi(s) + return int32(v) + case "int64": + v, _ := strconv.Atoi(s) + return int64(v) + case "uint": + v, _ := strconv.Atoi(s) + return uint(v) + case "float32": + v, _ := strconv.Atoi(s) + return float32(v) + case "float64": + v, _ := strconv.Atoi(s) + return float64(v) + case "bool": + return s == "true" || s == "1" || s == "Y" + } + return nil +} + +func Map(r *http.Request, in interface{}) error { + + tp := reflect.TypeOf(in) + vl := reflect.ValueOf(in) + if tp.Kind() == reflect.Ptr { + tp = tp.Elem() + vl = vl.Elem() + } + if tp.Kind() != reflect.Struct { + return errors.New("Type is not struct") + } + + for i := 0; i < tp.NumField(); i++ { + k, ok := tp.Field(i).Tag.Lookup("in") + if ok { + str := handleTag(k, r) + v := convert(str, tp.Field(i).Type.Name()) + strv := reflect.ValueOf(v) + vl.Field(i).Set(strv) + } + } + return nil +} + +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 h_Dosome(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + ctx = context.WithValue(r.Context(), "REQ", r) + ctx = context.WithValue(ctx, "RES", w) + req := &SomeReq{} + err := Map(r, req) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + 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 := Dosome(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_Dosome2(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + ctx = context.WithValue(r.Context(), "REQ", r) + ctx = context.WithValue(ctx, "RES", w) + req := &SomeReq2{} + err := Map(r, req) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + 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 := Dosome2(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 Init() *API { + mux := &http.ServeMux{} + + ret := &API{ + Mux: mux, + Perms: make(map[string]string), + } + mux.HandleFunc("/some", func(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case "POST": + h_Dosome(w, r) + default: + http.Error(w, "Method not allowed", 500) + } + }) + mux.HandleFunc("/some2", func(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case "POST": + h_Dosome2(w, r) + default: + http.Error(w, "Method not allowed", 500) + } + }) + return ret +} diff --git a/test/demo/api/bizrules.go b/test/demo/api/bizrules.go new file mode 100644 index 0000000..197e420 --- /dev/null +++ b/test/demo/api/bizrules.go @@ -0,0 +1,40 @@ +package api + +import ( + "context" + "fmt" + "time" +) + +//@API +type SomeReq struct { + Fielda string `json:"fielda"` + Fieldb time.Time `json:"fielda"` +} + +//@API +type SomeReq2 struct { + Fielda string `json:"fielda"` + Fieldb time.Time `json:"fielda"` +} + +//@API +type SomeRes struct { + Msg string `json:"msg"` +} + +//@API +//@PATH: /some +//@VERB: POST +func Dosome(ctx context.Context, req *SomeReq) (res *SomeRes, err error) { + res = &SomeRes{Msg: fmt.Sprintf("%s: %s", req.Fielda, req.Fieldb.String())} + return +} + +//@API +//@PATH: /some2 +//@VERB: POST +func Dosome2(ctx context.Context, req *SomeReq2) (res *SomeRes, err error) { + res = &SomeRes{Msg: fmt.Sprintf("%s: %s", req.Fielda, req.Fieldb.String())} + return +} diff --git a/test/demo/cli/cli.go b/test/demo/cli/cli.go new file mode 100644 index 0000000..c70ea25 --- /dev/null +++ b/test/demo/cli/cli.go @@ -0,0 +1,90 @@ +package api + +import "time" +import ( + "bytes" + "encoding/json" + "errors" + "io/ioutil" + "net/http" +) + +var Basepath string = "" +var Host string = "" +var ExtraHeaders map[string]string = make(map[string]string) + +var cli *http.Client + +func SetCli(nc *http.Client) { + cli = nc +} + +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) + } + + res, err := cli.Do(req) + + if err != nil { + return nil, err + } + + 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 +} + +type SomeReq struct { + Fielda string + Fieldb time.Time +} +type SomeReq2 struct { + Fielda string + Fieldb time.Time +} +type SomeRes struct { + Msg string +} + +func Dosome(req *SomeReq) (res *SomeRes, err error) { + var dec *json.Decoder + dec, err = invoke("POST", "/some", req) + if err != nil { + return + } + var ret *SomeRes = &SomeRes{} + err = dec.Decode(ret) + return ret, err +} +func Dosome2(req *SomeReq2) (res *SomeRes, err error) { + var dec *json.Decoder + dec, err = invoke("POST", "/some2", req) + if err != nil { + return + } + var ret *SomeRes = &SomeRes{} + err = dec.Decode(ret) + return ret, err +} diff --git a/test/demo/cmd/main.go b/test/demo/cmd/main.go new file mode 100644 index 0000000..1b40422 --- /dev/null +++ b/test/demo/cmd/main.go @@ -0,0 +1,21 @@ +package main + +import ( + api "go.digitalcircle.com.br/tools/apigen/test/demo/cli" + "log" + "net/http" + "time" +) + +func main() { + api.Host = "http://localhost:8080" + api.SetCli(&http.Client{}) + res, err := api.Dosome(&api.SomeReq{ + Fielda: "ASD123", + Fieldb: time.Unix(0, 0), + }) + if err != nil { + panic(err) + } + log.Printf("%#v", res) +} diff --git a/test/demo/server/main.go b/test/demo/server/main.go new file mode 100644 index 0000000..a754939 --- /dev/null +++ b/test/demo/server/main.go @@ -0,0 +1,11 @@ +package main + +import ( + "go.digitalcircle.com.br/tools/apigen/test/demo/api" + "net/http" +) + +func main() { + aapi := api.Init() + http.ListenAndServe(":8080", aapi.Mux) +} diff --git a/test/demo/test.http b/test/demo/test.http new file mode 100644 index 0000000..f7edad3 --- /dev/null +++ b/test/demo/test.http @@ -0,0 +1,3 @@ +POST http://localhost:8080/some + +{"fielda":"Hello World","fieldb":""} \ No newline at end of file diff --git a/test/goapi/test.go b/test/goapi/test.go index d20587e..834f5d5 100644 --- a/test/goapi/test.go +++ b/test/goapi/test.go @@ -25,7 +25,7 @@ type AStr struct { @PERM: ASD @VERB: POST */ -func SomeAPI(ctx context.Context, s *AStr) (out *AStr, err error) { +func SomeAPI(ctx context.Context, s *AStr, c int) (out *AStr, err error) { //print("Got:" + s) //out = time.Now().String() + " - Hey Ya!" return diff --git a/test/gocli/cli.go b/test/gocli/cli.go index 5567bb6..d9b3ab6 100644 --- a/test/gocli/cli.go +++ b/test/gocli/cli.go @@ -1,25 +1,33 @@ package goapi +import "time" +import "crypto" import ( "bytes" - "crypto" "encoding/json" "errors" "io/ioutil" "net/http" - "time" ) var Basepath string = "" var Host string = "" var ExtraHeaders map[string]string = make(map[string]string) +var Ver string = "" +var cli http.Client + +func SetCli(nc http.Client) { + cli = nc +} + 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 { @@ -32,19 +40,19 @@ func invoke(m string, path string, bodyo interface{}) (*json.Decoder, error) { 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 { - panic(err) + return nil, err } - return nil, errors.New(string(bs)) } @@ -53,65 +61,24 @@ func invoke(m string, path string, bodyo interface{}) (*json.Decoder, error) { } type AStr struct { - Arrofpstr []string - - City string - - Country string - - HouseNumber int64 - - IsCondo bool - - Recursive map[string]AStr - - Some crypto.Decrypter - + HouseNumber int64 SomeWeirdTest string - - When time.Time + Recursive map[string]AStr + When time.Time + Some crypto.Decrypter + Country string + City string + IsCondo bool + Arrofpstr []string } -func SomeAPI(req string) (res string, err error) { +func SomeAPI(req *AStr) (res *AStr, err error) { var dec *json.Decoder dec, err = invoke("POST", "/someapi", req) if err != nil { return } - var ret string - err = dec.Decode(ret) - return ret, err -} - -func SomeAPI2(req crypto.Hash) (res []string, err error) { - var dec *json.Decoder - dec, err = invoke("DELETE", "/someapi", req) - if err != nil { - return - } - var ret []string - err = dec.Decode(ret) - return ret, err -} - -func SomeGET(req string) (res string, err error) { - var dec *json.Decoder - dec, err = invoke("GET", "/someapi", req) - if err != nil { - return - } - var ret string - err = dec.Decode(ret) - return ret, err -} - -func SomePUT(req string) (res string, err error) { - var dec *json.Decoder - dec, err = invoke("PUT", "/someapi", req) - if err != nil { - return - } - var ret string + var ret *AStr = &AStr{} err = dec.Decode(ret) return ret, err }